mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 07:48:50 -06:00
Implement get_solution; improve logging
This commit is contained in:
@@ -53,6 +53,16 @@ Example
|
|||||||
"""
|
"""
|
||||||
function readfile(path::String)::ReverseManufacturingInstance
|
function readfile(path::String)::ReverseManufacturingInstance
|
||||||
json = JSON.parsefile(path)
|
json = JSON.parsefile(path)
|
||||||
|
|
||||||
|
for plant_name in keys(json["plants"])
|
||||||
|
"outputs" in keys(json["plants"][plant_name]) || continue
|
||||||
|
for output_name in keys(json["plants"][plant_name]["outputs"])
|
||||||
|
if json["plants"][plant_name]["outputs"][output_name] < 1e-5
|
||||||
|
delete!(json["plants"][plant_name]["outputs"], output_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
products = Dict(key => json["products"][key]
|
products = Dict(key => json["products"][key]
|
||||||
for key in keys(json["products"]))
|
for key in keys(json["products"]))
|
||||||
plants = Dict(key => json["plants"][key]
|
plants = Dict(key => json["plants"][key]
|
||||||
@@ -75,13 +85,14 @@ function readfile(path::String)::ReverseManufacturingInstance
|
|||||||
push!(input_product["input plants"], plant)
|
push!(input_product["input plants"], plant)
|
||||||
|
|
||||||
# Output products
|
# Output products
|
||||||
if haskey(plant, "outputs")
|
if !haskey(plant, "outputs")
|
||||||
|
plant["outputs"] = Dict()
|
||||||
|
end
|
||||||
for product_name in keys(plant["outputs"])
|
for product_name in keys(plant["outputs"])
|
||||||
product = products[product_name]
|
product = products[product_name]
|
||||||
push!(product["output plants"], plant)
|
push!(product["output plants"], plant)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
return ReverseManufacturingInstance(json, products, plants)
|
return ReverseManufacturingInstance(json, products, plants)
|
||||||
end
|
end
|
||||||
|
|||||||
191
src/model.jl
191
src/model.jl
@@ -47,9 +47,15 @@ function Base.show(io::IO, node::Node)
|
|||||||
end
|
end
|
||||||
|
|
||||||
mutable struct Arc
|
mutable struct Arc
|
||||||
|
# Origin of the arc
|
||||||
source::Node
|
source::Node
|
||||||
|
# Destination of the arc
|
||||||
dest::Node
|
dest::Node
|
||||||
|
# Costs dictionary. Each value in this dictionary is multiplied by the arc flow variable
|
||||||
|
# and added to the objective function.
|
||||||
costs::Dict
|
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
|
values::Dict
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -61,6 +67,7 @@ function build_model(instance::ReverseManufacturingInstance,
|
|||||||
optimizer,
|
optimizer,
|
||||||
) :: ReverseManufacturingModel
|
) :: ReverseManufacturingModel
|
||||||
|
|
||||||
|
println("Building optimization model...")
|
||||||
mip = isa(optimizer, JuMP.OptimizerFactory) ? Model(optimizer) : direct_model(optimizer)
|
mip = isa(optimizer, JuMP.OptimizerFactory) ? Model(optimizer) : direct_model(optimizer)
|
||||||
decision_nodes, process_nodes, arcs = create_nodes_and_arcs(instance)
|
decision_nodes, process_nodes, arcs = create_nodes_and_arcs(instance)
|
||||||
vars = DotDict()
|
vars = DotDict()
|
||||||
@@ -68,9 +75,19 @@ function build_model(instance::ReverseManufacturingInstance,
|
|||||||
vars.node = Dict(n => @variable(mip, binary=true) for n in values(process_nodes))
|
vars.node = Dict(n => @variable(mip, binary=true) for n in values(process_nodes))
|
||||||
create_decision_node_constraints!(mip, decision_nodes, vars)
|
create_decision_node_constraints!(mip, decision_nodes, vars)
|
||||||
create_process_node_constraints!(mip, process_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))
|
println(" Creating objective function...")
|
||||||
@objective(mip, Min, flow_costs + node_costs)
|
obj = @expression(mip, 0 * @variable(mip))
|
||||||
|
for a in arcs
|
||||||
|
for c in keys(a.costs)
|
||||||
|
add_to_expression!(obj, a.costs[c], vars.flow[a])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for n in values(process_nodes)
|
||||||
|
add_to_expression!(obj, n.cost, vars.node[n])
|
||||||
|
end
|
||||||
|
@objective(mip, Min, obj)
|
||||||
|
|
||||||
return return ReverseManufacturingModel(mip,
|
return return ReverseManufacturingModel(mip,
|
||||||
vars,
|
vars,
|
||||||
arcs,
|
arcs,
|
||||||
@@ -79,6 +96,7 @@ function build_model(instance::ReverseManufacturingInstance,
|
|||||||
end
|
end
|
||||||
|
|
||||||
function create_decision_node_constraints!(mip, nodes, vars)
|
function create_decision_node_constraints!(mip, nodes, vars)
|
||||||
|
println(" Creating decision-node constraints...")
|
||||||
for (id, n) in nodes
|
for (id, n) in 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) + n.balance ==
|
||||||
@@ -87,9 +105,10 @@ function create_decision_node_constraints!(mip, nodes, vars)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function create_process_node_constraints!(mip, nodes, vars)
|
function create_process_node_constraints!(mip, nodes, vars)
|
||||||
|
println(" Creating process-node constraints...")
|
||||||
for (id, n) in nodes
|
for (id, n) in nodes
|
||||||
# Output amount is implied by input amount
|
# Output amount is implied by input amount
|
||||||
input_sum = 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
|
||||||
@@ -99,6 +118,7 @@ function create_process_node_constraints!(mip, nodes, vars)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function create_nodes_and_arcs(instance)
|
function create_nodes_and_arcs(instance)
|
||||||
|
println(" Creating nodes and arcs...")
|
||||||
arcs = Arc[]
|
arcs = Arc[]
|
||||||
decision_nodes = Dict()
|
decision_nodes = Dict()
|
||||||
process_nodes = Dict()
|
process_nodes = Dict()
|
||||||
@@ -166,7 +186,7 @@ function create_nodes_and_arcs(instance)
|
|||||||
for source_location_name in keys(source_plant["locations"])
|
for source_location_name in keys(source_plant["locations"])
|
||||||
source_location = source_plant["locations"][source_location_name]
|
source_location = source_plant["locations"][source_location_name]
|
||||||
|
|
||||||
# Process-arcs within a plant
|
# Process arcs (conversions within a plant)
|
||||||
source = process_nodes[source_plant["input"], source_plant["name"], source_location_name]
|
source = process_nodes[source_plant["input"], source_plant["name"], source_location_name]
|
||||||
dest = decision_nodes[product_name, source_plant["name"], source_location_name]
|
dest = decision_nodes[product_name, source_plant["name"], source_location_name]
|
||||||
costs = Dict()
|
costs = Dict()
|
||||||
@@ -176,7 +196,7 @@ function create_nodes_and_arcs(instance)
|
|||||||
push!(source.outgoing_arcs, a)
|
push!(source.outgoing_arcs, a)
|
||||||
push!(dest.incoming_arcs, a)
|
push!(dest.incoming_arcs, a)
|
||||||
|
|
||||||
# Transportation-arcs from one plant to another
|
# Transportation arcs (from one plant to another)
|
||||||
for dest_plant in product["input plants"]
|
for dest_plant in product["input plants"]
|
||||||
for dest_location_name in keys(dest_plant["locations"])
|
for dest_location_name in keys(dest_plant["locations"])
|
||||||
dest_location = dest_plant["locations"][dest_location_name]
|
dest_location = dest_plant["locations"][dest_location_name]
|
||||||
@@ -207,7 +227,23 @@ function calculate_distance(source_lat, source_lon, dest_lat, dest_lon)::Float64
|
|||||||
return round(distance(x, y) / 1000.0, digits=2)
|
return round(distance(x, y) / 1000.0, digits=2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function print_solution(instance, model)
|
function solve(filename::String;
|
||||||
|
optimizer=with_optimizer(Cbc.Optimizer,
|
||||||
|
logLevel=0))
|
||||||
|
println("Reading $filename")
|
||||||
|
instance = ReverseManufacturing.readfile(filename)
|
||||||
|
model = ReverseManufacturing.build_model(instance, optimizer)
|
||||||
|
|
||||||
|
println("Optimizing...")
|
||||||
|
JuMP.optimize!(model.mip)
|
||||||
|
|
||||||
|
println("Extracting solution...")
|
||||||
|
return get_solution(instance, model, vals)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_solution(instance::ReverseManufacturingInstance,
|
||||||
|
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])
|
||||||
@@ -215,77 +251,78 @@ function print_solution(instance, model)
|
|||||||
for n in values(model.process_nodes)
|
for n in values(model.process_nodes)
|
||||||
vals[n] = JuMP.value(model.vars.node[n])
|
vals[n] = JuMP.value(model.vars.node[n])
|
||||||
end
|
end
|
||||||
for (plant_name, plant) in instance.plants
|
|
||||||
for (location_name, location) in plant["locations"]
|
|
||||||
unused = true
|
|
||||||
printstyled(@sprintf("%s (at %s):\n", plant_name, location_name),
|
|
||||||
bold=true,
|
|
||||||
color=:red)
|
|
||||||
for arc in model.arcs
|
|
||||||
arc.dest.plant_name == plant_name || continue
|
|
||||||
arc.dest.location_name == location_name || continue
|
|
||||||
arc.source.plant_name != plant_name || continue
|
|
||||||
vals[arc] > 0.0 || continue
|
|
||||||
@printf(" ← Receive %.2f kg of %s from %s (at %s)\n",
|
|
||||||
vals[arc],
|
|
||||||
arc.source.product_name,
|
|
||||||
arc.source.plant_name,
|
|
||||||
arc.source.location_name)
|
|
||||||
@printf(" Distance %.2f km\n", arc.values["distance"])
|
|
||||||
@printf(" Transportation cost %.2f USD\n",
|
|
||||||
vals[arc] * arc.costs["transportation"])
|
|
||||||
if arc.costs["variable"] > 0
|
|
||||||
@printf(" Variable operating cost %.2f USD\n",
|
|
||||||
arc.costs["variable"] * vals[arc])
|
|
||||||
else
|
|
||||||
@printf(" Revenue %.2f USD\n",
|
|
||||||
-arc.costs["variable"] * vals[arc])
|
|
||||||
end
|
|
||||||
unused = false
|
|
||||||
end
|
|
||||||
for arc in model.arcs
|
|
||||||
arc.source.plant_name == plant_name || continue
|
|
||||||
arc.source.location_name == location_name || continue
|
|
||||||
arc.dest.plant_name == plant_name || continue
|
|
||||||
arc.dest.location_name == location_name || continue
|
|
||||||
vals[arc] > 0.0 || continue
|
|
||||||
@printf(" ↺ Convert %.2f kg of %s into %.2f kg of %s\n",
|
|
||||||
vals[arc] / arc.values["weight"],
|
|
||||||
arc.source.product_name,
|
|
||||||
vals[arc],
|
|
||||||
arc.dest.product_name)
|
|
||||||
unused = false
|
|
||||||
end
|
|
||||||
for arc in model.arcs
|
|
||||||
arc.source.plant_name == plant_name || continue
|
|
||||||
arc.source.location_name == location_name || continue
|
|
||||||
arc.dest.plant_name != plant_name || continue
|
|
||||||
vals[arc] > 0.0 || continue
|
|
||||||
@printf(" → Send %.2f kg of %s to %s (at %s)\n",
|
|
||||||
vals[arc],
|
|
||||||
arc.dest.product_name,
|
|
||||||
arc.dest.plant_name,
|
|
||||||
arc.dest.location_name)
|
|
||||||
unused = false
|
|
||||||
end
|
|
||||||
if unused
|
|
||||||
println(" Not used")
|
|
||||||
end
|
|
||||||
println()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
printstyled(@sprintf("Total profit: %.2f USD", -objective_value(model.mip)),
|
|
||||||
bold=true,
|
|
||||||
color=:red)
|
|
||||||
end
|
|
||||||
|
|
||||||
function solve(filename::String;
|
output = Dict(
|
||||||
optimizer=with_optimizer(Cbc.Optimizer,
|
"plants" => Dict(),
|
||||||
logLevel=0))
|
"costs" => Dict(
|
||||||
instance = ReverseManufacturing.readfile(filename)
|
"fixed" => 0.0,
|
||||||
model = ReverseManufacturing.build_model(instance, optimizer)
|
"variable" => 0.0,
|
||||||
JuMP.optimize!(model.mip)
|
"transportation" => 0.0,
|
||||||
print_solution(instance, model)
|
"total" => 0.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (plant_name, plant) in instance.plants
|
||||||
|
input_product_name = plant["input"]
|
||||||
|
for (location_name, location) in plant["locations"]
|
||||||
|
skip_plant = true
|
||||||
|
process_node = model.process_nodes[input_product_name, plant_name, location_name]
|
||||||
|
|
||||||
|
dict = Dict{Any, Any}(
|
||||||
|
"input" => Dict(),
|
||||||
|
"output" => Dict(),
|
||||||
|
"total input" => 0.0,
|
||||||
|
"total output" => Dict(),
|
||||||
|
"transportation costs" => Dict(),
|
||||||
|
"variable costs" => Dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
dict["fixed cost"] = round(vals[process_node] * process_node.cost, digits=5)
|
||||||
|
output["costs"]["fixed"] += dict["fixed cost"]
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
for a in process_node.incoming_arcs
|
||||||
|
if vals[a] > 0
|
||||||
|
val = round(vals[a], digits=5)
|
||||||
|
skip_plant = false
|
||||||
|
source_key = "$(a.source.plant_name) ($(a.source.location_name))"
|
||||||
|
dict["input"][source_key] = val
|
||||||
|
dict["total input"] += val
|
||||||
|
|
||||||
|
cost_transportation = round(a.costs["transportation"] * val, digits=5)
|
||||||
|
cost_variable = round(a.costs["variable"] * val, digits=5)
|
||||||
|
dict["transportation costs"][source_key] = cost_transportation
|
||||||
|
dict["variable costs"][source_key] = cost_variable
|
||||||
|
output["costs"]["transportation"] += cost_transportation
|
||||||
|
output["costs"]["variable"] += cost_variable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Outputs
|
||||||
|
for output_product_name in keys(plant["outputs"])
|
||||||
|
dict["output"][output_product_name] = output_dict = Dict()
|
||||||
|
dict["total output"][output_product_name] = 0.0
|
||||||
|
decision_node = model.decision_nodes[output_product_name, plant_name, location_name]
|
||||||
|
for a in decision_node.outgoing_arcs
|
||||||
|
if vals[a] > 0
|
||||||
|
val = round(vals[a], digits=5)
|
||||||
|
skip_plant = false
|
||||||
|
dict["total output"][output_product_name] += val
|
||||||
|
dest_key = "$(a.dest.plant_name) ($(a.dest.location_name))"
|
||||||
|
output_dict[dest_key] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !skip_plant
|
||||||
|
key = "$(plant_name) ($(location_name))"
|
||||||
|
output["plants"][key] = dict
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output["costs"]["total"] = sum(values(output["costs"]))
|
||||||
|
return output
|
||||||
end
|
end
|
||||||
|
|
||||||
export FlowArc
|
export FlowArc
|
||||||
|
|||||||
Reference in New Issue
Block a user