You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GDPopt LBB treats solver RuntimeError as node infeasibility #4023
GDPopt LBB catches every RuntimeError raised while solving a node subproblem and converts it into synthetic bounds. For an evaluated node, _solve_rnGDP_subproblem() returns (inf, inf), which classifies the node as infeasible and can prune a feasible branch after an infrastructure, licensing, or solver-interface failure.
The optional local-screening path also catches every RuntimeError and continues with trivial bounds, hiding the failed solve instead of reporting an indeterminate solver failure.
Steps to reproduce the issue
On current Pyomo/pyomomain, use a fake subsolver whose solve() method raises RuntimeError("simulated solver infrastructure failure"), then invoke each LBB node-solve helper:
The evaluated-node path then returns (inf, inf). This was also observed with a real GAMS/BARON run: GAMS produced a nonzero process status during one node solve, and LBB treated the resulting Pyomo GAMS-interface RuntimeError as mathematical infeasibility. The isolated solver run was intermittent, but the unsafe LBB classification is deterministic for any RuntimeError.
Information on your system
Pyomo version: source at Pyomo/pyomo@2744a2446cc58598fb9bb53d41ad1b4bc5e86183
Python version: 3.12.13
Operating system: Linux under WSL2
How Pyomo was installed: source checkout with the repository's existing pixi environment
Solver: solver-independent regression; originally observed with GAMS 54.2.2 / BARON 26.5.27
Additional information
The two broad catches are in pyomo/contrib/gdpopt/branch_and_bound.py, in _solve_rnGDP_subproblem() and _solve_local_rnGDP_subproblem().
This is distinct from #2483, #3941, and #3945. Open PRs #4010 and #4012 modify the same module, but neither changes the broad RuntimeError handling; #4012 retains both catches at its current head.
Tracked as part of B2 (LBB rehabilitation) in bernalde#2.
Confirmed on fresh Pyomo/pyomo main at 2744a2446cc58598fb9bb53d41ad1b4bc5e86183: both broad RuntimeError catches described in the issue suppress an indeterminate solver failure.
# ____________________________________________________________________________________## Pyomo: Python Optimization Modeling Objects# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC# Under the terms of Contract DE-NA0003525 with National Technology and Engineering# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this# software. This software is distributed under the 3-clause BSD License.# ____________________________________________________________________________________"""Regression tests for solver-infrastructure failures in GDPopt LBB."""fromunittest.mockimportpatchimportpyomo.common.unittestasunittestimportpyomo.contrib.gdpopt.branch_and_boundasbranch_and_boundfrompyomo.contrib.gdpopt.branch_and_boundimportGDP_LBB_Solverfrompyomo.contrib.gdpopt.create_oa_subproblemsimport (
add_algebraic_variable_list,
add_util_block,
)
frompyomo.environimportConcreteModel, Objective, Var, minimizefrompyomo.optimportSolverResultsclass_FailingSolver:
defsolve(self, model, **kwds):
raiseRuntimeError("simulated solver infrastructure failure")
classTestGDPoptLBBSubproblemErrors(unittest.TestCase):
def_make_solver_model_and_config(self):
model=ConcreteModel()
model.x=Var(bounds=(0, 1))
model.objective=Objective(expr=model.x)
solver=GDP_LBB_Solver()
solver.pyomo_results=SolverResults()
solver.pyomo_results.problem.sense=minimizesolver.original_util_block=add_util_block(model)
add_algebraic_variable_list(solver.original_util_block)
config=solver.CONFIG()
config.minlp_solver="failing_solver"config.minlp_solver_args= {}
config.local_minlp_solver="failing_solver"config.local_minlp_solver_args= {}
config.integer_tolerance=1e-5config.time_limit=Nonereturnsolver, model, config@patch.object(branch_and_bound, "SolverFactory", return_value=_FailingSolver() )deftest_evaluated_node_solver_error_propagates(self, solver_factory):
solver, model, config=self._make_solver_model_and_config()
withself.assertRaisesRegex(
RuntimeError, "simulated solver infrastructure failure"
):
solver._solve_rnGDP_subproblem(model, config)
@patch.object(branch_and_bound, "SolverFactory", return_value=_FailingSolver() )deftest_screening_solver_error_propagates(self, solver_factory):
solver, model, config=self._make_solver_model_and_config()
withself.assertRaisesRegex(
RuntimeError, "simulated solver infrastructure failure"
):
solver._solve_local_rnGDP_subproblem(model, config)
if__name__=="__main__":
unittest.main()
Red output on unchanged main:
FF [100%]E AssertionError: RuntimeError not raisedE AssertionError: RuntimeError not raised2 failed in 0.82s
Path
Observed
Expected
Evaluated node
Logs “Treating as infeasible” and returns (inf, inf)
Surface an indeterminate solver failure; do not prune the node as mathematically infeasible
Local screening
Logs “Treating as infeasible” and returns (-inf, inf)
Surface the failed screening solve instead of silently continuing
Non-vacuity check: adding a throwaway raise at the start of each catch made this exact test file pass (2 passed in 0.21s); the source change was then discarded.
Acceptance criteria
A node-solver exception that does not prove mathematical infeasibility cannot produce infeasible node bounds or prune a branch.
The evaluated-node and optional local-screening paths both surface solver/infrastructure failures through an explicit failure contract.
Existing handling for FBBT-proven infeasibility and TerminationCondition.infeasible remains intact.
The regression above and the existing GDPopt LBB tests pass.
Activity
bernalde commentedon Aug 18, 2026
Reproduction confirmed
Confirmed on fresh
Pyomo/pyomomain at2744a2446cc58598fb9bb53d41ad1b4bc5e86183: both broadRuntimeErrorcatches described in the issue suppress an indeterminate solver failure.Environment and command:
Proposed path:
pyomo/contrib/gdpopt/tests/test_lbb_solver_errors.pyRed output on unchanged main:
(inf, inf)(-inf, inf)Non-vacuity check: adding a throwaway
raiseat the start of each catch made this exact test file pass (2 passed in 0.21s); the source change was then discarded.Acceptance criteria
TerminationCondition.infeasibleremains intact.