From 816df128b28820c190308f109cbd1f3e62fe54fd Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Mon, 15 Dec 2025 12:58:12 +0100 Subject: [PATCH 1/8] Added absolute tolerance to common interface --- pypesto/optimize/optimizer.py | 148 ++++++++++++++++++ .../test_optimizer_common_interface.py | 88 +++++++++++ 2 files changed, 236 insertions(+) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index 45fbdc2b9..8dc07ac85 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -405,6 +405,36 @@ def set_maxeval(self, evaluations: int) -> None: f"Check supports_maxeval() before calling set_maxeval()." ) + def supports_tol(self) -> bool: + """ + Check whether optimizer supports absolute tolerance. + + Returns + ------- + True if optimizer supports setting an absolute tolerance, + False otherwise. + """ + return False + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + + Raises + ------ + NotImplementedError + If the optimizer does not support absolute tolerance. + """ + raise NotImplementedError( + f"{self.__class__.__name__} does not support absolute tolerance. " + f"Check supports_tol() before calling set_tol()." + ) + class ScipyOptimizer(Optimizer): """ @@ -662,6 +692,21 @@ def set_maxiter(self, iterations: int) -> None: else: self.options["maxiter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + self.tol = tol + class IpoptOptimizer(Optimizer): """Use Ipopt (https://pypi.org/project/cyipopt/) for optimization.""" @@ -781,6 +826,23 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["max_iter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + if self.options is None: + self.options = {} + self.options["tol"] = tol + class DlibOptimizer(Optimizer): """Use the Dlib toolbox for optimization.""" @@ -949,6 +1011,23 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + if self.options is None: + self.options = {} + self.options["minfunc"] = tol + class CmaOptimizer(Optimizer): """ @@ -1062,6 +1141,23 @@ def set_maxeval(self, evaluations: int) -> None: self.options = {} self.options["maxfevals"] = evaluations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + if self.options is None: + self.options = {} + self.options["tolfun"] = tol + class CmaesOptimizer(CmaOptimizer): """Deprecated, use CmaOptimizer instead.""" @@ -1159,6 +1255,23 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + if self.options is None: + self.options = {} + self.options["atol"] = tol + class PyswarmsOptimizer(Optimizer): """ @@ -1572,6 +1685,21 @@ def set_maxeval(self, evaluations: int) -> None: """ self.options["maxeval"] = evaluations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + self.options["ftol_abs"] = tol + class FidesOptimizer(Optimizer): """ @@ -1796,3 +1924,23 @@ def set_maxiter(self, iterations: int) -> None: self.options[FidesOptions.MAXITER] = iterations except ImportError: raise OptimizerImportError("fides") from None + + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return True + + def set_tol(self, tol: float) -> None: + """ + Set the absolute tolerance for optimization. + + Parameters + ---------- + tol + Absolute tolerance for termination. + """ + try: + from fides.constants import Options as FidesOptions + + self.options[FidesOptions.FTOL] = tol + except ImportError: + raise OptimizerImportError("fides") from None diff --git a/test/optimize/test_optimizer_common_interface.py b/test/optimize/test_optimizer_common_interface.py index 63595cb91..9f534d173 100644 --- a/test/optimize/test_optimizer_common_interface.py +++ b/test/optimize/test_optimizer_common_interface.py @@ -200,3 +200,91 @@ def test_ipopt_optimizer_no_support(self): with pytest.raises(NotImplementedError): optimizer.set_maxeval(100) + + +class TestOptimizerTolInterface: + """Test the unified tolerance interface for optimizers.""" + + def test_scipy_optimizer_support(self): + """Test ScipyOptimizer tolerance support.""" + optimizer = optimize.ScipyOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-6) + assert optimizer.tol == 1e-6 + + # Test updating existing value + optimizer.set_tol(1e-8) + assert optimizer.tol == 1e-8 + + def test_ipopt_optimizer_support(self): + """Test IpoptOptimizer tolerance support.""" + optimizer = optimize.IpoptOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-7) + assert optimizer.options["tol"] == 1e-7 + + def test_nlopt_optimizer_support(self): + """Test NLoptOptimizer tolerance support.""" + optimizer = optimize.NLoptOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-5) + assert optimizer.options["ftol_abs"] == 1e-5 + + def test_fides_optimizer_support(self): + """Test FidesOptimizer tolerance support.""" + optimizer = optimize.FidesOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-6) + + from fides.constants import Options as FidesOptions + + assert FidesOptions.FTOL in optimizer.options + assert optimizer.options[FidesOptions.FTOL] == 1e-6 + + # Test updating existing value + optimizer.set_tol(1e-9) + assert optimizer.options[FidesOptions.FTOL] == 1e-9 + + def test_cma_optimizer_support(self): + """Test CmaOptimizer tolerance support.""" + optimizer = optimize.CmaOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-4) + assert optimizer.options["tolfun"] == 1e-4 + + def test_scipy_de_optimizer_support(self): + """Test ScipyDifferentialEvolutionOptimizer tolerance support.""" + optimizer = optimize.ScipyDifferentialEvolutionOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-5) + assert optimizer.options["atol"] == 1e-5 + + def test_pyswarm_optimizer_support(self): + """Test PyswarmOptimizer tolerance support.""" + optimizer = optimize.PyswarmOptimizer() + assert optimizer.supports_tol() is True + + optimizer.set_tol(1e-7) + assert optimizer.options["minfunc"] == 1e-7 + + def test_dlib_optimizer_no_support(self): + """Test that DlibOptimizer does not support tolerance.""" + optimizer = optimize.DlibOptimizer() + assert optimizer.supports_tol() is False + + with pytest.raises(NotImplementedError): + optimizer.set_tol(1e-6) + + def test_pyswarms_optimizer_no_support(self): + """Test that PyswarmsOptimizer does not support tolerance.""" + optimizer = optimize.PyswarmsOptimizer() + assert optimizer.supports_tol() is False + + with pytest.raises(NotImplementedError): + optimizer.set_tol(1e-6) From cef62b6600994ab27dcff8e7fa5529e5bfa44d18 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Mon, 15 Dec 2025 13:29:12 +0100 Subject: [PATCH 2/8] Restructured and also test for errors --- pypesto/optimize/optimizer.py | 85 ++++++++++--------- .../test_optimizer_common_interface.py | 22 ++++- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index 8dc07ac85..bda5886b8 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -414,7 +414,7 @@ def supports_tol(self) -> bool: True if optimizer supports setting an absolute tolerance, False otherwise. """ - return False + return True def set_tol(self, tol: float) -> None: """ @@ -435,6 +435,28 @@ def set_tol(self, tol: float) -> None: f"Check supports_tol() before calling set_tol()." ) + def _set_option_tol(self, tol: float, option_key: str) -> None: + """ + Set tolerance in options dict with validation. + + Parameters + ---------- + tol + Absolute tolerance value (must be positive). + option_key + The key to use in the options dictionary. + + Raises + ------ + ValueError + If tolerance is not positive. + """ + if tol <= 0: + raise ValueError(f"Tolerance must be positive, got {tol}") + if self.options is None: + self.options = {} + self.options[option_key] = tol + class ScipyOptimizer(Optimizer): """ @@ -692,10 +714,6 @@ def set_maxiter(self, iterations: int) -> None: else: self.options["maxiter"] = iterations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -704,7 +722,14 @@ def set_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ + if tol <= 0: + raise ValueError(f"Tolerance must be positive, got {tol}") self.tol = tol @@ -826,10 +851,6 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["max_iter"] = iterations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -839,9 +860,7 @@ def set_tol(self, tol: float) -> None: tol Absolute tolerance for termination. """ - if self.options is None: - self.options = {} - self.options["tol"] = tol + self._set_option_tol(tol, "tol") class DlibOptimizer(Optimizer): @@ -936,6 +955,10 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return False + class PyswarmOptimizer(Optimizer): """Global optimization using pyswarm.""" @@ -1011,10 +1034,6 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -1024,9 +1043,7 @@ def set_tol(self, tol: float) -> None: tol Absolute tolerance for termination. """ - if self.options is None: - self.options = {} - self.options["minfunc"] = tol + self._set_option_tol(tol, "minfunc") class CmaOptimizer(Optimizer): @@ -1141,10 +1158,6 @@ def set_maxeval(self, evaluations: int) -> None: self.options = {} self.options["maxfevals"] = evaluations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -1154,9 +1167,7 @@ def set_tol(self, tol: float) -> None: tol Absolute tolerance for termination. """ - if self.options is None: - self.options = {} - self.options["tolfun"] = tol + self._set_option_tol(tol, "tolfun") class CmaesOptimizer(CmaOptimizer): @@ -1255,10 +1266,6 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -1268,9 +1275,7 @@ def set_tol(self, tol: float) -> None: tol Absolute tolerance for termination. """ - if self.options is None: - self.options = {} - self.options["atol"] = tol + self._set_option_tol(tol, "atol") class PyswarmsOptimizer(Optimizer): @@ -1413,6 +1418,10 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations + def supports_tol(self) -> bool: + """Check whether optimizer supports absolute tolerance.""" + return False + class NLoptOptimizer(Optimizer): """ @@ -1685,10 +1694,6 @@ def set_maxeval(self, evaluations: int) -> None: """ self.options["maxeval"] = evaluations - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -1925,10 +1930,6 @@ def set_maxiter(self, iterations: int) -> None: except ImportError: raise OptimizerImportError("fides") from None - def supports_tol(self) -> bool: - """Check whether optimizer supports absolute tolerance.""" - return True - def set_tol(self, tol: float) -> None: """ Set the absolute tolerance for optimization. @@ -1941,6 +1942,6 @@ def set_tol(self, tol: float) -> None: try: from fides.constants import Options as FidesOptions - self.options[FidesOptions.FTOL] = tol + self.options[FidesOptions.FATOL] = tol except ImportError: raise OptimizerImportError("fides") from None diff --git a/test/optimize/test_optimizer_common_interface.py b/test/optimize/test_optimizer_common_interface.py index 9f534d173..beaf22990 100644 --- a/test/optimize/test_optimizer_common_interface.py +++ b/test/optimize/test_optimizer_common_interface.py @@ -242,12 +242,12 @@ def test_fides_optimizer_support(self): from fides.constants import Options as FidesOptions - assert FidesOptions.FTOL in optimizer.options - assert optimizer.options[FidesOptions.FTOL] == 1e-6 + assert FidesOptions.FATOL in optimizer.options + assert optimizer.options[FidesOptions.FATOL] == 1e-6 # Test updating existing value optimizer.set_tol(1e-9) - assert optimizer.options[FidesOptions.FTOL] == 1e-9 + assert optimizer.options[FidesOptions.FATOL] == 1e-9 def test_cma_optimizer_support(self): """Test CmaOptimizer tolerance support.""" @@ -288,3 +288,19 @@ def test_pyswarms_optimizer_no_support(self): with pytest.raises(NotImplementedError): optimizer.set_tol(1e-6) + + def test_tolerance_validation(self): + """Test that invalid tolerance values are rejected.""" + optimizer = optimize.ScipyOptimizer() + + # Test that positive values work + optimizer.set_tol(1e-6) + assert optimizer.tol == 1e-6 + + def test_tolerance_validation_options_based(self): + """Test tolerance validation for options-based optimizers.""" + optimizer = optimize.IpoptOptimizer() + + # Test that positive values work + optimizer.set_tol(1e-7) + assert optimizer.options["tol"] == 1e-7 From 649cb244b31a67017d06684ceae45cff6e49f5ce Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 16 Dec 2025 15:44:10 +0100 Subject: [PATCH 3/8] always use _set_option_tol wherever possible --- pypesto/optimize/optimizer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index bda5886b8..7a3448d11 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -451,7 +451,7 @@ def _set_option_tol(self, tol: float, option_key: str) -> None: ValueError If tolerance is not positive. """ - if tol <= 0: + if tol < 0: raise ValueError(f"Tolerance must be positive, got {tol}") if self.options is None: self.options = {} @@ -1703,7 +1703,7 @@ def set_tol(self, tol: float) -> None: tol Absolute tolerance for termination. """ - self.options["ftol_abs"] = tol + self._set_option_tol(tol, "ftol_abs") class FidesOptimizer(Optimizer): @@ -1942,6 +1942,6 @@ def set_tol(self, tol: float) -> None: try: from fides.constants import Options as FidesOptions - self.options[FidesOptions.FATOL] = tol + self._set_option_tol(tol, FidesOptions.FATOL) except ImportError: raise OptimizerImportError("fides") from None From d7e86fc27282c082083a8eb39e876b9a85141080 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 27 Jan 2026 08:55:05 +0100 Subject: [PATCH 4/8] abs_tol --- pypesto/optimize/optimizer.py | 44 +++++++++++-------- .../test_optimizer_common_interface.py | 16 +++++++ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index 451f746eb..79e24796c 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -413,34 +413,34 @@ def set_maxeval(self, evaluations: int) -> None: f"Check supports_maxeval() before calling set_maxeval()." ) - def supports_tol(self) -> bool: + def supports_f_abs_tol(self) -> bool: """ - Check whether optimizer supports absolute tolerance. + Check whether optimizer supports absolute function value tolerance. Returns ------- - True if optimizer supports setting an absolute tolerance, - False otherwise. + True if optimizer supports setting an absolute tolerance on the + objective function value, False otherwise. """ return True - def set_tol(self, tol: float) -> None: + def set_f_abs_tol(self, tol: float) -> None: """ - Set the absolute tolerance for optimization. + Set the absolute tolerance on function value for optimization. Parameters ---------- tol - Absolute tolerance for termination. + Absolute tolerance on objective function value for termination. Raises ------ NotImplementedError - If the optimizer does not support absolute tolerance. + If the optimizer does not support absolute function tolerance. """ raise NotImplementedError( - f"{self.__class__.__name__} does not support absolute tolerance. " - f"Check supports_tol() before calling set_tol()." + f"{self.__class__.__name__} does not support absolute function tolerance. " + f"Check supports_f_abs_tol() before calling set_f_abs_tol()." ) def _set_option_tol(self, tol: float, option_key: str) -> None: @@ -722,22 +722,22 @@ def set_maxiter(self, iterations: int) -> None: else: self.options["maxiter"] = iterations - def set_tol(self, tol: float) -> None: + def set_f_abs_tol(self, tol: float) -> None: """ - Set the absolute tolerance for optimization. + Set the absolute tolerance on function value for optimization. Parameters ---------- tol - Absolute tolerance for termination. + Absolute tolerance on objective function value for termination. Raises ------ ValueError - If tolerance is not positive. + If tolerance is negative. """ - if tol <= 0: - raise ValueError(f"Tolerance must be positive, got {tol}") + if tol < 0: + raise ValueError(f"Tolerance must be non-negative, got {tol}") self.tol = tol @@ -859,14 +859,20 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["max_iter"] = iterations - def set_tol(self, tol: float) -> None: + def supports_f_abs_tol(self) -> bool: + """Check whether optimizer supports absolute function tolerance.""" + return False + + def set_f_rel_tol(self, tol: float) -> None: """ - Set the absolute tolerance for optimization. + Set the relative tolerance on function value for optimization. + + Ipopt uses relative convergence tolerance, not absolute. Parameters ---------- tol - Absolute tolerance for termination. + Relative tolerance on objective function value for termination. """ self._set_option_tol(tol, "tol") diff --git a/test/optimize/test_optimizer_common_interface.py b/test/optimize/test_optimizer_common_interface.py index beaf22990..136000d91 100644 --- a/test/optimize/test_optimizer_common_interface.py +++ b/test/optimize/test_optimizer_common_interface.py @@ -297,6 +297,14 @@ def test_tolerance_validation(self): optimizer.set_tol(1e-6) assert optimizer.tol == 1e-6 + # Test that zero is allowed (optimize as accurately as possible) + optimizer.set_tol(0.0) + assert optimizer.tol == 0.0 + + # Test that negative values are rejected + with pytest.raises(ValueError, match="must be non-negative"): + optimizer.set_tol(-1e-6) + def test_tolerance_validation_options_based(self): """Test tolerance validation for options-based optimizers.""" optimizer = optimize.IpoptOptimizer() @@ -304,3 +312,11 @@ def test_tolerance_validation_options_based(self): # Test that positive values work optimizer.set_tol(1e-7) assert optimizer.options["tol"] == 1e-7 + + # Test that zero is allowed + optimizer.set_tol(0.0) + assert optimizer.options["tol"] == 0.0 + + # Test that negative values are rejected + with pytest.raises(ValueError, match="must be positive"): + optimizer.set_tol(-1e-7) From cbae8ffbbec4992f0aeb73d74a0a9f4ae973cf3f Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Fri, 27 Feb 2026 15:48:11 +0100 Subject: [PATCH 5/8] adjusted for naming in tests --- pypesto/optimize/optimizer.py | 2 +- .../test_optimizer_common_interface.py | 66 ++++++------------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index ac0c02b05..cda3d16e2 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -915,7 +915,7 @@ def supports_f_abs_tol(self) -> bool: """Check whether optimizer supports absolute function tolerance.""" return False - def set_f_rel_tol(self, tol: float) -> None: + def set_tol(self, tol: float) -> None: """ Set the convergence tolerance. diff --git a/test/optimize/test_optimizer_common_interface.py b/test/optimize/test_optimizer_common_interface.py index 8d32123bd..a4b2ce867 100644 --- a/test/optimize/test_optimizer_common_interface.py +++ b/test/optimize/test_optimizer_common_interface.py @@ -234,37 +234,29 @@ class TestOptimizerTolInterface: def test_scipy_optimizer_support(self): """Test ScipyOptimizer tolerance support.""" optimizer = optimize.ScipyOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-6) + optimizer.set_f_abs_tol(1e-6) assert optimizer.tol == 1e-6 # Test updating existing value - optimizer.set_tol(1e-8) + optimizer.set_f_abs_tol(1e-8) assert optimizer.tol == 1e-8 - def test_ipopt_optimizer_support(self): - """Test IpoptOptimizer tolerance support.""" - optimizer = optimize.IpoptOptimizer() - assert optimizer.supports_tol() is True - - optimizer.set_tol(1e-7) - assert optimizer.options["tol"] == 1e-7 - def test_nlopt_optimizer_support(self): """Test NLoptOptimizer tolerance support.""" optimizer = optimize.NLoptOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-5) + optimizer.set_f_abs_tol(1e-5) assert optimizer.options["ftol_abs"] == 1e-5 def test_fides_optimizer_support(self): """Test FidesOptimizer tolerance support.""" optimizer = optimize.FidesOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-6) + optimizer.set_f_abs_tol(1e-6) from fides.constants import Options as FidesOptions @@ -272,77 +264,61 @@ def test_fides_optimizer_support(self): assert optimizer.options[FidesOptions.FATOL] == 1e-6 # Test updating existing value - optimizer.set_tol(1e-9) + optimizer.set_f_abs_tol(1e-9) assert optimizer.options[FidesOptions.FATOL] == 1e-9 def test_cma_optimizer_support(self): """Test CmaOptimizer tolerance support.""" optimizer = optimize.CmaOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-4) + optimizer.set_f_abs_tol(1e-4) assert optimizer.options["tolfun"] == 1e-4 def test_scipy_de_optimizer_support(self): """Test ScipyDifferentialEvolutionOptimizer tolerance support.""" optimizer = optimize.ScipyDifferentialEvolutionOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-5) + optimizer.set_f_abs_tol(1e-5) assert optimizer.options["atol"] == 1e-5 def test_pyswarm_optimizer_support(self): """Test PyswarmOptimizer tolerance support.""" optimizer = optimize.PyswarmOptimizer() - assert optimizer.supports_tol() is True + assert optimizer.supports_f_abs_tol() is True - optimizer.set_tol(1e-7) + optimizer.set_f_abs_tol(1e-7) assert optimizer.options["minfunc"] == 1e-7 def test_dlib_optimizer_no_support(self): """Test that DlibOptimizer does not support tolerance.""" optimizer = optimize.DlibOptimizer() - assert optimizer.supports_tol() is False + assert optimizer.supports_f_abs_tol() is False with pytest.raises(NotImplementedError): - optimizer.set_tol(1e-6) + optimizer.set_f_abs_tol(1e-6) def test_pyswarms_optimizer_no_support(self): """Test that PyswarmsOptimizer does not support tolerance.""" optimizer = optimize.PyswarmsOptimizer() - assert optimizer.supports_tol() is False + assert optimizer.supports_f_abs_tol() is False with pytest.raises(NotImplementedError): - optimizer.set_tol(1e-6) + optimizer.set_f_abs_tol(1e-6) def test_tolerance_validation(self): """Test that invalid tolerance values are rejected.""" optimizer = optimize.ScipyOptimizer() # Test that positive values work - optimizer.set_tol(1e-6) + optimizer.set_f_abs_tol(1e-6) assert optimizer.tol == 1e-6 # Test that zero is allowed (optimize as accurately as possible) - optimizer.set_tol(0.0) + optimizer.set_f_abs_tol(0.0) assert optimizer.tol == 0.0 # Test that negative values are rejected with pytest.raises(ValueError, match="must be non-negative"): - optimizer.set_tol(-1e-6) - - def test_tolerance_validation_options_based(self): - """Test tolerance validation for options-based optimizers.""" - optimizer = optimize.IpoptOptimizer() - - # Test that positive values work - optimizer.set_tol(1e-7) - assert optimizer.options["tol"] == 1e-7 - - # Test that zero is allowed - optimizer.set_tol(0.0) - assert optimizer.options["tol"] == 0.0 - - # Test that negative values are rejected - with pytest.raises(ValueError, match="must be positive"): - optimizer.set_tol(-1e-7) + optimizer.set_f_abs_tol(-1e-6) From 4c7ad9c786f00b15c83548fa7259ccc594209915 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Wed, 4 Mar 2026 14:58:03 +0100 Subject: [PATCH 6/8] Fixed CmaOptimizer support (by chance deleted) and moved test to maxtime testing --- pypesto/optimize/optimizer.py | 12 ++++++++++-- test/optimize/test_optimizer_common_interface.py | 14 +++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index cda3d16e2..c1c48e73c 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -1051,7 +1051,7 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations - def supports_tol(self) -> bool: + def supports_f_abs_tol(self) -> bool: """Check whether optimizer supports absolute tolerance.""" return False @@ -1220,6 +1220,14 @@ def is_least_squares(self): """Check whether optimizer is a least squares optimizer.""" return False + def supports_maxtime(self): + """Check whether optimizer supports time limits.""" + return True + + def set_maxtime(self, seconds: float) -> None: + """Set the maximum wall time for optimization.""" + self.options["timeout"] = seconds + def supports_maxiter(self) -> bool: """Check whether optimizer supports iteration limits.""" return True @@ -1518,7 +1526,7 @@ def set_maxiter(self, iterations: int) -> None: self.options = {} self.options["maxiter"] = iterations - def supports_tol(self) -> bool: + def supports_f_abs_tol(self) -> bool: """Check whether optimizer supports absolute tolerance.""" return False diff --git a/test/optimize/test_optimizer_common_interface.py b/test/optimize/test_optimizer_common_interface.py index a4b2ce867..b2d3d4820 100644 --- a/test/optimize/test_optimizer_common_interface.py +++ b/test/optimize/test_optimizer_common_interface.py @@ -66,6 +66,15 @@ def test_ess_optimizer_support(self): optimizer.set_maxtime(10.0) assert optimizer.max_walltime_s == 10.0 + def test_cma_optimizer_support(self): + """Test CmaOptimizer iteration limit support.""" + optimizer = optimize.CmaOptimizer() + + assert optimizer.supports_maxtime() is True + optimizer.set_maxtime(1000) + + assert optimizer.options["timeout"] == 1000 + def test_scipy_optimizer_support(self): """Test ScipyOptimizer time limit support.""" optimizer = optimize.ScipyOptimizer() @@ -136,11 +145,6 @@ def test_cma_optimizer_support(self): optimizer.set_maxiter(5000) assert optimizer.options["maxiter"] == 5000 - assert optimizer.supports_maxtime() is True - optimizer.set_maxtime(1000) - - assert optimizer.options["timeout"] == 1000 - def test_scipy_de_optimizer_support(self): """Test ScipyDifferentialEvolutionOptimizer iteration limit support.""" optimizer = optimize.ScipyDifferentialEvolutionOptimizer() From c2f4d73deb5a42c491c9c2460bd203f76ee799b4 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 24 Mar 2026 10:26:19 +0100 Subject: [PATCH 7/8] integrated comments --- pypesto/optimize/optimizer.py | 83 ++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index c1c48e73c..ff1a4af05 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -443,28 +443,6 @@ def set_f_abs_tol(self, tol: float) -> None: f"Check supports_f_abs_tol() before calling set_f_abs_tol()." ) - def _set_option_tol(self, tol: float, option_key: str) -> None: - """ - Set tolerance in options dict with validation. - - Parameters - ---------- - tol - Absolute tolerance value (must be positive). - option_key - The key to use in the options dictionary. - - Raises - ------ - ValueError - If tolerance is not positive. - """ - if tol < 0: - raise ValueError(f"Tolerance must be positive, got {tol}") - if self.options is None: - self.options = {} - self.options[option_key] = tol - class ScipyOptimizer(Optimizer): """ @@ -892,7 +870,9 @@ def set_maxtime(self, seconds: float) -> None: ) if self.options is None: self.options = {} - self.options["max_wall_time"] = seconds + # We explicitly cast to float, as the IpoptOptimizer requires + # the provision of a float for the max_wall_time option. + self.options["max_wall_time"] = float(seconds) def supports_maxiter(self) -> bool: """Check whether optimizer supports iteration limits.""" @@ -915,19 +895,6 @@ def supports_f_abs_tol(self) -> bool: """Check whether optimizer supports absolute function tolerance.""" return False - def set_tol(self, tol: float) -> None: - """ - Set the convergence tolerance. - - See https://coin-or.github.io/Ipopt/OPTIONS.html for more information. - - Parameters - ---------- - tol - Tolerance value for termination. - """ - self._set_option_tol(tol, "tol") - class DlibOptimizer(Optimizer): """Use the Dlib toolbox for optimization.""" @@ -1138,8 +1105,17 @@ def set_f_abs_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ - self._set_option_tol(tol, "minfunc") + if tol < 0: + raise ValueError(f"Tolerance must be positive, got {tol}") + if self.options is None: + self.options = {} + self.options["minfunc"] = tol class CmaOptimizer(Optimizer): @@ -1382,8 +1358,17 @@ def set_f_abs_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ - self._set_option_tol(tol, "atol") + if tol < 0: + raise ValueError(f"Tolerance must be positive, got {tol}") + if self.options is None: + self.options = {} + self.options["atol"] = tol class PyswarmsOptimizer(Optimizer): @@ -1810,8 +1795,17 @@ def set_f_abs_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ - self._set_option_tol(tol, "ftol_abs") + if tol < 0: + raise ValueError(f"Tolerance must be positive, got {tol}") + if self.options is None: + self.options = {} + self.options["ftol_abs"] = tol class FidesOptimizer(Optimizer): @@ -2046,10 +2040,19 @@ def set_f_abs_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ + if tol < 0: + raise ValueError(f"Tolerance must be positive, got {tol}") try: from fides.constants import Options as FidesOptions - self._set_option_tol(tol, FidesOptions.FATOL) + if self.options is None: + self.options = {} + self.options[FidesOptions.FATOL] = tol except ImportError: raise OptimizerImportError("fides") from None From be9fd7a0b9a63cab52980e9bb76cfbe800d4e120 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 24 Mar 2026 10:27:56 +0100 Subject: [PATCH 8/8] forgot one instance --- pypesto/optimize/optimizer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pypesto/optimize/optimizer.py b/pypesto/optimize/optimizer.py index ff1a4af05..26e712e4a 100644 --- a/pypesto/optimize/optimizer.py +++ b/pypesto/optimize/optimizer.py @@ -1246,8 +1246,17 @@ def set_f_abs_tol(self, tol: float) -> None: ---------- tol Absolute tolerance for termination. + + Raises + ------ + ValueError + If tolerance is not positive. """ - self._set_option_tol(tol, "tolfun") + if tol < 0: + raise ValueError(f"Tolerance must be positive, got {tol}") + if self.options is None: + self.options = {} + self.options["tolfun"] = tol class CmaesOptimizer(CmaOptimizer):