3
3
import os
4
4
import subprocess
5
5
from math import inf
6
- from typing import List
6
+ from typing import List , Optional
7
7
8
8
from .. import constants
9
9
from .core import LpSolver , LpSolver_CMD , PulpSolverError
@@ -271,13 +271,17 @@ def readsol(self, filename):
271
271
return values
272
272
273
273
274
+ highspy = None
275
+
276
+
274
277
class HiGHS (LpSolver ):
275
278
name = "HiGHS"
276
279
277
280
try :
278
281
global highspy
279
282
import highspy # type: ignore[import-not-found, import-untyped, unused-ignore]
280
283
except :
284
+ hscb = None
281
285
282
286
def available (self ):
283
287
"""True if the solver is available"""
@@ -288,13 +292,7 @@ def actualSolve(self, lp, callback=None):
288
292
raise PulpSolverError ("HiGHS: Not Available" )
289
293
290
294
else :
291
- # Note(maciej): It was surprising to me that higshpy wasn't logging out of the box,
292
- # even with the different logging options set. This callback seems to work, but there
293
- # are probably better ways of doing this ¯\_(ツ)_/¯
294
- DEFAULT_CALLBACK = lambda logType , logMsg , callbackValue : print (
295
- f"[{ logType .name } ] { logMsg } "
296
- )
297
- DEFAULT_CALLBACK_VALUE = ""
295
+ hscb = highspy .cb # type: ignore[attr-defined, unused-ignore]
298
296
299
297
def __init__ (
300
298
self ,
@@ -305,21 +303,23 @@ def __init__(
305
303
gapRel = None ,
306
304
threads = None ,
307
305
timeLimit = None ,
306
+ callbacksToActivate : Optional [List [highspy .cb .HighsCallbackType ]] = None ,
308
307
** solverParams ,
309
308
):
310
309
"""
311
310
:param bool mip: if False, assume LP even if integer variables
312
311
:param bool msg: if False, no log is shown
313
- :param tuple callbackTuple: Tuple of log callback function (see DEFAULT_CALLBACK above for definition)
314
- and callbackValue (tag embedded in every callback)
312
+ :param tuple callbackTuple: Tuple of callback function and callbackValue (see tests for an example)
315
313
:param float gapRel: relative gap tolerance for the solver to stop (in fraction)
316
314
:param float gapAbs: absolute gap tolerance for the solver to stop
317
315
:param int threads: sets the maximum number of threads
318
316
:param float timeLimit: maximum time for solver (in seconds)
319
317
:param dict solverParams: list of named options to pass directly to the HiGHS solver
318
+ :param callbacksToActivate: list of callback types to start
320
319
"""
321
320
super ().__init__ (mip = mip , msg = msg , timeLimit = timeLimit , ** solverParams )
322
321
self .callbackTuple = callbackTuple
322
+ self .callbacksToActivate = callbacksToActivate
323
323
self .gapAbs = gapAbs
324
324
self .gapRel = gapRel
325
325
self .threads = threads
@@ -333,12 +333,12 @@ def callSolver(self, lp):
333
333
def createAndConfigureSolver (self , lp ):
334
334
lp .solverModel = highspy .Highs ()
335
335
336
- if self .msg and self . callbackTuple :
337
- callbackTuple = self .callbackTuple or (
338
- HiGHS . DEFAULT_CALLBACK ,
339
- HiGHS . DEFAULT_CALLBACK_VALUE ,
340
- )
341
- lp .solverModel .setLogCallback ( * callbackTuple )
336
+ if self .callbackTuple :
337
+ lp . solverModel . setCallback ( * self .callbackTuple )
338
+
339
+ if self . callbacksToActivate :
340
+ for cb_type in self . callbacksToActivate :
341
+ lp .solverModel .startCallback ( cb_type )
342
342
343
343
if not self .msg :
344
344
lp .solverModel .setOptionValue ("output_flag" , False )
@@ -465,6 +465,10 @@ def findSolutionValues(self, lp):
465
465
constants .LpStatusOptimal ,
466
466
constants .LpSolutionIntegerFeasible ,
467
467
),
468
+ HighsModelStatus .kInterrupt : (
469
+ constants .LpStatusOptimal ,
470
+ constants .LpSolutionIntegerFeasible ,
471
+ ),
468
472
HighsModelStatus .kTimeLimit : (
469
473
constants .LpStatusOptimal ,
470
474
constants .LpSolutionIntegerFeasible ,
0 commit comments