Merge branch 'dev' into feature/replay

This commit is contained in:
2024-11-21 09:40:06 -06:00
37 changed files with 1553 additions and 320 deletions

View File

@@ -7,6 +7,7 @@ version = "0.1.0"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
@@ -17,6 +18,8 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MIPLearn = "2b1277c3-b477-4c49-a15e-7ba350325c68"
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
SCIP = "82193955-e24f-5292-bf16-6f2c5261a85f"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]

BIN
test/fixtures/bell5.h5 vendored

Binary file not shown.

BIN
test/fixtures/stab-n50-00000.h5 vendored Normal file

Binary file not shown.

BIN
test/fixtures/stab-n50-00000.pkl.gz vendored Normal file

Binary file not shown.

BIN
test/fixtures/tsp-n20-00000.h5 vendored Normal file

Binary file not shown.

BIN
test/fixtures/tsp-n20-00000.mps.gz vendored Normal file

Binary file not shown.

BIN
test/fixtures/tsp-n20-00000.pkl.gz vendored Normal file

Binary file not shown.

BIN
test/fixtures/vpm2.h5 vendored

Binary file not shown.

View File

@@ -89,11 +89,7 @@ function bb_run(optimizer_name, optimizer; large=true)
BB.ReliabilityBranching(aggregation=:min, collect=true),
]
h5 = H5File("$FIXTURES/$instance.h5")
mip_lower_bound = h5.get_scalar("mip_lower_bound")
mip_upper_bound = h5.get_scalar("mip_upper_bound")
mip_sense = h5.get_scalar("mip_sense")
mip_primal_bound =
mip_sense == "min" ? mip_upper_bound : mip_lower_bound
mip_obj_bound = h5.get_scalar("mip_obj_bound")
h5.file.close()
mip = BB.init(optimizer)
@@ -101,10 +97,10 @@ function bb_run(optimizer_name, optimizer; large=true)
@info optimizer_name, branch_rule, instance
@time BB.solve!(
mip,
initial_primal_bound=mip_primal_bound,
print_interval=1,
node_limit=25,
branch_rule=branch_rule,
initial_primal_bound = mip_obj_bound,
print_interval = 1,
node_limit = 25,
branch_rule = branch_rule,
)
end
end

View File

@@ -0,0 +1,23 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using HiGHS
function test_cuts_tableau_gmi()
mps_filename = "$BASEDIR/../fixtures/bell5.mps.gz"
h5_filename = "$BASEDIR/../fixtures/bell5.h5"
collect_gmi(mps_filename, optimizer = HiGHS.Optimizer)
h5 = H5File(h5_filename, "r")
try
cuts_lb = h5.get_array("cuts_lb")
cuts_ub = h5.get_array("cuts_ub")
cuts_lhs = h5.get_sparse("cuts_lhs")
n_cuts = length(cuts_lb)
@test n_cuts > 0
@test n_cuts == length(cuts_ub)
@test cuts_lhs.shape[1] == n_cuts
finally
h5.close()
end
end

View File

@@ -0,0 +1,70 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using SCIP
using HiGHS
using MIPLearn.Cuts
function test_cuts_tableau_gmi_dual_collect()
mps_filename = "$BASEDIR/../fixtures/bell5.mps.gz"
h5_filename = "$BASEDIR/../fixtures/bell5.h5"
stats = collect_gmi_dual(mps_filename, optimizer = HiGHS.Optimizer)
h5 = H5File(h5_filename, "r")
try
cuts_basis_vars = h5.get_array("cuts_basis_vars")
cuts_basis_sizes = h5.get_array("cuts_basis_sizes")
cuts_rows = h5.get_array("cuts_rows")
@test size(cuts_basis_vars) == (15, 402)
@test size(cuts_basis_sizes) == (15, 4)
@test size(cuts_rows) == (15,)
finally
h5.close()
end
end
function test_cuts_tableau_gmi_dual_usage()
function build_model(mps_filename)
model = read_from_file(mps_filename)
set_optimizer(model, SCIP.Optimizer)
return JumpModel(model)
end
mps_filename = "$BASEDIR/../fixtures/bell5.mps.gz"
h5_filename = "$BASEDIR/../fixtures/bell5.h5"
rm(h5_filename, force=true)
# Run basic collector
bc = BasicCollector(write_mps = false, skip_lp = true)
bc.collect([mps_filename], build_model)
# Run dual GMI collector
@info "Running dual GMI collector..."
collect_gmi_dual(mps_filename, optimizer = HiGHS.Optimizer)
# # Test expert component
# solver = LearningSolver(
# components = [
# ExpertPrimalComponent(action = SetWarmStart()),
# ExpertDualGmiComponent(),
# ],
# skip_lp = true,
# )
# solver.optimize(mps_filename, build_model)
# Test kNN component
knn = KnnDualGmiComponent(
extractor = H5FieldsExtractor(instance_fields = ["static_var_obj_coeffs"]),
k = 2,
)
knn.fit([h5_filename, h5_filename])
solver = LearningSolver(
components = [
ExpertPrimalComponent(action = SetWarmStart()),
knn,
],
skip_lp = true,
)
solver.optimize(mps_filename, build_model)
return
end

