Make output file more readable; add option to export solution

gh-actions
Alinson S. Xavier 5 years ago
parent d8c9a1b30a
commit a815ec4230

@ -1,3 +1,9 @@
# Version 0.3.3 (Aug 13, 2020)
- Add option to write solution to JSON file in RELOG.solve
- Improve error message when instance is infeasible
- Make output file more readable
# Version 0.3.2 (Aug 7, 2020) # Version 0.3.2 (Aug 7, 2020)
- Add "building period" parameter - Add "building period" parameter

@ -17,9 +17,9 @@ version = "0.5.10"
[[Bzip2_jll]] [[Bzip2_jll]]
deps = ["Libdl", "Pkg"] deps = ["Libdl", "Pkg"]
git-tree-sha1 = "3663bfffede2ef41358b6fc2e1d8a6d50b3c3904" git-tree-sha1 = "5ccb0770e3d1c185a52e6d36e3ffb830639ed3d2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+2" version = "1.0.6+3"
[[CEnum]] [[CEnum]]
git-tree-sha1 = "1b77a77c3b28e0b3f413f7567c9bb8dd9bdccd14" git-tree-sha1 = "1b77a77c3b28e0b3f413f7567c9bb8dd9bdccd14"
@ -94,9 +94,9 @@ version = "0.6.0"
[[DataStructures]] [[DataStructures]]
deps = ["InteractiveUtils", "OrderedCollections"] deps = ["InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "edad9434967fdc0a2631a65d902228400642120c" git-tree-sha1 = "88d48e133e6d3dd68183309877eac74393daa7eb"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.17.19" version = "0.17.20"
[[Dates]] [[Dates]]
deps = ["Printf"] deps = ["Printf"]
@ -223,9 +223,9 @@ version = "1.0.2"
[[MbedTLS_jll]] [[MbedTLS_jll]]
deps = ["Libdl", "Pkg"] deps = ["Libdl", "Pkg"]
git-tree-sha1 = "f85473aeb7a2561a5c58c06c4868971ebe2bcbff" git-tree-sha1 = "a0cb0d489819fa7ea5f9fa84c7e7eba19d8073af"
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.16.6+0" version = "2.16.6+1"
[[Mmap]] [[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804" uuid = "a63ad114-7e13-5084-954f-fe012c677804"
@ -346,6 +346,6 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[[Zlib_jll]] [[Zlib_jll]]
deps = ["Libdl", "Pkg"] deps = ["Libdl", "Pkg"]
git-tree-sha1 = "622d8b6dc0c7e8029f17127703de9819134d1b71" git-tree-sha1 = "d5bba6485811931e4b8958e2d7ca3738273ac468"
uuid = "83775a58-1f1d-513f-b197-d71354ab007a" uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.11+14" version = "1.2.11+15"

@ -6,6 +6,7 @@ version = "0.3.2"
[deps] [deps]
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d" Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d" Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"

@ -2,7 +2,7 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures
mutable struct ManufacturingModel mutable struct ManufacturingModel
@ -126,7 +126,7 @@ function create_shipping_node_constraints!(model::ManufacturingModel)
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
eqs = model.eqs eqs = model.eqs
eqs.balance = Dict() eqs.balance = OrderedDict()
for t in 1:T for t in 1:T
# Collection centers # Collection centers
@ -197,7 +197,8 @@ default_lp_optimizer = optimizer_with_attributes(Clp.Optimizer, "LogLevel" => 0)
function solve(instance::Instance; function solve(instance::Instance;
milp_optimizer=default_milp_optimizer, milp_optimizer=default_milp_optimizer,
lp_optimizer=default_lp_optimizer) lp_optimizer=default_lp_optimizer,
output_filename=nothing)
@info "Building graph..." @info "Building graph..."
graph = RELOG.build_graph(instance) graph = RELOG.build_graph(instance)
@ -215,12 +216,12 @@ function solve(instance::Instance;
if !has_values(model.mip) if !has_values(model.mip)
@warn "No solution available" @warn "No solution available"
return Dict() return OrderedDict()
end end
@info "Re-optimizing with integer variables fixed..." @info "Re-optimizing with integer variables fixed..."
all_vars = JuMP.all_variables(model.mip) all_vars = JuMP.all_variables(model.mip)
vals = Dict(var => JuMP.value(var) for var in all_vars) vals = OrderedDict(var => JuMP.value(var) for var in all_vars)
JuMP.set_optimizer(model.mip, lp_optimizer) JuMP.set_optimizer(model.mip, lp_optimizer)
for var in all_vars for var in all_vars
if JuMP.is_binary(var) if JuMP.is_binary(var)
@ -231,29 +232,32 @@ function solve(instance::Instance;
JuMP.optimize!(model.mip) JuMP.optimize!(model.mip)
@info "Extracting solution..." @info "Extracting solution..."
return get_solution(model) solution = get_solution(model)
end
function solve(filename::String; if output_filename != nothing
milp_optimizer=default_milp_optimizer, @info "Writing solution: $output_filename"
lp_optimizer=default_lp_optimizer) open(output_filename, "w") do file
JSON.print(file, solution, 2)
end
end
return solution
end
function solve(filename::String; kwargs...)
@info "Reading $filename..." @info "Reading $filename..."
instance = RELOG.parsefile(filename) instance = RELOG.parsefile(filename)
return solve(instance; kwargs...)
return solve(instance,
milp_optimizer=milp_optimizer,
lp_optimizer=lp_optimizer)
end end
function get_solution(model::ManufacturingModel) function get_solution(model::ManufacturingModel)
mip, vars, eqs, graph, instance = model.mip, model.vars, model.eqs, model.graph, model.instance mip, vars, eqs, graph, instance = model.mip, model.vars, model.eqs, model.graph, model.instance
T = instance.time T = instance.time
output = Dict( output = OrderedDict(
"Plants" => Dict(), "Plants" => OrderedDict(),
"Products" => Dict(), "Products" => OrderedDict(),
"Costs" => Dict( "Costs" => OrderedDict(
"Fixed operating (\$)" => zeros(T), "Fixed operating (\$)" => zeros(T),
"Variable operating (\$)" => zeros(T), "Variable operating (\$)" => zeros(T),
"Opening (\$)" => zeros(T), "Opening (\$)" => zeros(T),
@ -262,18 +266,18 @@ function get_solution(model::ManufacturingModel)
"Expansion (\$)" => zeros(T), "Expansion (\$)" => zeros(T),
"Total (\$)" => zeros(T), "Total (\$)" => zeros(T),
), ),
"Energy" => Dict( "Energy" => OrderedDict(
"Plants (GJ)" => zeros(T), "Plants (GJ)" => zeros(T),
"Transportation (GJ)" => zeros(T), "Transportation (GJ)" => zeros(T),
), ),
"Emissions" => Dict( "Emissions" => OrderedDict(
"Plants (tonne)" => Dict(), "Plants (tonne)" => OrderedDict(),
"Transportation (tonne)" => Dict(), "Transportation (tonne)" => OrderedDict(),
), ),
) )
plant_to_process_node = Dict(n.location => n for n in graph.process_nodes) plant_to_process_node = OrderedDict(n.location => n for n in graph.process_nodes)
plant_to_shipping_nodes = Dict() plant_to_shipping_nodes = OrderedDict()
for p in instance.plants for p in instance.plants
plant_to_shipping_nodes[p] = [] plant_to_shipping_nodes[p] = []
for a in plant_to_process_node[p].outgoing_arcs for a in plant_to_process_node[p].outgoing_arcs
@ -283,12 +287,12 @@ function get_solution(model::ManufacturingModel)
# Products # Products
for n in graph.collection_shipping_nodes for n in graph.collection_shipping_nodes
location_dict = Dict{Any, Any}( location_dict = OrderedDict{Any, Any}(
"Marginal cost (\$/tonne)" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2) "Marginal cost (\$/tonne)" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2)
for t in 1:T], for t in 1:T],
) )
if n.product.name keys(output["Products"]) if n.product.name keys(output["Products"])
output["Products"][n.product.name] = Dict() output["Products"][n.product.name] = OrderedDict()
end end
output["Products"][n.product.name][n.location.name] = location_dict output["Products"][n.product.name][n.location.name] = location_dict
end end
@ -297,14 +301,14 @@ function get_solution(model::ManufacturingModel)
for plant in instance.plants for plant in instance.plants
skip_plant = true skip_plant = true
process_node = plant_to_process_node[plant] process_node = plant_to_process_node[plant]
plant_dict = Dict{Any, Any}( plant_dict = OrderedDict{Any, Any}(
"Input" => Dict(), "Input" => OrderedDict(),
"Output" => Dict( "Output" => OrderedDict(
"Send" => Dict(), "Send" => OrderedDict(),
"Dispose" => Dict(), "Dispose" => OrderedDict(),
), ),
"Total input (tonne)" => [0.0 for t in 1:T], "Total input (tonne)" => [0.0 for t in 1:T],
"Total output" => Dict(), "Total output" => OrderedDict(),
"Latitude (deg)" => plant.latitude, "Latitude (deg)" => plant.latitude,
"Longitude (deg)" => plant.longitude, "Longitude (deg)" => plant.longitude,
"Capacity (tonne)" => [JuMP.value(vars.capacity[process_node, t]) "Capacity (tonne)" => [JuMP.value(vars.capacity[process_node, t])
@ -338,7 +342,7 @@ function get_solution(model::ManufacturingModel)
continue continue
end end
skip_plant = false skip_plant = false
dict = Dict{Any, Any}( dict = OrderedDict{Any, Any}(
"Amount (tonne)" => vals, "Amount (tonne)" => vals,
"Distance (km)" => a.values["distance"], "Distance (km)" => a.values["distance"],
"Latitude (deg)" => a.source.location.latitude, "Latitude (deg)" => a.source.location.latitude,
@ -346,7 +350,7 @@ function get_solution(model::ManufacturingModel)
"Transportation cost (\$)" => a.source.product.transportation_cost .* vals .* a.values["distance"], "Transportation cost (\$)" => a.source.product.transportation_cost .* vals .* a.values["distance"],
"Variable operating cost (\$)" => plant.sizes[1].variable_operating_cost .* vals, "Variable operating cost (\$)" => plant.sizes[1].variable_operating_cost .* vals,
"Transportation energy (J)" => vals .* a.values["distance"] .* a.source.product.transportation_energy, "Transportation energy (J)" => vals .* a.values["distance"] .* a.source.product.transportation_energy,
"Emissions (tonne)" => Dict(), "Emissions (tonne)" => OrderedDict(),
) )
emissions_dict = output["Emissions"]["Transportation (tonne)"] emissions_dict = output["Emissions"]["Transportation (tonne)"]
for (em_name, em_values) in a.source.product.transportation_emissions for (em_name, em_values) in a.source.product.transportation_emissions
@ -365,7 +369,7 @@ function get_solution(model::ManufacturingModel)
end end
if plant_name keys(plant_dict["Input"]) if plant_name keys(plant_dict["Input"])
plant_dict["Input"][plant_name] = Dict() plant_dict["Input"][plant_name] = OrderedDict()
end end
plant_dict["Input"][plant_name][location_name] = dict plant_dict["Input"][plant_name][location_name] = dict
plant_dict["Total input (tonne)"] += vals plant_dict["Total input (tonne)"] += vals
@ -377,7 +381,7 @@ function get_solution(model::ManufacturingModel)
plant_dict["Energy (GJ)"] = plant_dict["Total input (tonne)"] .* plant.energy plant_dict["Energy (GJ)"] = plant_dict["Total input (tonne)"] .* plant.energy
output["Energy"]["Plants (GJ)"] += plant_dict["Energy (GJ)"] output["Energy"]["Plants (GJ)"] += plant_dict["Energy (GJ)"]
plant_dict["Emissions (tonne)"] = Dict() plant_dict["Emissions (tonne)"] = OrderedDict()
emissions_dict = output["Emissions"]["Plants (tonne)"] emissions_dict = output["Emissions"]["Plants (tonne)"]
for (em_name, em_values) in plant.emissions for (em_name, em_values) in plant.emissions
plant_dict["Emissions (tonne)"][em_name] = em_values .* plant_dict["Total input (tonne)"] plant_dict["Emissions (tonne)"][em_name] = em_values .* plant_dict["Total input (tonne)"]
@ -391,12 +395,12 @@ function get_solution(model::ManufacturingModel)
for shipping_node in plant_to_shipping_nodes[plant] for shipping_node in plant_to_shipping_nodes[plant]
product_name = shipping_node.product.name product_name = shipping_node.product.name
plant_dict["Total output"][product_name] = zeros(T) plant_dict["Total output"][product_name] = zeros(T)
plant_dict["Output"]["Send"][product_name] = product_dict = Dict() plant_dict["Output"]["Send"][product_name] = product_dict = OrderedDict()
disposal_amount = [JuMP.value(vars.dispose[shipping_node, t]) for t in 1:T] disposal_amount = [JuMP.value(vars.dispose[shipping_node, t]) for t in 1:T]
if sum(disposal_amount) > 1e-5 if sum(disposal_amount) > 1e-5
skip_plant = false skip_plant = false
plant_dict["Output"]["Dispose"][product_name] = disposal_dict = Dict() plant_dict["Output"]["Dispose"][product_name] = disposal_dict = OrderedDict()
disposal_dict["Amount (tonne)"] = [JuMP.value(model.vars.dispose[shipping_node, t]) disposal_dict["Amount (tonne)"] = [JuMP.value(model.vars.dispose[shipping_node, t])
for t in 1:T] for t in 1:T]
disposal_dict["Cost (\$)"] = [disposal_dict["Amount (tonne)"][t] * disposal_dict["Cost (\$)"] = [disposal_dict["Amount (tonne)"][t] *
@ -412,14 +416,14 @@ function get_solution(model::ManufacturingModel)
continue continue
end end
skip_plant = false skip_plant = false
dict = Dict( dict = OrderedDict(
"Amount (tonne)" => vals, "Amount (tonne)" => vals,
"Distance (km)" => a.values["distance"], "Distance (km)" => a.values["distance"],
"Latitude (deg)" => a.dest.location.latitude, "Latitude (deg)" => a.dest.location.latitude,
"Longitude (deg)" => a.dest.location.longitude, "Longitude (deg)" => a.dest.location.longitude,
) )
if a.dest.location.plant_name keys(product_dict) if a.dest.location.plant_name keys(product_dict)
product_dict[a.dest.location.plant_name] = Dict() product_dict[a.dest.location.plant_name] = OrderedDict()
end end
product_dict[a.dest.location.plant_name][a.dest.location.location_name] = dict product_dict[a.dest.location.plant_name][a.dest.location.location_name] = dict
plant_dict["Total output"][product_name] += vals plant_dict["Total output"][product_name] += vals
@ -428,7 +432,7 @@ function get_solution(model::ManufacturingModel)
if !skip_plant if !skip_plant
if plant.plant_name keys(output["Plants"]) if plant.plant_name keys(output["Plants"])
output["Plants"][plant.plant_name] = Dict() output["Plants"][plant.plant_name] = OrderedDict()
end end
output["Plants"][plant.plant_name][plant.location_name] = plant_dict output["Plants"][plant.plant_name][plant.location_name] = plant_dict
end end

@ -42,7 +42,8 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
end end
@testset "solve" begin @testset "solve" begin
solution = RELOG.solve("$(pwd())/../instances/s1.json") solution = RELOG.solve("$(pwd())/../instances/s1.json",
output_filename="$(pwd())/../tmp/sol.json")
@test "Costs" in keys(solution) @test "Costs" in keys(solution)
@test "Fixed operating (\$)" in keys(solution["Costs"]) @test "Fixed operating (\$)" in keys(solution["Costs"])

Loading…
Cancel
Save