Skip to content

Commit

Permalink
Warm Starts for HiGHS_CMD (#713)
Browse files Browse the repository at this point in the history
* typo fix

* another typo fix

* warmstart basic arg flow

* finished writesol implementation

* add highs_cmd into warmstart test solvers

* black

* Comment-out COPT while it is breaking master tests

* Re-add COPT
  • Loading branch information
aphi committed Jan 12, 2024
1 parent a6ae983 commit 9cf02bf
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 23 deletions.
2 changes: 1 addition & 1 deletion doc/KPyCon2009/code/wedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def happiness(table):

seating_model.solve()

print(f"The choosen tables are out of a total of {len(possible_tables)}:")
print(f"The chosen tables are out of a total of {len(possible_tables)}:")
for table in possible_tables:
if x[table].value() == 1.0:
print(table)
2 changes: 1 addition & 1 deletion examples/wedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def happiness(table):

seating_model.solve()

print(f"The choosen tables are out of a total of {len(possible_tables)}:")
print(f"The chosen tables are out of a total of {len(possible_tables)}:")
for table in possible_tables:
if x[table].value() == 1.0:
print(table)
2 changes: 1 addition & 1 deletion examples/wedding_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def happiness(table):
seating_model.solve(solver)


print(f"The choosen tables are out of a total of {len(possible_tables)}:")
print(f"The chosen tables are out of a total of {len(possible_tables)}:")
for table in possible_tables:
if x[table].value() == 1.0:
print(table)
6 changes: 3 additions & 3 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,21 @@ def __init__(
"""

if fracGap is not None:
warnings.warn("Parameter fracGap is being depreciated for gapRel")
warnings.warn("Parameter fracGap is being deprecated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and fracGap passed, using gapRel")
else:
gapRel = fracGap
if maxSeconds is not None:
warnings.warn("Parameter maxSeconds is being depreciated for timeLimit")
warnings.warn("Parameter maxSeconds is being deprecated for timeLimit")
if timeLimit is not None:
warnings.warn(
"Parameter timeLimit and maxSeconds passed, using timeLimit"
)
else:
timeLimit = maxSeconds
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
warnings.warn("Parameter mip_start is being deprecated for warmStart")
if warmStart:
warnings.warn(
"Parameter mipStart and mip_start passed, using warmStart"
Expand Down
6 changes: 3 additions & 3 deletions pulp/apis/copt_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(
Initialize command-line solver
"""
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
warnings.warn("Parameter mip_start is being deprecated for warmStart")
if warmStart:
warnings.warn(
"Parameter warmStart and mip_start passed, using warmStart"
Expand Down Expand Up @@ -334,7 +334,7 @@ def __init__(
Initialize COPT solver
"""
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
warnings.warn("Parameter mip_start is being deprecated for warmStart")
if warmStart:
warnings.warn(
"Parameter warmStart and mip_start passed, using warmStart"
Expand Down Expand Up @@ -904,7 +904,7 @@ def __init__(
:param float epgap: deprecated for gapRel
"""
if epgap is not None:
warnings.warn("Parameter epgap is being depreciated for gapRel")
warnings.warn("Parameter epgap is being deprecated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and epgap passed, using gapRel")
else:
Expand Down
8 changes: 4 additions & 4 deletions pulp/apis/cplex_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ def __init__(
:param float timelimit: deprecated for timeLimit
"""
if timelimit is not None:
warnings.warn("Parameter timelimit is being depreciated for timeLimit")
warnings.warn("Parameter timelimit is being deprecated for timeLimit")
if timeLimit is not None:
warnings.warn(
"Parameter timeLimit and timelimit passed, using timeLimit "
)
else:
timeLimit = timelimit
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
warnings.warn("Parameter mip_start is being deprecated for warmStart")
if warmStart:
warnings.warn(
"Parameter mipStart and mip_start passed, using warmStart"
Expand Down Expand Up @@ -309,13 +309,13 @@ def __init__(
:param int threads: number of threads to be used by CPLEX to solve a problem (default None uses all available)
"""
if epgap is not None:
warnings.warn("Parameter epgap is being depreciated for gapRel")
warnings.warn("Parameter epgap is being deprecated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and epgap passed, using gapRel")
else:
gapRel = epgap
if logfilename is not None:
warnings.warn("Parameter logfilename is being depreciated for logPath")
warnings.warn("Parameter logfilename is being deprecated for logPath")
if logPath is not None:
warnings.warn(
"Parameter logPath and logfilename passed, using logPath"
Expand Down
4 changes: 2 additions & 2 deletions pulp/apis/gurobi_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def __init__(
self.init_gurobi = False # whether env and model have been initialised

if epgap is not None:
warnings.warn("Parameter epgap is being depreciated for gapRel")
warnings.warn("Parameter epgap is being deprecated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and epgap passed, using gapRel")
else:
Expand Down Expand Up @@ -419,7 +419,7 @@ def __init__(
:param bool mip_start: deprecated for warmStart
"""
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
warnings.warn("Parameter mip_start is being deprecated for warmStart")
if warmStart:
warnings.warn(
"Parameter warmStart and mip_start passed, using warmStart"
Expand Down
39 changes: 33 additions & 6 deletions pulp/apis/highs_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(
gapAbs=None,
threads=None,
logPath=None,
warmStart=False,
):
"""
:param bool mip: if False, assume LP even if integer variables
Expand All @@ -66,6 +67,7 @@ def __init__(
:param str path: path to the solver binary (you can get binaries for your platform from https://github.com/JuliaBinaryWrappers/HiGHS_jll.jl/releases, or else compile from source - https://highs.dev)
:param int threads: sets the maximum number of threads
:param str logPath: path to the log file
:param bool warmStart: if True, the solver will use the current value of variables as a start
"""
LpSolver_CMD.__init__(
self,
Expand All @@ -79,6 +81,7 @@ def __init__(
keepFiles=keepFiles,
threads=threads,
logPath=logPath,
warmStart=warmStart,
)

def defaultPath(self):
Expand All @@ -94,8 +97,8 @@ def actualSolve(self, lp):
raise PulpSolverError("PuLP: cannot execute " + self.path)
lp.checkDuplicateVars()

tmpMps, tmpSol, tmpOptions, tmpLog = self.create_tmp_files(
lp.name, "mps", "sol", "HiGHS", "HiGHS_log"
tmpMps, tmpSol, tmpOptions, tmpLog, tmpMst = self.create_tmp_files(
lp.name, "mps", "sol", "HiGHS", "HiGHS_log", "mst"
)
lp.writeMPS(tmpMps, with_objsense=True)

Expand Down Expand Up @@ -127,6 +130,9 @@ def actualSolve(self, lp):
command.append("--solver=simplex")
if "threads" in self.optionsDict:
command.append("--parallel=on")
if self.optionsDict.get("warmStart", False):
self.writesol(tmpMst, lp)
command.append(f"--read_solution_file={tmpMst}")

options = iter(self.options)
for option in options:
Expand Down Expand Up @@ -199,18 +205,39 @@ def actualSolve(self, lp):
elif status_sol == constants.LpSolutionNoSolutionFound:
values = None
else:
values = self.readsol(lp.variables(), tmpSol)
values = self.readsol(tmpSol)

self.delete_tmp_files(tmpMps, tmpSol, tmpOptions, tmpLog)
self.delete_tmp_files(tmpMps, tmpSol, tmpOptions, tmpLog, tmpMst)
lp.assignStatus(status, status_sol)

if status == constants.LpStatusOptimal:
lp.assignVarsVals(values)

return status

@staticmethod
def readsol(variables, filename):
def writesol(self, filename, lp):
"""Writes a HiGHS solution file"""

variable_rows = []
for var in lp.variables(): # zero variables must be included
variable_rows.append(f"{var.name} {var.varValue or 0}")

# Required preamble for HiGHS to accept a solution
all_rows = [
"Model status",
"None",
"",
"# Primal solution values",
"Feasible",
"",
f"# Columns {len(variable_rows)}",
]
all_rows.extend(variable_rows)

with open(filename, "w") as file:
file.write("\n".join(all_rows))

def readsol(self, filename):
"""Read a HiGHS solution file"""
with open(filename) as file:
lines = file.readlines()
Expand Down
4 changes: 2 additions & 2 deletions pulp/apis/xpress_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ def __init__(
:param bool warmStart: if True, then use current variable values as start
"""
if maxSeconds:
warnings.warn("Parameter maxSeconds is being depreciated for timeLimit")
warnings.warn("Parameter maxSeconds is being deprecated for timeLimit")
if timeLimit is not None:
warnings.warn(
"Parameter timeLimit and maxSeconds passed, using timeLimit"
)
else:
timeLimit = maxSeconds
if targetGap is not None:
warnings.warn("Parameter targetGap is being depreciated for gapRel")
warnings.warn("Parameter targetGap is being deprecated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and epgap passed, using gapRel")
else:
Expand Down
1 change: 1 addition & 0 deletions pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ def test_pulp_022(self):
"CPLEX_CMD",
"CPLEX_PY",
"COPT",
"HiGHS_CMD",
]:
self.solver.optionsDict["warmStart"] = True
print("\t Testing Initial value in MIP solution")
Expand Down

0 comments on commit 9cf02bf

Please sign in to comment.