From b00b24ffbc7fa762fb28594522008c1759f3f798 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Tue, 22 Jun 2021 09:47:40 -0500 Subject: [PATCH] Reformat source code; set up lint GH Action --- .github/workflows/lint.yml | 27 +++ Makefile | 3 + src/RELOG.jl | 10 +- src/dotdict.jl | 4 +- src/graph.jl | 45 ++-- src/instance.jl | 145 +++++++------ src/model.jl | 409 ++++++++++++++++++++----------------- src/reports.jl | 255 +++++++++++++---------- src/sysimage.jl | 11 +- test/graph_test.jl | 15 +- test/instance_test.jl | 31 ++- test/model_test.jl | 38 ++-- test/reports_test.jl | 16 +- test/runtests.jl | 2 +- 14 files changed, 551 insertions(+), 460 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..b71acf6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,27 @@ +name: Lint +on: + push: + pull_request: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/setup-julia@latest + with: + version: '1' + - uses: actions/checkout@v1 + - name: Format check + shell: julia --color=yes {0} + run: | + using Pkg + Pkg.add(PackageSpec(name="JuliaFormatter", version="0.14.4")) + using JuliaFormatter + format("src", verbose=true) + format("test", verbose=true) + out = String(read(Cmd(`git diff`))) + if isempty(out) + exit(0) + end + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) diff --git a/Makefile b/Makefile index c2ff626..e86d416 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ clean: docs: mkdocs build -d ../docs/$(VERSION)/ +format: + julia -e 'using JuliaFormatter; format(["src", "test"], verbose=true);' + test: build/test.log test-watch: diff --git a/src/RELOG.jl b/src/RELOG.jl index 0c5f0f5..d410fec 100644 --- a/src/RELOG.jl +++ b/src/RELOG.jl @@ -3,9 +3,9 @@ # Released under the modified BSD license. See COPYING.md for more details. module RELOG - include("dotdict.jl") - include("instance.jl") - include("graph.jl") - include("model.jl") - include("reports.jl") +include("dotdict.jl") +include("instance.jl") +include("graph.jl") +include("model.jl") +include("reports.jl") end diff --git a/src/dotdict.jl b/src/dotdict.jl index a59df5e..9221397 100644 --- a/src/dotdict.jl +++ b/src/dotdict.jl @@ -58,11 +58,11 @@ function Base.show(io::IO, d::DotDict) end function recursive_to_dot_dict(el) - if typeof(el) == Dict{String, Any} + if typeof(el) == Dict{String,Any} return DotDict(Dict(Symbol(k) => recursive_to_dot_dict(el[k]) for k in keys(el))) else return el end end -export recursive_to_dot_dict \ No newline at end of file +export recursive_to_dot_dict diff --git a/src/graph.jl b/src/graph.jl index 41fdc21..cd606a7 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -5,14 +5,13 @@ using Geodesy -abstract type Node -end +abstract type Node end mutable struct Arc source::Node dest::Node - values::Dict{String, Float64} + values::Dict{String,Float64} end @@ -26,7 +25,7 @@ end mutable struct ShippingNode <: Node index::Int - location::Union{Plant, CollectionCenter} + location::Union{Plant,CollectionCenter} product::Product incoming_arcs::Array{Arc} outgoing_arcs::Array{Arc} @@ -47,26 +46,25 @@ function build_graph(instance::Instance)::Graph process_nodes = ProcessNode[] plant_shipping_nodes = ShippingNode[] collection_shipping_nodes = ShippingNode[] - - process_nodes_by_input_product = Dict(product => ProcessNode[] - for product in instance.products) - shipping_nodes_by_plant = Dict(plant => [] - for plant in instance.plants) - + + process_nodes_by_input_product = + Dict(product => ProcessNode[] for product in instance.products) + shipping_nodes_by_plant = Dict(plant => [] for plant in instance.plants) + # Build collection center shipping nodes for center in instance.collection_centers node = ShippingNode(next_index, center, center.product, [], []) next_index += 1 push!(collection_shipping_nodes, node) end - + # Build process and shipping nodes for plants for plant in instance.plants pn = ProcessNode(next_index, plant, [], []) next_index += 1 push!(process_nodes, pn) push!(process_nodes_by_input_product[plant.input], pn) - + for product in keys(plant.output) sn = ShippingNode(next_index, plant, product, [], []) next_index += 1 @@ -74,14 +72,16 @@ function build_graph(instance::Instance)::Graph push!(shipping_nodes_by_plant[plant], sn) end end - + # Build arcs from collection centers to plants, and from one plant to another for source in [collection_shipping_nodes; plant_shipping_nodes] for dest in process_nodes_by_input_product[source.product] - distance = calculate_distance(source.location.latitude, - source.location.longitude, - dest.location.latitude, - dest.location.longitude) + distance = calculate_distance( + source.location.latitude, + source.location.longitude, + dest.location.latitude, + dest.location.longitude, + ) values = Dict("distance" => distance) arc = Arc(source, dest, values) push!(source.outgoing_arcs, arc) @@ -89,7 +89,7 @@ function build_graph(instance::Instance)::Graph push!(arcs, arc) end end - + # Build arcs from process nodes to shipping nodes within a plant for source in process_nodes plant = source.location @@ -102,11 +102,8 @@ function build_graph(instance::Instance)::Graph push!(arcs, arc) end end - - return Graph(process_nodes, - plant_shipping_nodes, - collection_shipping_nodes, - arcs) + + return Graph(process_nodes, plant_shipping_nodes, collection_shipping_nodes, arcs) end @@ -122,5 +119,5 @@ end function calculate_distance(source_lat, source_lon, dest_lat, dest_lon)::Float64 x = LLA(source_lat, source_lon, 0.0) y = LLA(dest_lat, dest_lon, 0.0) - return round(distance(x, y) / 1000.0, digits=2) + return round(distance(x, y) / 1000.0, digits = 2) end diff --git a/src/instance.jl b/src/instance.jl index 2d5bacb..99d71c2 100644 --- a/src/instance.jl +++ b/src/instance.jl @@ -13,7 +13,7 @@ mutable struct Product name::String transportation_cost::Array{Float64} transportation_energy::Array{Float64} - transportation_emissions::Dict{String, Array{Float64}} + transportation_emissions::Dict{String,Array{Float64}} end @@ -40,14 +40,14 @@ mutable struct Plant plant_name::String location_name::String input::Product - output::Dict{Product, Float64} + output::Dict{Product,Float64} latitude::Float64 longitude::Float64 - disposal_limit::Dict{Product, Array{Float64}} - disposal_cost::Dict{Product, Array{Float64}} + disposal_limit::Dict{Product,Array{Float64}} + disposal_cost::Dict{Product,Array{Float64}} sizes::Array{PlantSize} energy::Array{Float64} - emissions::Dict{String, Array{Float64}} + emissions::Dict{String,Array{Float64}} storage_limit::Float64 storage_cost::Array{Float64} end @@ -55,9 +55,9 @@ end mutable struct Instance time::Int64 - products::Array{Product, 1} - collection_centers::Array{CollectionCenter, 1} - plants::Array{Plant, 1} + products::Array{Product,1} + collection_centers::Array{CollectionCenter,1} + plants::Array{Plant,1} building_period::Array{Int64} end @@ -88,104 +88,113 @@ function parse(json)::Instance basedir = dirname(@__FILE__) json_schema = JSON.parsefile("$basedir/schemas/input.json") validate(json, Schema(json_schema)) - + T = json["parameters"]["time horizon (years)"] json_schema["definitions"]["TimeSeries"]["minItems"] = T json_schema["definitions"]["TimeSeries"]["maxItems"] = T validate(json, Schema(json_schema)) - + building_period = [1] if "building period (years)" in keys(json) building_period = json["building period (years)"] end - + plants = Plant[] products = Product[] collection_centers = CollectionCenter[] - prod_name_to_product = Dict{String, Product}() - + prod_name_to_product = Dict{String,Product}() + # Create products for (product_name, product_dict) in json["products"] cost = product_dict["transportation cost (\$/km/tonne)"] energy = zeros(T) emissions = Dict() - + if "transportation energy (J/km/tonne)" in keys(product_dict) energy = product_dict["transportation energy (J/km/tonne)"] end - + if "transportation emissions (tonne/km/tonne)" in keys(product_dict) emissions = product_dict["transportation emissions (tonne/km/tonne)"] end - + product = Product(product_name, cost, energy, emissions) push!(products, product) prod_name_to_product[product_name] = product - + # Create collection centers if "initial amounts" in keys(product_dict) for (center_name, center_dict) in product_dict["initial amounts"] - center = CollectionCenter(length(collection_centers) + 1, - center_name, - center_dict["latitude (deg)"], - center_dict["longitude (deg)"], - product, - center_dict["amount (tonne)"]) + center = CollectionCenter( + length(collection_centers) + 1, + center_name, + center_dict["latitude (deg)"], + center_dict["longitude (deg)"], + product, + center_dict["amount (tonne)"], + ) push!(collection_centers, center) end end end - + # Create plants for (plant_name, plant_dict) in json["plants"] input = prod_name_to_product[plant_dict["input"]] output = Dict() - + # Plant outputs if "outputs (tonne/tonne)" in keys(plant_dict) - output = Dict(prod_name_to_product[key] => value - for (key, value) in plant_dict["outputs (tonne/tonne)"] - if value > 0) + output = Dict( + prod_name_to_product[key] => value for + (key, value) in plant_dict["outputs (tonne/tonne)"] if value > 0 + ) end - + energy = zeros(T) emissions = Dict() - + if "energy (GJ/tonne)" in keys(plant_dict) energy = plant_dict["energy (GJ/tonne)"] end - + if "emissions (tonne/tonne)" in keys(plant_dict) emissions = plant_dict["emissions (tonne/tonne)"] end - + for (location_name, location_dict) in plant_dict["locations"] sizes = PlantSize[] - disposal_limit = Dict(p => [0.0 for t in 1:T] for p in keys(output)) - disposal_cost = Dict(p => [0.0 for t in 1:T] for p in keys(output)) - + disposal_limit = Dict(p => [0.0 for t = 1:T] for p in keys(output)) + disposal_cost = Dict(p => [0.0 for t = 1:T] for p in keys(output)) + # Disposal if "disposal" in keys(location_dict) for (product_name, disposal_dict) in location_dict["disposal"] - limit = [1e8 for t in 1:T] + limit = [1e8 for t = 1:T] if "limit (tonne)" in keys(disposal_dict) - limit = disposal_dict["limit (tonne)"] + limit = disposal_dict["limit (tonne)"] end disposal_limit[prod_name_to_product[product_name]] = limit - disposal_cost[prod_name_to_product[product_name]] = disposal_dict["cost (\$/tonne)"] + disposal_cost[prod_name_to_product[product_name]] = + disposal_dict["cost (\$/tonne)"] end end - + # Capacities for (capacity_name, capacity_dict) in location_dict["capacities (tonne)"] - push!(sizes, PlantSize(Base.parse(Float64, capacity_name), - capacity_dict["variable operating cost (\$/tonne)"], - capacity_dict["fixed operating cost (\$)"], - capacity_dict["opening cost (\$)"])) + push!( + sizes, + PlantSize( + Base.parse(Float64, capacity_name), + capacity_dict["variable operating cost (\$/tonne)"], + capacity_dict["fixed operating cost (\$)"], + capacity_dict["opening cost (\$)"], + ), + ) end - length(sizes) > 1 || push!(sizes, sizes[1]) + length(sizes) > 1 || push!(sizes, sizes[1]) sort!(sizes, by = x -> x.capacity) - + # Storage storage_limit = 0 storage_cost = zeros(T) @@ -194,7 +203,7 @@ function parse(json)::Instance storage_limit = storage_dict["limit (tonne)"] storage_cost = storage_dict["cost (\$/tonne)"] end - + # Validation: Capacities if length(sizes) != 2 throw("At most two capacities are supported") @@ -203,28 +212,30 @@ function parse(json)::Instance throw("Variable operating costs must be the same for all capacities") end - plant = Plant(length(plants) + 1, - plant_name, - location_name, - input, - output, - location_dict["latitude (deg)"], - location_dict["longitude (deg)"], - disposal_limit, - disposal_cost, - sizes, - energy, - emissions, - storage_limit, - storage_cost) - + plant = Plant( + length(plants) + 1, + plant_name, + location_name, + input, + output, + location_dict["latitude (deg)"], + location_dict["longitude (deg)"], + disposal_limit, + disposal_cost, + sizes, + energy, + emissions, + storage_limit, + storage_cost, + ) + push!(plants, plant) end end - + @info @sprintf("%12d collection centers", length(collection_centers)) @info @sprintf("%12d candidate plant locations", length(plants)) - + return Instance(T, products, collection_centers, plants, building_period) end @@ -242,7 +253,7 @@ function _compress(instance::Instance)::Instance compressed = deepcopy(instance) compressed.time = 1 compressed.building_period = [1] - + # Compress products for p in compressed.products p.transportation_cost = [mean(p.transportation_cost)] @@ -251,12 +262,12 @@ function _compress(instance::Instance)::Instance p.transportation_emissions[emission_name] = [mean(emission_value)] end end - + # Compress collection centers for c in compressed.collection_centers c.amount = [maximum(c.amount) * T] end - + # Compress plants for plant in compressed.plants plant.energy = [mean(plant.energy)] @@ -276,6 +287,6 @@ function _compress(instance::Instance)::Instance plant.disposal_cost[prod_name] = [mean(disp_cost)] end end - + return compressed end diff --git a/src/model.jl b/src/model.jl index 2177d1e..5a40a1b 100644 --- a/src/model.jl +++ b/src/model.jl @@ -26,40 +26,51 @@ end function create_vars!(model::ManufacturingModel) mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time - - vars.flow = Dict((a, t) => @variable(mip, lower_bound=0) - for a in graph.arcs, t in 1:T) - - vars.dispose = Dict((n, t) => @variable(mip, - lower_bound=0, - upper_bound=n.location.disposal_limit[n.product][t]) - for n in values(graph.plant_shipping_nodes), t in 1:T) - - vars.store = Dict((n, t) => @variable(mip, - lower_bound=0, - upper_bound=n.location.storage_limit) - for n in values(graph.process_nodes), t in 1:T) - - vars.process = Dict((n, t) => @variable(mip, - lower_bound = 0) - for n in values(graph.process_nodes), t in 1:T) - - vars.open_plant = Dict((n, t) => @variable(mip, binary=true) - for n in values(graph.process_nodes), t in 1:T) - - vars.is_open = Dict((n, t) => @variable(mip, binary=true) - for n in values(graph.process_nodes), t in 1:T) - - vars.capacity = Dict((n, t) => @variable(mip, - lower_bound = 0, - upper_bound = n.location.sizes[2].capacity) - for n in values(graph.process_nodes), t in 1:T) - - vars.expansion = Dict((n, t) => @variable(mip, - lower_bound = 0, - upper_bound = n.location.sizes[2].capacity - - n.location.sizes[1].capacity) - for n in values(graph.process_nodes), t in 1:T) + + vars.flow = Dict((a, t) => @variable(mip, lower_bound = 0) for a in graph.arcs, t = 1:T) + + vars.dispose = Dict( + (n, t) => @variable( + mip, + lower_bound = 0, + upper_bound = n.location.disposal_limit[n.product][t] + ) for n in values(graph.plant_shipping_nodes), t = 1:T + ) + + vars.store = Dict( + (n, t) => + @variable(mip, lower_bound = 0, upper_bound = n.location.storage_limit) for + n in values(graph.process_nodes), t = 1:T + ) + + vars.process = Dict( + (n, t) => @variable(mip, lower_bound = 0) for n in values(graph.process_nodes), + t = 1:T + ) + + vars.open_plant = Dict( + (n, t) => @variable(mip, binary = true) for n in values(graph.process_nodes), + t = 1:T + ) + + vars.is_open = Dict( + (n, t) => @variable(mip, binary = true) for n in values(graph.process_nodes), + t = 1:T + ) + + vars.capacity = Dict( + (n, t) => + @variable(mip, lower_bound = 0, upper_bound = n.location.sizes[2].capacity) + for n in values(graph.process_nodes), t = 1:T + ) + + vars.expansion = Dict( + (n, t) => @variable( + mip, + lower_bound = 0, + upper_bound = n.location.sizes[2].capacity - n.location.sizes[1].capacity + ) for n in values(graph.process_nodes), t = 1:T + ) end @@ -68,7 +79,7 @@ function slope_open(plant, t) 0.0 else (plant.sizes[2].opening_cost[t] - plant.sizes[1].opening_cost[t]) / - (plant.sizes[2].capacity - plant.sizes[1].capacity) + (plant.sizes[2].capacity - plant.sizes[1].capacity) end end @@ -77,7 +88,7 @@ function slope_fix_oper_cost(plant, t) 0.0 else (plant.sizes[2].fixed_operating_cost[t] - plant.sizes[1].fixed_operating_cost[t]) / - (plant.sizes[2].capacity - plant.sizes[1].capacity) + (plant.sizes[2].capacity - plant.sizes[1].capacity) end end @@ -86,111 +97,119 @@ function create_objective_function!(model::ManufacturingModel) obj = AffExpr(0.0) # Process node costs - for n in values(graph.process_nodes), t in 1:T - + for n in values(graph.process_nodes), t = 1:T + # Transportation and variable operating costs for a in n.incoming_arcs c = n.location.input.transportation_cost[t] * a.values["distance"] add_to_expression!(obj, c, vars.flow[a, t]) end - + # Opening costs - add_to_expression!(obj, - n.location.sizes[1].opening_cost[t], - vars.open_plant[n, t]) - + add_to_expression!(obj, n.location.sizes[1].opening_cost[t], vars.open_plant[n, t]) + # Fixed operating costs (base) - add_to_expression!(obj, - n.location.sizes[1].fixed_operating_cost[t], - vars.is_open[n, t]) - + add_to_expression!( + obj, + n.location.sizes[1].fixed_operating_cost[t], + vars.is_open[n, t], + ) + # Fixed operating costs (expansion) - add_to_expression!(obj, - slope_fix_oper_cost(n.location, t), - vars.expansion[n, t]) - + add_to_expression!(obj, slope_fix_oper_cost(n.location, t), vars.expansion[n, t]) + # Processing costs - add_to_expression!(obj, - n.location.sizes[1].variable_operating_cost[t], - vars.process[n, t]) - + add_to_expression!( + obj, + n.location.sizes[1].variable_operating_cost[t], + vars.process[n, t], + ) + # Storage costs - add_to_expression!(obj, - n.location.storage_cost[t], - vars.store[n, t]) - + add_to_expression!(obj, n.location.storage_cost[t], vars.store[n, t]) + # Expansion costs if t < T - add_to_expression!(obj, - slope_open(n.location, t) - slope_open(n.location, t + 1), - vars.expansion[n, t]) + add_to_expression!( + obj, + slope_open(n.location, t) - slope_open(n.location, t + 1), + vars.expansion[n, t], + ) else - add_to_expression!(obj, - slope_open(n.location, t), - vars.expansion[n, t]) + add_to_expression!(obj, slope_open(n.location, t), vars.expansion[n, t]) end end # Shipping node costs - for n in values(graph.plant_shipping_nodes), t in 1:T - + for n in values(graph.plant_shipping_nodes), t = 1:T + # Disposal costs - add_to_expression!(obj, - n.location.disposal_cost[n.product][t], - vars.dispose[n, t]) + add_to_expression!(obj, n.location.disposal_cost[n.product][t], vars.dispose[n, t]) end @objective(mip, Min, obj) -end +end 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 = OrderedDict() - - for t in 1:T + + for t = 1:T # Collection centers for n in graph.collection_shipping_nodes - eqs.balance[n, t] = @constraint(mip, - sum(vars.flow[a, t] for a in n.outgoing_arcs) - == n.location.amount[t]) + eqs.balance[n, t] = @constraint( + mip, + sum(vars.flow[a, t] for a in n.outgoing_arcs) == n.location.amount[t] + ) end # Plants for n in graph.plant_shipping_nodes - @constraint(mip, + @constraint( + mip, sum(vars.flow[a, t] for a in n.incoming_arcs) == - sum(vars.flow[a, t] for a in n.outgoing_arcs) + vars.dispose[n, t]) + sum(vars.flow[a, t] for a in n.outgoing_arcs) + vars.dispose[n, t] + ) end end - + end function create_process_node_constraints!(model::ManufacturingModel) mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time - for t in 1:T, n in graph.process_nodes + for t = 1:T, n in graph.process_nodes input_sum = AffExpr(0.0) for a in n.incoming_arcs add_to_expression!(input_sum, 1.0, vars.flow[a, t]) end - + # Output amount is implied by amount processed for a in n.outgoing_arcs @constraint(mip, vars.flow[a, t] == a.values["weight"] * vars.process[n, t]) end - + # If plant is closed, capacity is zero - @constraint(mip, vars.capacity[n, t] <= n.location.sizes[2].capacity * vars.is_open[n, t]) + @constraint( + mip, + vars.capacity[n, t] <= n.location.sizes[2].capacity * vars.is_open[n, t] + ) # If plant is open, capacity is greater than base - @constraint(mip, vars.capacity[n, t] >= n.location.sizes[1].capacity * vars.is_open[n, t]) + @constraint( + mip, + vars.capacity[n, t] >= n.location.sizes[1].capacity * vars.is_open[n, t] + ) # Capacity is linked to expansion - @constraint(mip, vars.capacity[n, t] <= n.location.sizes[1].capacity + vars.expansion[n, t]) + @constraint( + mip, + vars.capacity[n, t] <= n.location.sizes[1].capacity + vars.expansion[n, t] + ) # Can only process up to capacity @constraint(mip, vars.process[n, t] <= vars.capacity[n, t]) @@ -200,7 +219,7 @@ function create_process_node_constraints!(model::ManufacturingModel) @constraint(mip, vars.capacity[n, t] >= vars.capacity[n, t-1]) @constraint(mip, vars.expansion[n, t] >= vars.expansion[n, t-1]) end - + # Amount received equals amount processed plus stored store_in = 0 if t > 1 @@ -209,18 +228,20 @@ function create_process_node_constraints!(model::ManufacturingModel) if t == T @constraint(mip, vars.store[n, t] == 0) end - @constraint(mip, - input_sum + store_in == vars.store[n, t] + vars.process[n, t]) - + @constraint(mip, input_sum + store_in == vars.store[n, t] + vars.process[n, t]) + # Plant is currently open if it was already open in the previous time period or # if it was built just now if t > 1 - @constraint(mip, vars.is_open[n, t] == vars.is_open[n, t-1] + vars.open_plant[n, t]) + @constraint( + mip, + vars.is_open[n, t] == vars.is_open[n, t-1] + vars.open_plant[n, t] + ) else @constraint(mip, vars.is_open[n, t] == vars.open_plant[n, t]) end - + # Plant can only be opened during building period if t ∉ model.instance.building_period @constraint(mip, vars.open_plant[n, t] == 0) @@ -231,37 +252,41 @@ end default_milp_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) default_lp_optimizer = optimizer_with_attributes(Clp.Optimizer, "LogLevel" => 0) -function solve(instance::Instance; - optimizer=nothing, - output=nothing, - marginal_costs=true, - ) - +function solve( + instance::Instance; + optimizer = nothing, + output = nothing, + marginal_costs = true, +) + milp_optimizer = lp_optimizer = optimizer if optimizer == nothing milp_optimizer = default_milp_optimizer lp_optimizer = default_lp_optimizer end - + @info "Building graph..." graph = RELOG.build_graph(instance) @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 shipping nodes (collection)", + length(graph.collection_shipping_nodes) + ) @info @sprintf(" %12d arcs", length(graph.arcs)) - + @info "Building optimization model..." model = RELOG.build_model(instance, graph, milp_optimizer) - + @info "Optimizing MILP..." JuMP.optimize!(model.mip) - + if !has_values(model.mip) @warn "No solution available" return OrderedDict() end - + if marginal_costs @info "Re-optimizing with integer variables fixed..." all_vars = JuMP.all_variables(model.mip) @@ -275,30 +300,24 @@ function solve(instance::Instance; end JuMP.optimize!(model.mip) end - + @info "Extracting solution..." - solution = get_solution(model, marginal_costs=marginal_costs) - + solution = get_solution(model, marginal_costs = marginal_costs) + if output != nothing write(solution, output) end - + return solution end -function solve(filename::AbstractString; - heuristic=false, - kwargs..., - ) +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...) + csol = solve(compressed; output = nothing, marginal_costs = false, kwargs...) @info "Filtering candidate locations..." selected_pairs = [] for (plant_name, plant_dict) in csol["Plants"] @@ -320,12 +339,11 @@ function solve(filename::AbstractString; end -function get_solution(model::ManufacturingModel; - marginal_costs=true, - ) - mip, vars, eqs, graph, instance = model.mip, model.vars, model.eqs, model.graph, model.instance +function get_solution(model::ManufacturingModel; marginal_costs = true) + mip, vars, eqs, graph, instance = + model.mip, model.vars, model.eqs, model.graph, model.instance T = instance.time - + output = OrderedDict( "Plants" => OrderedDict(), "Products" => OrderedDict(), @@ -339,16 +357,14 @@ function get_solution(model::ManufacturingModel; "Storage (\$)" => zeros(T), "Total (\$)" => zeros(T), ), - "Energy" => OrderedDict( - "Plants (GJ)" => zeros(T), - "Transportation (GJ)" => zeros(T), - ), + "Energy" => + OrderedDict("Plants (GJ)" => zeros(T), "Transportation (GJ)" => zeros(T)), "Emissions" => OrderedDict( "Plants (tonne)" => OrderedDict(), "Transportation (tonne)" => OrderedDict(), ), ) - + plant_to_process_node = OrderedDict(n.location => n for n in graph.process_nodes) plant_to_shipping_nodes = OrderedDict() for p in instance.plants @@ -357,13 +373,15 @@ function get_solution(model::ManufacturingModel; push!(plant_to_shipping_nodes[p], a.dest) end end - + # Products if marginal_costs for n in graph.collection_shipping_nodes - location_dict = OrderedDict{Any, Any}( - "Marginal cost (\$/tonne)" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2) - for t in 1:T] + location_dict = OrderedDict{Any,Any}( + "Marginal cost (\$/tonne)" => [ + round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits = 2) for + t = 1:T + ], ) if n.product.name ∉ keys(output["Products"]) output["Products"][n.product.name] = OrderedDict() @@ -371,83 +389,83 @@ function get_solution(model::ManufacturingModel; output["Products"][n.product.name][n.location.name] = location_dict end end - + # Plants for plant in instance.plants skip_plant = true process_node = plant_to_process_node[plant] - plant_dict = OrderedDict{Any, Any}( + plant_dict = OrderedDict{Any,Any}( "Input" => OrderedDict(), - "Output" => OrderedDict( - "Send" => OrderedDict(), - "Dispose" => OrderedDict(), - ), + "Output" => + OrderedDict("Send" => OrderedDict(), "Dispose" => OrderedDict()), "Input product" => plant.input.name, - "Total input (tonne)" => [0.0 for t in 1:T], + "Total input (tonne)" => [0.0 for t = 1:T], "Total output" => OrderedDict(), "Latitude (deg)" => plant.latitude, "Longitude (deg)" => plant.longitude, - "Capacity (tonne)" => [JuMP.value(vars.capacity[process_node, t]) - for t in 1:T], - "Opening cost (\$)" => [JuMP.value(vars.open_plant[process_node, t]) * - plant.sizes[1].opening_cost[t] - for t in 1:T], - "Fixed operating cost (\$)" => [JuMP.value(vars.is_open[process_node, t]) * - plant.sizes[1].fixed_operating_cost[t] + - JuMP.value(vars.expansion[process_node, t]) * - slope_fix_oper_cost(plant, t) - for t in 1:T], - "Expansion cost (\$)" => [(if t == 1 - slope_open(plant, t) * JuMP.value(vars.expansion[process_node, t]) - else - slope_open(plant, t) * ( - JuMP.value(vars.expansion[process_node, t]) - - JuMP.value(vars.expansion[process_node, t - 1]) - ) - end) - for t in 1:T], - "Process (tonne)" => [JuMP.value(vars.process[process_node, t]) - for t in 1:T], - "Variable operating cost (\$)" => [JuMP.value(vars.process[process_node, t]) * - plant.sizes[1].variable_operating_cost[t] - for t in 1:T], - "Storage (tonne)" => [JuMP.value(vars.store[process_node, t]) - for t in 1:T], - "Storage cost (\$)" => [JuMP.value(vars.store[process_node, t]) * - plant.storage_cost[t] - for t in 1:T], + "Capacity (tonne)" => + [JuMP.value(vars.capacity[process_node, t]) for t = 1:T], + "Opening cost (\$)" => [ + JuMP.value(vars.open_plant[process_node, t]) * + plant.sizes[1].opening_cost[t] for t = 1:T + ], + "Fixed operating cost (\$)" => [ + JuMP.value(vars.is_open[process_node, t]) * + plant.sizes[1].fixed_operating_cost[t] + + JuMP.value(vars.expansion[process_node, t]) * slope_fix_oper_cost(plant, t) for t = 1:T + ], + "Expansion cost (\$)" => [ + ( + if t == 1 + slope_open(plant, t) * JuMP.value(vars.expansion[process_node, t]) + else + slope_open(plant, t) * ( + JuMP.value(vars.expansion[process_node, t]) - + JuMP.value(vars.expansion[process_node, t-1]) + ) + end + ) for t = 1:T + ], + "Process (tonne)" => + [JuMP.value(vars.process[process_node, t]) for t = 1:T], + "Variable operating cost (\$)" => [ + JuMP.value(vars.process[process_node, t]) * + plant.sizes[1].variable_operating_cost[t] for t = 1:T + ], + "Storage (tonne)" => [JuMP.value(vars.store[process_node, t]) for t = 1:T], + "Storage cost (\$)" => [ + JuMP.value(vars.store[process_node, t]) * plant.storage_cost[t] for t = 1:T + ], ) output["Costs"]["Fixed operating (\$)"] += plant_dict["Fixed operating cost (\$)"] - output["Costs"]["Variable operating (\$)"] += plant_dict["Variable operating cost (\$)"] + output["Costs"]["Variable operating (\$)"] += + plant_dict["Variable operating cost (\$)"] output["Costs"]["Opening (\$)"] += plant_dict["Opening cost (\$)"] output["Costs"]["Expansion (\$)"] += plant_dict["Expansion cost (\$)"] output["Costs"]["Storage (\$)"] += plant_dict["Storage cost (\$)"] # Inputs for a in process_node.incoming_arcs - vals = [JuMP.value(vars.flow[a, t]) for t in 1:T] + vals = [JuMP.value(vars.flow[a, t]) for t = 1:T] if sum(vals) <= 1e-3 continue end skip_plant = false - dict = OrderedDict{Any, Any}( + dict = OrderedDict{Any,Any}( "Amount (tonne)" => vals, "Distance (km)" => a.values["distance"], "Latitude (deg)" => a.source.location.latitude, "Longitude (deg)" => a.source.location.longitude, - "Transportation cost (\$)" => a.source.product.transportation_cost .* - vals .* - a.values["distance"], - "Transportation energy (J)" => vals .* - a.values["distance"] .* - a.source.product.transportation_energy, + "Transportation cost (\$)" => + a.source.product.transportation_cost .* vals .* a.values["distance"], + "Transportation energy (J)" => + vals .* a.values["distance"] .* a.source.product.transportation_energy, "Emissions (tonne)" => OrderedDict(), ) emissions_dict = output["Emissions"]["Transportation (tonne)"] for (em_name, em_values) in a.source.product.transportation_emissions - dict["Emissions (tonne)"][em_name] = em_values .* - dict["Amount (tonne)"] .* - a.values["distance"] + dict["Emissions (tonne)"][em_name] = + em_values .* dict["Amount (tonne)"] .* a.values["distance"] if em_name ∉ keys(emissions_dict) emissions_dict[em_name] = zeros(T) end @@ -460,23 +478,25 @@ function get_solution(model::ManufacturingModel; plant_name = a.source.location.plant_name location_name = a.source.location.location_name end - + if plant_name ∉ keys(plant_dict["Input"]) plant_dict["Input"][plant_name] = OrderedDict() end plant_dict["Input"][plant_name][location_name] = dict plant_dict["Total input (tonne)"] += vals output["Costs"]["Transportation (\$)"] += dict["Transportation cost (\$)"] - output["Energy"]["Transportation (GJ)"] += dict["Transportation energy (J)"] / 1e9 + output["Energy"]["Transportation (GJ)"] += + dict["Transportation energy (J)"] / 1e9 end - + plant_dict["Energy (GJ)"] = plant_dict["Total input (tonne)"] .* plant.energy output["Energy"]["Plants (GJ)"] += plant_dict["Energy (GJ)"] - + 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)"] + plant_dict["Emissions (tonne)"][em_name] = + em_values .* plant_dict["Total input (tonne)"] if em_name ∉ keys(emissions_dict) emissions_dict[em_name] = zeros(T) end @@ -489,21 +509,23 @@ function get_solution(model::ManufacturingModel; plant_dict["Total output"][product_name] = zeros(T) 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 = 1:T] if sum(disposal_amount) > 1e-5 skip_plant = false - 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] * - plant.disposal_cost[shipping_node.product][t] - for t in 1:T] + plant_dict["Output"]["Dispose"][product_name] = + disposal_dict = OrderedDict() + disposal_dict["Amount (tonne)"] = + [JuMP.value(model.vars.dispose[shipping_node, t]) for t = 1:T] + disposal_dict["Cost (\$)"] = [ + disposal_dict["Amount (tonne)"][t] * + plant.disposal_cost[shipping_node.product][t] for t = 1:T + ] plant_dict["Total output"][product_name] += disposal_amount output["Costs"]["Disposal (\$)"] += disposal_dict["Cost (\$)"] end for a in shipping_node.outgoing_arcs - vals = [JuMP.value(vars.flow[a, t]) for t in 1:T] + vals = [JuMP.value(vars.flow[a, t]) for t = 1:T] if sum(vals) <= 1e-3 continue end @@ -517,11 +539,12 @@ function get_solution(model::ManufacturingModel; if a.dest.location.plant_name ∉ keys(product_dict) product_dict[a.dest.location.plant_name] = OrderedDict() 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 end end - + if !skip_plant if plant.plant_name ∉ keys(output["Plants"]) output["Plants"][plant.plant_name] = OrderedDict() diff --git a/src/reports.jl b/src/reports.jl index a068ae8..6aedb5f 100644 --- a/src/reports.jl +++ b/src/reports.jl @@ -27,41 +27,49 @@ function plants_report(solution)::DataFrame T = length(solution["Energy"]["Plants (GJ)"]) for (plant_name, plant_dict) in solution["Plants"] for (location_name, location_dict) in plant_dict - for year in 1:T - capacity = round(location_dict["Capacity (tonne)"][year], digits=2) - received = round(location_dict["Total input (tonne)"][year], digits=2) - processed = round(location_dict["Process (tonne)"][year], digits=2) - in_storage = round(location_dict["Storage (tonne)"][year], digits=2) - utilization_factor = round(processed / capacity * 100.0, digits=2) - energy = round(location_dict["Energy (GJ)"][year], digits=2) - latitude = round(location_dict["Latitude (deg)"], digits=6) - longitude = round(location_dict["Longitude (deg)"], digits=6) - opening_cost = round(location_dict["Opening cost (\$)"][year], digits=2) - expansion_cost = round(location_dict["Expansion cost (\$)"][year], digits=2) - fixed_cost = round(location_dict["Fixed operating cost (\$)"][year], digits=2) - var_cost = round(location_dict["Variable operating cost (\$)"][year], digits=2) - storage_cost = round(location_dict["Storage cost (\$)"][year], digits=2) - total_cost = round(opening_cost + expansion_cost + fixed_cost + - var_cost + storage_cost, digits=2) - push!(df, [ - plant_name, - location_name, - year, - latitude, - longitude, - capacity, - processed, - received, - in_storage, - utilization_factor, - energy, - opening_cost, - expansion_cost, - fixed_cost, - var_cost, - storage_cost, - total_cost, - ]) + for year = 1:T + capacity = round(location_dict["Capacity (tonne)"][year], digits = 2) + received = round(location_dict["Total input (tonne)"][year], digits = 2) + processed = round(location_dict["Process (tonne)"][year], digits = 2) + in_storage = round(location_dict["Storage (tonne)"][year], digits = 2) + utilization_factor = round(processed / capacity * 100.0, digits = 2) + energy = round(location_dict["Energy (GJ)"][year], digits = 2) + latitude = round(location_dict["Latitude (deg)"], digits = 6) + longitude = round(location_dict["Longitude (deg)"], digits = 6) + opening_cost = round(location_dict["Opening cost (\$)"][year], digits = 2) + expansion_cost = + round(location_dict["Expansion cost (\$)"][year], digits = 2) + fixed_cost = + round(location_dict["Fixed operating cost (\$)"][year], digits = 2) + var_cost = + round(location_dict["Variable operating cost (\$)"][year], digits = 2) + storage_cost = round(location_dict["Storage cost (\$)"][year], digits = 2) + total_cost = round( + opening_cost + expansion_cost + fixed_cost + var_cost + storage_cost, + digits = 2, + ) + push!( + df, + [ + plant_name, + location_name, + year, + latitude, + longitude, + capacity, + processed, + received, + in_storage, + utilization_factor, + energy, + opening_cost, + expansion_cost, + fixed_cost, + var_cost, + storage_cost, + total_cost, + ], + ) end end end @@ -84,7 +92,7 @@ function plant_outputs_report(solution)::DataFrame for (product_name, amount_produced) in location_dict["Total output"] send_dict = location_dict["Output"]["Send"] disposal_dict = location_dict["Output"]["Dispose"] - + sent = zeros(T) if product_name in keys(send_dict) for (dst_plant_name, dst_plant_dict) in send_dict[product_name] @@ -93,28 +101,31 @@ function plant_outputs_report(solution)::DataFrame end end end - sent = round.(sent, digits=2) - + sent = round.(sent, digits = 2) + disposal_amount = zeros(T) disposal_cost = zeros(T) if product_name in keys(disposal_dict) disposal_amount += disposal_dict[product_name]["Amount (tonne)"] disposal_cost += disposal_dict[product_name]["Cost (\$)"] end - disposal_amount = round.(disposal_amount, digits=2) - disposal_cost = round.(disposal_cost, digits=2) - - for year in 1:T - push!(df, [ - plant_name, - location_name, - year, - product_name, - round(amount_produced[year], digits=2), - sent[year], - disposal_amount[year], - disposal_cost[year], - ]) + disposal_amount = round.(disposal_amount, digits = 2) + disposal_cost = round.(disposal_cost, digits = 2) + + for year = 1:T + push!( + df, + [ + plant_name, + location_name, + year, + product_name, + round(amount_produced[year], digits = 2), + sent[year], + disposal_amount[year], + disposal_cost[year], + ], + ) end end end @@ -134,14 +145,17 @@ function plant_emissions_report(solution)::DataFrame for (plant_name, plant_dict) in solution["Plants"] for (location_name, location_dict) in plant_dict for (emission_name, emission_amount) in location_dict["Emissions (tonne)"] - for year in 1:T - push!(df, [ - plant_name, - location_name, - year, - emission_name, - round(emission_amount[year], digits=2), - ]) + for year = 1:T + push!( + df, + [ + plant_name, + location_name, + year, + emission_name, + round(emission_amount[year], digits = 2), + ], + ) end end end @@ -165,34 +179,49 @@ function transportation_report(solution)::DataFrame df."distance (km)" = Float64[] df."amount (tonne)" = Float64[] df."amount-distance (tonne-km)" = Float64[] - df."transportation cost (\$)" = Float64[] - df."transportation energy (GJ)" = Float64[] - + df."transportation cost (\$)" = Float64[] + df."transportation energy (GJ)" = Float64[] + T = length(solution["Energy"]["Plants (GJ)"]) for (dst_plant_name, dst_plant_dict) in solution["Plants"] for (dst_location_name, dst_location_dict) in dst_plant_dict for (src_plant_name, src_plant_dict) in dst_location_dict["Input"] for (src_location_name, src_location_dict) in src_plant_dict - for year in 1:T - push!(df, [ - src_plant_name, - src_location_name, - round(src_location_dict["Latitude (deg)"], digits=6), - round(src_location_dict["Longitude (deg)"], digits=6), - dst_plant_name, - dst_location_name, - round(dst_location_dict["Latitude (deg)"], digits=6), - round(dst_location_dict["Longitude (deg)"], digits=6), - dst_location_dict["Input product"], - year, - round(src_location_dict["Distance (km)"], digits=2), - round(src_location_dict["Amount (tonne)"][year], digits=2), - round(src_location_dict["Amount (tonne)"][year] * - src_location_dict["Distance (km)"], - digits=2), - round(src_location_dict["Transportation cost (\$)"][year], digits=2), - round(src_location_dict["Transportation energy (J)"][year] / 1e9, digits=2), - ]) + for year = 1:T + push!( + df, + [ + src_plant_name, + src_location_name, + round(src_location_dict["Latitude (deg)"], digits = 6), + round(src_location_dict["Longitude (deg)"], digits = 6), + dst_plant_name, + dst_location_name, + round(dst_location_dict["Latitude (deg)"], digits = 6), + round(dst_location_dict["Longitude (deg)"], digits = 6), + dst_location_dict["Input product"], + year, + round(src_location_dict["Distance (km)"], digits = 2), + round( + src_location_dict["Amount (tonne)"][year], + digits = 2, + ), + round( + src_location_dict["Amount (tonne)"][year] * + src_location_dict["Distance (km)"], + digits = 2, + ), + round( + src_location_dict["Transportation cost (\$)"][year], + digits = 2, + ), + round( + src_location_dict["Transportation energy (J)"][year] / + 1e9, + digits = 2, + ), + ], + ) end end end @@ -217,35 +246,44 @@ function transportation_emissions_report(solution)::DataFrame df."distance (km)" = Float64[] df."shipped amount (tonne)" = Float64[] df."shipped amount-distance (tonne-km)" = Float64[] - df."emission type" = String[] - df."emission amount (tonne)" = Float64[] - + df."emission type" = String[] + df."emission amount (tonne)" = Float64[] + T = length(solution["Energy"]["Plants (GJ)"]) for (dst_plant_name, dst_plant_dict) in solution["Plants"] for (dst_location_name, dst_location_dict) in dst_plant_dict for (src_plant_name, src_plant_dict) in dst_location_dict["Input"] for (src_location_name, src_location_dict) in src_plant_dict - for (emission_name, emission_amount) in src_location_dict["Emissions (tonne)"] - for year in 1:T - push!(df, [ - src_plant_name, - src_location_name, - round(src_location_dict["Latitude (deg)"], digits=6), - round(src_location_dict["Longitude (deg)"], digits=6), - dst_plant_name, - dst_location_name, - round(dst_location_dict["Latitude (deg)"], digits=6), - round(dst_location_dict["Longitude (deg)"], digits=6), - dst_location_dict["Input product"], - year, - round(src_location_dict["Distance (km)"], digits=2), - round(src_location_dict["Amount (tonne)"][year], digits=2), - round(src_location_dict["Amount (tonne)"][year] * - src_location_dict["Distance (km)"], - digits=2), - emission_name, - round(emission_amount[year], digits=2), - ]) + for (emission_name, emission_amount) in + src_location_dict["Emissions (tonne)"] + for year = 1:T + push!( + df, + [ + src_plant_name, + src_location_name, + round(src_location_dict["Latitude (deg)"], digits = 6), + round(src_location_dict["Longitude (deg)"], digits = 6), + dst_plant_name, + dst_location_name, + round(dst_location_dict["Latitude (deg)"], digits = 6), + round(dst_location_dict["Longitude (deg)"], digits = 6), + dst_location_dict["Input product"], + year, + round(src_location_dict["Distance (km)"], digits = 2), + round( + src_location_dict["Amount (tonne)"][year], + digits = 2, + ), + round( + src_location_dict["Amount (tonne)"][year] * + src_location_dict["Distance (km)"], + digits = 2, + ), + emission_name, + round(emission_amount[year], digits = 2), + ], + ) end end end @@ -262,8 +300,7 @@ function write(solution::AbstractDict, filename::AbstractString) end end -write_plants_report(solution, filename) = - CSV.write(filename, plants_report(solution)) +write_plants_report(solution, filename) = CSV.write(filename, plants_report(solution)) write_plant_outputs_report(solution, filename) = CSV.write(filename, plant_outputs_report(solution)) diff --git a/src/sysimage.jl b/src/sysimage.jl index ab72fde..162a2fc 100644 --- a/src/sysimage.jl +++ b/src/sysimage.jl @@ -9,14 +9,7 @@ using JuMP using MathOptInterface using ProgressBars -pkg = [:Cbc, - :Clp, - :Geodesy, - :JSON, - :JSONSchema, - :JuMP, - :MathOptInterface, - :ProgressBars] +pkg = [:Cbc, :Clp, :Geodesy, :JSON, :JSONSchema, :JuMP, :MathOptInterface, :ProgressBars] @info "Building system image..." -create_sysimage(pkg, sysimage_path="build/sysimage.so") \ No newline at end of file +create_sysimage(pkg, sysimage_path = "build/sysimage.so") diff --git a/test/graph_test.jl b/test/graph_test.jl index 5c2783d..872172f 100644 --- a/test/graph_test.jl +++ b/test/graph_test.jl @@ -8,13 +8,13 @@ using RELOG basedir = dirname(@__FILE__) instance = RELOG.parsefile("$basedir/../instances/s1.json") graph = RELOG.build_graph(instance) - process_node_by_location_name = Dict(n.location.location_name => n - for n in graph.process_nodes) - + process_node_by_location_name = + Dict(n.location.location_name => n for n in graph.process_nodes) + @test length(graph.plant_shipping_nodes) == 8 @test length(graph.collection_shipping_nodes) == 10 @test length(graph.process_nodes) == 6 - + node = graph.collection_shipping_nodes[1] @test node.location.name == "C1" @test length(node.incoming_arcs) == 0 @@ -23,20 +23,19 @@ using RELOG @test node.outgoing_arcs[1].dest.location.plant_name == "F1" @test node.outgoing_arcs[1].dest.location.location_name == "L1" @test node.outgoing_arcs[1].values["distance"] == 1095.62 - + node = process_node_by_location_name["L1"] @test node.location.plant_name == "F1" @test node.location.location_name == "L1" @test length(node.incoming_arcs) == 10 @test length(node.outgoing_arcs) == 2 - + node = process_node_by_location_name["L3"] @test node.location.plant_name == "F2" @test node.location.location_name == "L3" @test length(node.incoming_arcs) == 2 @test length(node.outgoing_arcs) == 2 - + @test length(graph.arcs) == 38 end end - diff --git a/test/instance_test.jl b/test/instance_test.jl index eb35bf3..2991cf9 100644 --- a/test/instance_test.jl +++ b/test/instance_test.jl @@ -7,13 +7,13 @@ using RELOG @testset "load" begin basedir = dirname(@__FILE__) instance = RELOG.parsefile("$basedir/../instances/s1.json") - + centers = instance.collection_centers plants = instance.plants products = instance.products location_name_to_plant = Dict(p.location_name => p for p in plants) product_name_to_product = Dict(p.name => p for p in products) - + @test length(centers) == 10 @test centers[1].name == "C1" @test centers[1].latitude == 7 @@ -21,7 +21,7 @@ using RELOG @test centers[1].longitude == 7 @test centers[1].amount == [934.56, 934.56] @test centers[1].product.name == "P1" - + @test length(plants) == 6 plant = location_name_to_plant["L1"] @@ -30,7 +30,7 @@ using RELOG @test plant.input.name == "P1" @test plant.latitude == 0 @test plant.longitude == 0 - + @test length(plant.sizes) == 2 @test plant.sizes[1].capacity == 250 @test plant.sizes[1].opening_cost == [500, 500] @@ -40,7 +40,7 @@ using RELOG @test plant.sizes[2].opening_cost == [1250, 1250] @test plant.sizes[2].fixed_operating_cost == [30, 30] @test plant.sizes[2].variable_operating_cost == [30, 30] - + p2 = product_name_to_product["P2"] p3 = product_name_to_product["P3"] @test length(plant.output) == 2 @@ -50,36 +50,36 @@ using RELOG @test plant.disposal_limit[p3] == [1, 1] @test plant.disposal_cost[p2] == [-10, -10] @test plant.disposal_cost[p3] == [-10, -10] - + plant = location_name_to_plant["L3"] @test plant.location_name == "L3" @test plant.input.name == "P2" @test plant.latitude == 25 @test plant.longitude == 65 - + @test length(plant.sizes) == 2 @test plant.sizes[1].capacity == 1000.0 @test plant.sizes[1].opening_cost == [3000, 3000] @test plant.sizes[1].fixed_operating_cost == [50, 50] @test plant.sizes[1].variable_operating_cost == [50, 50] @test plant.sizes[1] == plant.sizes[2] - + p4 = product_name_to_product["P4"] @test plant.output[p3] == 0.05 @test plant.output[p4] == 0.8 @test plant.disposal_limit[p3] == [1e8, 1e8] @test plant.disposal_limit[p4] == [0, 0] end - + @testset "validate timeseries" begin @test_throws String RELOG.parsefile("fixtures/s1-wrong-length.json") end - + @testset "compress" begin basedir = dirname(@__FILE__) instance = RELOG.parsefile("$basedir/../instances/s1.json") compressed = RELOG._compress(instance) - + product_name_to_product = Dict(p.name => p for p in compressed.products) location_name_to_facility = Dict() for p in compressed.plants @@ -88,7 +88,7 @@ using RELOG for c in compressed.collection_centers location_name_to_facility[c.name] = c end - + p1 = product_name_to_product["P1"] p2 = product_name_to_product["P2"] p3 = product_name_to_product["P3"] @@ -103,10 +103,10 @@ using RELOG @test p1.transportation_energy ≈ [0.115] @test p1.transportation_emissions["CO2"] ≈ [0.051] @test p1.transportation_emissions["CH4"] ≈ [0.0025] - + @test c1.name == "C1" @test c1.amount ≈ [1869.12] - + @test l1.plant_name == "F1" @test l1.location_name == "L1" @test l1.energy ≈ [0.115] @@ -121,7 +121,6 @@ using RELOG @test l1.disposal_limit[p2] ≈ [2.0] @test l1.disposal_limit[p3] ≈ [2.0] @test l1.disposal_cost[p2] ≈ [-10.0] - @test l1.disposal_cost[p3] ≈ [-10.0] + @test l1.disposal_cost[p3] ≈ [-10.0] end end - diff --git a/test/model_test.jl b/test/model_test.jl index c66e6e7..0583e1c 100644 --- a/test/model_test.jl +++ b/test/model_test.jl @@ -11,12 +11,14 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats model = RELOG.build_model(instance, graph, Cbc.Optimizer) set_optimizer_attribute(model.mip, "logLevel", 0) - process_node_by_location_name = Dict(n.location.location_name => n - for n in graph.process_nodes) + process_node_by_location_name = + Dict(n.location.location_name => n for n in graph.process_nodes) + + shipping_node_by_location_and_product_names = Dict( + (n.location.location_name, n.product.name) => n for + n in graph.plant_shipping_nodes + ) - shipping_node_by_location_and_product_names = Dict((n.location.location_name, n.product.name) => n - for n in graph.plant_shipping_nodes) - @test length(model.vars.flow) == 76 @test length(model.vars.dispose) == 16 @test length(model.vars.open_plant) == 12 @@ -27,15 +29,15 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats v = model.vars.capacity[l1, 1] @test lower_bound(v) == 0.0 @test upper_bound(v) == 1000.0 - + v = model.vars.expansion[l1, 1] @test lower_bound(v) == 0.0 @test upper_bound(v) == 750.0 - + v = model.vars.dispose[shipping_node_by_location_and_product_names["L1", "P2"], 1] @test lower_bound(v) == 0.0 @test upper_bound(v) == 1.0 - + # dest = FileFormats.Model(format = FileFormats.FORMAT_LP) # MOI.copy_to(dest, model.mip) # MOI.write_to_file(dest, "model.lp") @@ -44,14 +46,14 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats @testset "solve (exact)" begin solution_filename_a = tempname() solution_filename_b = tempname() - solution = RELOG.solve("$(pwd())/../instances/s1.json", - output=solution_filename_a) + solution = + RELOG.solve("$(pwd())/../instances/s1.json", output = solution_filename_a) @test isfile(solution_filename_a) - + RELOG.write(solution, solution_filename_b) @test isfile(solution_filename_b) - + @test "Costs" in keys(solution) @test "Fixed operating (\$)" in keys(solution["Costs"]) @test "Transportation (\$)" in keys(solution["Costs"]) @@ -64,11 +66,11 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats @test "F3" in keys(solution["Plants"]) @test "F4" in keys(solution["Plants"]) end - - + + @testset "solve (heuristic)" begin # Should not crash - solution = RELOG.solve("$(pwd())/../instances/s1.json", heuristic=true) + solution = RELOG.solve("$(pwd())/../instances/s1.json", heuristic = true) end @testset "infeasible solve" begin @@ -78,21 +80,21 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats end RELOG.solve(RELOG.parse(json)) end - + @testset "storage" begin basedir = dirname(@__FILE__) filename = "$basedir/fixtures/storage.json" instance = RELOG.parsefile(filename) @test instance.plants[1].storage_limit == 50.0 @test instance.plants[1].storage_cost == [2.0, 1.5, 1.0] - + solution = RELOG.solve(filename) plant_dict = solution["Plants"]["mega plant"]["Chicago"] @test plant_dict["Variable operating cost (\$)"] == [500.0, 0.0, 100.0] @test plant_dict["Process (tonne)"] == [50.0, 0.0, 50.0] @test plant_dict["Storage (tonne)"] == [50.0, 50.0, 0.0] @test plant_dict["Storage cost (\$)"] == [100.0, 75.0, 0.0] - + @test solution["Costs"]["Variable operating (\$)"] == [500.0, 0.0, 100.0] @test solution["Costs"]["Storage (\$)"] == [100.0, 75.0, 0.0] @test solution["Costs"]["Total (\$)"] == [600.0, 75.0, 100.0] diff --git a/test/reports_test.jl b/test/reports_test.jl index 991b7a2..abbd693 100644 --- a/test/reports_test.jl +++ b/test/reports_test.jl @@ -20,14 +20,14 @@ load_json_gz(filename) = JSON.parse(GZip.gzopen(filename)) # end @testset "Reports" begin -# @testset "from fixture" begin -# check(RELOG.write_plants_report, "fixtures/nimh_plants.csv") -# check(RELOG.write_plant_outputs_report, "fixtures/nimh_plant_outputs.csv") -# check(RELOG.write_plant_emissions_report, "fixtures/nimh_plant_emissions.csv") -# check(RELOG.write_transportation_report, "fixtures/nimh_transportation.csv") -# check(RELOG.write_transportation_emissions_report, "fixtures/nimh_transportation_emissions.csv") -# end - + # @testset "from fixture" begin + # check(RELOG.write_plants_report, "fixtures/nimh_plants.csv") + # check(RELOG.write_plant_outputs_report, "fixtures/nimh_plant_outputs.csv") + # check(RELOG.write_plant_emissions_report, "fixtures/nimh_plant_emissions.csv") + # check(RELOG.write_transportation_report, "fixtures/nimh_transportation.csv") + # check(RELOG.write_transportation_emissions_report, "fixtures/nimh_transportation_emissions.csv") + # end + @testset "from solve" begin solution = RELOG.solve("$(pwd())/../instances/s1.json") tmp_filename = tempname() diff --git a/test/runtests.jl b/test/runtests.jl index 25944ff..bd7059c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,4 +8,4 @@ using Test include("graph_test.jl") include("model_test.jl") include("reports_test.jl") -end \ No newline at end of file +end