From f6e8d57e7c7590c1d007a36b08c5dbb67eaa0b20 Mon Sep 17 00:00:00 2001 From: mickaelbegon Date: Wed, 22 Jul 2026 11:06:53 -0400 Subject: [PATCH 1/2] Expose Ipopt termination controls --- bioptim/interfaces/ipopt_options.py | 32 +++++++++++++++++++++++++++++ tests/shard5/test_solver_options.py | 12 +++++++++++ 2 files changed, 44 insertions(+) diff --git a/bioptim/interfaces/ipopt_options.py b/bioptim/interfaces/ipopt_options.py index 80c302690..84ea61be9 100644 --- a/bioptim/interfaces/ipopt_options.py +++ b/bioptim/interfaces/ipopt_options.py @@ -92,10 +92,14 @@ class IPOPT(GenericSolver): _constr_viol_tol: Float = 0.0001 _compl_inf_tol: Float = 0.0001 _acceptable_tol: Float = 1e-6 + _acceptable_iter: Int = 15 + _acceptable_obj_change_tol: Float = 1e20 _acceptable_dual_inf_tol: Float = 1e10 _acceptable_constr_viol_tol: Float = 1e-2 _acceptable_compl_inf_tol: Float = 1e-2 _max_iter: Int = 1000 + _max_wall_time: Float = 1e20 + _max_cpu_time: Float = 1e20 _hessian_approximation: Str = "exact" # "exact", "limited-memory" _nlp_scaling_method: Str = "gradient-based" # "none" _limited_memory_max_history: Int = 50 @@ -133,6 +137,14 @@ def compl_inf_tol(self) -> Float: def acceptable_tol(self) -> Float: return self._acceptable_tol + @property + def acceptable_iter(self) -> Int: + return self._acceptable_iter + + @property + def acceptable_obj_change_tol(self) -> Float: + return self._acceptable_obj_change_tol + @property def acceptable_dual_inf_tol(self) -> Float: return self._acceptable_dual_inf_tol @@ -149,6 +161,14 @@ def acceptable_compl_inf_tol(self) -> Float: def max_iter(self) -> Int: return self._max_iter + @property + def max_wall_time(self) -> Float: + return self._max_wall_time + + @property + def max_cpu_time(self) -> Float: + return self._max_cpu_time + @property def hessian_approximation(self) -> Str: return self._hessian_approximation @@ -228,6 +248,12 @@ def set_compl_inf_tol(self, val: Float) -> None: def set_acceptable_tol(self, val: Float) -> None: self._acceptable_tol = val + def set_acceptable_iter(self, num: Int) -> None: + self._acceptable_iter = num + + def set_acceptable_obj_change_tol(self, val: Float) -> None: + self._acceptable_obj_change_tol = val + def set_acceptable_dual_inf_tol(self, val: Float) -> None: self._acceptable_dual_inf_tol = val @@ -240,6 +266,12 @@ def set_acceptable_compl_inf_tol(self, val: Float) -> None: def set_maximum_iterations(self, num: Int) -> None: self._max_iter = num + def set_maximum_wall_time(self, val: Float) -> None: + self._max_wall_time = val + + def set_maximum_cpu_time(self, val: Float) -> None: + self._max_cpu_time = val + def set_hessian_approximation(self, val: Str) -> None: self._hessian_approximation = val diff --git a/tests/shard5/test_solver_options.py b/tests/shard5/test_solver_options.py index d4cde2e5d..6cc7ef6f5 100644 --- a/tests/shard5/test_solver_options.py +++ b/tests/shard5/test_solver_options.py @@ -32,10 +32,14 @@ def test_ipopt_solver_options(): assert solver.constr_viol_tol == 0.0001 assert solver.compl_inf_tol == 0.0001 assert solver.acceptable_tol == 1e-6 + assert solver.acceptable_iter == 15 + assert solver.acceptable_obj_change_tol == 1e20 assert solver.acceptable_dual_inf_tol == 1e10 assert solver.acceptable_constr_viol_tol == 1e-2 assert solver.acceptable_compl_inf_tol == 1e-2 assert solver.max_iter == 1000 + assert solver.max_wall_time == 1e20 + assert solver.max_cpu_time == 1e20 assert solver.hessian_approximation == "exact" assert solver.limited_memory_max_history == 50 assert solver.linear_solver == "mumps" @@ -65,6 +69,10 @@ def test_ipopt_solver_options(): assert solver.compl_inf_tol == 5 solver.set_acceptable_tol(6) assert solver.acceptable_tol == 6 + solver.set_acceptable_iter(7) + assert solver.acceptable_iter == 7 + solver.set_acceptable_obj_change_tol(8) + assert solver.acceptable_obj_change_tol == 8 solver.set_acceptable_dual_inf_tol(7) assert solver.acceptable_dual_inf_tol == 7 solver.set_acceptable_constr_viol_tol(8) @@ -73,6 +81,10 @@ def test_ipopt_solver_options(): assert solver.acceptable_compl_inf_tol == 9 solver.set_maximum_iterations(10) assert solver.max_iter == 10 + solver.set_maximum_wall_time(11) + assert solver.max_wall_time == 11 + solver.set_maximum_cpu_time(12) + assert solver.max_cpu_time == 12 solver.set_hessian_approximation("hello bioptim") assert solver.hessian_approximation == "hello bioptim" solver.set_nlp_scaling_method("how are you?") From e8b4779d8205f7d197b67ccade6b9f630149123e Mon Sep 17 00:00:00 2001 From: mickaelbegon Date: Wed, 22 Jul 2026 11:09:24 -0400 Subject: [PATCH 2/2] Expose Ipopt derivative checker options --- bioptim/interfaces/ipopt_options.py | 50 ++++++++++++++++++++++++++++- tests/shard5/test_solver_options.py | 18 +++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/bioptim/interfaces/ipopt_options.py b/bioptim/interfaces/ipopt_options.py index 84ea61be9..1822eea9b 100644 --- a/bioptim/interfaces/ipopt_options.py +++ b/bioptim/interfaces/ipopt_options.py @@ -116,6 +116,12 @@ class IPOPT(GenericSolver): _print_level: Int = 5 _c_compile: Bool = False _check_derivatives_for_naninf: Str = "no" # "yes" + _derivative_test: Str = "none" + _derivative_test_first_index: Int = -2 + _derivative_test_perturbation: Float = 1e-8 + _derivative_test_tol: Float = 1e-4 + _derivative_test_print_all: Str = "no" + _point_perturbation_radius: Float = 10.0 @property def tol(self) -> Float: @@ -230,9 +236,33 @@ def c_compile(self) -> Bool: return self._c_compile @property - def check_derivatives_for_naninf(self) -> Bool: + def check_derivatives_for_naninf(self) -> Str: return self._check_derivatives_for_naninf + @property + def derivative_test(self) -> Str: + return self._derivative_test + + @property + def derivative_test_first_index(self) -> Int: + return self._derivative_test_first_index + + @property + def derivative_test_perturbation(self) -> Float: + return self._derivative_test_perturbation + + @property + def derivative_test_tol(self) -> Float: + return self._derivative_test_tol + + @property + def derivative_test_print_all(self) -> Str: + return self._derivative_test_print_all + + @property + def point_perturbation_radius(self) -> Float: + return self._point_perturbation_radius + def set_tol(self, val: Float) -> None: self._tol = val @@ -321,6 +351,24 @@ def set_check_derivatives_for_naninf(self, val: Bool) -> None: string_val = "yes" if val else "no" self._check_derivatives_for_naninf = string_val + def set_derivative_test(self, val: Str) -> None: + self._derivative_test = val + + def set_derivative_test_first_index(self, index: Int) -> None: + self._derivative_test_first_index = index + + def set_derivative_test_perturbation(self, val: Float) -> None: + self._derivative_test_perturbation = val + + def set_derivative_test_tol(self, val: Float) -> None: + self._derivative_test_tol = val + + def set_derivative_test_print_all(self, val: Bool) -> None: + self._derivative_test_print_all = "yes" if val else "no" + + def set_point_perturbation_radius(self, val: Float) -> None: + self._point_perturbation_radius = val + def set_convergence_tolerance(self, val: Float) -> None: self._tol = val self._compl_inf_tol = val diff --git a/tests/shard5/test_solver_options.py b/tests/shard5/test_solver_options.py index 6cc7ef6f5..42962454e 100644 --- a/tests/shard5/test_solver_options.py +++ b/tests/shard5/test_solver_options.py @@ -56,6 +56,12 @@ def test_ipopt_solver_options(): assert solver.print_level == 5 assert solver.c_compile is False assert solver.check_derivatives_for_naninf == "no" + assert solver.derivative_test == "none" + assert solver.derivative_test_first_index == -2 + assert solver.derivative_test_perturbation == 1e-8 + assert solver.derivative_test_tol == 1e-4 + assert solver.derivative_test_print_all == "no" + assert solver.point_perturbation_radius == 10.0 solver.set_linear_solver("ma57") assert solver.linear_solver == "ma57" @@ -115,6 +121,18 @@ def test_ipopt_solver_options(): assert solver.c_compile is True solver.set_check_derivatives_for_naninf(True) assert solver.check_derivatives_for_naninf == "yes" + solver.set_derivative_test("first-order") + assert solver.derivative_test == "first-order" + solver.set_derivative_test_first_index(3) + assert solver.derivative_test_first_index == 3 + solver.set_derivative_test_perturbation(1e-7) + assert solver.derivative_test_perturbation == 1e-7 + solver.set_derivative_test_tol(1e-3) + assert solver.derivative_test_tol == 1e-3 + solver.set_derivative_test_print_all(True) + assert solver.derivative_test_print_all == "yes" + solver.set_point_perturbation_radius(2.5) + assert solver.point_perturbation_radius == 2.5 solver.set_convergence_tolerance(21) assert solver.tol == 21