From b1f9ab13567d21b60453781bc9532b3890c50349 Mon Sep 17 00:00:00 2001 From: dance858 Date: Wed, 6 May 2026 20:57:24 +0200 Subject: [PATCH] better handling of all infeasible/unbounded in best of --- cvxpy/reductions/solvers/nlp_solving_chain.py | 11 ++++++++--- cvxpy/tests/nlp_tests/test_best_of.py | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cvxpy/reductions/solvers/nlp_solving_chain.py b/cvxpy/reductions/solvers/nlp_solving_chain.py index 954f37444b..5e245b4c16 100644 --- a/cvxpy/reductions/solvers/nlp_solving_chain.py +++ b/cvxpy/reductions/solvers/nlp_solving_chain.py @@ -209,12 +209,17 @@ def solve_nlp(problem, solver, warm_start, verbose, **kwargs): verbose, solver_opts=kwargs, solver_cache=solver_cache) - # Unpack to get the objective value in the original problem space + # unpack to get the objective value in the original problem space. + # (+inf for infeasible runs, -inf for unbounded runs) problem.unpack_results(solution, nlp_chain, inverse_data) - obj_value = problem.objective.value + obj_value = problem.value all_objs[run] = obj_value - if obj_value is not None and obj_value < best_obj: + + # always set best_solution with the first run so that even an + # all-infeasible best_of has a solution to unpack at the end (its + # INFEASIBLE status then propagates through unpack_results). + if best_solution is None or obj_value < best_obj: best_obj = obj_value best_solution = solution diff --git a/cvxpy/tests/nlp_tests/test_best_of.py b/cvxpy/tests/nlp_tests/test_best_of.py index 27bd3dfcd0..c5fb6797df 100644 --- a/cvxpy/tests/nlp_tests/test_best_of.py +++ b/cvxpy/tests/nlp_tests/test_best_of.py @@ -114,4 +114,21 @@ def test_path_planning_best_of_five(self): all_objs = prob.solver_stats.extra_stats['all_objs_from_best_of'] assert len(all_objs) == 3 -# TODO add a test that best_of actually caches the sparsity pattern between solves + def test_best_of_infeasible_problem(self): + # test that if the problem is infeasible, then best_of returns inf as the objective value + x = cp.Variable(bounds=[-5, 5]) + y = cp.Variable(bounds=[-3, 3]) + constraints = [x + y == 10] + obj = cp.Minimize((x - 1) ** 2 + (y - 2) ** 2) + prob = cp.Problem(obj, constraints) + prob.solve(nlp=True, best_of=20, verbose=True) + assert prob.value == float("inf") + + def test_best_of_with_unbounded(self): + # test that if the problem is unbounded, then best_of returns -inf as the objective value + x = cp.Variable() + x.sample_bounds = [-5, 5] + obj = cp.Minimize(x) + prob = cp.Problem(obj) + prob.solve(nlp=True, best_of=20, verbose=True) + assert prob.value == float("-inf")