diff --git a/mip/highs.py b/mip/highs.py index b1897b77..4e002398 100644 --- a/mip/highs.py +++ b/mip/highs.py @@ -720,7 +720,7 @@ def __init__(self, model: mip.Model, name: str, sense: str): # Buffer string for storing names self._name_buffer = ffi.new(f"char[{self._lib.kHighsMaximumStringLength}]") - # type conversion maps + # type conversion maps (can not distinguish binary from integer!) self._var_type_map = { mip.CONTINUOUS: self._lib.kHighsVarTypeContinuous, mip.BINARY: self._lib.kHighsVarTypeInteger, @@ -760,8 +760,8 @@ def _get_double_option_value(self: "SolverHighs", name: str) -> float: ) return value[0] - def _get_bool_option_value(self: "SolverHighs", name: str) -> float: - value = ffi.new("bool*") + def _get_bool_option_value(self: "SolverHighs", name: str) -> int: + value = ffi.new("int*") check( self._lib.Highs_getBoolOptionValue(self._model, name.encode("UTF-8"), value) ) @@ -779,7 +779,7 @@ def _set_double_option_value(self: "SolverHighs", name: str, value: float): ) ) - def _set_bool_option_value(self: "SolverHighs", name: str, value: float): + def _set_bool_option_value(self: "SolverHighs", name: str, value: int): check( self._lib.Highs_setBoolOptionValue(self._model, name.encode("UTF-8"), value) ) @@ -815,6 +815,8 @@ def add_var( if name: check(self._lib.Highs_passColName(self._model, col, name.encode("utf-8"))) if var_type != mip.CONTINUOUS: + # Note that HiGHS doesn't distinguish binary and integer variables + # by type. There is only a boolean flag for "integrality". self._num_int_vars += 1 check( self._lib.Highs_changeColIntegrality( @@ -1035,6 +1037,18 @@ def set_start(self: "SolverHighs", start: List[Tuple["mip.Var", numbers.Real]]): self._lib.Highs_setSolution(self._model, cval, ffi.NULL, ffi.NULL, ffi.NULL) def set_objective(self: "SolverHighs", lin_expr: "mip.LinExpr", sense: str = ""): + # first reset old objective (all 0) + n = self.num_cols() + costs = ffi.new("double[]", n) # initialized to 0 + check( + self._lib.Highs_changeColsCostByRange( + self._model, + 0, # from_col + n - 1, # to_col + costs, + ) + ) + # set coefficients for var, coef in lin_expr.expr.items(): check(self._lib.Highs_changeColCost(self._model, var.idx, coef)) @@ -1518,7 +1532,11 @@ def remove_vars(self: "SolverHighs", varsList: List[int]): def var_get_index(self: "SolverHighs", name: str) -> int: idx = ffi.new("int *") - self._lib.Highs_getColByName(self._model, name.encode("utf-8"), idx) + status = self._lib.Highs_getColByName(self._model, name.encode("utf-8"), idx) + if status == STATUS_ERROR: + # This means that no var with that name was found. Unfortunately, + # HiGHS::getColByName doesn't assign a value to idx in that case. + return -1 return idx[0] def get_problem_name(self: "SolverHighs") -> str: diff --git a/test/mip_files_test.py b/test/mip_files_test.py index 785688c9..11a0daf3 100644 --- a/test/mip_files_test.py +++ b/test/mip_files_test.py @@ -139,6 +139,9 @@ def test_mip_file(solver: str, instance: str): max_dif = max(max(abs(ub), abs(lb)) * 0.01, TOL) + if solver == HIGHS and instance.endswith(".gz"): + pytest.skip("HiGHS does not support .gz files.") + m.read(instance) if bas_file: m.verbose = True diff --git a/test/mip_test.py b/test/mip_test.py index 10ab6cb0..053d2b3e 100644 --- a/test/mip_test.py +++ b/test/mip_test.py @@ -556,11 +556,21 @@ def test_constr_by_name_rhs(self, solver): assert model.constr_by_name("row0").rhs == val @pytest.mark.parametrize("solver", SOLVERS) - def test_var_by_name_rhs(self, solver): + def test_var_by_name_valid(self, solver): n, model = self.build_model(solver) - v = model.var_by_name("x({},{})".format(0, 0)) + name = "x({},{})".format(0, 0) + v = model.var_by_name(name) assert v is not None + assert isinstance(v, mip.Var) + assert v.name == name + + @pytest.mark.parametrize("solver", SOLVERS) + def test_var_by_name_invalid(self, solver): + n, model = self.build_model(solver) + + v = model.var_by_name("xyz_invalid_name") + assert v is None @pytest.mark.parametrize("solver", SOLVERS) def test_obj_const1(self, solver: str): diff --git a/test/test_model.py b/test/test_model.py index 00629a55..2d6de869 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -1294,6 +1294,21 @@ def test_copy(solver): assert id(term.expr) != id(term_copy.expr) +@skip_on(NotImplementedError) +@pytest.mark.parametrize("solver", SOLVERS) +def test_verbose(solver): + # set and get verbose flag + m = Model(solver_name=solver) + + # active + m.verbose = 1 + assert m.verbose == 1 + + # inactive + m.verbose = 0 + assert m.verbose == 0 + + @skip_on(NotImplementedError) @pytest.mark.parametrize("solver", SOLVERS) def test_constraint_with_lin_expr_and_lin_expr(solver): @@ -1356,6 +1371,7 @@ def test_objective(solver): m = Model(solver_name=solver, sense=MAXIMIZE) x = m.add_var(name="x", lb=0, ub=1) y = m.add_var(name="y", lb=0, ub=1) + z = m.add_var(name="z", lb=0, ub=1) m.objective = x - y + 0.5 assert m.objective.x is None @@ -1374,13 +1390,13 @@ def test_objective(solver): # Test changing the objective - m.objective = x + y + 1.5 + m.objective = y + 2*z + 1.5 m.sense = MINIMIZE # TODO: assert m.objective.sense == MINIMIZE assert len(m.objective.expr) == 2 - assert m.objective.expr[x] == 1 assert m.objective.expr[y] == 1 + assert m.objective.expr[z] == 2 assert m.objective.const == 1.5 status = m.optimize()