Add support for MIQPs; implement max cut model

dev
Alinson S. Xavier 4 months ago
parent 9ac2f74856
commit 2ea0043c03

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__()

@ -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()

@ -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
Loading…
Cancel
Save