From 375d062c74d26ba73765af5d3717c2e5967d0d92 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Thu, 10 Apr 2025 18:24:00 +0530 Subject: [PATCH 1/8] Added support for infinite gradients --- .../internal_optimization_problem.py | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 6e0e58e46..3789d6ff8 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -11,7 +11,11 @@ from optimagic.batch_evaluators import process_batch_evaluator from optimagic.differentiation.derivatives import first_derivative from optimagic.differentiation.numdiff_options import NumdiffOptions -from optimagic.exceptions import UserFunctionRuntimeError, get_traceback +from optimagic.exceptions import ( + InvalidFunctionError, + UserFunctionRuntimeError, + get_traceback, +) from optimagic.logging.logger import LogStore from optimagic.logging.types import IterationState from optimagic.optimization.fun_value import ( @@ -448,6 +452,8 @@ def _pure_evaluate_jac( params = self._converter.params_from_internal(x) try: jac_value = self._jac(params) + # Check for infinite values in the user-provided gradient + self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -508,6 +514,7 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: p = self._converter.params_from_internal(x) return self._fun(p) + params = self._converter.params_from_internal(x) try: numdiff_res = first_derivative( func, @@ -519,6 +526,8 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: ) fun_value = numdiff_res.func_value jac_value = numdiff_res.derivative + # Check for infinite values in the numerical gradient + self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -648,6 +657,8 @@ def _pure_evaluate_fun_and_jac( try: fun_value, jac_value = self._fun_and_jac(params) + # Check for infinite values in the user-provided gradient + self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -704,6 +715,32 @@ def _pure_evaluate_fun_and_jac( return (algo_fun_value, out_jac), hist_entry, log_entry + def _check_infinite_gradients(self, params: PyTree, grad: PyTree) -> None: + """Check for infinite values in gradients and raise an error if found. + + Args: + params: The parameters at which the gradient was evaluated. + grad: The gradient to check, which can be a numpy array or a dictionary + of numpy arrays. + + Raises: + InvalidFunctionError: If any infinite values are found in the gradient. + + """ + if isinstance(grad, dict): + # Convert dictionary to flattened numpy array for checking + flat_grad = np.concatenate([np.ravel(np.array(v)) for v in grad.values()]) + else: + flat_grad = np.ravel(np.array(grad)) + + if np.any(np.isinf(flat_grad)): + msg = ( + "Infinite values found in gradient.\n" + f"Parameters: {params},\n" + f"Gradient: {grad}" + ) + raise InvalidFunctionError(msg) + def _process_fun_value( value: SpecificFunctionValue, From 7d3ea5a0de1a1353bc60b7f516190cba4f45bd16 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Wed, 16 Apr 2025 17:45:42 +0530 Subject: [PATCH 2/8] Added Infinite Gradient handling --- .../internal_optimization_problem.py | 41 ++++++++++--------- src/optimagic/parameters/space_conversion.py | 1 - 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 3789d6ff8..37ea21781 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -452,8 +452,6 @@ def _pure_evaluate_jac( params = self._converter.params_from_internal(x) try: jac_value = self._jac(params) - # Check for infinite values in the user-provided gradient - self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -477,6 +475,8 @@ def _pure_evaluate_jac( out_jac = _process_jac_value( value=jac_value, direction=self._direction, converter=self._converter, x=x ) + # Check for infinite values in the user-provided gradient + self._check_infinite_gradients(params, out_jac, jac_value) stop_time = time.perf_counter() @@ -526,8 +526,6 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: ) fun_value = numdiff_res.func_value jac_value = numdiff_res.derivative - # Check for infinite values in the numerical gradient - self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -552,6 +550,9 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: warnings.warn(msg) fun_value, jac_value = self._error_penalty_func(x) + # Check for infinite values in the numerical gradient + self._check_infinite_gradients(params, jac_value, jac_value) + algo_fun_value, hist_fun_value = _process_fun_value( value=fun_value, # type: ignore solver_type=self._solver_type, @@ -657,8 +658,6 @@ def _pure_evaluate_fun_and_jac( try: fun_value, jac_value = self._fun_and_jac(params) - # Check for infinite values in the user-provided gradient - self._check_infinite_gradients(params, jac_value) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -693,6 +692,9 @@ def _pure_evaluate_fun_and_jac( if self._direction == Direction.MAXIMIZE: out_jac = -out_jac + # Check for infinite values in the user-provided gradient + self._check_infinite_gradients(params, out_jac, jac_value) + stop_time = time.perf_counter() hist_entry = HistoryEntry( @@ -715,29 +717,30 @@ def _pure_evaluate_fun_and_jac( return (algo_fun_value, out_jac), hist_entry, log_entry - def _check_infinite_gradients(self, params: PyTree, grad: PyTree) -> None: + def _check_infinite_gradients( + self, + params: PyTree, + out_jac: NDArray[np.float64], + jac_value: PyTree, + ) -> None: """Check for infinite values in gradients and raise an error if found. Args: - params: The parameters at which the gradient was evaluated. - grad: The gradient to check, which can be a numpy array or a dictionary - of numpy arrays. + x: internal parameter vector at which the gradient was evaluated. + params: user-facing parameter representation at evaluation point. + out_jac: internal processed gradient to check for infinities. + jac_value: original gradient value as returned by the user function, + included in error messages for debugging. Raises: InvalidFunctionError: If any infinite values are found in the gradient. """ - if isinstance(grad, dict): - # Convert dictionary to flattened numpy array for checking - flat_grad = np.concatenate([np.ravel(np.array(v)) for v in grad.values()]) - else: - flat_grad = np.ravel(np.array(grad)) - - if np.any(np.isinf(flat_grad)): + if np.any(np.isinf(out_jac)) or np.any(np.isnan(out_jac)): msg = ( - "Infinite values found in gradient.\n" + "Infinite or NaN values found in gradient.\n" f"Parameters: {params},\n" - f"Gradient: {grad}" + f"Gradient: {jac_value}" ) raise InvalidFunctionError(msg) diff --git a/src/optimagic/parameters/space_conversion.py b/src/optimagic/parameters/space_conversion.py index 73ed5edd3..f0710cf28 100644 --- a/src/optimagic/parameters/space_conversion.py +++ b/src/optimagic/parameters/space_conversion.py @@ -145,7 +145,6 @@ def get_space_converter( soft_lower_bounds=_soft_lower, soft_upper_bounds=_soft_upper, ) - return converter, params From b9fc5d252770de0375f633b625c2f65d411fd726 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Sat, 19 Apr 2025 11:49:18 +0530 Subject: [PATCH 3/8] minor fixes --- .../internal_optimization_problem.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 37ea21781..4b1c33cbb 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -475,8 +475,7 @@ def _pure_evaluate_jac( out_jac = _process_jac_value( value=jac_value, direction=self._direction, converter=self._converter, x=x ) - # Check for infinite values in the user-provided gradient - self._check_infinite_gradients(params, out_jac, jac_value) + self._assert_finite_jac(out_jac, jac_value, params) stop_time = time.perf_counter() @@ -550,8 +549,7 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: warnings.warn(msg) fun_value, jac_value = self._error_penalty_func(x) - # Check for infinite values in the numerical gradient - self._check_infinite_gradients(params, jac_value, jac_value) + self._assert_finite_jac(jac_value, jac_value, params) algo_fun_value, hist_fun_value = _process_fun_value( value=fun_value, # type: ignore @@ -692,8 +690,7 @@ def _pure_evaluate_fun_and_jac( if self._direction == Direction.MAXIMIZE: out_jac = -out_jac - # Check for infinite values in the user-provided gradient - self._check_infinite_gradients(params, out_jac, jac_value) + self._assert_finite_jac(out_jac, jac_value, params) stop_time = time.perf_counter() @@ -717,16 +714,13 @@ def _pure_evaluate_fun_and_jac( return (algo_fun_value, out_jac), hist_entry, log_entry - def _check_infinite_gradients( - self, - params: PyTree, - out_jac: NDArray[np.float64], - jac_value: PyTree, + def _assert_finite_jac( + self, out_jac: NDArray[np.float64], jac_value: PyTree, params: PyTree ) -> None: - """Check for infinite values in gradients and raise an error if found. + """Check for infinite and NaN values in the jacobian and raise an error if + found. Args: - x: internal parameter vector at which the gradient was evaluated. params: user-facing parameter representation at evaluation point. out_jac: internal processed gradient to check for infinities. jac_value: original gradient value as returned by the user function, @@ -736,7 +730,7 @@ def _check_infinite_gradients( InvalidFunctionError: If any infinite values are found in the gradient. """ - if np.any(np.isinf(out_jac)) or np.any(np.isnan(out_jac)): + if not np.all(np.isfinite(out_jac)): msg = ( "Infinite or NaN values found in gradient.\n" f"Parameters: {params},\n" From 107b0b92c37ab140881519a83658856fd42141fe Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Sat, 19 Apr 2025 12:38:01 +0530 Subject: [PATCH 4/8] added tests --- .../test_invalid_jacobian_value.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/optimagic/optimization/test_invalid_jacobian_value.py diff --git a/tests/optimagic/optimization/test_invalid_jacobian_value.py b/tests/optimagic/optimization/test_invalid_jacobian_value.py new file mode 100644 index 000000000..3a12bdacb --- /dev/null +++ b/tests/optimagic/optimization/test_invalid_jacobian_value.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +import pytest + +from optimagic.exceptions import InvalidFunctionError +from optimagic.optimization.fun_value import ( + LeastSquaresFunctionValue, + LikelihoodFunctionValue, + ScalarFunctionValue, +) +from optimagic.optimization.optimize import minimize + +SCALAR_VALUES = [ + ScalarFunctionValue(5), +] + +LS_VALUES = [ + LeastSquaresFunctionValue(np.array([1, 2])), + LeastSquaresFunctionValue({"a": 1, "b": 2}), +] + +LIKELIHOOD_VALUES = [ + LikelihoodFunctionValue(np.array([1, 4])), + LikelihoodFunctionValue({"a": 1, "b": 4}), +] + + +def test_with_infinite_jacobian_value_in_lists(): + def sphere(params): + return params @ params + + def sphere_gradient(params): + grad = 2 * params + grad[(abs(grad) < 1.0) & (abs(grad) > 0.0)] = ( + np.sign(grad)[(abs(grad) < 1.0) & (abs(grad) > 0.0)] * np.inf + ) + return grad + + with pytest.raises(InvalidFunctionError): + minimize( + fun=sphere, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) + + +def test_with_infinite_jacobian_value_in_dicts(): + def sphere(params): + return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + + def sphere_gradient(params): + grad = { + "a": 2 * params["a"] + if not ((abs(params["a"]) < 1.0) & (abs(params["a"]) > 0.0)) + else np.sign(params["a"]) * np.inf, + "b": 2 * params["b"] + if not ((abs(params["b"]) < 1.0) & (abs(params["b"]) > 0.0)) + else np.sign(params["b"]) * np.inf, + "c": 2 * params["c"] + if not ((abs(params["c"].sum()) < 1.0) & (abs(params["c"].sum()) > 0.0)) + else np.sign(params["c"]) * np.inf, + } + return grad + + with pytest.raises(InvalidFunctionError): + minimize( + fun=sphere, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) + + +def test_with_nan_jacobian_value_in_lists(): + def sphere(params): + return params @ params + + def sphere_gradient(params): + grad = 2 * params + grad[(abs(grad) < 1.0) & (abs(grad) > 0.0)] = ( + np.sign(grad)[(abs(grad) < 1.0) & (abs(grad) > 0.0)] * np.nan + ) + return grad + + with pytest.raises(InvalidFunctionError): + minimize( + fun=sphere, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) + + +def test_with_nan_jacobian_value_in_dicts(): + def sphere(params): + return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + + def sphere_gradient(params): + grad = { + "a": 2 * params["a"] + if not ((abs(params["a"]) < 1.0) & (abs(params["a"]) > 0.0)) + else np.sign(params["a"]) * np.nan, + "b": 2 * params["b"] + if not ((abs(params["b"]) < 1.0) & (abs(params["b"]) > 0.0)) + else np.sign(params["b"]) * np.nan, + "c": 2 * params["c"] + if not ((abs(params["c"].sum()) < 1.0) & (abs(params["c"].sum()) > 0.0)) + else np.sign(params["c"]) * np.nan, + } + return grad + + with pytest.raises(InvalidFunctionError): + minimize( + fun=sphere, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) From d399ea4b9f6134a33c9aff9dbf60f626e9b6c336 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Sat, 19 Apr 2025 12:46:31 +0530 Subject: [PATCH 5/8] final polishing --- src/optimagic/optimization/internal_optimization_problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 4b1c33cbb..107dc49a7 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -721,10 +721,10 @@ def _assert_finite_jac( found. Args: - params: user-facing parameter representation at evaluation point. out_jac: internal processed gradient to check for infinities. jac_value: original gradient value as returned by the user function, included in error messages for debugging. + params: user-facing parameter representation at evaluation point. Raises: InvalidFunctionError: If any infinite values are found in the gradient. From 7910e133182b670745e5daf873e8a807d98070d3 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Tue, 29 Apr 2025 21:00:05 +0530 Subject: [PATCH 6/8] added exhaustive tests --- .../internal_optimization_problem.py | 17 +- .../test_invalid_jacobian_value.py | 343 +++++++++++++++--- 2 files changed, 292 insertions(+), 68 deletions(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 107dc49a7..5b4d970e2 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -12,7 +12,6 @@ from optimagic.differentiation.derivatives import first_derivative from optimagic.differentiation.numdiff_options import NumdiffOptions from optimagic.exceptions import ( - InvalidFunctionError, UserFunctionRuntimeError, get_traceback, ) @@ -721,22 +720,24 @@ def _assert_finite_jac( found. Args: - out_jac: internal processed gradient to check for infinities. - jac_value: original gradient value as returned by the user function, + out_jac: internal processed jacobian to check for infinities. + jac_value: original jacobian value as returned by the user function, included in error messages for debugging. params: user-facing parameter representation at evaluation point. Raises: - InvalidFunctionError: If any infinite values are found in the gradient. + UserFunctionRuntimeError: If any infinite values are found in the jacobian. """ if not np.all(np.isfinite(out_jac)): msg = ( - "Infinite or NaN values found in gradient.\n" - f"Parameters: {params},\n" - f"Gradient: {jac_value}" + "The optimization received Jacobian containing infinite ", + "or NaN values.\nCheck your objective function or its " + "jacobian, or try a different optimizer.\n", + f"Parameters at evaluation point: {params}\n" + f"Jacobian values: {jac_value}", ) - raise InvalidFunctionError(msg) + raise UserFunctionRuntimeError(msg) def _process_fun_value( diff --git a/tests/optimagic/optimization/test_invalid_jacobian_value.py b/tests/optimagic/optimization/test_invalid_jacobian_value.py index 3a12bdacb..edc9ccf1e 100644 --- a/tests/optimagic/optimization/test_invalid_jacobian_value.py +++ b/tests/optimagic/optimization/test_invalid_jacobian_value.py @@ -2,41 +2,43 @@ import pandas as pd import pytest -from optimagic.exceptions import InvalidFunctionError -from optimagic.optimization.fun_value import ( - LeastSquaresFunctionValue, - LikelihoodFunctionValue, - ScalarFunctionValue, -) +from optimagic.exceptions import UserFunctionRuntimeError from optimagic.optimization.optimize import minimize -SCALAR_VALUES = [ - ScalarFunctionValue(5), -] -LS_VALUES = [ - LeastSquaresFunctionValue(np.array([1, 2])), - LeastSquaresFunctionValue({"a": 1, "b": 2}), -] +def test_with_infinite_jac_value_unconditional_in_lists(): + def sphere(params): + return params @ params -LIKELIHOOD_VALUES = [ - LikelihoodFunctionValue(np.array([1, 4])), - LikelihoodFunctionValue({"a": 1, "b": 4}), -] + def sphere_gradient(params): + return np.full_like(params, np.inf) + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun=sphere, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) -def test_with_infinite_jacobian_value_in_lists(): + +def test_with_infinite_jac_value_conditional_in_lists(): def sphere(params): return params @ params + def true_gradient(params): + return 2 * params + + def param_norm(params): + return np.norm(params) + def sphere_gradient(params): - grad = 2 * params - grad[(abs(grad) < 1.0) & (abs(grad) > 0.0)] = ( - np.sign(grad)[(abs(grad) < 1.0) & (abs(grad) > 0.0)] * np.inf - ) - return grad + if param_norm(params) >= 1: + return true_gradient(params) + else: + return np.full_like(params, np.inf) - with pytest.raises(InvalidFunctionError): + with pytest.raises(UserFunctionRuntimeError): minimize( fun=sphere, params=np.arange(10) + 400, @@ -45,25 +47,82 @@ def sphere_gradient(params): ) -def test_with_infinite_jacobian_value_in_dicts(): +def test_with_infinite_fun_and_jac_value_unconditional_in_lists(): + def sphere_and_gradient(params): + function_value = params @ params + grad = np.full_like(params, np.inf) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + ) + + +def test_with_infinite_fun_and_jac_value_conditional_in_lists(): + def true_gradient(params): + return 2 * params + + def param_norm(params): + return np.norm(params) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return np.full_like(params, np.inf) + + def sphere_and_gradient(params): + function_value = params @ params + grad = sphere_gradient(params) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + ) + + +def test_with_infinite_jac_value_unconditional_in_dicts(): + def sphere(params): + return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + + def sphere_gradient(params): + return {"a": np.inf, "b": np.inf, "c": np.full_like(params["c"], np.inf)} + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun=sphere, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) + + +def test_with_infinite_jac_value_conditional_in_dicts(): def sphere(params): return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + def true_gradient(params): + return {"a": 2 * params["a"], "b": 2 * params["b"], "c": 2 * params["c"]} + + def param_norm(params): + squared_norm = ( + params["a"] ** 2 + params["b"] ** 2 + np.linalg.norm(params["c"]) ** 2 + ) + return np.sqrt(squared_norm) + def sphere_gradient(params): - grad = { - "a": 2 * params["a"] - if not ((abs(params["a"]) < 1.0) & (abs(params["a"]) > 0.0)) - else np.sign(params["a"]) * np.inf, - "b": 2 * params["b"] - if not ((abs(params["b"]) < 1.0) & (abs(params["b"]) > 0.0)) - else np.sign(params["b"]) * np.inf, - "c": 2 * params["c"] - if not ((abs(params["c"].sum()) < 1.0) & (abs(params["c"].sum()) > 0.0)) - else np.sign(params["c"]) * np.inf, - } - return grad - - with pytest.raises(InvalidFunctionError): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return {"a": np.inf, "b": np.inf, "c": np.full_like(params["c"], np.inf)} + + with pytest.raises(UserFunctionRuntimeError): minimize( fun=sphere, params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, @@ -72,18 +131,82 @@ def sphere_gradient(params): ) -def test_with_nan_jacobian_value_in_lists(): +def test_with_infinite_fun_and_jac_value_unconditional_in_dicts(): + def sphere_and_gradient(params): + function_value = params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + grad = {"a": np.inf, "b": np.inf, "c": np.full_like(params["c"], np.inf)} + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + ) + + +def test_with_infinite_fun_and_jac_value_conditional_in_dicts(): + def true_gradient(params): + return {"a": 2 * params["a"], "b": 2 * params["b"], "c": 2 * params["c"]} + + def param_norm(params): + squared_norm = ( + params["a"] ** 2 + params["b"] ** 2 + np.linalg.norm(params["c"]) ** 2 + ) + return np.sqrt(squared_norm) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return {"a": np.inf, "b": np.inf, "c": np.full_like(params["c"], np.inf)} + + def sphere_and_gradient(params): + function_value = params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + grad = sphere_gradient(params) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + ) + + +def test_with_nan_jac_value_unconditional_in_lists(): def sphere(params): return params @ params def sphere_gradient(params): - grad = 2 * params - grad[(abs(grad) < 1.0) & (abs(grad) > 0.0)] = ( - np.sign(grad)[(abs(grad) < 1.0) & (abs(grad) > 0.0)] * np.nan + return np.full_like(params, np.nan) + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun=sphere, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, ) - return grad - with pytest.raises(InvalidFunctionError): + +def test_with_nan_jac_value_conditional_in_lists(): + def sphere(params): + return params @ params + + def true_gradient(params): + return 2 * params + + def param_norm(params): + return np.norm(params) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return np.full_like(params, np.nan) + + with pytest.raises(UserFunctionRuntimeError): minimize( fun=sphere, params=np.arange(10) + 400, @@ -92,28 +215,128 @@ def sphere_gradient(params): ) -def test_with_nan_jacobian_value_in_dicts(): +def test_with_nan_fun_and_jac_value_unconditional_in_lists(): + def sphere_and_gradient(params): + function_value = params @ params + grad = np.full_like(params, np.nan) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + ) + + +def test_with_nan_fun_and_jac_value_conditional_in_lists(): + def true_gradient(params): + return 2 * params + + def param_norm(params): + return np.norm(params) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return np.full_like(params, np.nan) + + def sphere_and_gradient(params): + function_value = params @ params + grad = sphere_gradient(params) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params=np.arange(10) + 400, + algorithm="scipy_lbfgsb", + ) + + +def test_with_nan_jac_value_unconditional_in_dicts(): def sphere(params): return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() def sphere_gradient(params): - grad = { - "a": 2 * params["a"] - if not ((abs(params["a"]) < 1.0) & (abs(params["a"]) > 0.0)) - else np.sign(params["a"]) * np.nan, - "b": 2 * params["b"] - if not ((abs(params["b"]) < 1.0) & (abs(params["b"]) > 0.0)) - else np.sign(params["b"]) * np.nan, - "c": 2 * params["c"] - if not ((abs(params["c"].sum()) < 1.0) & (abs(params["c"].sum()) > 0.0)) - else np.sign(params["c"]) * np.nan, - } - return grad - - with pytest.raises(InvalidFunctionError): + return {"a": np.nan, "b": np.nan, "c": np.full_like(params["c"], np.nan)} + + with pytest.raises(UserFunctionRuntimeError): minimize( fun=sphere, params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, algorithm="scipy_lbfgsb", jac=sphere_gradient, ) + + +def test_with_nan_jac_value_conditional_in_dicts(): + def sphere(params): + return params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + + def true_gradient(params): + return {"a": 2 * params["a"], "b": 2 * params["b"], "c": 2 * params["c"]} + + def param_norm(params): + squared_norm = ( + params["a"] ** 2 + params["b"] ** 2 + np.linalg.norm(params["c"]) ** 2 + ) + return np.sqrt(squared_norm) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return {"a": np.nan, "b": np.nan, "c": np.full_like(params["c"], np.nan)} + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun=sphere, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + jac=sphere_gradient, + ) + + +def test_with_nan_fun_and_jac_value_unconditional_in_dicts(): + def sphere_and_gradient(params): + function_value = params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + grad = {"a": np.nan, "b": np.nan, "c": np.full_like(params["c"], np.nan)} + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + ) + + +def test_with_nan_fun_and_jac_value_conditional_in_dicts(): + def true_gradient(params): + return {"a": 2 * params["a"], "b": 2 * params["b"], "c": 2 * params["c"]} + + def param_norm(params): + squared_norm = ( + params["a"] ** 2 + params["b"] ** 2 + np.linalg.norm(params["c"]) ** 2 + ) + return np.sqrt(squared_norm) + + def sphere_gradient(params): + if param_norm(params) >= 1: + return true_gradient(params) + else: + return {"a": np.nan, "b": np.nan, "c": np.full_like(params["c"], np.nan)} + + def sphere_and_gradient(params): + function_value = params["a"] ** 2 + params["b"] ** 2 + (params["c"] ** 2).sum() + grad = sphere_gradient(params) + return function_value, grad + + with pytest.raises(UserFunctionRuntimeError): + minimize( + fun_and_jac=sphere_and_gradient, + params={"a": 400, "b": 400, "c": pd.Series([200, 300, 400])}, + algorithm="scipy_lbfgsb", + ) From 8980861412b09271193f5cc07a7b55e445fb0f58 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Tue, 29 Apr 2025 23:01:32 +0530 Subject: [PATCH 7/8] bug fixes --- .../optimization/internal_optimization_problem.py | 7 ++++--- .../optimagic/optimization/test_invalid_jacobian_value.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 5b4d970e2..9e2ccb807 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -451,6 +451,7 @@ def _pure_evaluate_jac( params = self._converter.params_from_internal(x) try: jac_value = self._jac(params) + # print('jac ', jac_value[0], 'params ', params[0]) except (KeyboardInterrupt, SystemExit): raise except Exception as e: @@ -731,11 +732,11 @@ def _assert_finite_jac( """ if not np.all(np.isfinite(out_jac)): msg = ( - "The optimization received Jacobian containing infinite ", + "The optimization received Jacobian containing infinite " "or NaN values.\nCheck your objective function or its " - "jacobian, or try a different optimizer.\n", + "jacobian, or try a different optimizer.\n" f"Parameters at evaluation point: {params}\n" - f"Jacobian values: {jac_value}", + f"Jacobian values: {jac_value}" ) raise UserFunctionRuntimeError(msg) diff --git a/tests/optimagic/optimization/test_invalid_jacobian_value.py b/tests/optimagic/optimization/test_invalid_jacobian_value.py index edc9ccf1e..906579b24 100644 --- a/tests/optimagic/optimization/test_invalid_jacobian_value.py +++ b/tests/optimagic/optimization/test_invalid_jacobian_value.py @@ -30,7 +30,7 @@ def true_gradient(params): return 2 * params def param_norm(params): - return np.norm(params) + return np.linalg.norm(params) def sphere_gradient(params): if param_norm(params) >= 1: @@ -66,7 +66,7 @@ def true_gradient(params): return 2 * params def param_norm(params): - return np.norm(params) + return np.linalg.norm(params) def sphere_gradient(params): if param_norm(params) >= 1: @@ -198,7 +198,7 @@ def true_gradient(params): return 2 * params def param_norm(params): - return np.norm(params) + return np.linalg.norm(params) def sphere_gradient(params): if param_norm(params) >= 1: @@ -234,7 +234,7 @@ def true_gradient(params): return 2 * params def param_norm(params): - return np.norm(params) + return np.linalg.norm(params) def sphere_gradient(params): if param_norm(params) >= 1: From 4989d2bc06a3f27f6e8d4a967e79705c1091b814 Mon Sep 17 00:00:00 2001 From: Aziz-Shameem Date: Tue, 29 Apr 2025 23:01:54 +0530 Subject: [PATCH 8/8] bug fixes --- src/optimagic/optimization/internal_optimization_problem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 9e2ccb807..5205fd3af 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -451,7 +451,6 @@ def _pure_evaluate_jac( params = self._converter.params_from_internal(x) try: jac_value = self._jac(params) - # print('jac ', jac_value[0], 'params ', params[0]) except (KeyboardInterrupt, SystemExit): raise except Exception as e: