Skip to content

Commit

Permalink
make explicit inits for all solvers with standard interface. (#326)
Browse files Browse the repository at this point in the history
* make explicit inits for all solvers with standard interface.

* new changes to apis. Took out warmStart from the core object and into optionsDict.

* bump version

* fixes #280
  • Loading branch information
pchtsp authored Aug 4, 2020
1 parent 1915f19 commit 76b28fb
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 173 deletions.
25 changes: 25 additions & 0 deletions HISTORY
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# PuLP, Copyright J.S. Roy (js@jeannot.org), 2002-2005
# Copyright S.A.Mitchell (s.mitchell@auckland.ac.nz), 2007-
# Copyright F.Peschiera (pchtsp@gmail.com), 2019-
# See the LICENSE file for copyright information.
2.3 2020-08-04
Added plugin page in docs
Standardize arguments of solvers to appear in docs
Fixes to import and export of LpProblem and solver
Added warm start to GUROBI
2.2 2020-04-06
Contribution files
Standard arguments: logPath, gapRel, gapAbs.
Import and export solver objects
Import and export LpProblem
Took out amply to its own package
Standard tmp file handling in _CMD solvers
Refactored writeMPS
2.1 2020-04-06
Added MOSEK solver
New documentation
Put tests inside package
Added warm start to CPLEX_PY
2.0 2019-11-23
Restructured solvers code
Added unittests package
Added CHOCO solver
Added warm start for CBC_CMD, GUROBI_CMD, CPLEX_CMD
Automated deploy
1.6.1, 2015-12-25
Fix for dummy variables
1.5.4, 2013-03-18
Expand Down
34 changes: 0 additions & 34 deletions ROADMAP

This file was deleted.

20 changes: 15 additions & 5 deletions pulp/apis/choco_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ class CHOCO_CMD(LpSolver_CMD):
"""The CHOCO_CMD solver"""
name = 'CHOCO_CMD'

def __init__(self, path=None, keepFiles=False, mip=True, msg=True, options=None, timeLimit=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 list options: list of additional options to pass to solver
:param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
:param str path: path to the solver binary
"""
LpSolver_CMD.__init__(self, mip=mip, msg=msg, timeLimit=timeLimit,
options=options, path=path, keepFiles=keepFiles)

def defaultPath(self):
return self.executableExtension("choco-parsers-with-dependencies.jar")

Expand Down Expand Up @@ -146,11 +158,9 @@ def actualSolve(self, lp, callback=None):
"""Solve a well formulated lp problem"""
raise PulpSolverError("PULP_CHOCO_CMD: Not Available (check permissions on %s)" % self.pulp_choco_path)
else:
def __init__(self, path=None, *args, **kwargs):
"""
just loads up CHOCO_CMD with the path set
"""
def __init__(self, path=None, keepFiles=0, mip=True, msg=True, options=None, timeLimit=None):
if path is not None:
raise PulpSolverError('Use CHOCO_CMD if you want to set a path')
# check that the file is executable
CHOCO_CMD.__init__(self, path=self.pulp_choco_path, *args, **kwargs)
CHOCO_CMD.__init__(self, path=self.pulp_choco_path, keepFiles=keepFiles,
mip=mip, msg=msg, options=options, timeLimit=timeLimit)
78 changes: 55 additions & 23 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,53 @@ class COIN_CMD(LpSolver_CMD):
def defaultPath(self):
return self.executableExtension(cbc_path)

def __init__(self, fracGap = None, maxSeconds = None, *args, **kwargs):
def __init__(self, mip=True, msg=True, timeLimit=None,
fracGap=None, maxSeconds=None, gapRel=None, gapAbs=None,
presolve=None, cuts=None, strong=None, options=None,
warmStart=False, keepFiles=False, path=None, threads=None,
logPath=None, mip_start=False):
"""
:param fracGap:
:param maxSeconds:
:param args:
:param kwargs: includes presolve, cuts, strong
: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 float gapAbs: absolute gap tolerance for the solver to stop
:param int threads: sets the maximum number of threads
:param list options: list of additional options to pass to solver
:param bool warmStart: if True, the solver will use the current value of variables as a start
:param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
:param str path: path to the solver binary
:param str logPath: path to the log file
:param bool presolve: if True, adds presolve on
:param bool cuts: if True, adds gomory on knapsack on probing on
:param bool strong: if True, adds strong
:param float fracGap: deprecated for gapRel
:param float maxSeconds: deprecated for timeLimit
:param bool mip_start: deprecated for warmStart
"""

if fracGap:
warnings.warn("Parameter fracGap is being depreciated for standard 'gapRel'")
if 'gapRel' in kwargs:
warnings.warn("Parameter kwargs and fracGap passed, using kwargs")
if fracGap is not None:
warnings.warn("Parameter fracGap is being depreciated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and fracGap passed, using gapRel")
else:
kwargs['gapRel'] = fracGap
LpSolver_CMD.__init__(self, *args, **kwargs)
if maxSeconds:
warnings.warn("Parameter maxSeconds is being depreciated for standard 'timeLimit'")
if self.timeLimit:
warnings.warn("Parameter timeLimit and maxSeconds passed, using timeLimit ")
gapRel = fracGap
if maxSeconds is not None:
warnings.warn("Parameter maxSeconds is being depreciated for timeLimit")
if timeLimit is not None:
warnings.warn("Parameter timeLimit and maxSeconds passed, using timeLimit")
else:
self.timeLimit = maxSeconds
timeLimit = maxSeconds
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
if warmStart:
warnings.warn("Parameter mipStart and mip_start passed, using warmStart")
else:
warmStart = mip_start
LpSolver_CMD.__init__(self, gapRel=gapRel, mip=mip, msg=msg, timeLimit=timeLimit,
presolve=presolve, cuts=cuts, strong=strong, options=options,
warmStart=warmStart, path=path, keepFiles=keepFiles,
threads=threads, gapAbs=gapAbs, logPath=logPath)

def copy(self):
"""Make a copy of self"""
Expand Down Expand Up @@ -96,7 +122,7 @@ def solve_CBC(self, lp, use_mps=True):
constraintsNames = dict((c, c) for c in lp.constraints)
objectiveName = None
cmds = ' ' + tmpLp + " "
if self.warmStart:
if self.optionsDict.get('warmStart', False):
self.writesol(tmpMst, lp, vs, variablesNames, constraintsNames)
cmds += 'mips {} '.format(tmpMst)
if self.timeLimit is not None:
Expand All @@ -114,7 +140,8 @@ def solve_CBC(self, lp, use_mps=True):
pipe = None
else:
pipe = open(os.devnull, 'w')
if 'logPath' in self.optionsDict:
logPath = self.optionsDict.get('logPath')
if logPath:
if self.msg:
warnings.warn('`logPath` argument replaces `msg=1`. The output will be redirected to the log file.')
pipe = open(self.optionsDict['logPath'], 'w')
Expand Down Expand Up @@ -258,14 +285,19 @@ def actualSolve(self, lp, callback = None):
"""Solve a well formulated lp problem"""
raise PulpSolverError("PULP_CBC_CMD: Not Available (check permissions on %s)" % self.pulp_cbc_path)
else:
def __init__(self, path=None, *args, **kwargs):
"""
just loads up COIN_CMD with the path set
"""
def __init__(self, mip=True, msg=True, timeLimit=None,
fracGap=None, maxSeconds=None, gapRel=None, gapAbs=None,
presolve=None, cuts=None, strong=None, options=None,
warmStart=False, keepFiles=False, path=None, threads=None,
logPath=None, mip_start=False):
if path is not None:
raise PulpSolverError('Use COIN_CMD if you want to set a path')
#check that the file is executable
COIN_CMD.__init__(self, path=self.pulp_cbc_path, *args, **kwargs)
COIN_CMD.__init__(self, path=self.pulp_cbc_path, mip=mip, msg=msg, timeLimit=timeLimit,
fracGap=fracGap, maxSeconds=maxSeconds, gapRel=gapRel, gapAbs=gapAbs,
presolve=presolve, cuts=cuts, strong=strong, options=options,
warmStart=warmStart, keepFiles=keepFiles, threads=threads,
logPath=logPath, mip_start=mip_start)


def COINMP_DLL_load_dll(path):
Expand Down
40 changes: 20 additions & 20 deletions pulp/apis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,32 +173,22 @@ class LpSolver:
"""A generic LP Solver"""
name = 'LpSolver'

def __init__(self, mip = True, msg = True, options = None, mip_start=False, timeLimit=None,
warmStart=False, *args, **kwargs):
def __init__(self, mip=True, msg=True, options=None, timeLimit=None,
*args, **kwargs):
"""
:param bool mip: if False, assume LP even if integer variables.
:param bool msg: if False, no log is shown.
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param list options:
:param bool warmStart:
:param float timeLimit: maximum time for solver
:param float timeLimit: maximum time for solver (in seconds)
:param args:
:param kwargs: optional named options to pass to each solver,
e.g. gapRel=0.1, gapAbs=10, logPath="",
"""
if options is None:
options = []
self.mip = mip
self.msg = msg
self.options = options
self.warmStart = warmStart
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for standard 'warmStart'")
if warmStart:
warnings.warn("Parameter mipStart and mip_start passed, using warmStart")
else:
self.warmStart = mip_start
self.timeLimit = timeLimit

# here we will store all other relevant information including:
Expand Down Expand Up @@ -330,11 +320,17 @@ def getCplexStyleArrays(self,lp, senseDict=None, LpVarCategories=None, LpObjSens

def to_dict(self):
data = dict(solver=self.name)
translate = {}
for v in ['mip', 'msg', 'warmStart', 'timeLimit', 'options', 'keepFiles']:
k = translate.get(v, v)
for k in ['mip', 'msg', 'keepFiles']:
try:
data[k] = getattr(self, k)
except AttributeError:
pass
for k in ['timeLimit', 'options']:
# with these ones, we only export if it has some content:
try:
data[k] = getattr(self, v)
value = getattr(self, k)
if value:
data[k] = value
except AttributeError:
pass
data.update(self.optionsDict)
Expand All @@ -350,9 +346,13 @@ class LpSolver_CMD(LpSolver):

name = 'LpSolver_CMD'

def __init__(self, path=None, keepFiles=0, *args, **kwargs):
def __init__(self, path=None, keepFiles=False, *args, **kwargs):
"""
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param list options: list of additional options to pass to solver (format depends on the solver)
:param float timeLimit: maximum time for solver (in seconds)
:param str path: a path to the solver binary
:param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
:param args: parameters to pass to :py:class:`LpSolver`
Expand Down
Loading

0 comments on commit 76b28fb

Please sign in to comment.