Skip to content

Commit

Permalink
Change in highs api and status handling in both cases (#682)
Browse files Browse the repository at this point in the history
* Change in highs api and status handling in both cases

* Use a mapping dictionary with the tuples

* Small change

* Added new test to take into account that a time limit can be reached without solution. Test available for HiGHS and CBC for now

* Wrong mapping

---------

Co-authored-by: pchtsp <pchtsp@gmail.com>
  • Loading branch information
ggsdc and pchtsp authored Jan 12, 2024
1 parent 9cf02bf commit b0856c2
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 22 deletions.
104 changes: 83 additions & 21 deletions pulp/apis/highs_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# PuLP : Python LP Modeler
# Version 2.4
from math import inf

# Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
# Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
Expand Down Expand Up @@ -186,12 +187,12 @@ def actualSolve(self, lp):
elif model_status.lower() == "infeasible": # infeasible
status, status_sol = (
constants.LpStatusInfeasible,
constants.LpSolutionNoSolutionFound,
constants.LpSolutionInfeasible,
)
elif model_status.lower() == "unbounded": # unbounded
status, status_sol = (
constants.LpStatusUnbounded,
constants.LpSolutionNoSolutionFound,
constants.LpSolutionUnbounded,
)
else: # no solution
status, status_sol = (
Expand Down Expand Up @@ -390,47 +391,108 @@ def buildSolverModel(self, lp):

def findSolutionValues(self, lp):
status = lp.solverModel.getModelStatus()
obj_value = lp.solverModel.getObjectiveValue()

solution = lp.solverModel.getSolution()
HighsModelStatus = highspy.HighsModelStatus

status_dict = {
HighsModelStatus.kNotset: constants.LpStatusNotSolved,
HighsModelStatus.kLoadError: constants.LpStatusNotSolved,
HighsModelStatus.kModelError: constants.LpStatusNotSolved,
HighsModelStatus.kPresolveError: constants.LpStatusNotSolved,
HighsModelStatus.kSolveError: constants.LpStatusNotSolved,
HighsModelStatus.kPostsolveError: constants.LpStatusNotSolved,
HighsModelStatus.kModelEmpty: constants.LpStatusNotSolved,
HighsModelStatus.kOptimal: constants.LpStatusOptimal,
HighsModelStatus.kInfeasible: constants.LpStatusInfeasible,
HighsModelStatus.kUnboundedOrInfeasible: constants.LpStatusInfeasible,
HighsModelStatus.kUnbounded: constants.LpStatusUnbounded,
HighsModelStatus.kObjectiveBound: constants.LpStatusNotSolved,
HighsModelStatus.kObjectiveTarget: constants.LpStatusNotSolved,
HighsModelStatus.kTimeLimit: constants.LpStatusNotSolved,
HighsModelStatus.kIterationLimit: constants.LpStatusNotSolved,
HighsModelStatus.kUnknown: constants.LpStatusNotSolved,
HighsModelStatus.kNotset: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kLoadError: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kModelError: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kPresolveError: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kSolveError: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kPostsolveError: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kModelEmpty: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
HighsModelStatus.kOptimal: (
constants.LpStatusOptimal,
constants.LpSolutionOptimal,
),
HighsModelStatus.kInfeasible: (
constants.LpStatusInfeasible,
constants.LpSolutionInfeasible,
),
HighsModelStatus.kUnboundedOrInfeasible: (
constants.LpStatusInfeasible,
constants.LpSolutionInfeasible,
),
HighsModelStatus.kUnbounded: (
constants.LpStatusUnbounded,
constants.LpSolutionUnbounded,
),
HighsModelStatus.kObjectiveBound: (
constants.LpStatusOptimal,
constants.LpSolutionIntegerFeasible,
),
HighsModelStatus.kObjectiveTarget: (
constants.LpStatusOptimal,
constants.LpSolutionIntegerFeasible,
),
HighsModelStatus.kTimeLimit: (
constants.LpStatusOptimal,
constants.LpSolutionIntegerFeasible,
),
HighsModelStatus.kIterationLimit: (
constants.LpStatusOptimal,
constants.LpSolutionIntegerFeasible,
),
HighsModelStatus.kUnknown: (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
),
}

col_values = list(solution.col_value)

# Assign values to the variables as with lp.assignVarsVals()
for var in lp.variables():
var.varValue = col_values[var.index]

return status_dict[status]
if obj_value == float(inf) and status in (
HighsModelStatus.kTimeLimit,
HighsModelStatus.kIterationLimit,
):
return constants.LpStatusNotSolved, constants.LpSolutionNoSolutionFound
else:
return status_dict[status]

def actualSolve(self, lp):
self.createAndConfigureSolver(lp)
self.buildSolverModel(lp)
self.callSolver(lp)

solutionStatus = self.findSolutionValues(lp)
status, sol_status = self.findSolutionValues(lp)

for var in lp.variables():
var.modified = False

for constraint in lp.constraints.values():
constraint.modifier = False

return solutionStatus
lp.assignStatus(status, sol_status)

return status

def actualResolve(self, lp, **kwargs):
raise PulpSolverError("HiGHS: Resolving is not supported")
21 changes: 20 additions & 1 deletion pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,8 @@ def test_measuring_solving_time(self):
return
prob = create_bin_packing_problem(bins=bins, seed=99)
self.solver.timeLimit = time_limit
prob.solve(self.solver)
status = prob.solve(self.solver)

delta = 20
reported_time = prob.solutionTime
if self.solver.name in ["PULP_CBC_CMD", "COIN_CMD"]:
Expand All @@ -1304,9 +1305,27 @@ def test_measuring_solving_time(self):
msg=f"optimization time for solver {self.solver.name}",
)
self.assertTrue(prob.objective.value() is not None)
self.assertEqual(status, const.LpStatusOptimal)
for v in prob.variables():
self.assertTrue(v.varValue is not None)

@gurobi_test
def test_time_limit_no_solution(self):
print("\t Test time limit with no solution")

time_limit = 1
solver_settings = dict(HiGHS=50, PULP_CBC_CMD=30, COIN_CMD=30)
bins = solver_settings.get(self.solver.name)
if bins is None:
# not all solvers have timeLimit support
return
prob = create_bin_packing_problem(bins=bins, seed=99)
self.solver.timeLimit = time_limit
status = prob.solve(self.solver)
self.assertEqual(prob.status, const.LpStatusNotSolved)
self.assertEqual(status, const.LpStatusNotSolved)
self.assertEqual(prob.sol_status, const.LpSolutionNoSolutionFound)

def test_invalid_var_names(self):
prob = LpProblem(self._testMethodName, const.LpMinimize)
x = LpVariable("a")
Expand Down

0 comments on commit b0856c2

Please sign in to comment.