镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

GDPopt LBB treats solver RuntimeError as node infeasibility #4023

Open

Description

@bernalde

Summary

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/pyomo main, use a fake subsolver whose solve() method raises RuntimeError("simulated solver infrastructure failure"), then invoke each LBB node-solve helper:

$ python -m pytest -q pyomo/contrib/gdpopt/tests/test_lbb_solver_errors.py
FF

The complete solver-independent regression test and red output are posted in the triage comment below.

Error Message

Instead of propagating the solver failure, both paths log:

Solver encountered RuntimeError. Treating as infeasible. Msg: simulated solver infrastructure failure

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.

Activity

  1. bernalde commented on Aug 18, 2026

    @bernalde
    ContributorAuthor

    Reproduction confirmed

    Confirmed on fresh Pyomo/pyomo main at 2744a2446cc58598fb9bb53d41ad1b4bc5e86183: both broad RuntimeError catches described in the issue suppress an indeterminate solver failure.

    Environment and command:

    Python 3.12.13
    /home/bernalde/repos/pyomo/.pixi/envs/mindtpy-open/bin/python -m pytest -q -ra pyomo/contrib/gdpopt/tests/test_lbb_solver_errors.py

    Proposed path: pyomo/contrib/gdpopt/tests/test_lbb_solver_errors.py

    # ____________________________________________________________________________________
    #
    # 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."""
    
    from unittest.mock import patch
    
    import pyomo.common.unittest as unittest
    import pyomo.contrib.gdpopt.branch_and_bound as branch_and_bound
    from pyomo.contrib.gdpopt.branch_and_bound import GDP_LBB_Solver
    from pyomo.contrib.gdpopt.create_oa_subproblems import (
        add_algebraic_variable_list,
        add_util_block,
    )
    from pyomo.environ import ConcreteModel, Objective, Var, minimize
    from pyomo.opt import SolverResults
    
    
    class _FailingSolver:
        def solve(self, model, **kwds):
            raise RuntimeError("simulated solver infrastructure failure")
    
    
    class TestGDPoptLBBSubproblemErrors(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 = minimize
            solver.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-5
            config.time_limit = None
            return solver, model, config
    
        @patch.object(
            branch_and_bound, "SolverFactory", return_value=_FailingSolver()
        )
        def test_evaluated_node_solver_error_propagates(self, solver_factory):
            solver, model, config = self._make_solver_model_and_config()
    
            with self.assertRaisesRegex(
                RuntimeError, "simulated solver infrastructure failure"
            ):
                solver._solve_rnGDP_subproblem(model, config)
    
        @patch.object(
            branch_and_bound, "SolverFactory", return_value=_FailingSolver()
        )
        def test_screening_solver_error_propagates(self, solver_factory):
            solver, model, config = self._make_solver_model_and_config()
    
            with self.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 raised
    E       AssertionError: RuntimeError not raised
    2 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions