mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 15:48:51 -06:00
Initial version
This commit is contained in:
8
src/ReverseManufacturing.jl
Normal file
8
src/ReverseManufacturing.jl
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (C) 2020 Argonne National Laboratory
|
||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||
|
||||
module ReverseManufacturing
|
||||
include("dotdict.jl")
|
||||
include("instance.jl")
|
||||
include("model.jl")
|
||||
end
|
||||
67
src/dotdict.jl
Normal file
67
src/dotdict.jl
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright (C) 2019 Argonne National Laboratory
|
||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||
|
||||
struct DotDict
|
||||
inner::Dict
|
||||
end
|
||||
|
||||
DotDict() = DotDict(Dict())
|
||||
|
||||
function Base.setproperty!(d::DotDict, key::Symbol, value)
|
||||
setindex!(getfield(d, :inner), value, key)
|
||||
end
|
||||
|
||||
function Base.getproperty(d::DotDict, key::Symbol)
|
||||
(key == :inner ? getfield(d, :inner) : d.inner[key])
|
||||
end
|
||||
|
||||
function Base.getindex(d::DotDict, key::Int64)
|
||||
d.inner[Symbol(key)]
|
||||
end
|
||||
|
||||
function Base.getindex(d::DotDict, key::Symbol)
|
||||
d.inner[key]
|
||||
end
|
||||
|
||||
function Base.keys(d::DotDict)
|
||||
keys(d.inner)
|
||||
end
|
||||
|
||||
function Base.values(d::DotDict)
|
||||
values(d.inner)
|
||||
end
|
||||
|
||||
function Base.iterate(d::DotDict)
|
||||
iterate(values(d.inner))
|
||||
end
|
||||
|
||||
function Base.iterate(d::DotDict, v::Int64)
|
||||
iterate(values(d.inner), v)
|
||||
end
|
||||
|
||||
function Base.length(d::DotDict)
|
||||
length(values(d.inner))
|
||||
end
|
||||
|
||||
function Base.show(io::IO, d::DotDict)
|
||||
print(io, "DotDict with $(length(keys(d.inner))) entries:\n")
|
||||
count = 0
|
||||
for k in keys(d.inner)
|
||||
count += 1
|
||||
if count > 10
|
||||
print(io, " ...\n")
|
||||
break
|
||||
end
|
||||
print(io, " :$(k) => $(d.inner[k])\n")
|
||||
end
|
||||
end
|
||||
|
||||
function recursive_to_dot_dict(el)
|
||||
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
|
||||
89
src/instance.jl
Normal file
89
src/instance.jl
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright (C) 2020 Argonne National Laboratory
|
||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||
|
||||
using Printf, JSON
|
||||
import Base.getindex, Base.time
|
||||
|
||||
"""
|
||||
mutable struct ReverseManufacturingInstance
|
||||
|
||||
Representation of an instance of the Facility Location for Reverse Manufacturing problem.
|
||||
"""
|
||||
mutable struct ReverseManufacturingInstance
|
||||
json::Dict
|
||||
products::Dict
|
||||
plants::Dict
|
||||
end
|
||||
|
||||
function Base.show(io::IO, instance::ReverseManufacturingInstance)
|
||||
n_plants = length(instance["plants"])
|
||||
n_products = length(instance["products"])
|
||||
print(io, "ReverseManufacturingInstance with ")
|
||||
print(io, "$n_plants plants, ")
|
||||
print(io, "$n_products products")
|
||||
end
|
||||
|
||||
"""
|
||||
load(name::String)::ReverseManufacturingInstance
|
||||
|
||||
Loads an instance from the benchmark set.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
julia> ReverseManufacturing.load("samples/s1.json")
|
||||
|
||||
"""
|
||||
function load(name::String) :: ReverseManufacturingInstance
|
||||
basedir = dirname(@__FILE__)
|
||||
return ReverseManufacturing.readfile("$basedir/../instances/$name.json")
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
readfile(path::String)::ReverseManufacturingInstance
|
||||
|
||||
Loads an instance from the given JSON file.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
julia> ReverseManufacturing.load("/home/user/instance.json")
|
||||
|
||||
"""
|
||||
function readfile(path::String)::ReverseManufacturingInstance
|
||||
json = JSON.parsefile(path)
|
||||
products = Dict(key => json["products"][key]
|
||||
for key in keys(json["products"]))
|
||||
plants = Dict(key => json["plants"][key]
|
||||
for key in keys(json["plants"]))
|
||||
|
||||
for product_name in keys(products)
|
||||
product = products[product_name]
|
||||
product["name"] = product_name
|
||||
product["input plants"] = []
|
||||
product["output plants"] = []
|
||||
end
|
||||
|
||||
for plant_name in keys(plants)
|
||||
plant = plants[plant_name]
|
||||
plant["name"] = plant_name
|
||||
|
||||
# Input product
|
||||
input_product = products[plant["input"]]
|
||||
plant["input product"] = input_product
|
||||
push!(input_product["input plants"], plant)
|
||||
|
||||
# Output products
|
||||
if haskey(plant, "outputs")
|
||||
for product_name in keys(plant["outputs"])
|
||||
product = products[product_name]
|
||||
push!(product["output plants"], plant)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ReverseManufacturingInstance(json, products, plants)
|
||||
end
|
||||
|
||||
export ReverseManufacturingInstance
|
||||
210
src/model.jl
Normal file
210
src/model.jl
Normal file
@@ -0,0 +1,210 @@
|
||||
# Copyright (C) 2019 Argonne National Laboratory
|
||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||
|
||||
using JuMP, LinearAlgebra, Geodesy
|
||||
|
||||
mutable struct ReverseManufacturingModel
|
||||
mip::JuMP.Model
|
||||
vars::DotDict
|
||||
arcs
|
||||
decision_nodes
|
||||
process_nodes
|
||||
end
|
||||
|
||||
mutable struct Node
|
||||
product_name::String
|
||||
plant_name::String
|
||||
location_name::String
|
||||
balance::Float64
|
||||
incoming_arcs::Array
|
||||
outgoing_arcs::Array
|
||||
cost::Float64
|
||||
end
|
||||
|
||||
function Node(product_name::String,
|
||||
plant_name::String,
|
||||
location_name::String;
|
||||
balance::Float64 = 0.0,
|
||||
incoming_arcs::Array = [],
|
||||
outgoing_arcs::Array = [],
|
||||
cost::Float64 = 0.0,
|
||||
) :: Node
|
||||
return Node(product_name,
|
||||
plant_name,
|
||||
location_name,
|
||||
balance,
|
||||
incoming_arcs,
|
||||
outgoing_arcs,
|
||||
cost)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, node::Node)
|
||||
print(io, "Node($(node.product_name), $(node.plant_name), $(node.location_name)")
|
||||
if node.balance != 0.0
|
||||
print(io, ", $(node.balance)")
|
||||
end
|
||||
print(io, ")")
|
||||
end
|
||||
|
||||
mutable struct Arc
|
||||
source::Node
|
||||
dest::Node
|
||||
costs::Dict
|
||||
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
|
||||
|
||||
mip = isa(optimizer, JuMP.OptimizerFactory) ? Model(optimizer) : direct_model(optimizer)
|
||||
decision_nodes, process_nodes, arcs = create_nodes_and_arcs(instance)
|
||||
vars = DotDict()
|
||||
vars.flow = Dict(a => @variable(mip, lower_bound=0) for a in arcs)
|
||||
vars.node = Dict(n => @variable(mip, binary=true) for n in values(process_nodes))
|
||||
create_decision_node_constraints!(mip, decision_nodes, vars)
|
||||
create_process_node_constraints!(mip, process_nodes, vars)
|
||||
flow_costs = sum(a.costs[c] * vars.flow[a] for a in arcs for c in keys(a.costs))
|
||||
node_costs = sum(n.cost * vars.node[n] for n in values(process_nodes))
|
||||
@objective(mip, Min, flow_costs + node_costs)
|
||||
return return ReverseManufacturingModel(mip,
|
||||
vars,
|
||||
arcs,
|
||||
decision_nodes,
|
||||
process_nodes)
|
||||
end
|
||||
|
||||
function create_decision_node_constraints!(mip, nodes, vars)
|
||||
for (id, n) in nodes
|
||||
@constraint(mip,
|
||||
sum(vars.flow[a] for a in n.incoming_arcs) + n.balance ==
|
||||
sum(vars.flow[a] for a in n.outgoing_arcs))
|
||||
end
|
||||
end
|
||||
|
||||
function create_process_node_constraints!(mip, nodes, vars)
|
||||
for (id, n) in nodes
|
||||
# Output amount is implied by input amount
|
||||
input_sum = sum(vars.flow[a] for a in n.incoming_arcs)
|
||||
for a in n.outgoing_arcs
|
||||
@constraint(mip, vars.flow[a] == a.values["weight"] * input_sum)
|
||||
end
|
||||
# If plant is closed, input must be zero
|
||||
@constraint(mip, input_sum <= 1e6 * vars.node[n])
|
||||
end
|
||||
end
|
||||
|
||||
function create_nodes_and_arcs(instance)
|
||||
arcs = Arc[]
|
||||
decision_nodes = Dict()
|
||||
process_nodes = Dict()
|
||||
|
||||
# Create all nodes
|
||||
for (product_name, product) in instance.products
|
||||
|
||||
# Decision nodes for initial amounts
|
||||
if haskey(product, "initial amounts")
|
||||
for location_name in keys(product["initial amounts"])
|
||||
amount = product["initial amounts"][location_name]["amount"]
|
||||
n = Node(product_name, "Origin", location_name, balance=amount)
|
||||
decision_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"]
|
||||
cost = location["opening cost"] + location["fixed operating cost"]
|
||||
n = Node(product_name, plant["name"], location_name, cost=cost)
|
||||
process_nodes[n.product_name, n.plant_name, n.location_name] = n
|
||||
end
|
||||
end
|
||||
|
||||
# Decision nodes for each plant
|
||||
for plant in product["output plants"]
|
||||
for location_name in keys(plant["locations"])
|
||||
n = Node(product_name, plant["name"], location_name)
|
||||
decision_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 = decision_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 within a plant
|
||||
source = process_nodes[source_plant["input"], source_plant["name"], source_location_name]
|
||||
dest = decision_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 = decision_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 decision_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
|
||||
|
||||
export FlowArc
|
||||
Reference in New Issue
Block a user