parent
4d5b7e971c
commit
190c288203
@ -0,0 +1,71 @@
|
|||||||
|
# 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 JuMP
|
||||||
|
|
||||||
|
global TravelingSalesmanData = PyNULL()
|
||||||
|
global TravelingSalesmanGenerator = PyNULL()
|
||||||
|
|
||||||
|
function __init_problems_tsp__()
|
||||||
|
copy!(TravelingSalesmanData, pyimport("miplearn.problems.tsp").TravelingSalesmanData)
|
||||||
|
copy!(TravelingSalesmanGenerator, pyimport("miplearn.problems.tsp").TravelingSalesmanGenerator)
|
||||||
|
end
|
||||||
|
|
||||||
|
function build_tsp_model_jump(data::Any; optimizer=HiGHS.Optimizer)
|
||||||
|
nx = pyimport("networkx")
|
||||||
|
|
||||||
|
if data isa String
|
||||||
|
data = read_pkl_gz(data)
|
||||||
|
end
|
||||||
|
model = Model(optimizer)
|
||||||
|
edges = [(i, j) for i in 1:data.n_cities for j in (i+1):data.n_cities]
|
||||||
|
x = @variable(model, x[edges], Bin)
|
||||||
|
@objective(model, Min, sum(
|
||||||
|
x[(i, j)] * data.distances[i, j] for (i, j) in edges
|
||||||
|
))
|
||||||
|
|
||||||
|
# Eq: Must choose two edges adjacent to each node
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
eq_degree[i in 1:data.n_cities],
|
||||||
|
sum(x[(min(i, j), max(i, j))] for j in 1:data.n_cities if i != j) == 2
|
||||||
|
)
|
||||||
|
|
||||||
|
function lazy_separate(cb_data)
|
||||||
|
x_val = callback_value.(Ref(cb_data), x)
|
||||||
|
violations = []
|
||||||
|
selected_edges = [e for e in edges if x_val[e] > 0.5]
|
||||||
|
graph = nx.Graph()
|
||||||
|
graph.add_edges_from(selected_edges)
|
||||||
|
for component in nx.connected_components(graph)
|
||||||
|
if length(component) < data.n_cities
|
||||||
|
cut_edges = [
|
||||||
|
[e[1], e[2]]
|
||||||
|
for e in edges
|
||||||
|
if (e[1] ∈ component && e[2] ∉ component)
|
||||||
|
||
|
||||||
|
(e[1] ∉ component && e[2] ∈ component)
|
||||||
|
]
|
||||||
|
push!(violations, cut_edges)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return violations
|
||||||
|
end
|
||||||
|
|
||||||
|
function lazy_enforce(violations)
|
||||||
|
@info "Adding $(length(violations)) subtour elimination eqs..."
|
||||||
|
for violation in violations
|
||||||
|
constr = @build_constraint(sum(x[(e[1], e[2])] for e in violation) >= 2)
|
||||||
|
submit(model, constr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return JumpModel(
|
||||||
|
model,
|
||||||
|
lazy_enforce=lazy_enforce,
|
||||||
|
lazy_separate=lazy_separate,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
export TravelingSalesmanData, TravelingSalesmanGenerator, build_tsp_model_jump
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,46 @@
|
|||||||
|
# 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(write_mps=false)
|
||||||
|
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)
|
||||||
|
stats = solver.optimize(
|
||||||
|
data_filenames[1],
|
||||||
|
data -> build_tsp_model_jump(data, optimizer=GLPK.Optimizer),
|
||||||
|
)
|
||||||
|
@test stats["Lazy Constraints: AOT"] > 0
|
||||||
|
end
|
@ -0,0 +1,27 @@
|
|||||||
|
# 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
|
Loading…
Reference in new issue