Summary
When solving MIP problems with HiGHS and some active solver limits, eg, mip_max_nodes=50, the solver may stop prematurely, ie, before solving the problem to optimality. In that case, the status given by HiGHS is kSolutionLimit. But this is not handled in the if/else chain here:
|
if status == highspy.HighsModelStatus.kNotset: |
|
results.termination_condition = TerminationCondition.unknown |
|
elif status == highspy.HighsModelStatus.kLoadError: |
|
results.termination_condition = TerminationCondition.error |
|
elif status == highspy.HighsModelStatus.kModelError: |
|
results.termination_condition = TerminationCondition.error |
|
elif status == highspy.HighsModelStatus.kPresolveError: |
|
results.termination_condition = TerminationCondition.error |
|
elif status == highspy.HighsModelStatus.kSolveError: |
|
results.termination_condition = TerminationCondition.error |
|
elif status == highspy.HighsModelStatus.kPostsolveError: |
|
results.termination_condition = TerminationCondition.error |
|
elif status == highspy.HighsModelStatus.kModelEmpty: |
|
results.termination_condition = TerminationCondition.unknown |
|
elif status == highspy.HighsModelStatus.kOptimal: |
|
results.termination_condition = TerminationCondition.optimal |
|
elif status == highspy.HighsModelStatus.kInfeasible: |
|
results.termination_condition = TerminationCondition.infeasible |
|
elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: |
|
results.termination_condition = TerminationCondition.infeasibleOrUnbounded |
|
elif status == highspy.HighsModelStatus.kUnbounded: |
|
results.termination_condition = TerminationCondition.unbounded |
|
elif status == highspy.HighsModelStatus.kObjectiveBound: |
|
results.termination_condition = TerminationCondition.objectiveLimit |
|
elif status == highspy.HighsModelStatus.kObjectiveTarget: |
|
results.termination_condition = TerminationCondition.objectiveLimit |
|
elif status == highspy.HighsModelStatus.kTimeLimit: |
|
results.termination_condition = TerminationCondition.maxTimeLimit |
|
elif status == highspy.HighsModelStatus.kIterationLimit: |
|
results.termination_condition = TerminationCondition.maxIterations |
|
elif status == highspy.HighsModelStatus.kUnknown: |
|
results.termination_condition = TerminationCondition.unknown |
|
else: |
|
results.termination_condition = TerminationCondition.unknown |
Hence, the Pyomo user just gets the result.solver:
- Status: unknown
Termination condition: unknown
Termination message: TerminationCondition.unknown
Even though, looking at the solver log, we know quite a bit more, eg, that a feasible solution exists:
Solving report
Status Solution limit reached
Primal bound 153.831642438
Dual bound 154.134587306
Gap 0.197% (tolerance: 0.01%)
P-D integral 0.020659109043
Solution status feasible
153.831642438 (objective)
0 (bound viol.)
0 (int. viol.)
0 (row viol.)
Timing 0.17 (total)
0.00 (presolve)
0.00 (solve)
0.00 (postsolve)
Max sub-MIP depth 3
Nodes 1
Steps to reproduce the issue
I can provide code to generate tricky Knapsack instances, if needed.
The important bit is to use options=dict(mip_max_nodes=1) or similar.
Information on your system
Pyomo version: 6.7.3 (but the relevant code is still the same on main branch)
Python version: 3.12.
Operating system: Ubuntu 24.04
How Pyomo was installed (PyPI, conda, source): PyPI
Solver (if applicable): HiGHS v1.11.0
Additional information
I'm happy to have a go at this in a PR, but might not get to it immediately.
Summary
When solving MIP problems with HiGHS and some active solver limits, eg,
mip_max_nodes=50, the solver may stop prematurely, ie, before solving the problem to optimality. In that case, the status given by HiGHS iskSolutionLimit. But this is not handled in theif/elsechain here:pyomo/pyomo/contrib/appsi/solvers/highs.py
Lines 657 to 690 in dc503c2
Hence, the Pyomo user just gets the
result.solver:Even though, looking at the solver log, we know quite a bit more, eg, that a feasible solution exists:
Steps to reproduce the issue
I can provide code to generate tricky Knapsack instances, if needed.
The important bit is to use
options=dict(mip_max_nodes=1)or similar.Information on your system
Pyomo version: 6.7.3 (but the relevant code is still the same on
mainbranch)Python version: 3.12.
Operating system: Ubuntu 24.04
How Pyomo was installed (PyPI, conda, source): PyPI
Solver (if applicable): HiGHS v1.11.0
Additional information
I'm happy to have a go at this in a PR, but might not get to it immediately.