View File

@@ -16,10 +16,16 @@ FIXTURES = "$BASEDIR/../fixtures"
include("fixtures.jl")
include("BB/test_bb.jl")
include("components/test_cuts.jl")
include("components/test_lazy.jl")
include("Cuts/BlackBox/test_cplex.jl")
include("Cuts/tableau/test_gmi.jl")
include("Cuts/tableau/test_gmi_dual.jl")
include("problems/test_setcover.jl")
include("test_io.jl")
include("problems/test_stab.jl")
include("problems/test_tsp.jl")
include("solvers/test_jump.jl")
include("test_io.jl")
include("test_usage.jl")
function runtests()
@@ -27,11 +33,14 @@ function runtests()
@testset "BB" begin
test_bb()
end
# test_cuts_blackbox_cplex()
test_io()
test_problems_setcover()
test_problems_stab()
test_problems_tsp()
test_solvers_jump()
test_usage()
test_cuts()
test_lazy()
end
end

View File

@@ -0,0 +1,41 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using SCIP
function gen_stab()
np = pyimport("numpy")
uniform = pyimport("scipy.stats").uniform
randint = pyimport("scipy.stats").randint
np.random.seed(42)
gen = MaxWeightStableSetGenerator(
w = uniform(10.0, scale = 1.0),
n = randint(low = 50, high = 51),
p = uniform(loc = 0.5, scale = 0.0),
fix_graph = true,
)
data = gen.generate(1)
data_filenames = write_pkl_gz(data, "$BASEDIR/../fixtures", prefix = "stab-n50-")
collector = BasicCollector()
collector.collect(
data_filenames,
data -> build_stab_model_jump(data, optimizer = SCIP.Optimizer),
progress = true,
verbose = true,
)
end
function test_cuts()
data_filenames = ["$BASEDIR/../fixtures/stab-n50-00000.pkl.gz"]
clf = pyimport("sklearn.dummy").DummyClassifier()
extractor = H5FieldsExtractor(instance_fields = ["static_var_obj_coeffs"])
comp = MemorizingCutsComponent(clf = clf, extractor = extractor)
solver = LearningSolver(components = [comp])
solver.fit(data_filenames)
model, stats = solver.optimize(
data_filenames[1],
data -> build_stab_model_jump(data, optimizer = SCIP.Optimizer),
)
@test stats["Cuts: AOT"] > 0
end

View File

@@ -0,0 +1,44 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using GLPK
function gen_tsp()
np = pyimport("numpy")
uniform = pyimport("scipy.stats").uniform
randint = pyimport("scipy.stats").randint
np.random.seed(42)
gen = TravelingSalesmanGenerator(
x = uniform(loc = 0.0, scale = 1000.0),
y = uniform(loc = 0.0, scale = 1000.0),
n = randint(low = 20, high = 21),
gamma = uniform(loc = 1.0, scale = 0.25),
fix_cities = true,
round = true,
)
data = gen.generate(1)
data_filenames = write_pkl_gz(data, "$BASEDIR/../fixtures", prefix = "tsp-n20-")
collector = BasicCollector()
collector.collect(
data_filenames,
data -> build_tsp_model_jump(data, optimizer = GLPK.Optimizer),
progress = true,
verbose = true,
)
end
function test_lazy()
data_filenames = ["$BASEDIR/../fixtures/tsp-n20-00000.pkl.gz"]
clf = pyimport("sklearn.dummy").DummyClassifier()
extractor = H5FieldsExtractor(instance_fields = ["static_var_obj_coeffs"])
comp = MemorizingLazyComponent(clf = clf, extractor = extractor)
solver = LearningSolver(components = [comp])
solver.fit(data_filenames)
model, stats = solver.optimize(
data_filenames[1],
data -> build_tsp_model_jump(data, optimizer = GLPK.Optimizer),
)
@test stats["Lazy Constraints: AOT"] > 0
end

