WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 237e12b

Browse files
committed
cuopt service correct definition for status value in lp result spec
1 parent 7162916 commit 237e12b

File tree

2 files changed

+101
-23
lines changed

2 files changed

+101
-23
lines changed

python/cuopt_server/cuopt_server/tests/test_lp.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,31 @@ def test_barrier_solver_options(
211211
res.json()["response"]["solver_response"],
212212
LPTerminationStatus.Optimal.name,
213213
)
214+
215+
216+
def test_termination_status_enum_sync():
217+
"""
218+
Ensure local status values in data_definition.py stay in sync
219+
with actual LPTerminationStatus and MILPTerminationStatus enums.
220+
221+
The data_definition module cannot import these enums directly
222+
because it triggers CUDA/RMM initialization before the server
223+
has configured memory management. So we maintain local copies
224+
and this test ensures they stay in sync.
225+
"""
226+
from cuopt_server.utils.linear_programming.data_definition import (
227+
LP_STATUS_NAMES,
228+
MILP_STATUS_NAMES,
229+
)
230+
231+
expected_lp = {e.name for e in LPTerminationStatus}
232+
expected_milp = {e.name for e in MILPTerminationStatus}
233+
234+
assert LP_STATUS_NAMES == expected_lp, (
235+
f"LP_STATUS_NAMES out of sync with LPTerminationStatus enum. "
236+
f"Expected: {expected_lp}, Got: {LP_STATUS_NAMES}"
237+
)
238+
assert MILP_STATUS_NAMES == expected_milp, (
239+
f"MILP_STATUS_NAMES out of sync with MILPTerminationStatus enum. "
240+
f"Expected: {expected_milp}, Got: {MILP_STATUS_NAMES}"
241+
)

python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -810,27 +810,77 @@ class SolutionData(StrictModel):
810810
)
811811

812812

813+
# LP termination status values
814+
# NOTE: These must match LPTerminationStatus from
815+
# cuopt.linear_programming.solver.solver_wrapper
816+
# We cannot import them directly because it triggers CUDA/RMM initialization
817+
# before the server has configured memory management.
818+
# See test_termination_status_enum_sync() in test_lp.py to ensure these stay in sync.
819+
LP_STATUS_NAMES = frozenset(
820+
{
821+
"NoTermination",
822+
"NumericalError",
823+
"Optimal",
824+
"PrimalInfeasible",
825+
"DualInfeasible",
826+
"IterationLimit",
827+
"TimeLimit",
828+
"PrimalFeasible",
829+
}
830+
)
831+
832+
# MILP termination status values
833+
# NOTE: These must match MILPTerminationStatus from
834+
# cuopt.linear_programming.solver.solver_wrapper
835+
MILP_STATUS_NAMES = frozenset(
836+
{
837+
"NoTermination",
838+
"Optimal",
839+
"FeasibleFound",
840+
"Infeasible",
841+
"Unbounded",
842+
"TimeLimit",
843+
}
844+
)
845+
846+
# Combined set of all valid status names
847+
ALL_STATUS_NAMES = LP_STATUS_NAMES | MILP_STATUS_NAMES
848+
849+
850+
def validate_termination_status(v):
851+
"""Validate that status is a valid LP or MILP termination status name."""
852+
if v not in ALL_STATUS_NAMES:
853+
raise ValueError(
854+
f"status must be one of {sorted(ALL_STATUS_NAMES)}, got '{v}'"
855+
)
856+
return v
857+
858+
813859
class SolutionResultData(StrictModel):
814-
status: int = Field(
815-
default=0,
816-
examples=[1],
817-
description=(
818-
"In case of LP : \n\n"
819-
"0 - No Termination \n\n"
820-
"1 - Optimal solution is available \n\n"
821-
"2 - Primal Infeasible solution \n\n"
822-
"3 - Dual Infeasible solution \n\n"
823-
"4 - Iteration Limit reached \n\n"
824-
"5 - TimeLimit reached \n\n"
825-
"6 - Primal Feasible \n\n"
826-
"---------------------- \n\n"
827-
"In case of MILP/IP : \n\n"
828-
"0 - No Termination \n\n"
829-
"1 - Optimal solution is available \n\n"
830-
"2 - Feasible solution is available \n\n"
831-
"3 - Infeasible \n\n"
832-
"4 - Unbounded\n\n"
833-
),
860+
status: Annotated[str, PlainValidator(validate_termination_status)] = (
861+
Field(
862+
default="NoTermination",
863+
examples=["Optimal"],
864+
description=(
865+
"In case of LP : \n\n"
866+
"NoTermination - No Termination \n\n"
867+
"NumericalError - Numerical Error \n\n"
868+
"Optimal - Optimal solution is available \n\n"
869+
"PrimalInfeasible - Primal Infeasible solution \n\n"
870+
"DualInfeasible - Dual Infeasible solution \n\n"
871+
"IterationLimit - Iteration Limit reached \n\n"
872+
"TimeLimit - TimeLimit reached \n\n"
873+
"PrimalFeasible - Primal Feasible \n\n"
874+
"---------------------- \n\n"
875+
"In case of MILP/IP : \n\n"
876+
"NoTermination - No Termination \n\n"
877+
"Optimal - Optimal solution is available \n\n"
878+
"FeasibleFound - Feasible solution is available \n\n"
879+
"Infeasible - Infeasible \n\n"
880+
"Unbounded - Unbounded \n\n"
881+
"TimeLimit - TimeLimit reached \n\n"
882+
),
883+
)
834884
)
835885
solution: SolutionData = Field(
836886
default=SolutionData(), description=("Solution of the LP problem")
@@ -896,7 +946,7 @@ class IncumbentSolution(StrictModel):
896946
"value": {
897947
"response": {
898948
"solver_response": {
899-
"status": 1,
949+
"status": "Optimal",
900950
"solution": {
901951
"problem_category": 0,
902952
"primal_solution": [0.0, 0.0],
@@ -925,7 +975,7 @@ class IncumbentSolution(StrictModel):
925975
"value": {
926976
"response": {
927977
"solver_response": {
928-
"status": 2,
978+
"status": "FeasibleFound",
929979
"solution": {
930980
"problem_category": 1,
931981
"primal_solution": [0.0, 0.0],
@@ -956,7 +1006,7 @@ class IncumbentSolution(StrictModel):
9561006
"value": {
9571007
"response": {
9581008
"solver_response": {
959-
"status": 2,
1009+
"status": "FeasibleFound",
9601010
"solution": {
9611011
"problem_category": 1,
9621012
"primal_solution": [0.0, 0.0],

0 commit comments

Comments
 (0)