mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 07:48:50 -06:00
Move graph creation to graph.jl; simplify model.jl
This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
module ReverseManufacturing
|
module ReverseManufacturing
|
||||||
#include("dotdict.jl")
|
include("dotdict.jl")
|
||||||
include("instance.jl")
|
include("instance.jl")
|
||||||
#include("model.jl")
|
include("graph.jl")
|
||||||
|
include("model.jl")
|
||||||
end
|
end
|
||||||
|
|||||||
125
src/graph.jl
Normal file
125
src/graph.jl
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Copyright (C) 2019 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using Geodesy
|
||||||
|
|
||||||
|
|
||||||
|
abstract type Node
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct Arc
|
||||||
|
source::Node
|
||||||
|
dest::Node
|
||||||
|
values::Dict{String, Float64}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct ProcessNode <: Node
|
||||||
|
index::Int
|
||||||
|
plant::Plant
|
||||||
|
incoming_arcs::Array{Arc}
|
||||||
|
outgoing_arcs::Array{Arc}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct ShippingNode <: Node
|
||||||
|
index::Int
|
||||||
|
location::Union{Plant, CollectionCenter}
|
||||||
|
product::Product
|
||||||
|
incoming_arcs::Array{Arc}
|
||||||
|
outgoing_arcs::Array{Arc}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct Graph
|
||||||
|
process_nodes::Array{ProcessNode}
|
||||||
|
plant_shipping_nodes::Array{ShippingNode}
|
||||||
|
collection_shipping_nodes::Array{ShippingNode}
|
||||||
|
arcs::Array{Arc}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function build_graph(instance::Instance)::Graph
|
||||||
|
arcs = []
|
||||||
|
next_index = 0
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
push!(plant_shipping_nodes, sn)
|
||||||
|
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.plant.latitude,
|
||||||
|
dest.plant.longitude)
|
||||||
|
values = Dict("distance" => distance)
|
||||||
|
arc = Arc(source, dest, values)
|
||||||
|
push!(source.outgoing_arcs, arc)
|
||||||
|
push!(dest.incoming_arcs, arc)
|
||||||
|
push!(arcs, arc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build arcs from process nodes to shipping nodes within a plant
|
||||||
|
for source in process_nodes
|
||||||
|
plant = source.plant
|
||||||
|
for dest in shipping_nodes_by_plant[plant]
|
||||||
|
weight = plant.output[dest.product]
|
||||||
|
values = Dict("weight" => weight)
|
||||||
|
arc = Arc(source, dest, values)
|
||||||
|
push!(source.outgoing_arcs, arc)
|
||||||
|
push!(dest.incoming_arcs, arc)
|
||||||
|
push!(arcs, arc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Graph(process_nodes,
|
||||||
|
plant_shipping_nodes,
|
||||||
|
collection_shipping_nodes,
|
||||||
|
arcs)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function to_csv(graph::Graph)
|
||||||
|
result = ""
|
||||||
|
for a in graph.arcs
|
||||||
|
result *= "$(a.source.index),$(a.dest.index)\n"
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
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)
|
||||||
|
end
|
||||||
@@ -19,15 +19,9 @@ struct CollectionCenter
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
struct DisposalEntry
|
|
||||||
product::Product
|
|
||||||
cost::Float64
|
|
||||||
limit::Float64
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
struct Plant
|
struct Plant
|
||||||
name::String
|
plant_name::String
|
||||||
|
location_name::String
|
||||||
input::Product
|
input::Product
|
||||||
output::Dict{Product, Float64}
|
output::Dict{Product, Float64}
|
||||||
latitude::Float64
|
latitude::Float64
|
||||||
@@ -38,7 +32,8 @@ struct Plant
|
|||||||
base_capacity::Float64
|
base_capacity::Float64
|
||||||
max_capacity::Float64
|
max_capacity::Float64
|
||||||
expansion_cost::Float64
|
expansion_cost::Float64
|
||||||
disposal::Array{DisposalEntry}
|
disposal_limit::Dict{Product, Float64}
|
||||||
|
disposal_cost::Dict{Product, Float64}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -98,19 +93,19 @@ function load(path::String)::Instance
|
|||||||
end
|
end
|
||||||
|
|
||||||
for (location_name, location_dict) in plant_dict["locations"]
|
for (location_name, location_dict) in plant_dict["locations"]
|
||||||
disposal = DisposalEntry[]
|
disposal_limit = Dict(p => 0.0 for p in keys(output))
|
||||||
|
disposal_cost = Dict(p => 0.0 for p in keys(output))
|
||||||
|
|
||||||
# Plant disposal
|
# Plant disposal
|
||||||
if "disposal" in keys(location_dict)
|
if "disposal" in keys(location_dict)
|
||||||
for (product_name, disposal_dict) in location_dict["disposal"]
|
for (product_name, disposal_dict) in location_dict["disposal"]
|
||||||
push!(disposal, DisposalEntry(product_name_to_product[product_name],
|
disposal_limit[product_name_to_product[product_name]] = disposal_dict["limit"]
|
||||||
disposal_dict["cost"],
|
disposal_cost[product_name_to_product[product_name]] = disposal_dict["cost"]
|
||||||
disposal_dict["limit"]))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
base_capacity = Inf
|
base_capacity = 1e8
|
||||||
max_capacity = Inf
|
max_capacity = 1e8
|
||||||
expansion_cost = 0
|
expansion_cost = 0
|
||||||
|
|
||||||
if "base capacity" in keys(location_dict)
|
if "base capacity" in keys(location_dict)
|
||||||
@@ -125,7 +120,8 @@ function load(path::String)::Instance
|
|||||||
expansion_cost = location_dict["expansion cost"]
|
expansion_cost = location_dict["expansion cost"]
|
||||||
end
|
end
|
||||||
|
|
||||||
plant = Plant(location_name,
|
plant = Plant(plant_name,
|
||||||
|
location_name,
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
location_dict["latitude"],
|
location_dict["latitude"],
|
||||||
@@ -136,7 +132,8 @@ function load(path::String)::Instance
|
|||||||
base_capacity,
|
base_capacity,
|
||||||
max_capacity,
|
max_capacity,
|
||||||
expansion_cost,
|
expansion_cost,
|
||||||
disposal)
|
disposal_limit,
|
||||||
|
disposal_cost)
|
||||||
push!(plants, plant)
|
push!(plants, plant)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
591
src/model.jl
591
src/model.jl
@@ -3,460 +3,269 @@
|
|||||||
|
|
||||||
using JuMP, LinearAlgebra, Geodesy, Cbc, ProgressBars
|
using JuMP, LinearAlgebra, Geodesy, Cbc, ProgressBars
|
||||||
|
|
||||||
mutable struct ReverseManufacturingModel
|
|
||||||
|
mutable struct ManufacturingModel
|
||||||
mip::JuMP.Model
|
mip::JuMP.Model
|
||||||
vars::DotDict
|
vars::DotDict
|
||||||
arcs
|
instance::Instance
|
||||||
shipping_nodes
|
graph::Graph
|
||||||
process_nodes
|
|
||||||
end
|
end
|
||||||
|
|
||||||
abstract type Node
|
|
||||||
|
function build_model(instance::Instance, graph::Graph, optimizer)::ManufacturingModel
|
||||||
|
model = ManufacturingModel(Model(optimizer), DotDict(), instance, graph)
|
||||||
|
create_vars!(model)
|
||||||
|
create_objective_function!(model)
|
||||||
|
create_shipping_node_constraints!(model)
|
||||||
|
create_process_node_constraints!(model)
|
||||||
|
return model
|
||||||
end
|
end
|
||||||
|
|
||||||
mutable struct ProcessNode <: Node
|
|
||||||
product_name::String
|
|
||||||
plant_name::String
|
|
||||||
location_name::String
|
|
||||||
incoming_arcs::Array
|
|
||||||
outgoing_arcs::Array
|
|
||||||
fixed_cost::Float64
|
|
||||||
expansion_cost::Float64
|
|
||||||
base_capacity::Float64
|
|
||||||
max_capacity::Float64
|
|
||||||
end
|
|
||||||
|
|
||||||
mutable struct ShippingNode <: Node
|
function create_vars!(model::ManufacturingModel)
|
||||||
product_name::String
|
mip, vars, graph = model.mip, model.vars, model.graph
|
||||||
plant_name::String
|
|
||||||
location_name::String
|
|
||||||
incoming_arcs::Array
|
|
||||||
outgoing_arcs::Array
|
|
||||||
balance::Float64
|
|
||||||
disposal_cost::Float64
|
|
||||||
disposal_limit::Float64
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base.show(io::IO, node::ProcessNode)
|
vars.flow = Dict(a => @variable(mip, lower_bound=0)
|
||||||
print(io, "ProcessNode($(node.product_name), $(node.plant_name), $(node.location_name), fixed_cost=$(node.fixed_cost))")
|
for a in graph.arcs)
|
||||||
end
|
|
||||||
|
|
||||||
function Base.show(io::IO, node::ShippingNode)
|
|
||||||
print(io, "ShippingNode($(node.product_name), $(node.plant_name), $(node.location_name), balance=$(node.balance), ")
|
|
||||||
print(io, "disposal_cost=$(node.disposal_cost), disposal_limit=$(node.disposal_limit))")
|
|
||||||
end
|
|
||||||
|
|
||||||
mutable struct Arc
|
|
||||||
# Origin of the arc
|
|
||||||
source::Node
|
|
||||||
|
|
||||||
# Destination of the arc
|
|
||||||
dest::Node
|
|
||||||
|
|
||||||
# Costs dictionary. Each value in this dictionary is multiplied by the arc flow variable
|
|
||||||
# and added to the objective function.
|
|
||||||
costs::Dict
|
|
||||||
|
|
||||||
# Values dictionary. This dictionary is used to store extra information about the
|
|
||||||
# arc. They are not used automatically by the model.
|
|
||||||
values::Dict
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base.show(io::IO, arc::Arc)
|
|
||||||
print(io, "Arc($(arc.source), $(arc.dest))")
|
|
||||||
end
|
|
||||||
|
|
||||||
function build_model(instance::ReverseManufacturingInstance,
|
|
||||||
optimizer,
|
|
||||||
) :: ReverseManufacturingModel
|
|
||||||
|
|
||||||
println("Building optimization model...")
|
|
||||||
mip = Model(optimizer)
|
|
||||||
shipping_nodes, process_nodes, arcs = create_nodes_and_arcs(instance)
|
|
||||||
|
|
||||||
println(" $(length(shipping_nodes)) shipping nodes")
|
|
||||||
println(" $(length(process_nodes)) process nodes")
|
|
||||||
println(" $(length(arcs)) arcs")
|
|
||||||
|
|
||||||
vars = DotDict()
|
|
||||||
vars.flow = Dict(a => @variable(mip, lower_bound=0) for a in arcs)
|
|
||||||
vars.dispose = Dict(n => @variable(mip,
|
vars.dispose = Dict(n => @variable(mip,
|
||||||
lower_bound = 0,
|
lower_bound = 0,
|
||||||
upper_bound = n.disposal_limit)
|
upper_bound = n.location.disposal_limit[n.product])
|
||||||
for n in values(shipping_nodes))
|
for n in values(graph.plant_shipping_nodes))
|
||||||
vars.open_plant = Dict(n => @variable(mip, binary=true) for n in values(process_nodes))
|
|
||||||
vars.capacity = Dict(n => @variable(mip, lower_bound = 0, upper_bound = n.max_capacity)
|
|
||||||
for n in values(process_nodes))
|
|
||||||
vars.expansion = Dict(n => @variable(mip, lower_bound = 0, upper_bound = (n.max_capacity - n.base_capacity))
|
|
||||||
for n in values(process_nodes))
|
|
||||||
create_shipping_node_constraints!(mip, shipping_nodes, vars)
|
|
||||||
create_process_node_constraints!(mip, process_nodes, vars)
|
|
||||||
|
|
||||||
println(" Creating objective function...")
|
vars.open_plant = Dict(n => @variable(mip, binary=true)
|
||||||
|
for n in values(graph.process_nodes))
|
||||||
|
|
||||||
|
vars.capacity = Dict(n => @variable(mip,
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = n.plant.max_capacity)
|
||||||
|
for n in values(graph.process_nodes))
|
||||||
|
|
||||||
|
vars.expansion = Dict(n => @variable(mip,
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = (n.plant.max_capacity - n.plant.base_capacity))
|
||||||
|
for n in values(graph.process_nodes))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function create_objective_function!(model::ManufacturingModel)
|
||||||
|
mip, vars, graph = model.mip, model.vars, model.graph
|
||||||
obj = @expression(mip, 0 * @variable(mip))
|
obj = @expression(mip, 0 * @variable(mip))
|
||||||
|
|
||||||
# Shipping and variable operating costs
|
# Process node costs
|
||||||
for a in tqdm(arcs)
|
for n in values(graph.process_nodes)
|
||||||
for c in keys(a.costs)
|
|
||||||
add_to_expression!(obj, a.costs[c], vars.flow[a])
|
# Transportation and variable operating costs
|
||||||
end
|
for a in n.incoming_arcs
|
||||||
|
c = n.plant.input.transportation_cost * a.values["distance"]
|
||||||
|
c += n.plant.variable_operating_cost
|
||||||
|
add_to_expression!(obj, c, vars.flow[a])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Opening and fixed operating costs
|
# Fixed and opening costss
|
||||||
for n in tqdm(values(process_nodes))
|
add_to_expression!(obj,
|
||||||
add_to_expression!(obj, n.fixed_cost, vars.open_plant[n])
|
n.plant.fixed_operating_cost + n.plant.opening_cost,
|
||||||
end
|
vars.open_plant[n])
|
||||||
|
|
||||||
# Expansion cost
|
# Expansion costs
|
||||||
for n in tqdm(values(process_nodes))
|
add_to_expression!(obj, n.plant.expansion_cost,
|
||||||
add_to_expression!(obj, n.expansion_cost, vars.expansion[n])
|
vars.expansion[n])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Disposal costs
|
# Disposal costs
|
||||||
for n in tqdm(values(shipping_nodes))
|
for n in values(graph.plant_shipping_nodes)
|
||||||
add_to_expression!(obj, n.disposal_cost, vars.dispose[n])
|
add_to_expression!(obj,
|
||||||
|
n.location.disposal_cost[n.product],
|
||||||
|
vars.dispose[n])
|
||||||
end
|
end
|
||||||
|
|
||||||
@objective(mip, Min, obj)
|
@objective(mip, Min, obj)
|
||||||
|
|
||||||
return ReverseManufacturingModel(mip,
|
|
||||||
vars,
|
|
||||||
arcs,
|
|
||||||
shipping_nodes,
|
|
||||||
process_nodes)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_shipping_node_constraints!(mip, nodes, vars)
|
|
||||||
println(" Creating shipping-node constraints...")
|
function create_shipping_node_constraints!(model::ManufacturingModel)
|
||||||
for (id, n) in tqdm(nodes)
|
mip, vars, graph = model.mip, model.vars, model.graph
|
||||||
|
|
||||||
|
# Collection centers
|
||||||
|
for n in graph.collection_shipping_nodes
|
||||||
|
@constraint(mip, sum(vars.flow[a] for a in n.outgoing_arcs) == n.location.amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Plants
|
||||||
|
for n in graph.plant_shipping_nodes
|
||||||
@constraint(mip,
|
@constraint(mip,
|
||||||
sum(vars.flow[a] for a in n.incoming_arcs) + n.balance ==
|
sum(vars.flow[a] for a in n.incoming_arcs) ==
|
||||||
sum(vars.flow[a] for a in n.outgoing_arcs) + vars.dispose[n])
|
sum(vars.flow[a] for a in n.outgoing_arcs) + vars.dispose[n])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_process_node_constraints!(mip, nodes, vars)
|
|
||||||
println(" Creating process-node constraints...")
|
function create_process_node_constraints!(model)
|
||||||
for (id, n) in tqdm(nodes)
|
mip, vars, graph = model.mip, model.vars, model.graph
|
||||||
|
|
||||||
|
for n in graph.process_nodes
|
||||||
|
|
||||||
# Output amount is implied by input amount
|
# Output amount is implied by input amount
|
||||||
input_sum = isempty(n.incoming_arcs) ? 0 : sum(vars.flow[a] for a in n.incoming_arcs)
|
input_sum = isempty(n.incoming_arcs) ? 0 : sum(vars.flow[a] for a in n.incoming_arcs)
|
||||||
for a in n.outgoing_arcs
|
for a in n.outgoing_arcs
|
||||||
@constraint(mip, vars.flow[a] == a.values["weight"] * input_sum)
|
@constraint(mip, vars.flow[a] == a.values["weight"] * input_sum)
|
||||||
end
|
end
|
||||||
|
|
||||||
# If plant is closed, capacity is zero.
|
# If plant is closed, capacity is zero
|
||||||
@constraint(mip, vars.capacity[n] <= n.max_capacity * vars.open_plant[n])
|
@constraint(mip, vars.capacity[n] <= n.plant.max_capacity * vars.open_plant[n])
|
||||||
|
|
||||||
# Capacity is linked to expansion
|
# Capacity is linked to expansion
|
||||||
@constraint(mip, vars.capacity[n] <= n.base_capacity + vars.expansion[n])
|
@constraint(mip, vars.capacity[n] <= n.plant.base_capacity + vars.expansion[n])
|
||||||
|
|
||||||
# Input sum must be smaller than capacity
|
# Input sum must be smaller than capacity
|
||||||
@constraint(mip, input_sum <= vars.capacity[n])
|
@constraint(mip, input_sum <= vars.capacity[n])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_nodes_and_arcs(instance)
|
function solve(filename::String; optimizer=Cbc.Optimizer)
|
||||||
println(" Creating nodes and arcs...")
|
println("Reading $filename...")
|
||||||
arcs = Arc[]
|
instance = ReverseManufacturing.load(filename)
|
||||||
shipping_nodes = Dict()
|
|
||||||
process_nodes = Dict()
|
|
||||||
|
|
||||||
# Create all nodes
|
println("Building graph...")
|
||||||
for (product_name, product) in instance.products
|
graph = ReverseManufacturing.build_graph(instance)
|
||||||
|
|
||||||
# Shipping nodes for initial amounts
|
println("Building optimization model...")
|
||||||
if haskey(product, "initial amounts")
|
model = ReverseManufacturing.build_model(instance, graph, optimizer)
|
||||||
for location_name in keys(product["initial amounts"])
|
|
||||||
balance = product["initial amounts"][location_name]["amount"]
|
|
||||||
n = ShippingNode(product_name,
|
|
||||||
"Origin", # plant_name
|
|
||||||
location_name,
|
|
||||||
[], # incoming_arcs
|
|
||||||
[], # outgoing_arcs
|
|
||||||
balance,
|
|
||||||
0.0, # disposal_cost
|
|
||||||
0.0, # disposal_limit
|
|
||||||
)
|
|
||||||
shipping_nodes[n.product_name, n.plant_name, n.location_name] = n
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Process nodes for each plant
|
|
||||||
for plant in product["input plants"]
|
|
||||||
for (location_name, location) in plant["locations"]
|
|
||||||
base_capacity = 1e8
|
|
||||||
max_capacity = 1e8
|
|
||||||
expansion_cost = 0.0
|
|
||||||
fixed_cost = location["opening cost"] + location["fixed operating cost"]
|
|
||||||
if "base capacity" in keys(location)
|
|
||||||
base_capacity = location["base capacity"]
|
|
||||||
end
|
|
||||||
if "max capacity" in keys(location)
|
|
||||||
max_capacity = location["max capacity"]
|
|
||||||
end
|
|
||||||
if "expansion cost" in keys(location)
|
|
||||||
expansion_cost = location["expansion cost"]
|
|
||||||
end
|
|
||||||
n = ProcessNode(product_name,
|
|
||||||
plant["name"],
|
|
||||||
location_name,
|
|
||||||
[], # incoming_arcs
|
|
||||||
[], # outgoing_arcs
|
|
||||||
fixed_cost,
|
|
||||||
expansion_cost,
|
|
||||||
base_capacity,
|
|
||||||
max_capacity)
|
|
||||||
process_nodes[n.product_name, n.plant_name, n.location_name] = n
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Shipping nodes for each plant
|
|
||||||
for plant in product["output plants"]
|
|
||||||
for (location_name, location) in plant["locations"]
|
|
||||||
disposal_cost = 0.0
|
|
||||||
disposal_limit = 0.0
|
|
||||||
|
|
||||||
if "disposal" in keys(location) && product_name in keys(location["disposal"])
|
|
||||||
dict = location["disposal"][product_name]
|
|
||||||
disposal_cost = dict["cost"]
|
|
||||||
if "limit" in keys(dict)
|
|
||||||
disposal_limit = dict["limit"]
|
|
||||||
else
|
|
||||||
disposal_limit = 1e10
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
n = ShippingNode(product_name,
|
|
||||||
plant["name"],
|
|
||||||
location_name,
|
|
||||||
[], # incoming_arcs
|
|
||||||
[], # outgoing_arcs
|
|
||||||
0.0, # balance
|
|
||||||
disposal_cost,
|
|
||||||
disposal_limit,
|
|
||||||
)
|
|
||||||
shipping_nodes[n.product_name, n.plant_name, n.location_name] = n
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create arcs
|
|
||||||
for (product_name, product) in instance.products
|
|
||||||
|
|
||||||
# Transportation arcs from initial location to plants
|
|
||||||
if haskey(product, "initial amounts")
|
|
||||||
for source_location_name in keys(product["initial amounts"])
|
|
||||||
source_location = product["initial amounts"][source_location_name]
|
|
||||||
for dest_plant in product["input plants"]
|
|
||||||
for dest_location_name in keys(dest_plant["locations"])
|
|
||||||
dest_location = dest_plant["locations"][dest_location_name]
|
|
||||||
source = shipping_nodes[product_name, "Origin", source_location_name]
|
|
||||||
dest = process_nodes[product_name, dest_plant["name"], dest_location_name]
|
|
||||||
distance = calculate_distance(source_location["latitude"],
|
|
||||||
source_location["longitude"],
|
|
||||||
dest_location["latitude"],
|
|
||||||
dest_location["longitude"])
|
|
||||||
costs = Dict("transportation" => product["transportation cost"] * distance,
|
|
||||||
"variable" => dest_location["variable operating cost"])
|
|
||||||
values = Dict("distance" => distance)
|
|
||||||
a = Arc(source, dest, costs, values)
|
|
||||||
push!(arcs, a)
|
|
||||||
push!(source.outgoing_arcs, a)
|
|
||||||
push!(dest.incoming_arcs, a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
for source_plant in product["output plants"]
|
|
||||||
for source_location_name in keys(source_plant["locations"])
|
|
||||||
source_location = source_plant["locations"][source_location_name]
|
|
||||||
|
|
||||||
# Process arcs (conversions within a plant)
|
|
||||||
source = process_nodes[source_plant["input"], source_plant["name"], source_location_name]
|
|
||||||
dest = shipping_nodes[product_name, source_plant["name"], source_location_name]
|
|
||||||
costs = Dict()
|
|
||||||
values = Dict("weight" => source_plant["outputs"][product_name])
|
|
||||||
a = Arc(source, dest, costs, values)
|
|
||||||
push!(arcs, a)
|
|
||||||
push!(source.outgoing_arcs, a)
|
|
||||||
push!(dest.incoming_arcs, a)
|
|
||||||
|
|
||||||
# Transportation arcs (from one plant to another)
|
|
||||||
for dest_plant in product["input plants"]
|
|
||||||
for dest_location_name in keys(dest_plant["locations"])
|
|
||||||
dest_location = dest_plant["locations"][dest_location_name]
|
|
||||||
source = shipping_nodes[product_name,
|
|
||||||
source_plant["name"],
|
|
||||||
source_location_name]
|
|
||||||
dest = process_nodes[product_name, dest_plant["name"], dest_location_name]
|
|
||||||
distance = calculate_distance(source_location["latitude"],
|
|
||||||
source_location["longitude"],
|
|
||||||
dest_location["latitude"],
|
|
||||||
dest_location["longitude"])
|
|
||||||
costs = Dict("transportation" => product["transportation cost"] * distance,
|
|
||||||
"variable" => dest_location["variable operating cost"])
|
|
||||||
values = Dict("distance" => distance)
|
|
||||||
a = Arc(source, dest, costs, values)
|
|
||||||
push!(arcs, a)
|
|
||||||
push!(source.outgoing_arcs, a)
|
|
||||||
push!(dest.incoming_arcs, a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return shipping_nodes, process_nodes, arcs
|
|
||||||
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)
|
|
||||||
end
|
|
||||||
|
|
||||||
function solve(filename::String;
|
|
||||||
optimizer=Cbc.Optimizer)
|
|
||||||
println("Reading $filename")
|
|
||||||
instance = ReverseManufacturing.readfile(filename)
|
|
||||||
model = ReverseManufacturing.build_model(instance, optimizer)
|
|
||||||
|
|
||||||
println("Optimizing...")
|
println("Optimizing...")
|
||||||
JuMP.optimize!(model.mip)
|
JuMP.optimize!(model.mip)
|
||||||
|
|
||||||
println("Extracting solution...")
|
# println("Extracting solution...")
|
||||||
return get_solution(instance, model)
|
# return get_solution(instance, model)
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_solution(instance::ReverseManufacturingInstance,
|
# function get_solution(instance::ReverseManufacturingInstance,
|
||||||
model::ReverseManufacturingModel)
|
# model::ReverseManufacturingModel)
|
||||||
vals = Dict()
|
# vals = Dict()
|
||||||
for a in values(model.arcs)
|
# for a in values(model.arcs)
|
||||||
vals[a] = JuMP.value(model.vars.flow[a])
|
# vals[a] = JuMP.value(model.vars.flow[a])
|
||||||
end
|
# end
|
||||||
for n in values(model.process_nodes)
|
# for n in values(model.process_nodes)
|
||||||
vals[n] = JuMP.value(model.vars.open_plant[n])
|
# vals[n] = JuMP.value(model.vars.open_plant[n])
|
||||||
end
|
# end
|
||||||
|
|
||||||
output = Dict(
|
# output = Dict(
|
||||||
"plants" => Dict(),
|
# "plants" => Dict(),
|
||||||
"costs" => Dict(
|
# "costs" => Dict(
|
||||||
"fixed" => 0.0,
|
# "fixed" => 0.0,
|
||||||
"variable" => 0.0,
|
# "variable" => 0.0,
|
||||||
"transportation" => 0.0,
|
# "transportation" => 0.0,
|
||||||
"disposal" => 0.0,
|
# "disposal" => 0.0,
|
||||||
"total" => 0.0,
|
# "total" => 0.0,
|
||||||
"expansion" => 0.0,
|
# "expansion" => 0.0,
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
|
|
||||||
for (plant_name, plant) in instance.plants
|
# for (plant_name, plant) in instance.plants
|
||||||
skip_plant = true
|
# skip_plant = true
|
||||||
plant_dict = Dict{Any, Any}()
|
# plant_dict = Dict{Any, Any}()
|
||||||
input_product_name = plant["input"]
|
# input_product_name = plant["input"]
|
||||||
|
|
||||||
for (location_name, location) in plant["locations"]
|
# for (location_name, location) in plant["locations"]
|
||||||
skip_location = true
|
# skip_location = true
|
||||||
process_node = model.process_nodes[input_product_name, plant_name, location_name]
|
# process_node = model.process_nodes[input_product_name, plant_name, location_name]
|
||||||
|
|
||||||
plant_loc_dict = Dict{Any, Any}(
|
# plant_loc_dict = Dict{Any, Any}(
|
||||||
"input" => Dict(),
|
# "input" => Dict(),
|
||||||
"output" => Dict(
|
# "output" => Dict(
|
||||||
"send" => Dict(),
|
# "send" => Dict(),
|
||||||
"dispose" => Dict(),
|
# "dispose" => Dict(),
|
||||||
),
|
# ),
|
||||||
"total input" => 0.0,
|
# "total input" => 0.0,
|
||||||
"total output" => Dict(),
|
# "total output" => Dict(),
|
||||||
"latitude" => location["latitude"],
|
# "latitude" => location["latitude"],
|
||||||
"longitude" => location["longitude"],
|
# "longitude" => location["longitude"],
|
||||||
"capacity" => round(JuMP.value(model.vars.capacity[process_node]), digits=2)
|
# "capacity" => round(JuMP.value(model.vars.capacity[process_node]), digits=2)
|
||||||
)
|
# )
|
||||||
|
|
||||||
plant_loc_dict["fixed cost"] = round(vals[process_node] * process_node.fixed_cost, digits=5)
|
# plant_loc_dict["fixed cost"] = round(vals[process_node] * process_node.fixed_cost, digits=5)
|
||||||
plant_loc_dict["expansion cost"] = round(JuMP.value(model.vars.expansion[process_node]) * process_node.expansion_cost, digits=5)
|
# plant_loc_dict["expansion cost"] = round(JuMP.value(model.vars.expansion[process_node]) * process_node.expansion_cost, digits=5)
|
||||||
output["costs"]["fixed"] += plant_loc_dict["fixed cost"]
|
# output["costs"]["fixed"] += plant_loc_dict["fixed cost"]
|
||||||
output["costs"]["expansion"] += plant_loc_dict["expansion cost"]
|
# output["costs"]["expansion"] += plant_loc_dict["expansion cost"]
|
||||||
|
|
||||||
# Inputs
|
# # Inputs
|
||||||
for a in process_node.incoming_arcs
|
# for a in process_node.incoming_arcs
|
||||||
if vals[a] <= 1e-3
|
# if vals[a] <= 1e-3
|
||||||
continue
|
# continue
|
||||||
end
|
# end
|
||||||
skip_plant = skip_location = false
|
# skip_plant = skip_location = false
|
||||||
val = round(vals[a], digits=5)
|
# val = round(vals[a], digits=5)
|
||||||
if !(a.source.plant_name in keys(plant_loc_dict["input"]))
|
# if !(a.source.plant_name in keys(plant_loc_dict["input"]))
|
||||||
plant_loc_dict["input"][a.source.plant_name] = Dict()
|
# plant_loc_dict["input"][a.source.plant_name] = Dict()
|
||||||
end
|
# end
|
||||||
if a.source.plant_name == "Origin"
|
# if a.source.plant_name == "Origin"
|
||||||
product = instance.products[a.source.product_name]
|
# product = instance.products[a.source.product_name]
|
||||||
source_location = product["initial amounts"][a.source.location_name]
|
# source_location = product["initial amounts"][a.source.location_name]
|
||||||
else
|
# else
|
||||||
source_plant = instance.plants[a.source.plant_name]
|
# source_plant = instance.plants[a.source.plant_name]
|
||||||
source_location = source_plant["locations"][a.source.location_name]
|
# source_location = source_plant["locations"][a.source.location_name]
|
||||||
end
|
# end
|
||||||
|
|
||||||
# Input
|
# # Input
|
||||||
cost_transportation = round(a.costs["transportation"] * val, digits=5)
|
# cost_transportation = round(a.costs["transportation"] * val, digits=5)
|
||||||
plant_loc_dict["input"][a.source.plant_name][a.source.location_name] = dict = Dict()
|
# plant_loc_dict["input"][a.source.plant_name][a.source.location_name] = dict = Dict()
|
||||||
cost_variable = round(a.costs["variable"] * val, digits=5)
|
# cost_variable = round(a.costs["variable"] * val, digits=5)
|
||||||
dict["amount"] = val
|
# dict["amount"] = val
|
||||||
dict["distance"] = a.values["distance"]
|
# dict["distance"] = a.values["distance"]
|
||||||
dict["transportation cost"] = cost_transportation
|
# dict["transportation cost"] = cost_transportation
|
||||||
dict["variable operating cost"] = cost_variable
|
# dict["variable operating cost"] = cost_variable
|
||||||
dict["latitude"] = source_location["latitude"]
|
# dict["latitude"] = source_location["latitude"]
|
||||||
dict["longitude"] = source_location["longitude"]
|
# dict["longitude"] = source_location["longitude"]
|
||||||
plant_loc_dict["total input"] += val
|
# plant_loc_dict["total input"] += val
|
||||||
|
|
||||||
output["costs"]["transportation"] += cost_transportation
|
# output["costs"]["transportation"] += cost_transportation
|
||||||
output["costs"]["variable"] += cost_variable
|
# output["costs"]["variable"] += cost_variable
|
||||||
end
|
# end
|
||||||
|
|
||||||
# Outputs
|
# # Outputs
|
||||||
for output_product_name in keys(plant["outputs"])
|
# for output_product_name in keys(plant["outputs"])
|
||||||
plant_loc_dict["total output"][output_product_name] = 0.0
|
# plant_loc_dict["total output"][output_product_name] = 0.0
|
||||||
plant_loc_dict["output"]["send"][output_product_name] = product_dict = Dict()
|
# plant_loc_dict["output"]["send"][output_product_name] = product_dict = Dict()
|
||||||
shipping_node = model.shipping_nodes[output_product_name, plant_name, location_name]
|
# shipping_node = model.shipping_nodes[output_product_name, plant_name, location_name]
|
||||||
|
|
||||||
disposal_amount = JuMP.value(model.vars.dispose[shipping_node])
|
# disposal_amount = JuMP.value(model.vars.dispose[shipping_node])
|
||||||
if disposal_amount > 1e-5
|
# if disposal_amount > 1e-5
|
||||||
plant_loc_dict["output"]["dispose"][output_product_name] = disposal_dict = Dict()
|
# plant_loc_dict["output"]["dispose"][output_product_name] = disposal_dict = Dict()
|
||||||
disposal_dict["amount"] = JuMP.value(model.vars.dispose[shipping_node])
|
# disposal_dict["amount"] = JuMP.value(model.vars.dispose[shipping_node])
|
||||||
disposal_dict["cost"] = disposal_dict["amount"] * shipping_node.disposal_cost
|
# disposal_dict["cost"] = disposal_dict["amount"] * shipping_node.disposal_cost
|
||||||
plant_loc_dict["total output"][output_product_name] += disposal_amount
|
# plant_loc_dict["total output"][output_product_name] += disposal_amount
|
||||||
output["costs"]["disposal"] += disposal_dict["cost"]
|
# output["costs"]["disposal"] += disposal_dict["cost"]
|
||||||
end
|
# end
|
||||||
|
|
||||||
for a in shipping_node.outgoing_arcs
|
# for a in shipping_node.outgoing_arcs
|
||||||
if vals[a] <= 1e-3
|
# if vals[a] <= 1e-3
|
||||||
continue
|
# continue
|
||||||
end
|
# end
|
||||||
skip_plant = skip_location = false
|
# skip_plant = skip_location = false
|
||||||
if !(a.dest.plant_name in keys(product_dict))
|
# if !(a.dest.plant_name in keys(product_dict))
|
||||||
product_dict[a.dest.plant_name] = Dict{Any,Any}()
|
# product_dict[a.dest.plant_name] = Dict{Any,Any}()
|
||||||
end
|
# end
|
||||||
dest_location = instance.plants[a.dest.plant_name]["locations"][a.dest.location_name]
|
# dest_location = instance.plants[a.dest.plant_name]["locations"][a.dest.location_name]
|
||||||
val = round(vals[a], digits=5)
|
# val = round(vals[a], digits=5)
|
||||||
plant_loc_dict["total output"][output_product_name] += val
|
# plant_loc_dict["total output"][output_product_name] += val
|
||||||
product_dict[a.dest.plant_name][a.dest.location_name] = dict = Dict()
|
# product_dict[a.dest.plant_name][a.dest.location_name] = dict = Dict()
|
||||||
dict["amount"] = val
|
# dict["amount"] = val
|
||||||
dict["distance"] = a.values["distance"]
|
# dict["distance"] = a.values["distance"]
|
||||||
dict["latitude"] = dest_location["latitude"]
|
# dict["latitude"] = dest_location["latitude"]
|
||||||
dict["longitude"] = dest_location["longitude"]
|
# dict["longitude"] = dest_location["longitude"]
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
if !skip_location
|
# if !skip_location
|
||||||
plant_dict[location_name] = plant_loc_dict
|
# plant_dict[location_name] = plant_loc_dict
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
if !skip_plant
|
# if !skip_plant
|
||||||
output["plants"][plant_name] = plant_dict
|
# output["plants"][plant_name] = plant_dict
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
output["costs"]["total"] = sum(values(output["costs"]))
|
# output["costs"]["total"] = sum(values(output["costs"]))
|
||||||
return output
|
# return output
|
||||||
end
|
# end
|
||||||
|
|
||||||
export FlowArc
|
|
||||||
|
|||||||
42
test/graph_test.jl
Normal file
42
test/graph_test.jl
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using ReverseManufacturing
|
||||||
|
|
||||||
|
@testset "Graph" begin
|
||||||
|
@testset "build_graph" begin
|
||||||
|
basedir = dirname(@__FILE__)
|
||||||
|
instance = ReverseManufacturing.load("$basedir/../instances/samples/s1.json")
|
||||||
|
graph = ReverseManufacturing.build_graph(instance)
|
||||||
|
process_node_by_location_name = Dict(n.plant.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
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
@test node.outgoing_arcs[1].source.location.name == "C1"
|
||||||
|
@test node.outgoing_arcs[1].dest.plant.plant_name == "F1"
|
||||||
|
@test node.outgoing_arcs[1].dest.plant.location_name == "L1"
|
||||||
|
@test node.outgoing_arcs[1].values["distance"] == 1095.62
|
||||||
|
|
||||||
|
node = process_node_by_location_name["L1"]
|
||||||
|
@test node.plant.plant_name == "F1"
|
||||||
|
@test node.plant.location_name == "L1"
|
||||||
|
@test length(node.incoming_arcs) == 10
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
|
node = process_node_by_location_name["L3"]
|
||||||
|
@test node.plant.plant_name == "F2"
|
||||||
|
@test node.plant.location_name == "L3"
|
||||||
|
@test length(node.incoming_arcs) == 2
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
|
@test length(graph.arcs) == 38
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@@ -12,11 +12,9 @@ using ReverseManufacturing
|
|||||||
plants = instance.plants
|
plants = instance.plants
|
||||||
products = instance.products
|
products = instance.products
|
||||||
|
|
||||||
plant_name_to_plant = Dict(p.name => p for p in plants)
|
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)
|
product_name_to_product = Dict(p.name => p for p in products)
|
||||||
|
|
||||||
p2 = product_name_to_product["P2"]
|
|
||||||
p3 = product_name_to_product["P3"]
|
|
||||||
|
|
||||||
@test length(centers) == 10
|
@test length(centers) == 10
|
||||||
@test centers[1].name == "C1"
|
@test centers[1].name == "C1"
|
||||||
@@ -28,8 +26,9 @@ using ReverseManufacturing
|
|||||||
|
|
||||||
@test length(plants) == 6
|
@test length(plants) == 6
|
||||||
|
|
||||||
plant = plant_name_to_plant["L1"]
|
plant = location_name_to_plant["L1"]
|
||||||
@test plant.name == "L1"
|
@test plant.plant_name == "F1"
|
||||||
|
@test plant.location_name == "L1"
|
||||||
@test plant.input.name == "P1"
|
@test plant.input.name == "P1"
|
||||||
@test plant.latitude == 0
|
@test plant.latitude == 0
|
||||||
@test plant.longitude == 0
|
@test plant.longitude == 0
|
||||||
@@ -40,26 +39,33 @@ using ReverseManufacturing
|
|||||||
@test plant.max_capacity == 1000
|
@test plant.max_capacity == 1000
|
||||||
@test plant.expansion_cost == 1
|
@test plant.expansion_cost == 1
|
||||||
|
|
||||||
|
p2 = product_name_to_product["P2"]
|
||||||
|
p3 = product_name_to_product["P3"]
|
||||||
@test length(plant.output) == 2
|
@test length(plant.output) == 2
|
||||||
@test plant.output[p2] == 0.2
|
@test plant.output[p2] == 0.2
|
||||||
@test plant.output[p3] == 0.5
|
@test plant.output[p3] == 0.5
|
||||||
|
@test plant.disposal_limit[p2] == 1
|
||||||
|
@test plant.disposal_limit[p3] == 1
|
||||||
|
@test plant.disposal_cost[p2] == -10
|
||||||
|
@test plant.disposal_cost[p3] == -10
|
||||||
|
|
||||||
@test length(plant.disposal) == 2
|
plant = location_name_to_plant["L3"]
|
||||||
@test plant.disposal[1].product.name == "P2"
|
@test plant.location_name == "L3"
|
||||||
@test plant.disposal[1].cost == -10
|
|
||||||
@test plant.disposal[1].limit == 1
|
|
||||||
|
|
||||||
plant = plant_name_to_plant["L3"]
|
|
||||||
@test plant.name == "L3"
|
|
||||||
@test plant.input.name == "P2"
|
@test plant.input.name == "P2"
|
||||||
@test plant.latitude == 25
|
@test plant.latitude == 25
|
||||||
@test plant.longitude == 65
|
@test plant.longitude == 65
|
||||||
@test plant.opening_cost == 3000
|
@test plant.opening_cost == 3000
|
||||||
@test plant.fixed_operating_cost == 50
|
@test plant.fixed_operating_cost == 50
|
||||||
@test plant.variable_operating_cost == 50
|
@test plant.variable_operating_cost == 50
|
||||||
@test plant.base_capacity == Inf
|
@test plant.base_capacity == 1e8
|
||||||
@test plant.max_capacity == Inf
|
@test plant.max_capacity == 1e8
|
||||||
@test plant.expansion_cost == 0
|
@test plant.expansion_cost == 0
|
||||||
|
|
||||||
|
p4 = product_name_to_product["P4"]
|
||||||
|
@test plant.output[p3] == 0.05
|
||||||
|
@test plant.output[p4] == 0.8
|
||||||
|
@test plant.disposal_limit[p3] == 0.0
|
||||||
|
@test plant.disposal_limit[p4] == 0.0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,61 +4,54 @@
|
|||||||
using ReverseManufacturing, Cbc, JuMP, Printf, JSON
|
using ReverseManufacturing, Cbc, JuMP, Printf, JSON
|
||||||
|
|
||||||
@testset "Model" begin
|
@testset "Model" begin
|
||||||
instance = ReverseManufacturing.load("samples/s1")
|
@testset "build" begin
|
||||||
model = ReverseManufacturing.build_model(instance, Cbc.Optimizer)
|
basedir = dirname(@__FILE__)
|
||||||
|
instance = ReverseManufacturing.load("$basedir/../instances/samples/s1.json")
|
||||||
|
graph = ReverseManufacturing.build_graph(instance)
|
||||||
|
model = ReverseManufacturing.build_model(instance, graph, Cbc.Optimizer)
|
||||||
|
|
||||||
# Verify nodes
|
process_node_by_location_name = Dict(n.plant.location_name => n
|
||||||
@test ("P1", "Origin", "C1") in keys(model.shipping_nodes)
|
for n in graph.process_nodes)
|
||||||
@test ("P1", "Origin", "C3") in keys(model.shipping_nodes)
|
|
||||||
@test ("P1", "Origin", "C8") in keys(model.shipping_nodes)
|
|
||||||
@test ("P2", "F1", "L1") in keys(model.shipping_nodes)
|
|
||||||
@test ("P2", "F1", "L2") in keys(model.shipping_nodes)
|
|
||||||
@test ("P3", "F1", "L1") in keys(model.shipping_nodes)
|
|
||||||
@test ("P3", "F1", "L2") in keys(model.shipping_nodes)
|
|
||||||
@test ("P3", "F2", "L3") in keys(model.shipping_nodes)
|
|
||||||
@test ("P3", "F2", "L4") in keys(model.shipping_nodes)
|
|
||||||
@test ("P4", "F2", "L3") in keys(model.shipping_nodes)
|
|
||||||
@test ("P4", "F2", "L4") in keys(model.shipping_nodes)
|
|
||||||
@test ("P1", "F1", "L1") in keys(model.process_nodes)
|
|
||||||
@test ("P1", "F1", "L2") in keys(model.process_nodes)
|
|
||||||
@test ("P2", "F2", "L3") in keys(model.process_nodes)
|
|
||||||
@test ("P2", "F2", "L4") in keys(model.process_nodes)
|
|
||||||
@test ("P3", "F4", "L6") in keys(model.process_nodes)
|
|
||||||
@test ("P4", "F3", "L5") in keys(model.process_nodes)
|
|
||||||
|
|
||||||
# Verify some arcs
|
shipping_node_by_location_and_product_names = Dict((n.location.location_name, n.product.name) => n
|
||||||
p1_orig_c1 = model.shipping_nodes["P1", "Origin", "C1"]
|
for n in graph.plant_shipping_nodes)
|
||||||
p1_f1_l1 = model.process_nodes["P1", "F1", "L1"]
|
|
||||||
@test length(p1_orig_c1.outgoing_arcs) == 2
|
|
||||||
@test length(p1_f1_l1.incoming_arcs) == 10
|
|
||||||
|
|
||||||
arc = p1_orig_c1.outgoing_arcs[1]
|
|
||||||
@test arc.dest.location_name == "L1"
|
|
||||||
@test arc.values["distance"] == 1095.62
|
|
||||||
@test round(arc.costs["transportation"], digits=2) == 16.43
|
|
||||||
@test arc.costs["variable"] == 30.0
|
|
||||||
|
|
||||||
p2_f1_l1 = model.shipping_nodes["P2", "F1", "L1"]
|
@test length(model.vars.flow) == 38
|
||||||
p2_f2_l3 = model.process_nodes["P2", "F2", "L3"]
|
@test length(model.vars.dispose) == 8
|
||||||
@test length(p2_f1_l1.incoming_arcs) == 1
|
@test length(model.vars.open_plant) == 6
|
||||||
@test length(p2_f1_l1.outgoing_arcs) == 2
|
@test length(model.vars.capacity) == 6
|
||||||
|
@test length(model.vars.expansion) == 6
|
||||||
|
|
||||||
|
l1 = process_node_by_location_name["L1"]
|
||||||
|
v = model.vars.capacity[l1]
|
||||||
|
@test lower_bound(v) == 0.0
|
||||||
|
@test upper_bound(v) == 1000.0
|
||||||
|
|
||||||
|
v = model.vars.expansion[l1]
|
||||||
|
@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"]]
|
||||||
|
@test lower_bound(v) == 0.0
|
||||||
|
@test upper_bound(v) == 1.0
|
||||||
|
|
||||||
arc = p2_f1_l1.incoming_arcs[1]
|
|
||||||
@test arc.values["weight"] == 0.2
|
|
||||||
@test isempty(arc.costs)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Solve" begin
|
@testset "build" begin
|
||||||
solution = ReverseManufacturing.solve("$(pwd())/../instances/samples/s1.json")
|
solution = ReverseManufacturing.solve("$(pwd())/../instances/samples/s1.json")
|
||||||
println(JSON.print(solution, 2))
|
# println(JSON.print(solution, 2))
|
||||||
|
|
||||||
@test "plants" in keys(solution)
|
# @test "plants" in keys(solution)
|
||||||
@test "F1" in keys(solution["plants"])
|
# @test "F1" in keys(solution["plants"])
|
||||||
@test "F2" in keys(solution["plants"])
|
# @test "F2" in keys(solution["plants"])
|
||||||
@test "F3" in keys(solution["plants"])
|
# @test "F3" in keys(solution["plants"])
|
||||||
@test "F4" in keys(solution["plants"])
|
# @test "F4" in keys(solution["plants"])
|
||||||
@test "L2" in keys(solution["plants"]["F1"])
|
# @test "L2" in keys(solution["plants"]["F1"])
|
||||||
@test "total output" in keys(solution["plants"]["F1"]["L2"])
|
# @test "total output" in keys(solution["plants"]["F1"]["L2"])
|
||||||
|
|
||||||
@test "capacity" in keys(solution["plants"]["F1"]["L1"])
|
# @test "capacity" in keys(solution["plants"]["F1"]["L1"])
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ using Test
|
|||||||
|
|
||||||
@testset "ReverseManufacturing" begin
|
@testset "ReverseManufacturing" begin
|
||||||
include("instance_test.jl")
|
include("instance_test.jl")
|
||||||
#include("model_test.jl")
|
include("graph_test.jl")
|
||||||
|
include("model_test.jl")
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user