Skip to content

GDPopt LOA reports non-rigorous nonconvex bounds as optimal LB=UB #3947

@bernalde

Description

@bernalde

Summary

GDPopt LOA can report TerminationCondition.optimal with problem.lower_bound == problem.upper_bound on nonconvex GDPs even when those bounds are not rigorous.

This is not a request for LOA to prove global optimality on nonconvex problems. Pyomo already documents that "For nonconvex problems, LOA may not report rigorous lower/upper bounds." The issue is that the returned SolverResults object still uses the unqualified global-looking fields:

  • results.solver.termination_condition = optimal
  • results.problem.lower_bound = <local incumbent>
  • results.problem.upper_bound = <same local incumbent>

For benchmark and downstream tooling, that is indistinguishable from a globally certified solve unless the caller already knows the model is nonconvex and knows this GDPopt LOA caveat.

Minimal result-finalization reproducer

This reproducer does not require external solvers. It isolates the relevant result semantics: when a LOA run has a local primal incumbent and a non-rigorous OA/discrete bound that has crossed it, the generic GDPopt convergence logic force-updates the dual bound to the primal bound and returns optimal with equal bounds.

import logging

import pyomo.environ as pyo
from pyomo.common.timing import default_timer
from pyomo.contrib.gdpopt.loa import GDP_LOA_Solver
from pyomo.opt import SolverResults

solver = GDP_LOA_Solver()
solver.pyomo_results = SolverResults()
solver.pyomo_results.problem.sense = pyo.maximize

# This mimics a nonconvex LOA state:
# - LB is a feasible incumbent from a local NLP subproblem.
# - UB is the OA/discrete bound. For a nonconvex problem this bound is not
#   necessarily globally valid, and it has crossed the incumbent.
solver.LB = 1011.6577899409375
solver.UB = 1000.0
solver.iteration = 1
solver.timing.main_timer_start_time = default_timer()
solver.timing.total = 0.0

config = solver.CONFIG()
config.bound_tolerance = 1e-6
config.logger = logging.getLogger("gdpopt-loa-mwe")
config.logger.handlers[:] = [logging.StreamHandler()]
config.logger.setLevel(logging.INFO)

print("before", solver.LB, solver.UB, solver.pyomo_results.solver.termination_condition)
print("converged", solver.bounds_converged(config))
print("after", solver.LB, solver.UB, solver.pyomo_results.solver.termination_condition)

results = solver._get_final_pyomo_results_object()
print(
    "results",
    results.problem.lower_bound,
    results.problem.upper_bound,
    results.solver.termination_condition,
)

Observed output with Pyomo 6.10.0:

        1                      1011.65779    1011.65779      0.00%      0.00
GDPopt exiting--bounds have converged or crossed.
before 1011.6577899409375 1000.0 unknown
converged True
after 1011.6577899409375 1011.6577899409375 optimal
results 1011.6577899409375 1011.6577899409375 optimal

The problematic behavior is not that the internal crossed-bound logic exists for algorithms with rigorous bounds. The problematic behavior is that LOA uses the same finalization path even though its dual bound can be non-rigorous on nonconvex models.

Downstream evidence from GDPLib

This surfaced while benchmarking GDPLib's HDA model:

For the HDA instance, the local LOA benchmark row reported:

strategy: gdpopt.loa
solver profile: gams-local
termination: optimal
lower bound: 1011.6577899409375
upper bound: 1011.6577899409375
iterations: 1

However, the HDA model is nonconvex, and other GDPopt/global-capable routes found a better feasible solution:

gdpopt.gloa + gams-local: optimal, objective 1105.3441871686007
gdpopt.ric + gams-local: optimal, objective 1105.3441871686007
gdpopt.ric + gams-gurobi: optimal, objective 1105.3441871686007
gdpopt.ric + gams-baron: optimal, objective 1105.339918385673

For a maximization problem, a reported LOA upper bound of 1011.6577899409375 is therefore not a valid global upper bound once a feasible point around 1105.34 is known. The LB = UB result gives the appearance of a globally certified incumbent even though it is a local/non-rigorous LOA result.

Expected behavior

For LOA on models where the OA/discrete bound is not known to be rigorous, GDPopt should avoid returning globally certified-looking result metadata.

Possible approaches:

  • Return a non-global termination condition such as feasible or locallyOptimal when LOA converges only by non-rigorous/crossed bounds on a nonconvex problem.
  • Preserve the incumbent as the primal bound but avoid force-setting the dual bound equal to the incumbent when the dual bound is not rigorous.
  • Add explicit result metadata indicating that the reported bound is not rigorous.
  • Add an option such as assume_convex=True/False or certify_bounds=True/False, with conservative default result semantics for LOA unless convexity/global validity is asserted.

The important downstream requirement is that JSON/benchmark consumers should not have to infer from solver.name == "GDPopt ... - LOA" that optimal and LB = UB may not mean globally optimal.

Environment

Observed locally with:

Python 3.12.13
Pyomo 6.10.0
OS/platform: Linux WSL2, glibc 2.39

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions