Skip to content

Commit

Permalink
Fix time accounting (#470)
Browse files Browse the repository at this point in the history
* re-arranged some things. tested on some solvers.

* we give more time to CBC.
  • Loading branch information
pchtsp authored Aug 11, 2021
1 parent f2af74e commit de4dbb7
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 31 deletions.
7 changes: 3 additions & 4 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def solve_CBC(self, lp, use_mps=True):
# In the Lp we do not create new variable or constraint names:
variablesNames = dict((v.name, v.name) for v in vs)
constraintsNames = dict((c, c) for c in lp.constraints)
objectiveName = None
cmds = " " + tmpLp + " "
if self.optionsDict.get("warmStart", False):
self.writesol(tmpMst, lp, vs, variablesNames, constraintsNames)
Expand Down Expand Up @@ -522,16 +521,16 @@ def actualSolve(self, lp):
hProb, self.COIN_INT_LOGLEVEL, ctypes.c_int(self.msg)
)

if self.timelimit:
if self.timeLimit:
if self.mip:
self.lib.CoinSetRealOption(
hProb, self.COIN_REAL_MIPMAXSEC, ctypes.c_double(self.timelimit)
hProb, self.COIN_REAL_MIPMAXSEC, ctypes.c_double(self.timeLimit)
)
else:
self.lib.CoinSetRealOption(
hProb,
self.COIN_REAL_MAXSECONDS,
ctypes.c_double(self.timelimit),
ctypes.c_double(self.timeLimit),
)
if self.fracGap:
# Hopefully this is the bound gap tolerance
Expand Down
3 changes: 3 additions & 0 deletions pulp/apis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ def __init__(
self.options = options
self.timeLimit = timeLimit

# this keeps the solver time in cpu time
self.solution_time = 0

# here we will store all other relevant information including:
# gapRel, gapAbs, maxMemory, maxNodes, threads, logPath
self.optionsDict = {k: v for k, v in kwargs.items() if v is not None}
Expand Down
22 changes: 16 additions & 6 deletions pulp/pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,8 @@ def __init__(self, name="NoName", sense=const.LpMinimize):
self._variables = []
self._variable_ids = {} # old school using dict.keys() for a set
self.dummyVar = None
self.solutionTime = 0
self.solutionCpuTime = 0

# locals
self.lastUnused = 0
Expand Down Expand Up @@ -1874,15 +1876,23 @@ def solve(self, solver=None, **kwargs):
solver = LpSolverDefault
wasNone, dummyVar = self.fixObjective()
# time it
self.solutionCpuTime = -clock()
self.solutionTime = -time()
self.startClock()
status = solver.actualSolve(self, **kwargs)
self.solutionTime += time()
self.solutionCpuTime += clock()
self.stopClock()
self.restoreObjective(wasNone, dummyVar)
self.solver = solver
return status

def startClock(self):
"initializes properties with the current time"
self.solutionCpuTime = -clock()
self.solutionTime = -time()

def stopClock(self):
"updates time wall time and cpu time"
self.solutionTime += time()
self.solutionCpuTime += clock()

def sequentialSolve(
self, objectives, absoluteTols=None, relativeTols=None, solver=None, debug=False
):
Expand Down Expand Up @@ -1911,7 +1921,7 @@ def sequentialSolve(
if not (relativeTols):
relativeTols = [1] * len(objectives)
# time it
self.solutionTime = -clock()
self.startClock()
statuses = []
for i, (obj, absol, rel) in enumerate(
zip(objectives, absoluteTols, relativeTols)
Expand All @@ -1925,7 +1935,7 @@ def sequentialSolve(
self += obj <= value(obj) * rel + absol, "%s_Sequence_Objective" % i
elif self.sense == const.LpMaximize:
self += obj >= value(obj) * rel + absol, "%s_Sequence_Objective" % i
self.solutionTime += clock()
self.stopClock()
self.solver = solver
return statuses

Expand Down
45 changes: 24 additions & 21 deletions pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1152,28 +1152,27 @@ def test_measuring_solving_time(self):
solver_settings = dict(
PULP_CBC_CMD=30, COIN_CMD=30, SCIP_CMD=30, GUROBI_CMD=50, CPLEX_CMD=50
)

bins = solver_settings.get(self.solver.name)
if bins is not None:
prob = create_bin_packing_problem(bins=bins)
self.solver.timeLimit = time_limit
delta = 2
if self.solver.name in ["CPLEX_CMD", "GUROBI_CMD"]:
self.solver.optionsDict["threads"] = 1
prob.solve(self.solver)
if self.solver.name in ["PULP_CBC_CMD", "COIN_CMD"]:
reported_time = prob.solutionCpuTime
delta = 4
else:
reported_time = prob.solutionTime

self.assertAlmostEqual(
reported_time,
time_limit,
delta=delta,
msg="optimization time for solver {}".format(self.solver.name),
)
self.assertTrue(True)
if bins is None:
# not all solvers have timeLimit support
return
prob = create_bin_packing_problem(bins=bins)
self.solver.timeLimit = time_limit
prob.solve(self.solver)
delta = 2
reported_time = prob.solutionTime
if self.solver.name in ["PULP_CBC_CMD", "COIN_CMD"]:
# CBC uses cpu-time for timeLimit
# also: CBC is less exact with the timeLimit
reported_time = prob.solutionCpuTime
delta = 4

self.assertAlmostEqual(
reported_time,
time_limit,
delta=delta,
msg="optimization time for solver {}".format(self.solver.name),
)


class PULP_CBC_CMDTest(BaseSolverTest.PuLPTest):
Expand Down Expand Up @@ -1336,3 +1335,7 @@ def getSortedDict(prob, keyCons="name", keyVars="name"):
_dict["constraints"].sort(key=lambda v: v[keyCons])
_dict["variables"].sort(key=lambda v: v[keyVars])
return _dict


if __name__ == "__main__":
unittest.main()

0 comments on commit de4dbb7

Please sign in to comment.