Skip to content

Add LD-BD implementation merged with main#8

Merged
bernalde merged 44 commits intoAlbertLee125:mainfrom
Toflamus:fix/ldbd-merge
Feb 25, 2026
Merged

Add LD-BD implementation merged with main#8
bernalde merged 44 commits intoAlbertLee125:mainfrom
Toflamus:fix/ldbd-merge

Conversation

@Toflamus
Copy link
Copy Markdown
Collaborator

Fixes # .

Summary/Motivation:

Add LD-BD implementation

Changes proposed in this PR:

  • Add pyomo\pyomo\contrib\gdpopt\ldbd.py
  • Add pyomo\pyomo\contrib\gdpopt\discrete_search_enums.py
  • Add pyomo\pyomo\contrib\gdpopt\discrete_algorithm_base_class.py
  • Add pyomo\pyomo\contrib\gdpopt\tests\test_ldbd.py

Legal Acknowledgement

By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new GDPopt discrete-search solver implementation for Logic-based Discrete Benders Decomposition (LD-BD), along with supporting shared discrete-search infrastructure (base class + enums), configuration options, plugin registration, and a comprehensive test suite.

Changes:

  • Introduce gdpopt.ldbd solver implementation and register it via GDPopt plugins.
  • Add shared discrete-search primitives (SearchPhase, DirectionNorm) and a discrete algorithm base class with point tracking / caching support.
  • Add LD-BD unit and integration-style tests, plus configuration wiring for LD-BD-specific options.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pyomo/contrib/gdpopt/ldbd.py Implements the LD-BD algorithm, master MILP, separation LP, and refinement loop.
pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Adds a reusable discrete-search base with point registry, caching, and subproblem evaluation helpers used by LD-BD.
pyomo/contrib/gdpopt/discrete_search_enums.py Defines lightweight enums used by discrete-search solvers for phases and neighborhood norms.
pyomo/contrib/gdpopt/config_options.py Adds _add_ldbd_configs to expose LD-BD-specific configuration options.
pyomo/contrib/gdpopt/plugins.py Registers the new gdpopt.ldbd solver module for discovery/loading.
pyomo/contrib/gdpopt/tests/test_ldbd.py Adds extensive unit tests (mock-based) and an integration-style solve test for LD-BD.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/ldbd.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_search_enums.py Outdated
Comment thread pyomo/contrib/gdpopt/ldbd.py
Comment thread pyomo/contrib/gdpopt/tests/test_ldbd.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pyomo/contrib/gdpopt/config_options.py
Comment thread pyomo/contrib/gdpopt/config_options.py
Comment thread pyomo/contrib/gdpopt/ldbd.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Toflamus
Copy link
Copy Markdown
Collaborator Author

@bernalde @AlbertLee125 The PR of LD-BD is ready to review

Copy link
Copy Markdown
Collaborator

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some changes need to be addressed before merging this PR. More importantly, the tests are failing, so we need to make sure that those are passing

Comment thread pyomo/contrib/gdpopt/tests/test_ldbd.py Outdated
Comment thread pyomo/contrib/gdpopt/tests/test_ldbd.py Outdated
Comment thread pyomo/contrib/gdpopt/tests/test_ldbd.py Outdated
Comment thread pyomo/contrib/gdpopt/tests/test_ldbd.py
Comment thread pyomo/contrib/gdpopt/config_options.py
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
Comment thread pyomo/contrib/gdpopt/ldbd.py Outdated
Toflamus and others added 2 commits February 23, 2026 13:29
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Toflamus
Copy link
Copy Markdown
Collaborator Author

Toflamus commented Feb 23, 2026

I just checked the test failures, those failures are not because of test_ldbd.py, but other tests in the pyomo

@bernalde
Copy link
Copy Markdown
Collaborator

There are some linting mistakes related to the files changed here and some coverage files not generated correctly. I believe these are your errors:

>  from pyomo.common.errors import InfeasibleConstraintException
>  from pyomo.contrib.fbbt.fbbt import fbbt
>  
>  from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm
> would reformat /home/runner/work/pyomo/pyomo/pyomo/contrib/gdpopt/discrete_algorithm_base_class.py
>  from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning, get_main_elapsed_time
> -from pyomo.core import Constraint, Var, minimize, TransformationFactory, Objective, value
> +from pyomo.core import (
> +    Constraint,
> +    Var,
> +    minimize,
> +    TransformationFactory,
> +    Objective,
> +    value,
> +)
>  from pyomo.core.base import ComponentUID
>  from pyomo.core.expr.visitor import polynomial_degree
>  from pyomo.opt import SolverFactory
>  from pyomo.opt import TerminationCondition as tc
>  from pyomo.core.expr.logical_expr import ExactlyExpression
> 
> Oh no! 💥 💔 💥
> 1 file would be reformatted, 1807 files would be left unchanged.

Copy link
Copy Markdown
Collaborator

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the small comments the tests are fialing. Here are some excerpts of those tests, but you need to make sure they pass before merging:

> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB::test_LBB_8PP - ValueError: No value for uninitialized VarData object flow[4]
> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB::test_LBB_8PP_max - ValueError: No value for uninitialized VarData object flow[4]
> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB::test_LBB_8PP_with_screening - AssertionError: False is not true
> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB::test_LBB_strip_pack - ValueError: No value for uninitialized ScalarVar object strip_length
> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB_Z3::test_LBB_8PP - ValueError: No value for uninitialized VarData object flow[4]
> FAILED pyomo/contrib/gdpopt/tests/test_LBB.py::TestGDPopt_LBB_Z3::test_LBB_strip_pack - ValueError: No value for uninitialized ScalarVar object strip_length
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_Boolean_variables - RuntimeError: Solver does not currently have valid duals. Please check the termination condition.
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_discrete_variables - RuntimeError: Solver does not currently have valid duals. Please check the termination condition.
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_Boolean_variables - RuntimeError: Solver does not currently have valid duals. Please check the termination condition.
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_discrete_variables - RuntimeError: Solver does not currently have valid duals. Please check the termination condition.
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_unbounded_GDP - RuntimeError: A feasible solution was not found, so no solution can be loaded. If using the appsi.solvers.Highs interface, you can set opt.config.load_solution=False. If using the environ.SolverFactory interface, you can set opt.solve(model, load_solutions = False). Then you can check results.termination_condition and results.best_feasible_objective before loading a solution.
> FAILED pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPopt::test_LOA_8PP_logical_default_init - pyomo.common.errors.DeveloperError: Internal Pyomo implementation error:
>         'GDPopt unable to handle MINLP subproblem termination condition of
>         unknown. Results: \nSolver: \n- Status: ok\n  Termination condition:
>         unknown\n  Error rc: 0\n  Time: 0.019835710525512695\n'
>     Please report this to the Pyomo Developers.
> FAILED pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPopt::test_LOA_8PP_logical_maxBinary - pyomo.common.errors.DeveloperError: Internal Pyomo implementation error:
>         'GDPopt unable to handle MINLP subproblem termination condition of
>         unknown. Results: \nSolver: \n- Status: ok\n  Termination condition:
>         unknown\n  Error rc: 0\n  Time: 0.019213199615478516\n'
>     Please report this to the Pyomo Developers.
> FAILED pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptRIC::test_RIC_8PP_logical_default_init - pyomo.common.errors.DeveloperError: Internal Pyomo implementation error:
>         'GDPopt unable to handle MINLP subproblem termination condition of
>         unknown. Results: \nSolver: \n- Status: ok\n  Termination condition:
>         unknown\n  Error rc: 0\n  Time: 0.019325733184814453\n'
>     Please report this to the Pyomo Developers.

Comment thread pyomo/contrib/gdpopt/discrete_algorithm_base_class.py Outdated
@bernalde
Copy link
Copy Markdown
Collaborator

Tests are failing because you are missing an is appsi highs available check for them


> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_Boolean_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_discrete_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_Boolean_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_discrete_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_two_term_disjunction - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_unbounded_GDP - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> = 6 failed, 10098 passed, 7224 skipped, 2162 deselected, 12 xfailed, 4 subtests passed in 274.92s (0:04:34) =

@Toflamus
Copy link
Copy Markdown
Collaborator Author

Tests are failing because you are missing an is appsi highs available check for them


> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_Boolean_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_do_not_iterate_over_discrete_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_Boolean_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_GDP_iterate_over_discrete_variables - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_solve_two_term_disjunction - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> FAILED pyomo/contrib/gdpopt/tests/test_enumerate.py::TestGDPoptEnumerate::test_unbounded_GDP - pyomo.common.errors.ApplicationError: Solver <class 'pyomo.contrib.appsi.base.SolverFactoryClass.register.<locals>.decorator.<locals>.LegacySolver'> is not available (NotFound).
> = 6 failed, 10098 passed, 7224 skipped, 2162 deselected, 12 xfailed, 4 subtests passed in 274.92s (0:04:34) =

I will review the tests. Btw, that is very strange. I did not change anything in the tests. They are running well in other PRs but fail in this PR.

Copy link
Copy Markdown
Collaborator

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would revent all the changes in the GDPOpt tests and file issues on the pyomo repo to be fixed later. Leave breadcrumbs at most

# import duals whenever `model.dual.import_enabled()` is true. HiGHS does not
# provide valid duals for MIP/MILP in general, which can raise at load time.
# Additionally, APPSI HiGHS raises if asked to load solutions when no feasible
# solution exists (e.g., unbounded/infeasible), which breaks the unbounded test.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let's leave this here, but this reveals a larger bug with GDPOpt. File a bug with a MWE in the pyomo branch and add the issue number here

# By default, do not run large example-based tests (e.g., logical 8PP) or
# global-solver-dependent tests (GLOA). These can require commercial solvers or
# be slow/fragile across environments. Enable explicitly if desired.
RUN_LARGE_TESTS = bool(int(os.environ.get('PYOMO_GDPOPT_RUN_LARGE_TESTS', '0')))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires adding some new global variables to the execution of the code, which will not fly in the pyomo repository. Let's just skip these tests if you do not have baron installed and file a bug about this as well

@Toflamus
Copy link
Copy Markdown
Collaborator Author

Hi team,

I’ve investigated the current CI failures across Linux, macOS, and Windows. The failures in pyomo/contrib/solver/tests/solvers are not caused by the code changes in this PR.

The root cause is an expired GAMS license in the test environment. It appears the license might have expired a few days ago, which is why we are seeing identical failures across all platforms.

Interestingly, the tests for MindtPy (pyomo/main Pyomo#3861) and GDPOpt are still passing (likely because they use different solver paths or have different fallback mechanisms), but the core solver tests are blocked. This same issue is also affecting the LDBD PR and other active MindtPy PRs.

Once the GAMS license is renewed in the CI environment, these tests should return to green.

@bernalde bernalde merged commit 26b9055 into AlbertLee125:main Feb 25, 2026
9 of 30 checks passed
@bernalde
Copy link
Copy Markdown
Collaborator

I merged this PR as it is the stepping stone for upcoming PRs with work on LDSDA and LBBD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants