Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more config parameters to HiGHS python API + set up logging #606

Merged
merged 3 commits into from
Jul 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions pulp/apis/highs_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,29 +248,66 @@ def actualSolve(self, lp, callback=None):
raise PulpSolverError("HiGHS: Not Available")

else:
# Note(maciej): It was surprising to me that higshpy wasn't logging out of the box,
# even with the different logging options set. This callback seems to work, but there
# are probably better ways of doing this ¯\_(ツ)_/¯
DEFAULT_CALLBACK = lambda logType, logMsg, callbackValue: print(
f"[{logType.name}] {logMsg}"
)
DEFAULT_CALLBACK_VALUE = ""

def __init__(
self,
mip=True,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we prefer having the explicit enumeration of parameters, together with a description. This produces the documentation of the APIs in the docs. It's the only way to know in the docs which parameters are supported and which ones are not.
See

pulp/pulp/apis/cplex_api.py

Lines 296 to 316 in a017fdb

def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
gapRel=None,
warmStart=False,
logPath=None,
epgap=None,
logfilename=None,
):
"""
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param float timeLimit: maximum time for solver (in seconds)
:param float gapRel: relative gap tolerance for the solver to stop (in fraction)
:param bool warmStart: if True, the solver will use the current value of variables as a start
:param str logPath: path to the log file
:param float epgap: deprecated for gapRel
:param str logfilename: deprecated for logPath
"""

msg=True,
callbackTuple=None,
gapAbs=None,
gapRel=None,
threads=None,
timeLimit=None,
warmStart=False,
logPath=None,
**solverParams,
):
super().__init__(mip, msg, timeLimit=timeLimit, **solverParams)
"""
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param tuple callbackTuple: Tuple of log callback function (see DEFAULT_CALLBACK above for definition)
and callbackValue (tag embedded in every callback)
:param float gapRel: relative gap tolerance for the solver to stop (in fraction)
:param float gapAbs: absolute gap tolerance for the solver to stop
:param int threads: sets the maximum number of threads
:param float timeLimit: maximum time for solver (in seconds)
:param dict solverParams: list of named options to pass directly to the HiGHS solver
"""
super().__init__(mip=mip, msg=msg, timeLimit=timeLimit, **solverParams)
self.callbackTuple = callbackTuple
self.gapAbs = gapAbs
self.gapRel = gapRel
self.threads = threads

def available(self):
return True

def callSolver(self, lp):
lp.solverModel.run()

def buildSolverModel(self, lp):
def createAndConfigureSolver(self, lp):
lp.solverModel = highspy.Highs()

gapRel = self.optionsDict.get("gapRel", 0)
lp.solverModel.setOptionValue("mip_rel_gap", gapRel)
if self.msg or self.callbackTuple:
callbackTuple = self.callbackTuple or (
HiGHS.DEFAULT_CALLBACK,
HiGHS.DEFAULT_CALLBACK_VALUE,
)
lp.solverModel.setLogCallback(*callbackTuple)

if self.gapRel is not None:
lp.solverModel.setOptionValue("mip_rel_gap", self.gapRel)

if self.gapAbs is not None:
lp.solverModel.setOptionValue("mip_abs_gap", self.gapAbs)

if self.threads is not None:
lp.solverModel.setOptionValue("threads", self.threads)

if self.timeLimit is not None:
lp.solverModel.setOptionValue("time_limit", float(self.timeLimit))
Expand All @@ -279,6 +316,7 @@ def buildSolverModel(self, lp):
for key, value in self.optionsDict.items():
lp.solverModel.setOptionValue(key, value)

def buildSolverModel(self, lp):
inf = highspy.kHighsInf

obj_mult = -1 if lp.sense == constants.LpMaximize else 1
Expand Down Expand Up @@ -353,6 +391,7 @@ def findSolutionValues(self, lp):
return status_dict[status]

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

Expand Down