mirror of
https://github.com/ANL-CEEESA/MIPLearn.jl.git
synced 2025-12-06 08:28:52 -06:00
Add support for MIQPs; implement max cut model
This commit is contained in:
2
deps/build.jl
vendored
2
deps/build.jl
vendored
@@ -5,7 +5,7 @@ function install_miplearn()
|
|||||||
Conda.update()
|
Conda.update()
|
||||||
pip = joinpath(dirname(pyimport("sys").executable), "pip")
|
pip = joinpath(dirname(pyimport("sys").executable), "pip")
|
||||||
isfile(pip) || error("$pip: invalid path")
|
isfile(pip) || error("$pip: invalid path")
|
||||||
run(`$pip install miplearn==0.4.2`)
|
run(`$pip install miplearn==0.4.4`)
|
||||||
end
|
end
|
||||||
|
|
||||||
install_miplearn()
|
install_miplearn()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ include("collectors.jl")
|
|||||||
include("components.jl")
|
include("components.jl")
|
||||||
include("extractors.jl")
|
include("extractors.jl")
|
||||||
include("io.jl")
|
include("io.jl")
|
||||||
|
include("problems/maxcut.jl")
|
||||||
include("problems/setcover.jl")
|
include("problems/setcover.jl")
|
||||||
include("problems/stab.jl")
|
include("problems/stab.jl")
|
||||||
include("problems/tsp.jl")
|
include("problems/tsp.jl")
|
||||||
@@ -24,6 +25,7 @@ function __init__()
|
|||||||
__init_components__()
|
__init_components__()
|
||||||
__init_extractors__()
|
__init_extractors__()
|
||||||
__init_io__()
|
__init_io__()
|
||||||
|
__init_problems_maxcut__()
|
||||||
__init_problems_setcover__()
|
__init_problems_setcover__()
|
||||||
__init_problems_stab__()
|
__init_problems_stab__()
|
||||||
__init_problems_tsp__()
|
__init_problems_tsp__()
|
||||||
|
|||||||
31
src/problems/maxcut.jl
Normal file
31
src/problems/maxcut.jl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||||
|
# Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using JuMP
|
||||||
|
|
||||||
|
global MaxCutData = PyNULL()
|
||||||
|
global MaxCutGenerator = PyNULL()
|
||||||
|
|
||||||
|
function __init_problems_maxcut__()
|
||||||
|
copy!(MaxCutData, pyimport("miplearn.problems.maxcut").MaxCutData)
|
||||||
|
copy!(MaxCutGenerator, pyimport("miplearn.problems.maxcut").MaxCutGenerator)
|
||||||
|
end
|
||||||
|
|
||||||
|
function build_maxcut_model_jump(data::Any; optimizer)
|
||||||
|
if data isa String
|
||||||
|
data = read_pkl_gz(data)
|
||||||
|
end
|
||||||
|
nodes = collect(data.graph.nodes())
|
||||||
|
edges = collect(data.graph.edges())
|
||||||
|
model = Model(optimizer)
|
||||||
|
@variable(model, x[nodes], Bin)
|
||||||
|
@objective(
|
||||||
|
model,
|
||||||
|
Min,
|
||||||
|
sum(-data.weights[i] * x[e[1]] * (1 - x[e[2]]) for (i, e) in enumerate(edges))
|
||||||
|
)
|
||||||
|
return JumpModel(model)
|
||||||
|
end
|
||||||
|
|
||||||
|
export MaxCutData, MaxCutGenerator, build_maxcut_model_jump
|
||||||
@@ -89,14 +89,27 @@ function _extract_after_load_vars(model::JuMP.Model, h5)
|
|||||||
for v in vars
|
for v in vars
|
||||||
]
|
]
|
||||||
types = [JuMP.is_binary(v) ? "B" : JuMP.is_integer(v) ? "I" : "C" for v in vars]
|
types = [JuMP.is_binary(v) ? "B" : JuMP.is_integer(v) ? "I" : "C" for v in vars]
|
||||||
obj = objective_function(model, AffExpr)
|
|
||||||
obj_coeffs = [v ∈ keys(obj.terms) ? obj.terms[v] : 0.0 for v in vars]
|
# Linear obj terms
|
||||||
|
obj = objective_function(model, QuadExpr)
|
||||||
|
obj_coeffs_linear = [v ∈ keys(obj.aff.terms) ? obj.aff.terms[v] : 0.0 for v in vars]
|
||||||
|
|
||||||
|
# Quadratic obj terms
|
||||||
|
if length(obj) > 0
|
||||||
|
nvars = length(vars)
|
||||||
|
obj_coeffs_quad = zeros(nvars, nvars)
|
||||||
|
for (pair, coeff) in obj.terms
|
||||||
|
obj_coeffs_quad[pair.a.index.value, pair.b.index.value] = coeff
|
||||||
|
end
|
||||||
|
h5.put_array("static_var_obj_coeffs_quad", obj_coeffs_quad)
|
||||||
|
end
|
||||||
|
|
||||||
h5.put_array("static_var_names", to_str_array(JuMP.name.(vars)))
|
h5.put_array("static_var_names", to_str_array(JuMP.name.(vars)))
|
||||||
h5.put_array("static_var_types", to_str_array(types))
|
h5.put_array("static_var_types", to_str_array(types))
|
||||||
h5.put_array("static_var_lower_bounds", lb)
|
h5.put_array("static_var_lower_bounds", lb)
|
||||||
h5.put_array("static_var_upper_bounds", ub)
|
h5.put_array("static_var_upper_bounds", ub)
|
||||||
h5.put_array("static_var_obj_coeffs", obj_coeffs)
|
h5.put_array("static_var_obj_coeffs", obj_coeffs_linear)
|
||||||
h5.put_scalar("static_obj_offset", obj.constant)
|
h5.put_scalar("static_obj_offset", obj.aff.constant)
|
||||||
end
|
end
|
||||||
|
|
||||||
function _extract_after_load_constrs(model::JuMP.Model, h5)
|
function _extract_after_load_constrs(model::JuMP.Model, h5)
|
||||||
@@ -143,7 +156,7 @@ function _extract_after_load_constrs(model::JuMP.Model, h5)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if isempty(names)
|
if isempty(names)
|
||||||
error("no model constraints found; note that MIPLearn ignores unnamed constraints")
|
return
|
||||||
end
|
end
|
||||||
lhs = sparse(lhs_rows, lhs_cols, lhs_values, length(rhs), JuMP.num_variables(model))
|
lhs = sparse(lhs_rows, lhs_cols, lhs_values, length(rhs), JuMP.num_variables(model))
|
||||||
h5.put_sparse("static_constr_lhs", lhs)
|
h5.put_sparse("static_constr_lhs", lhs)
|
||||||
@@ -282,9 +295,11 @@ function _extract_after_mip(model::JuMP.Model, h5)
|
|||||||
|
|
||||||
# Slacks
|
# Slacks
|
||||||
lhs = h5.get_sparse("static_constr_lhs")
|
lhs = h5.get_sparse("static_constr_lhs")
|
||||||
rhs = h5.get_array("static_constr_rhs")
|
if lhs !== nothing
|
||||||
slacks = abs.(lhs * x - rhs)
|
rhs = h5.get_array("static_constr_rhs")
|
||||||
h5.put_array("mip_constr_slacks", slacks)
|
slacks = abs.(lhs * x - rhs)
|
||||||
|
h5.put_array("mip_constr_slacks", slacks)
|
||||||
|
end
|
||||||
|
|
||||||
# Cuts and lazy constraints
|
# Cuts and lazy constraints
|
||||||
ext = model.ext[:miplearn]
|
ext = model.ext[:miplearn]
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ include("Cuts/tableau/test_gmi_dual.jl")
|
|||||||
include("problems/test_setcover.jl")
|
include("problems/test_setcover.jl")
|
||||||
include("problems/test_stab.jl")
|
include("problems/test_stab.jl")
|
||||||
include("problems/test_tsp.jl")
|
include("problems/test_tsp.jl")
|
||||||
|
include("problems/test_maxcut.jl")
|
||||||
include("solvers/test_jump.jl")
|
include("solvers/test_jump.jl")
|
||||||
include("test_io.jl")
|
include("test_io.jl")
|
||||||
include("test_usage.jl")
|
include("test_usage.jl")
|
||||||
@@ -37,6 +38,7 @@ function runtests()
|
|||||||
test_problems_setcover()
|
test_problems_setcover()
|
||||||
test_problems_stab()
|
test_problems_stab()
|
||||||
test_problems_tsp()
|
test_problems_tsp()
|
||||||
|
test_problems_maxcut()
|
||||||
test_solvers_jump()
|
test_solvers_jump()
|
||||||
test_usage()
|
test_usage()
|
||||||
test_cuts()
|
test_cuts()
|
||||||
|
|||||||
54
test/src/problems/test_maxcut.jl
Normal file
54
test/src/problems/test_maxcut.jl
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||||
|
# Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using PyCall
|
||||||
|
|
||||||
|
function test_problems_maxcut()
|
||||||
|
np = pyimport("numpy")
|
||||||
|
random = pyimport("random")
|
||||||
|
scipy_stats = pyimport("scipy.stats")
|
||||||
|
randint = scipy_stats.randint
|
||||||
|
uniform = scipy_stats.uniform
|
||||||
|
|
||||||
|
# Set random seed
|
||||||
|
random.seed(42)
|
||||||
|
np.random.seed(42)
|
||||||
|
|
||||||
|
# Build random instance
|
||||||
|
data = MaxCutGenerator(
|
||||||
|
n = randint(low = 10, high = 11),
|
||||||
|
p = uniform(loc = 0.5, scale = 0.0),
|
||||||
|
fix_graph = false,
|
||||||
|
).generate(
|
||||||
|
1,
|
||||||
|
)[1]
|
||||||
|
|
||||||
|
# Build model
|
||||||
|
model = build_maxcut_model_jump(data, optimizer = SCIP.Optimizer)
|
||||||
|
|
||||||
|
# Check static features
|
||||||
|
h5 = H5File(tempname(), "w")
|
||||||
|
model.extract_after_load(h5)
|
||||||
|
obj_linear = h5.get_array("static_var_obj_coeffs")
|
||||||
|
obj_quad = h5.get_array("static_var_obj_coeffs_quad")
|
||||||
|
@test obj_linear == [3.0, 1.0, 3.0, 1.0, -1.0, 0.0, -1.0, 0.0, -1.0, 0.0]
|
||||||
|
@test obj_quad == [
|
||||||
|
0.0 0.0 -1.0 1.0 -1.0 0.0 0.0 0.0 -1.0 -1.0
|
||||||
|
0.0 0.0 1.0 -1.0 0.0 -1.0 -1.0 0.0 0.0 1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 -1.0 0.0 0.0 -1.0 -1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 -1.0 1.0 -1.0 0.0 0.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
|
||||||
|
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check optimal solution
|
||||||
|
model.optimize()
|
||||||
|
model.extract_after_mip(h5)
|
||||||
|
@test h5.get_scalar("mip_obj_value") == -4
|
||||||
|
h5.close()
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user