View File

@@ -14,5 +14,5 @@ function fixture_setcover_data()
end
function fixture_setcover_model()
return build_setcover_model(fixture_setcover_data())
return build_setcover_model_jump(fixture_setcover_data())
end

View File

@@ -51,7 +51,7 @@ function test_problems_setcover_model()
)
h5 = H5File(tempname(), "w")
model = build_setcover_model(data)
model = build_setcover_model_jump(data)
model.extract_after_load(h5)
model.optimize()
model.extract_after_mip(h5)

View File

@@ -0,0 +1,22 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using PyCall
using SCIP
function test_problems_stab()
nx = pyimport("networkx")
data = MaxWeightStableSetData(
graph = nx.gnp_random_graph(25, 0.5, seed = 42),
weights = repeat([1.0], 25),
)
h5 = H5File(tempname(), "w")
model = build_stab_model_jump(data, optimizer = SCIP.Optimizer)
model.extract_after_load(h5)
model.optimize()
model.extract_after_mip(h5)
@test h5.get_scalar("mip_obj_value") == -6
@test h5.get_scalar("mip_cuts")[1:20] == "[[0,8,11,13],[0,8,13"
h5.close()
end

View File

@@ -0,0 +1,22 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2024, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using GLPK
using JuMP
function test_problems_tsp()
pdist = pyimport("scipy.spatial.distance").pdist
squareform = pyimport("scipy.spatial.distance").squareform
data = TravelingSalesmanData(
n_cities = 6,
distances = squareform(
pdist([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0], [0.0, 1.0], [3.0, 1.0]]),
),
)
model = build_tsp_model_jump(data, optimizer = GLPK.Optimizer)
model.optimize()
@test objective_value(model.inner) == 8.0
return
end

View File

@@ -4,6 +4,7 @@
using MIPLearn
using JLD2
using SparseArrays
struct _TestStruct
n::Int
@@ -35,6 +36,8 @@ function test_h5()
_test_roundtrip_array(h5, [1, 2, 3])
_test_roundtrip_array(h5, [1.0, 2.0, 3.0])
_test_roundtrip_str_array(h5, ["A", "BB", "CCC"])
_test_roundtrip_sparse(h5, sparse([1; 2; 3], [1; 2; 3], [1; 2; 3]))
# _test_roundtrip_sparse(h5, sparse([1; 2; 3], [1; 2; 3], [1; 2; 3], 4, 4))
@test h5.get_array("unknown-key") === nothing
h5.close()
end
@@ -46,7 +49,7 @@ function test_jld2()
_TestStruct(2, [1.0, 2.0, 3.0]),
_TestStruct(3, [3.0, 3.0, 3.0]),
]
filenames = write_jld2(data, dirname, prefix="obj")
filenames = write_jld2(data, dirname, prefix = "obj")
@test all(
filenames .==
["$dirname/obj00001.jld2", "$dirname/obj00002.jld2", "$dirname/obj00003.jld2"],
@@ -79,3 +82,11 @@ function _test_roundtrip_str_array(h5, original)
@test recovered !== nothing
@test all(original .== recovered)
end
function _test_roundtrip_sparse(h5, original)
h5.put_sparse("key", original)
recovered = MIPLearn.convert(SparseMatrixCSC, h5.get_sparse("key"))
@test recovered !== nothing
@test size(original) == size(recovered)
@test all(original .== recovered)
end

View File

@@ -29,13 +29,13 @@ function test_usage()
@debug "Collecting training data..."
bc = BasicCollector()
bc.collect(data_filenames, build_setcover_model)
bc.collect(data_filenames, build_setcover_model_jump)
@debug "Training models..."
solver.fit(data_filenames)
@debug "Solving model..."
solver.optimize(data_filenames[1], build_setcover_model)
solver.optimize(data_filenames[1], build_setcover_model_jump)
@debug "Checking solution..."
h5 = H5File(h5_filenames[1])