diff --git a/src/solvers/jump.jl b/src/solvers/jump.jl index bfa6e51..25e8467 100644 --- a/src/solvers/jump.jl +++ b/src/solvers/jump.jl @@ -12,7 +12,24 @@ function _add_constrs( constrs_sense, constrs_rhs, stats, -) end +) + n, m = length(var_names), length(constrs_rhs) + vars = [variable_by_name(model, v) for v in var_names] + for i = 1:m + lhs = sum(constrs_lhs[i, j] * vars[j] for j = 1:n) + sense = constrs_sense[i] + rhs = constrs_rhs[i] + if sense == "=" + @constraint(model, lhs == rhs) + elseif sense == ">" + @constraint(model, lhs >= rhs) + elseif sense == "<" + @constraint(model, lhs <= rhs) + else + error("Unknown sense: $sense") + end + end +end function _extract_after_load(model::JuMP.Model, h5) if JuMP.objective_sense(model) == MOI.MIN_SENSE @@ -230,7 +247,12 @@ function _extract_after_mip(model::JuMP.Model, h5) h5.put_array("mip_constr_slacks", slacks) end -function _fix_variables(model::JuMP.Model, var_names, var_values, stats) end +function _fix_variables(model::JuMP.Model, var_names, var_values, stats) + vars = [variable_by_name(model, v) for v in var_names] + for (i, var) in enumerate(vars) + fix(var, var_values[i], force=true) + end +end function _optimize(model::JuMP.Model) optimize!(model) @@ -245,9 +267,18 @@ function _relax(model::JuMP.Model) return relaxed end -function _set_warm_starts(model::JuMP.Model, var_names, var_values, stats) end +function _set_warm_starts(model::JuMP.Model, var_names, var_values, stats) + (n_starts, _) = size(var_values) + n_starts == 1 || error("JuMP does not support multiple warm starts") + vars = [variable_by_name(model, v) for v in var_names] + for (i, var) in enumerate(vars) + set_start_value(var, var_values[i]) + end +end -function _write(model::JuMP.Model, filename) end +function _write(model::JuMP.Model, filename) + write_to_file(model, filename) +end # ----------------------------------------------------------------------------- @@ -258,15 +289,21 @@ function __init_solvers_jump__() self.inner = inner end - add_constrs(self, var_names, constrs_lhs, constrs_sense, constrs_rhs, stats) = - _add_constrs( - self.inner, - var_names, - constrs_lhs, - constrs_sense, - constrs_rhs, - stats, - ) + add_constrs( + self, + var_names, + constrs_lhs, + constrs_sense, + constrs_rhs, + stats = nothing, + ) = _add_constrs( + self.inner, + from_str_array(var_names), + constrs_lhs, + from_str_array(constrs_sense), + constrs_rhs, + stats, + ) extract_after_load(self, h5) = _extract_after_load(self.inner, h5) @@ -274,15 +311,15 @@ function __init_solvers_jump__() extract_after_mip(self, h5) = _extract_after_mip(self.inner, h5) - fix_variables(self, var_names, var_values, stats) = - _fix_variables(self.inner, var_names, var_values, stats) + fix_variables(self, var_names, var_values, stats = nothing) = + _fix_variables(self.inner, from_str_array(var_names), var_values, stats) optimize(self) = _optimize(self.inner) relax(self) = Class(_relax(self.inner)) - set_warm_starts(self, var_names, var_values, stats) = - _set_warm_starts(self.inner, var_names, var_values, stats) + set_warm_starts(self, var_names, var_values, stats = nothing) = + _set_warm_starts(self.inner, from_str_array(var_names), var_values, stats) write(self, filename) = _write(self.inner, filename) end diff --git a/test/Project.toml b/test/Project.toml index 99916fa..c1e4ef7 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -6,6 +6,7 @@ version = "0.1.0" [deps] HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MIPLearn = "2b1277c3-b477-4c49-a15e-7ba350325c68" diff --git a/test/fixtures/bell5.h5 b/test/fixtures/bell5.h5 index 8eb7f23..461b261 100644 Binary files a/test/fixtures/bell5.h5 and b/test/fixtures/bell5.h5 differ diff --git a/test/src/solvers/test_jump.jl b/test/src/solvers/test_jump.jl index 4772f5e..ba3af8d 100644 --- a/test/src/solvers/test_jump.jl +++ b/test/src/solvers/test_jump.jl @@ -1,3 +1,6 @@ +using JuMP +import MIPLearn: from_str_array, to_str_array + function build_model() data = SetCoverData( costs = [5, 10, 12, 6, 8], @@ -12,6 +15,10 @@ end function test_solvers_jump() test_solvers_jump_extract() + test_solvers_jump_add_constrs() + test_solvers_jump_fix_vars() + test_solvers_jump_warm_starts() + test_solvers_jump_write() end function test_solvers_jump_extract() @@ -30,7 +37,7 @@ function test_solvers_jump_extract() end function test_str_array(key, expected) - actual = MIPLearn.from_str_array(h5.get_array(key)) + actual = from_str_array(h5.get_array(key)) @debug actual, expected @test actual !== nothing @test all(actual .== expected) @@ -74,7 +81,7 @@ function test_solvers_jump_extract() test_array("lp_var_reduced_costs", [-5, 0, 6, 0, 2]) test_array("lp_var_values", [1, 0, 0, 1, 0]) test_str_array("lp_var_basis_status", ["U", "B", "L", "B", "L"]) - test_str_array("lp_constr_basis_status", ["B","N","N"]) + test_str_array("lp_constr_basis_status", ["B", "N", "N"]) test_array("lp_constr_sa_rhs_up", [2, 2, 1]) test_array("lp_constr_sa_rhs_down", [-Inf, 1, 0]) test_array("lp_var_sa_obj_up", [10, Inf, Inf, 8, Inf]) @@ -96,3 +103,54 @@ function test_solvers_jump_extract() mip_wallclock_time = h5.get_scalar("mip_wallclock_time") @test mip_wallclock_time >= 0 end + +function test_solvers_jump_add_constrs() + h5 = H5File(tempname(), "w") + model = build_model() + model.extract_after_load(h5) + model.add_constrs( + to_str_array(["x[2]", "x[3]"]), + [ + 0 1 + 1 0 + ], + to_str_array(["=", "="]), + [0, 0], + ) + model.optimize() + model.extract_after_mip(h5) + @test all(h5.get_array("mip_var_values") .≈ [1, 0, 0, 0, 1]) +end + +function test_solvers_jump_fix_vars() + h5 = H5File(tempname(), "w") + model = build_model() + model.extract_after_load(h5) + model.fix_variables( + to_str_array(["x[2]", "x[3]"]), + [0, 0], + ) + model.optimize() + model.extract_after_mip(h5) + @test all(h5.get_array("mip_var_values") .≈ [1, 0, 0, 0, 1]) +end + +function test_solvers_jump_warm_starts() + # TODO: Check presence of warm start on log file + h5 = H5File(tempname(), "w") + model = build_model() + model.extract_after_load(h5) + model.set_warm_starts( + to_str_array(["x[0]", "x[1]", "x[2]", "x[3]", "x[4]"]), + [1 0 0 0 1], + ) + model.optimize() +end + +function test_solvers_jump_write() + mps_filename = "$(tempname()).mps" + model = build_model() + model.write(mps_filename) + @test isfile(mps_filename) + rm(mps_filename) +end \ No newline at end of file