Skip to content

Commit 1137fe9

Browse files
authored
Merge pull request #3766 from eminyouskn/knitro-load-solutions
Fix KNITRO solution status mapping
2 parents 8397f66 + 67b8f95 commit 1137fe9

File tree

2 files changed

+72
-38
lines changed

2 files changed

+72
-38
lines changed

pyomo/contrib/solver/solvers/knitro/base.py

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
NoReducedCostsError,
3434
NoSolutionError,
3535
)
36-
from pyomo.contrib.solver.solvers.knitro.api import knitro
3736
from pyomo.contrib.solver.solvers.knitro.config import KnitroConfig
3837
from pyomo.contrib.solver.solvers.knitro.engine import Engine
3938
from pyomo.contrib.solver.solvers.knitro.package import PackageChecker
@@ -204,57 +203,43 @@ def _get_items(self, item_type: type[ItemType]) -> Sequence[ItemType]:
204203

205204
@staticmethod
206205
def _get_solution_status(status: int) -> SolutionStatus:
207-
if (
208-
status == knitro.KN_RC_OPTIMAL
209-
or status == knitro.KN_RC_OPTIMAL_OR_SATISFACTORY
210-
or status == knitro.KN_RC_NEAR_OPT
211-
):
206+
"""
207+
Map KNITRO status codes to Pyomo SolutionStatus values.
208+
209+
See https://www.artelys.com/app/docs/knitro/3_referenceManual/returnCodes.html
210+
"""
211+
if status in {0, -100}:
212212
return SolutionStatus.optimal
213-
elif status == knitro.KN_RC_FEAS_NO_IMPROVE:
213+
elif -101 >= status >= -199 or -400 >= status >= -409:
214214
return SolutionStatus.feasible
215-
elif (
216-
status == knitro.KN_RC_INFEASIBLE
217-
or status == knitro.KN_RC_INFEAS_CON_BOUNDS
218-
or status == knitro.KN_RC_INFEAS_VAR_BOUNDS
219-
or status == knitro.KN_RC_INFEAS_NO_IMPROVE
220-
):
215+
elif status in {-200, -204, -205, -206}:
221216
return SolutionStatus.infeasible
222217
else:
223218
return SolutionStatus.noSolution
224219

225220
@staticmethod
226221
def _get_termination_condition(status: int) -> TerminationCondition:
227-
if (
228-
status == knitro.KN_RC_OPTIMAL
229-
or status == knitro.KN_RC_OPTIMAL_OR_SATISFACTORY
230-
or status == knitro.KN_RC_NEAR_OPT
231-
):
222+
"""
223+
Map KNITRO status codes to Pyomo TerminationCondition values.
224+
225+
See https://www.artelys.com/app/docs/knitro/3_referenceManual/returnCodes.html
226+
"""
227+
if status in {0, -100}:
232228
return TerminationCondition.convergenceCriteriaSatisfied
233-
elif status == knitro.KN_RC_INFEAS_NO_IMPROVE:
229+
elif status == -202:
234230
return TerminationCondition.locallyInfeasible
235-
elif (
236-
status == knitro.KN_RC_INFEASIBLE
237-
or status == knitro.KN_RC_INFEAS_CON_BOUNDS
238-
or status == knitro.KN_RC_INFEAS_VAR_BOUNDS
239-
):
231+
elif status in {-200, -204, -205}:
240232
return TerminationCondition.provenInfeasible
241-
elif (
242-
status == knitro.KN_RC_UNBOUNDED_OR_INFEAS
243-
or status == knitro.KN_RC_UNBOUNDED
244-
):
233+
elif status in {-300, -301}:
245234
return TerminationCondition.infeasibleOrUnbounded
246-
elif (
247-
status == knitro.KN_RC_ITER_LIMIT_FEAS
248-
or status == knitro.KN_RC_ITER_LIMIT_INFEAS
249-
):
235+
elif status in {-400, -410}:
250236
return TerminationCondition.iterationLimit
251-
elif (
252-
status == knitro.KN_RC_TIME_LIMIT_FEAS
253-
or status == knitro.KN_RC_TIME_LIMIT_INFEAS
254-
):
237+
elif status in {-401, -411}:
255238
return TerminationCondition.maxTimeLimit
256-
elif status == knitro.KN_RC_USER_TERMINATION:
239+
elif status == -500:
257240
return TerminationCondition.interrupted
241+
elif -500 > status >= -599:
242+
return TerminationCondition.error
258243
else:
259244
return TerminationCondition.unknown
260245

pyomo/contrib/solver/tests/solvers/test_knitro_direct.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
from contextlib import redirect_stdout
1414

1515
import pyomo.common.unittest as unittest
16+
import pyomo.environ as pyo
1617

18+
from pyomo.contrib.solver.common.results import SolutionStatus, TerminationCondition
1719
from pyomo.contrib.solver.solvers.knitro.config import KnitroConfig
1820
from pyomo.contrib.solver.solvers.knitro.direct import KnitroDirectSolver
19-
import pyomo.environ as pyo
2021

2122
avail = KnitroDirectSolver().available()
2223

@@ -84,6 +85,54 @@ def test_available_cache(self):
8485
self.assertTrue(opt._available_cache)
8586
self.assertIsNotNone(opt._available_cache)
8687

88+
def test_solution_status_mapping(self):
89+
opt = KnitroDirectSolver()
90+
for opt_status in [0, -100]:
91+
status = opt._get_solution_status(opt_status)
92+
self.assertEqual(status, SolutionStatus.optimal)
93+
94+
for opt_status in [*range(-101, -103, -1), *range(-400, -406, -1)]:
95+
status = opt._get_solution_status(opt_status)
96+
self.assertEqual(status, SolutionStatus.feasible)
97+
98+
for opt_status in [-200, -204, -205, -206]:
99+
status = opt._get_solution_status(opt_status)
100+
self.assertEqual(status, SolutionStatus.infeasible)
101+
102+
for opt_status in [-501, -99999, -1]:
103+
status = opt._get_solution_status(opt_status)
104+
self.assertEqual(status, SolutionStatus.noSolution)
105+
106+
def test_termination_condition_mapping(self):
107+
opt = KnitroDirectSolver()
108+
for opt_status in [0, -100]:
109+
term_cond = opt._get_termination_condition(opt_status)
110+
self.assertEqual(
111+
term_cond, TerminationCondition.convergenceCriteriaSatisfied
112+
)
113+
term_cond = opt._get_termination_condition(-202)
114+
self.assertEqual(term_cond, TerminationCondition.locallyInfeasible)
115+
for opt_status in [-200, -204, -205]:
116+
term_cond = opt._get_termination_condition(opt_status)
117+
self.assertEqual(term_cond, TerminationCondition.provenInfeasible)
118+
for opt_status in [-300, -301]:
119+
term_cond = opt._get_termination_condition(opt_status)
120+
self.assertEqual(term_cond, TerminationCondition.infeasibleOrUnbounded)
121+
for opt_status in [-400, -410]:
122+
term_cond = opt._get_termination_condition(opt_status)
123+
self.assertEqual(term_cond, TerminationCondition.iterationLimit)
124+
for opt_status in [-401, -411]:
125+
term_cond = opt._get_termination_condition(opt_status)
126+
self.assertEqual(term_cond, TerminationCondition.maxTimeLimit)
127+
term_cond = opt._get_termination_condition(-500)
128+
self.assertEqual(term_cond, TerminationCondition.interrupted)
129+
for opt_status in [-501, -550, -599]:
130+
term_cond = opt._get_termination_condition(opt_status)
131+
self.assertEqual(term_cond, TerminationCondition.error)
132+
for opt_status in [-600, -99999, -1]:
133+
term_cond = opt._get_termination_condition(opt_status)
134+
self.assertEqual(term_cond, TerminationCondition.unknown)
135+
87136

88137
@unittest.skipIf(not avail, "KNITRO solver is not available")
89138
class TestKnitroDirectSolver(unittest.TestCase):

0 commit comments

Comments
 (0)