mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 15:48:51 -06:00
Make output file more readable; add option to export solution
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
86
src/model.jl
86
src/model.jl
@@ -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)
|
||||||
|
|
||||||
|
if output_filename != nothing
|
||||||
|
@info "Writing solution: $output_filename"
|
||||||
|
open(output_filename, "w") do file
|
||||||
|
JSON.print(file, solution, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return solution
|
||||||
end
|
end
|
||||||
|
|
||||||
function solve(filename::String;
|
function solve(filename::String; kwargs...)
|
||||||
milp_optimizer=default_milp_optimizer,
|
|
||||||
lp_optimizer=default_lp_optimizer)
|
|
||||||
|
|
||||||
@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"])
|
||||||
|
|||||||
Reference in New Issue
Block a user