Implement linear plant sizes

v0.1
Alinson S. Xavier 5 years ago
parent 1335dc42b3
commit 968697dafa

@ -163,7 +163,7 @@
"latitude": 100.0,
"longitude": 100.0,
"capacities": {
"10000": {
"15000": {
"opening cost": [0.0, 0.0],
"fixed operating cost": [0.0, 0.0],
"variable operating cost": [-15.0, -15.0]

@ -140,12 +140,7 @@ The keys in the `capacities` dictionary should be the amounts (in tonnes). The v
"500": {
"opening cost": [750, 760],
"fixed operating cost": [400.0, 450.0],
"variable operating cost": [4.5, 4.7]
},
"700": {
"opening cost": [1000, 1000],
"fixed operating cost": [500.0, 600.0],
"variable operating cost": [4.0, 4.4]
"variable operating cost": [5.0, 5.2]
}
}
}
@ -155,9 +150,9 @@ The keys in the `capacities` dictionary should be the amounts (in tonnes). The v
}
```
Model Assumptions
-----------------
## Current limitations
* Each plant can only be opened exactly once. After open, the plant remains open until the end of the simulation.
* Plants can be expanded at any time, even long after they are open.
* All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next.
* Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.

@ -21,6 +21,14 @@ mutable struct CollectionCenter
end
mutable struct PlantSize
capacity::Float64
variable_operating_cost::Array{Float64}
fixed_operating_cost::Array{Float64}
opening_cost::Array{Float64}
end
mutable struct Plant
index::Int64
plant_name::String
@ -29,14 +37,9 @@ mutable struct Plant
output::Dict{Product, Float64}
latitude::Float64
longitude::Float64
variable_operating_cost::Array{Float64}
fixed_operating_cost::Array{Float64}
opening_cost::Array{Float64}
base_capacity::Float64
max_capacity::Float64
expansion_cost::Array{Float64}
disposal_limit::Dict{Product, Array{Float64}}
disposal_cost::Dict{Product, Array{Float64}}
sizes::Array{PlantSize}
end
@ -68,13 +71,13 @@ function load(path::String)::Instance
plants = Plant[]
products = Product[]
collection_centers = CollectionCenter[]
product_name_to_product = Dict{String, Product}()
prod_name_to_product = Dict{String, Product}()
# Create products
for (product_name, product_dict) in json["products"]
product = Product(product_name, product_dict["transportation cost"])
push!(products, product)
product_name_to_product[product_name] = product
prod_name_to_product[product_name] = product
# Create collection centers
if "initial amounts" in keys(product_dict)
@ -92,48 +95,51 @@ function load(path::String)::Instance
# Create plants
for (plant_name, plant_dict) in json["plants"]
input = product_name_to_product[plant_dict["input"]]
input = prod_name_to_product[plant_dict["input"]]
output = Dict()
# Plant outputs
if "outputs" in keys(plant_dict)
output = Dict(product_name_to_product[key] => value
output = Dict(prod_name_to_product[key] => value
for (key, value) in plant_dict["outputs"]
if value > 0)
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))
# Plant disposal
# Disposal
if "disposal" in keys(location_dict)
for (product_name, disposal_dict) in location_dict["disposal"]
limit = [1e8 for t in 1:T]
if "limit" in keys(disposal_dict)
limit = disposal_dict["limit"]
end
disposal_limit[product_name_to_product[product_name]] = limit
disposal_cost[product_name_to_product[product_name]] = disposal_dict["cost"]
disposal_limit[prod_name_to_product[product_name]] = limit
disposal_cost[prod_name_to_product[product_name]] = disposal_dict["cost"]
end
end
base_capacity = 1e8
max_capacity = 1e8
expansion_cost = [0.0 for t in 1:T]
if "base capacity" in keys(location_dict)
base_capacity = location_dict["base capacity"]
# Capacities
for (capacity_name, capacity_dict) in location_dict["capacities"]
push!(sizes, PlantSize(parse(Float64, capacity_name),
capacity_dict["variable operating cost"],
capacity_dict["fixed operating cost"],
capacity_dict["opening cost"]))
end
length(sizes) > 1 || push!(sizes, sizes[1])
sort!(sizes, by = x -> x.capacity)
if "max capacity" in keys(location_dict)
max_capacity = location_dict["max capacity"]
# Validation: Capacities
if length(sizes) != 2
throw("At most two capacities are supported")
end
if "expansion cost" in keys(location_dict)
expansion_cost = location_dict["expansion cost"]
if sizes[1].variable_operating_cost != sizes[2].variable_operating_cost
throw("Variable operating costs must be the same for all capacities")
end
plant = Plant(length(plants) + 1,
plant_name,
location_name,
@ -141,14 +147,10 @@ function load(path::String)::Instance
output,
location_dict["latitude"],
location_dict["longitude"],
location_dict["variable operating cost"],
location_dict["fixed operating cost"],
location_dict["opening cost"],
base_capacity,
max_capacity,
expansion_cost,
disposal_limit,
disposal_cost)
disposal_cost,
sizes)
push!(plants, plant)
end
end

@ -19,6 +19,7 @@ function build_model(instance::Instance, graph::Graph, optimizer)::Manufacturing
create_objective_function!(model)
create_shipping_node_constraints!(model)
create_process_node_constraints!(model)
JuMP.write_to_file(model.mip, "model.lp")
return model
end
@ -49,18 +50,37 @@ function create_vars!(model::ManufacturingModel)
vars.capacity = Dict((n, t) => @variable(mip,
lower_bound = 0,
upper_bound = n.location.max_capacity,
upper_bound = n.location.sizes[2].capacity,
base_name="capacity($(n.location.index),$t)")
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.max_capacity - n.location.base_capacity),
upper_bound = n.location.sizes[2].capacity -
n.location.sizes[1].capacity,
base_name="expansion($(n.location.index),$t)")
for n in values(graph.process_nodes), t in 1:T)
end
function slope_open(plant, t)
if plant.sizes[2].capacity <= plant.sizes[1].capacity
0.0
else
(plant.sizes[2].opening_cost[t] - plant.sizes[1].opening_cost[t]) /
(plant.sizes[2].capacity - plant.sizes[1].capacity)
end
end
function slope_fix_oper_cost(plant, t)
if plant.sizes[2].capacity <= plant.sizes[1].capacity
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)
end
end
function create_objective_function!(model::ManufacturingModel)
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
obj = @expression(mip, 0 * @variable(mip))
@ -71,23 +91,34 @@ function create_objective_function!(model::ManufacturingModel)
# Transportation and variable operating costs
for a in n.incoming_arcs
c = n.location.input.transportation_cost[t] * a.values["distance"]
c += n.location.variable_operating_cost[t]
c += n.location.sizes[1].variable_operating_cost[t]
add_to_expression!(obj, c, vars.flow[a, t])
end
# Opening costs
add_to_expression!(obj, n.location.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])
# Fixed operating costss
add_to_expression!(obj, n.location.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])
# Expansion costs
if t < T
add_to_expression!(obj,
n.location.expansion_cost[t] - n.location.expansion_cost[t + 1],
slope_open(n.location, t) - slope_open(n.location, t + 1),
vars.expansion[n, t])
else
add_to_expression!(obj, n.location.expansion_cost[t], vars.expansion[n, t])
add_to_expression!(obj,
slope_open(n.location, t),
vars.expansion[n, t])
end
end
@ -130,10 +161,13 @@ function create_process_node_constraints!(model::ManufacturingModel)
end
# If plant is closed, capacity is zero
@constraint(mip, vars.capacity[n, t] <= n.location.max_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])
# Capacity is linked to expansion
@constraint(mip, vars.capacity[n, t] <= n.location.base_capacity + vars.expansion[n, t])
@constraint(mip, vars.capacity[n, t] <= n.location.sizes[1].capacity + vars.expansion[n, t])
# Input sum must be smaller than capacity
@constraint(mip, input_sum <= vars.capacity[n, t])
@ -212,15 +246,19 @@ function get_solution(model::ManufacturingModel)
"longitude" => plant.longitude,
"capacity" => [JuMP.value(vars.capacity[process_node, t])
for t in 1:T],
"opening cost" => [JuMP.value(vars.open_plant[process_node, t]) * plant.opening_cost[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.fixed_operating_cost[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" => [plant.expansion_cost[t] *
(if t > 1
JuMP.value(vars.expansion[process_node, t]) - JuMP.value(vars.expansion[process_node, t-1])
"expansion cost" => [JuMP.value(vars.expansion[process_node, t]) *
(if t < T
slope_open(plant, t) - slope_open(plant, t + 1)
else
JuMP.value(vars.expansion[process_node, t])
slope_open(plant, t)
end)
for t in 1:T],
)
@ -242,7 +280,7 @@ function get_solution(model::ManufacturingModel)
"longitude" => a.source.location.longitude,
"transportation cost" => [a.source.product.transportation_cost[t] * vals[t]
for t in 1:T],
"variable operating cost" => [plant.variable_operating_cost[t] * vals[t]
"variable operating cost" => [plant.sizes[1].variable_operating_cost[t] * vals[t]
for t in 1:T],
)
if a.source.location isa CollectionCenter
@ -273,7 +311,8 @@ function get_solution(model::ManufacturingModel)
skip_plant = false
plant_dict["output"]["dispose"][product_name] = disposal_dict = Dict()
disposal_dict["amount"] = [JuMP.value(model.vars.dispose[shipping_node, t]) for t in 1:T]
disposal_dict["cost"] = [disposal_dict["amount"][t] * plant.disposal_cost[shipping_node.product][t]
disposal_dict["cost"] = [disposal_dict["amount"][t] *
plant.disposal_cost[shipping_node.product][t]
for t in 1:T]
plant_dict["total output"][product_name] += disposal_amount
output["costs"]["disposal"] += disposal_dict["cost"]

@ -15,7 +15,6 @@ using RELOG
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
@ -32,12 +31,16 @@ using RELOG
@test plant.input.name == "P1"
@test plant.latitude == 0
@test plant.longitude == 0
@test plant.opening_cost == [500, 500]
@test plant.fixed_operating_cost == [30, 30]
@test plant.variable_operating_cost == [30, 30]
@test plant.base_capacity == 250
@test plant.max_capacity == 1000
@test plant.expansion_cost == [1, 1]
@test length(plant.sizes) == 2
@test plant.sizes[1].capacity == 250
@test plant.sizes[1].opening_cost == [500, 500]
@test plant.sizes[1].fixed_operating_cost == [30, 30]
@test plant.sizes[1].variable_operating_cost == [30, 30]
@test plant.sizes[2].capacity == 1000
@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"]
@ -54,12 +57,13 @@ using RELOG
@test plant.input.name == "P2"
@test plant.latitude == 25
@test plant.longitude == 65
@test plant.opening_cost == [3000, 3000]
@test plant.fixed_operating_cost == [50, 50]
@test plant.variable_operating_cost == [50, 50]
@test plant.base_capacity == 1e8
@test plant.max_capacity == 1e8
@test plant.expansion_cost == [0, 0]
@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

Loading…
Cancel
Save