Skip to content

Rs/highs fixes #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions mip/highs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
)
Expand All @@ -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)
)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions test/mip_files_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions test/mip_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
20 changes: 18 additions & 2 deletions test/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down