11"Python-MIP interface to the HiGHS solver."
22
3- import glob
43import numbers
54import logging
65import os .path
2221 libfile = os .environ [ENV_KEY ]
2322 logger .debug ("Choosing HiGHS library {libfile} via {ENV_KEY}." )
2423 else :
25- # try library shipped with highspy packaged
26- import highspy
24+ # Try library from highsbox, which is optional dependency.
25+ import highsbox
2726
28- pkg_path = os . path . dirname ( highspy . __file__ )
27+ root = highsbox . highs_dist_dir ( )
2928
30- # need library matching operating system
29+ # Need library matching operating system.
30+ # Following: PyOptInterface/src/pyoptinterface/_src/highs.py
3131 platform = sys .platform .lower ()
3232 if "linux" in platform :
33- patterns = [ "highs_bindings.*.so" , "_core.*. so" ]
33+ libfile = os . path . join ( root , "lib" , "libhighs. so" )
3434 elif platform .startswith ("win" ):
35- patterns = [ "highs_bindings.*.pyd" , "_core.*.pyd" ]
35+ libfile = os . path . join ( root , "bin" , "highs.dll" )
3636 elif any (platform .startswith (p ) for p in ("darwin" , "macos" )):
37- patterns = [ "highs_bindings.*.so" , "_core.*.so" ]
37+ libfile = os . path . join ( root , "lib" , "libhighs.dylib" )
3838 else :
3939 raise NotImplementedError (f"{ sys .platform } not supported!" )
40-
41- # there should only be one match
42- matched_files = []
43- for pattern in patterns :
44- matched_files .extend (glob .glob (os .path .join (pkg_path , pattern )))
45- if len (matched_files ) != 1 :
46- raise FileNotFoundError (f"Could not find HiGHS library in { pkg_path } ." )
47- [libfile ] = matched_files
48- logger .debug ("Choosing HiGHS library {libfile} via highspy package." )
40+ logger .debug ("Choosing HiGHS library {libfile} via highsbox package." )
4941
5042 highslib = ffi .dlopen (libfile )
5143 has_highs = True
687679def check (status ):
688680 if status == STATUS_ERROR :
689681 raise mip .InterfacingError ("Unknown error in call to HiGHS." )
682+ return status
690683
691684
692685class SolverHighs (mip .Solver ):
@@ -720,7 +713,7 @@ def __init__(self, model: mip.Model, name: str, sense: str):
720713 # Buffer string for storing names
721714 self ._name_buffer = ffi .new (f"char[{ self ._lib .kHighsMaximumStringLength } ]" )
722715
723- # type conversion maps
716+ # type conversion maps (can not distinguish binary from integer!)
724717 self ._var_type_map = {
725718 mip .CONTINUOUS : self ._lib .kHighsVarTypeContinuous ,
726719 mip .BINARY : self ._lib .kHighsVarTypeInteger ,
@@ -760,8 +753,8 @@ def _get_double_option_value(self: "SolverHighs", name: str) -> float:
760753 )
761754 return value [0 ]
762755
763- def _get_bool_option_value (self : "SolverHighs" , name : str ) -> float :
764- value = ffi .new ("bool *" )
756+ def _get_bool_option_value (self : "SolverHighs" , name : str ) -> int :
757+ value = ffi .new ("int *" )
765758 check (
766759 self ._lib .Highs_getBoolOptionValue (self ._model , name .encode ("UTF-8" ), value )
767760 )
@@ -779,7 +772,7 @@ def _set_double_option_value(self: "SolverHighs", name: str, value: float):
779772 )
780773 )
781774
782- def _set_bool_option_value (self : "SolverHighs" , name : str , value : float ):
775+ def _set_bool_option_value (self : "SolverHighs" , name : str , value : int ):
783776 check (
784777 self ._lib .Highs_setBoolOptionValue (self ._model , name .encode ("UTF-8" ), value )
785778 )
@@ -815,6 +808,8 @@ def add_var(
815808 if name :
816809 check (self ._lib .Highs_passColName (self ._model , col , name .encode ("utf-8" )))
817810 if var_type != mip .CONTINUOUS :
811+ # Note that HiGHS doesn't distinguish binary and integer variables
812+ # by type. There is only a boolean flag for "integrality".
818813 self ._num_int_vars += 1
819814 check (
820815 self ._lib .Highs_changeColIntegrality (
@@ -1035,6 +1030,18 @@ def set_start(self: "SolverHighs", start: List[Tuple["mip.Var", numbers.Real]]):
10351030 self ._lib .Highs_setSolution (self ._model , cval , ffi .NULL , ffi .NULL , ffi .NULL )
10361031
10371032 def set_objective (self : "SolverHighs" , lin_expr : "mip.LinExpr" , sense : str = "" ):
1033+ # first reset old objective (all 0)
1034+ n = self .num_cols ()
1035+ costs = ffi .new ("double[]" , n ) # initialized to 0
1036+ check (
1037+ self ._lib .Highs_changeColsCostByRange (
1038+ self ._model ,
1039+ 0 , # from_col
1040+ n - 1 , # to_col
1041+ costs ,
1042+ )
1043+ )
1044+
10381045 # set coefficients
10391046 for var , coef in lin_expr .expr .items ():
10401047 check (self ._lib .Highs_changeColCost (self ._model , var .idx , coef ))
@@ -1323,7 +1330,11 @@ def remove_constrs(self: "SolverHighs", constrsList: List[int]):
13231330
13241331 def constr_get_index (self : "SolverHighs" , name : str ) -> int :
13251332 idx = ffi .new ("int *" )
1326- self ._lib .Highs_getRowByName (self ._model , name .encode ("utf-8" ), idx )
1333+ status = self ._lib .Highs_getRowByName (self ._model , name .encode ("utf-8" ), idx )
1334+ if status == STATUS_ERROR :
1335+ # This means that no constraint with that name was found. Unfortunately,
1336+ # Highs: getRowByName doesn't assign a value to idx in that case.
1337+ return - 1
13271338 return idx [0 ]
13281339
13291340 # Variable-related getters/setters
@@ -1422,12 +1433,15 @@ def var_set_obj(self: "SolverHighs", var: "mip.Var", value: numbers.Real):
14221433 check (self ._lib .Highs_changeColCost (self ._model , var .idx , value ))
14231434
14241435 def var_get_var_type (self : "SolverHighs" , var : "mip.Var" ) -> str :
1436+ # Highs_getColIntegrality only works if some variable is not continuous.
1437+ # Since we want this method to always work, we need to catch this case first.
1438+ if self ._num_int_vars == 0 :
1439+ return mip .CONTINUOUS
1440+
14251441 var_type = ffi .new ("int*" )
1426- ret = self ._lib .Highs_getColIntegrality (self ._model , var .idx , var_type )
1442+ check ( self ._lib .Highs_getColIntegrality (self ._model , var .idx , var_type ) )
14271443 if var_type [0 ] not in self ._highs_type_map :
1428- raise ValueError (
1429- f"Invalid variable type returned by HiGHS: { var_type [0 ]} (ret={ ret } )"
1430- )
1444+ raise ValueError (f"Invalid variable type returned by HiGHS: { var_type [0 ]} ." )
14311445 return self ._highs_type_map [var_type [0 ]]
14321446
14331447 def var_set_var_type (self : "SolverHighs" , var : "mip.Var" , value : str ):
@@ -1518,7 +1532,11 @@ def remove_vars(self: "SolverHighs", varsList: List[int]):
15181532
15191533 def var_get_index (self : "SolverHighs" , name : str ) -> int :
15201534 idx = ffi .new ("int *" )
1521- self ._lib .Highs_getColByName (self ._model , name .encode ("utf-8" ), idx )
1535+ status = self ._lib .Highs_getColByName (self ._model , name .encode ("utf-8" ), idx )
1536+ if status == STATUS_ERROR :
1537+ # This means that no var with that name was found. Unfortunately,
1538+ # HiGHS::getColByName doesn't assign a value to idx in that case.
1539+ return - 1
15221540 return idx [0 ]
15231541
15241542 def get_problem_name (self : "SolverHighs" ) -> str :
0 commit comments