You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MIPLearn/tests/test_solvers.py

182 lines
6.9 KiB

# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2022, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from tempfile import NamedTemporaryFile
import numpy as np
import pytest
from miplearn.h5 import H5File
from miplearn.problems.setcover import (
SetCoverData,
build_setcover_model_gurobipy,
build_setcover_model_pyomo,
)
from miplearn.solvers.abstract import AbstractModel
inf = float("inf")
@pytest.fixture
def data() -> SetCoverData:
return SetCoverData(
costs=np.array([5, 10, 12, 6, 8]),
incidence_matrix=np.array(
[
[1, 0, 0, 1, 0],
[1, 1, 0, 0, 0],
[0, 0, 1, 1, 1],
],
),
)
def test_gurobi(data: SetCoverData) -> None:
_test_solver(build_setcover_model_gurobipy, data)
def test_pyomo_persistent(data: SetCoverData) -> None:
_test_solver(lambda d: build_setcover_model_pyomo(d, "gurobi_persistent"), data)
def _test_solver(build_model, data):
_test_extract(build_model(data))
_test_add_constr(build_model(data))
_test_fix_vars(build_model(data))
_test_infeasible(build_model(data))
def _test_extract(model):
with NamedTemporaryFile() as tempfile:
with H5File(tempfile.name) as h5:
def test_scalar(key, expected_value):
actual_value = h5.get_scalar(key)
assert actual_value is not None
assert actual_value == expected_value
def test_array(key, expected_value):
actual_value = h5.get_array(key)
assert actual_value is not None
assert actual_value.tolist() == expected_value
def test_sparse(key, expected_value):
actual_value = h5.get_sparse(key)
assert actual_value is not None
assert actual_value.todense().tolist() == expected_value
model.extract_after_load(h5)
test_sparse(
"static_constr_lhs",
[
[1.0, 0.0, 0.0, 1.0, 0.0],
[1.0, 1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
],
)
test_array("static_constr_names", [b"eqs[0]", b"eqs[1]", b"eqs[2]"])
test_array("static_constr_rhs", [1, 1, 1])
test_array("static_constr_sense", [b">", b">", b">"])
test_scalar("static_obj_offset", 0.0)
test_scalar("static_sense", "min")
test_array("static_var_lower_bounds", [0.0, 0.0, 0.0, 0.0, 0.0])
test_array(
"static_var_names",
[
b"x[0]",
b"x[1]",
b"x[2]",
b"x[3]",
b"x[4]",
],
)
test_array("static_var_obj_coeffs", [5.0, 10.0, 12.0, 6.0, 8.0])
test_array("static_var_types", [b"B", b"B", b"B", b"B", b"B"])
test_array("static_var_upper_bounds", [1.0, 1.0, 1.0, 1.0, 1.0])
relaxed = model.relax()
relaxed.optimize()
relaxed.extract_after_lp(h5)
test_array("lp_constr_dual_values", [0, 5, 6])
test_array("lp_constr_slacks", [1, 0, 0])
test_scalar("lp_obj_value", 11.0)
test_array("lp_var_reduced_costs", [0.0, 5.0, 6.0, 0.0, 2.0])
test_array("lp_var_values", [1.0, 0.0, 0.0, 1.0, 0.0])
if model._supports_basis_status:
test_array("lp_var_basis_status", [b"B", b"L", b"L", b"B", b"L"])
test_array("lp_constr_basis_status", [b"B", b"N", b"N"])
if model._supports_sensitivity_analysis:
test_array("lp_constr_sa_rhs_up", [2, 1, 1])
test_array("lp_constr_sa_rhs_down", [-inf, 0, 0])
test_array("lp_var_sa_obj_up", [10.0, inf, inf, 8.0, inf])
test_array("lp_var_sa_obj_down", [0.0, 5.0, 6.0, 0.0, 6.0])
test_array("lp_var_sa_ub_up", [inf, inf, inf, inf, inf])
test_array("lp_var_sa_ub_down", [1.0, 0.0, 0.0, 1.0, 0.0])
test_array("lp_var_sa_lb_up", [1.0, 1.0, 1.0, 1.0, 1.0])
test_array("lp_var_sa_lb_down", [-inf, 0.0, 0.0, -inf, 0.0])
lp_wallclock_time = h5.get_scalar("lp_wallclock_time")
assert lp_wallclock_time is not None
assert lp_wallclock_time >= 0
model.optimize()
model.extract_after_mip(h5)
test_array("mip_constr_slacks", [1, 0, 0])
test_array("mip_var_values", [1.0, 0.0, 0.0, 1.0, 0.0])
test_scalar("mip_gap", 0)
test_scalar("mip_obj_bound", 11.0)
test_scalar("mip_obj_value", 11.0)
mip_wallclock_time = h5.get_scalar("mip_wallclock_time")
assert mip_wallclock_time is not None
assert mip_wallclock_time > 0
if model._supports_node_count:
count = h5.get_scalar("mip_node_count")
assert count is not None
assert count >= 0
if model._supports_solution_pool:
pool_var_values = h5.get_array("pool_var_values")
pool_obj_values = h5.get_array("pool_obj_values")
assert pool_var_values is not None
assert pool_obj_values is not None
assert len(pool_obj_values.shape) == 1
n_sols = len(pool_obj_values)
assert pool_var_values.shape == (n_sols, 5)
def _test_add_constr(model: AbstractModel):
with NamedTemporaryFile() as tempfile:
with H5File(tempfile.name) as h5:
model.add_constrs(
np.array([b"x[2]", b"x[3]"], dtype="S"),
np.array([[0, 1], [1, 0]]),
np.array(["=", "="], dtype="S"),
np.array([0, 0]),
)
model.optimize()
model.extract_after_mip(h5)
assert h5.get_array("mip_var_values").tolist() == [1, 0, 0, 0, 1]
def _test_fix_vars(model: AbstractModel):
with NamedTemporaryFile() as tempfile:
with H5File(tempfile.name) as h5:
model.fix_variables(
var_names=np.array([b"x[2]", b"x[3]"], dtype="S"),
var_values=np.array([0, 0]),
)
model.optimize()
model.extract_after_mip(h5)
assert h5.get_array("mip_var_values").tolist() == [1, 0, 0, 0, 1]
def _test_infeasible(model: AbstractModel):
with NamedTemporaryFile() as tempfile:
with H5File(tempfile.name) as h5:
model.fix_variables(
var_names=np.array([b"x[0]", b"x[3]"], dtype="S"),
var_values=np.array([0, 0]),
)
model.optimize()
model.extract_after_mip(h5)
assert h5.get_array("mip_var_values") is None