mirror of https://github.com/ANL-CEEESA/RELOG.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
110 lines
3.3 KiB
110 lines
3.3 KiB
# RELOG: Reverse Logistics Optimization
|
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
# Released under the modified BSD license. See COPYING.md for more details.
|
|
|
|
using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures
|
|
|
|
function _get_default_milp_optimizer()
|
|
return optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
|
|
end
|
|
|
|
function _get_default_lp_optimizer()
|
|
return optimizer_with_attributes(Clp.Optimizer, "LogLevel" => 0)
|
|
end
|
|
|
|
|
|
function _print_graph_stats(instance::Instance, graph::Graph)::Nothing
|
|
@info @sprintf("%12d time periods", instance.time)
|
|
@info @sprintf("%12d process nodes", length(graph.process_nodes))
|
|
@info @sprintf("%12d shipping nodes (plant)", length(graph.plant_shipping_nodes))
|
|
@info @sprintf(
|
|
"%12d shipping nodes (collection)",
|
|
length(graph.collection_shipping_nodes)
|
|
)
|
|
@info @sprintf("%12d arcs", length(graph.arcs))
|
|
return
|
|
end
|
|
|
|
function solve(
|
|
instance::Instance;
|
|
optimizer = nothing,
|
|
output = nothing,
|
|
marginal_costs = true,
|
|
return_model = false,
|
|
)
|
|
|
|
milp_optimizer = lp_optimizer = optimizer
|
|
if optimizer == nothing
|
|
milp_optimizer = _get_default_milp_optimizer()
|
|
lp_optimizer = _get_default_lp_optimizer()
|
|
end
|
|
|
|
@info "Building graph..."
|
|
graph = RELOG.build_graph(instance)
|
|
_print_graph_stats(instance, graph)
|
|
|
|
@info "Building optimization model..."
|
|
model = RELOG.build_model(instance, graph, milp_optimizer)
|
|
|
|
@info "Optimizing MILP..."
|
|
JuMP.optimize!(model)
|
|
|
|
if !has_values(model)
|
|
error("No solution available")
|
|
end
|
|
|
|
if marginal_costs
|
|
@info "Re-optimizing with integer variables fixed..."
|
|
all_vars = JuMP.all_variables(model)
|
|
vals = OrderedDict(var => JuMP.value(var) for var in all_vars)
|
|
JuMP.set_optimizer(model, lp_optimizer)
|
|
for var in all_vars
|
|
if JuMP.is_binary(var)
|
|
JuMP.unset_binary(var)
|
|
JuMP.fix(var, vals[var])
|
|
end
|
|
end
|
|
JuMP.optimize!(model)
|
|
end
|
|
|
|
@info "Extracting solution..."
|
|
solution = get_solution(model, marginal_costs = marginal_costs)
|
|
|
|
if output != nothing
|
|
write(solution, output)
|
|
end
|
|
|
|
if return_model
|
|
return solution, model
|
|
else
|
|
return solution
|
|
end
|
|
end
|
|
|
|
function solve(filename::AbstractString; heuristic = false, kwargs...)
|
|
@info "Reading $filename..."
|
|
instance = RELOG.parsefile(filename)
|
|
if heuristic && instance.time > 1
|
|
@info "Solving single-period version..."
|
|
compressed = _compress(instance)
|
|
csol = solve(compressed; output = nothing, marginal_costs = false, kwargs...)
|
|
@info "Filtering candidate locations..."
|
|
selected_pairs = []
|
|
for (plant_name, plant_dict) in csol["Plants"]
|
|
for (location_name, location_dict) in plant_dict
|
|
push!(selected_pairs, (plant_name, location_name))
|
|
end
|
|
end
|
|
filtered_plants = []
|
|
for p in instance.plants
|
|
if (p.plant_name, p.location_name) in selected_pairs
|
|
push!(filtered_plants, p)
|
|
end
|
|
end
|
|
instance.plants = filtered_plants
|
|
@info "Solving original version..."
|
|
end
|
|
sol = solve(instance; kwargs...)
|
|
return sol
|
|
end
|