From a815ec42305c004f933562b798b26cb61cec3140 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Thu, 13 Aug 2020 12:53:13 -0500 Subject: [PATCH] Make output file more readable; add option to export solution --- CHANGELOG.md | 6 ++++ Manifest.toml | 16 ++++----- Project.toml | 1 + src/model.jl | 86 ++++++++++++++++++++++++---------------------- test/model_test.jl | 3 +- 5 files changed, 62 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f1de4..b5097cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) - Add "building period" parameter diff --git a/Manifest.toml b/Manifest.toml index 1366765..e841010 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -17,9 +17,9 @@ version = "0.5.10" [[Bzip2_jll]] deps = ["Libdl", "Pkg"] -git-tree-sha1 = "3663bfffede2ef41358b6fc2e1d8a6d50b3c3904" +git-tree-sha1 = "5ccb0770e3d1c185a52e6d36e3ffb830639ed3d2" uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.6+2" +version = "1.0.6+3" [[CEnum]] git-tree-sha1 = "1b77a77c3b28e0b3f413f7567c9bb8dd9bdccd14" @@ -94,9 +94,9 @@ version = "0.6.0" [[DataStructures]] deps = ["InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "edad9434967fdc0a2631a65d902228400642120c" +git-tree-sha1 = "88d48e133e6d3dd68183309877eac74393daa7eb" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.17.19" +version = "0.17.20" [[Dates]] deps = ["Printf"] @@ -223,9 +223,9 @@ version = "1.0.2" [[MbedTLS_jll]] deps = ["Libdl", "Pkg"] -git-tree-sha1 = "f85473aeb7a2561a5c58c06c4868971ebe2bcbff" +git-tree-sha1 = "a0cb0d489819fa7ea5f9fa84c7e7eba19d8073af" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.16.6+0" +version = "2.16.6+1" [[Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -346,6 +346,6 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[Zlib_jll]] deps = ["Libdl", "Pkg"] -git-tree-sha1 = "622d8b6dc0c7e8029f17127703de9819134d1b71" +git-tree-sha1 = "d5bba6485811931e4b8958e2d7ca3738273ac468" uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.11+14" +version = "1.2.11+15" diff --git a/Project.toml b/Project.toml index f591559..75f025a 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.3.2" [deps] Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d" +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" diff --git a/src/model.jl b/src/model.jl index a83f742..f0efe1f 100644 --- a/src/model.jl +++ b/src/model.jl @@ -2,7 +2,7 @@ # 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 +using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures 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 eqs = model.eqs - eqs.balance = Dict() + eqs.balance = OrderedDict() for t in 1:T # Collection centers @@ -197,7 +197,8 @@ default_lp_optimizer = optimizer_with_attributes(Clp.Optimizer, "LogLevel" => 0) function solve(instance::Instance; milp_optimizer=default_milp_optimizer, - lp_optimizer=default_lp_optimizer) + lp_optimizer=default_lp_optimizer, + output_filename=nothing) @info "Building graph..." graph = RELOG.build_graph(instance) @@ -215,12 +216,12 @@ function solve(instance::Instance; if !has_values(model.mip) @warn "No solution available" - return Dict() + return OrderedDict() end @info "Re-optimizing with integer variables fixed..." 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) for var in all_vars if JuMP.is_binary(var) @@ -231,29 +232,32 @@ function solve(instance::Instance; JuMP.optimize!(model.mip) @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 -function solve(filename::String; - milp_optimizer=default_milp_optimizer, - lp_optimizer=default_lp_optimizer) - +function solve(filename::String; kwargs...) @info "Reading $filename..." instance = RELOG.parsefile(filename) - - return solve(instance, - milp_optimizer=milp_optimizer, - lp_optimizer=lp_optimizer) + return solve(instance; kwargs...) end function get_solution(model::ManufacturingModel) mip, vars, eqs, graph, instance = model.mip, model.vars, model.eqs, model.graph, model.instance T = instance.time - output = Dict( - "Plants" => Dict(), - "Products" => Dict(), - "Costs" => Dict( + output = OrderedDict( + "Plants" => OrderedDict(), + "Products" => OrderedDict(), + "Costs" => OrderedDict( "Fixed operating (\$)" => zeros(T), "Variable operating (\$)" => zeros(T), "Opening (\$)" => zeros(T), @@ -262,18 +266,18 @@ function get_solution(model::ManufacturingModel) "Expansion (\$)" => zeros(T), "Total (\$)" => zeros(T), ), - "Energy" => Dict( + "Energy" => OrderedDict( "Plants (GJ)" => zeros(T), "Transportation (GJ)" => zeros(T), ), - "Emissions" => Dict( - "Plants (tonne)" => Dict(), - "Transportation (tonne)" => Dict(), + "Emissions" => OrderedDict( + "Plants (tonne)" => OrderedDict(), + "Transportation (tonne)" => OrderedDict(), ), ) - plant_to_process_node = Dict(n.location => n for n in graph.process_nodes) - plant_to_shipping_nodes = Dict() + plant_to_process_node = OrderedDict(n.location => n for n in graph.process_nodes) + plant_to_shipping_nodes = OrderedDict() for p in instance.plants plant_to_shipping_nodes[p] = [] for a in plant_to_process_node[p].outgoing_arcs @@ -283,12 +287,12 @@ function get_solution(model::ManufacturingModel) # Products 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) for t in 1:T], ) if n.product.name ∉ keys(output["Products"]) - output["Products"][n.product.name] = Dict() + output["Products"][n.product.name] = OrderedDict() end output["Products"][n.product.name][n.location.name] = location_dict end @@ -297,14 +301,14 @@ function get_solution(model::ManufacturingModel) for plant in instance.plants skip_plant = true process_node = plant_to_process_node[plant] - plant_dict = Dict{Any, Any}( - "Input" => Dict(), - "Output" => Dict( - "Send" => Dict(), - "Dispose" => Dict(), + plant_dict = OrderedDict{Any, Any}( + "Input" => OrderedDict(), + "Output" => OrderedDict( + "Send" => OrderedDict(), + "Dispose" => OrderedDict(), ), "Total input (tonne)" => [0.0 for t in 1:T], - "Total output" => Dict(), + "Total output" => OrderedDict(), "Latitude (deg)" => plant.latitude, "Longitude (deg)" => plant.longitude, "Capacity (tonne)" => [JuMP.value(vars.capacity[process_node, t]) @@ -338,7 +342,7 @@ function get_solution(model::ManufacturingModel) continue end skip_plant = false - dict = Dict{Any, Any}( + dict = OrderedDict{Any, Any}( "Amount (tonne)" => vals, "Distance (km)" => a.values["distance"], "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"], "Variable operating cost (\$)" => plant.sizes[1].variable_operating_cost .* vals, "Transportation energy (J)" => vals .* a.values["distance"] .* a.source.product.transportation_energy, - "Emissions (tonne)" => Dict(), + "Emissions (tonne)" => OrderedDict(), ) emissions_dict = output["Emissions"]["Transportation (tonne)"] for (em_name, em_values) in a.source.product.transportation_emissions @@ -365,7 +369,7 @@ function get_solution(model::ManufacturingModel) end if plant_name ∉ keys(plant_dict["Input"]) - plant_dict["Input"][plant_name] = Dict() + plant_dict["Input"][plant_name] = OrderedDict() end plant_dict["Input"][plant_name][location_name] = dict 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 output["Energy"]["Plants (GJ)"] += plant_dict["Energy (GJ)"] - plant_dict["Emissions (tonne)"] = Dict() + plant_dict["Emissions (tonne)"] = OrderedDict() emissions_dict = output["Emissions"]["Plants (tonne)"] for (em_name, em_values) in plant.emissions 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] product_name = shipping_node.product.name 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] if sum(disposal_amount) > 1e-5 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]) for t in 1:T] disposal_dict["Cost (\$)"] = [disposal_dict["Amount (tonne)"][t] * @@ -412,14 +416,14 @@ function get_solution(model::ManufacturingModel) continue end skip_plant = false - dict = Dict( + dict = OrderedDict( "Amount (tonne)" => vals, "Distance (km)" => a.values["distance"], "Latitude (deg)" => a.dest.location.latitude, "Longitude (deg)" => a.dest.location.longitude, ) 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 product_dict[a.dest.location.plant_name][a.dest.location.location_name] = dict plant_dict["Total output"][product_name] += vals @@ -428,7 +432,7 @@ function get_solution(model::ManufacturingModel) if !skip_plant if plant.plant_name ∉ keys(output["Plants"]) - output["Plants"][plant.plant_name] = Dict() + output["Plants"][plant.plant_name] = OrderedDict() end output["Plants"][plant.plant_name][plant.location_name] = plant_dict end diff --git a/test/model_test.jl b/test/model_test.jl index ba807cd..06b9dc6 100644 --- a/test/model_test.jl +++ b/test/model_test.jl @@ -42,7 +42,8 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats end @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 "Fixed operating (\$)" in keys(solution["Costs"])