mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-05 23:38:52 -06:00
Implement first version of multi-period simulations
This commit is contained in:
@@ -10,6 +10,7 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
||||
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||
|
||||
68
README.md
68
README.md
@@ -32,45 +32,63 @@ Typical Usage
|
||||
|
||||
### Describing an instance
|
||||
|
||||
The first step when using ReverseManufacturing.jl is describing the reverse manufacturing pipeline and the relevant data. Each input file is a JSON file with two sections: `products` and `plants`. Below, we describe each section in more detail. For a concrete example, see the file `instances/samples/s2.json`.
|
||||
The first step when using ReverseManufacturing.jl is describing the reverse manufacturing pipeline and the relevant data. Each input file is a JSON file with three sections: `parameters`, `products` and `plants`. Below, we describe each section in more detail. For a concrete example, see the file `instances/samples/s2.json`.
|
||||
|
||||
### Parameters
|
||||
|
||||
The **parameters** section describes details about the simulation itself.
|
||||
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
|`time periods` | Number of time periods in the simulation.
|
||||
|
||||
#### Products
|
||||
|
||||
The **products** section describes all products and subproducts in the simulation. The field `instance["products"]` is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys:
|
||||
|
||||
* `transportation cost`, the cost (in dollars per km per kg) to transport this product.
|
||||
* `initial amounts,` a dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted.
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
|`transportation cost` | The cost (in dollars per km per tonnes) to transport this product. Must be a timeseries.
|
||||
|`initial amounts` | A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries.
|
||||
|
||||
Each product may have some amount available at the beginning of the simulation. In this case, the key `initial amounts` maps to a dictionary with the following keys:
|
||||
|
||||
* `latitude`, the latitude of the location, in degrees.
|
||||
* `longitude`, the longitude of the location, in degrees.
|
||||
* `amount`, the amount (in kg) of the product initially available at the location.
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
| `latitude` | The latitude of the location, in degrees.
|
||||
| `longitude` | The longitude of the location, in degrees.
|
||||
| `amount` | The amount (in tonnes) of the product initially available at the location. Must be a timeseries.
|
||||
|
||||
#### Processing Plants
|
||||
|
||||
The **plants** section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field `instance["plants"]` is a dictionary mapping the name of the plant to a dictionary with the following keys:
|
||||
|
||||
* `input`, the name of the product that this plant takes as input. Only one input is accepted per plant.
|
||||
* `outputs`, a dictionary specifying how many kg of each product is produced for each kg of input. For example, if the plant outputs 0.5 kg of P2 and 0.25 kg of P3 for each kg of P1 provided, then this entry should be `{"P2": 0.5, "P3": 0.25}`. If the plant does not output anything, this key may be omitted.
|
||||
* `locations`, a dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below).
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
| `input` | The name of the product that this plant takes as input. Only one input is accepted per plant.
|
||||
| `outputs` | A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be `{"P2": 0.5, "P3": 0.25}`. If the plant does not output anything, this key may be omitted.
|
||||
| `locations` | A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below).
|
||||
|
||||
Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys:
|
||||
|
||||
* `latitude`, the latitude of the location, in degrees.
|
||||
* `longitude`, the longitude of the location, in degrees.
|
||||
* `opening cost`, the cost (in dollars) to open the plant.
|
||||
* `fixed operating cost`, the cost (in dollars) to keep the plant open, even if the plant doesn't process anything.
|
||||
* `variable operating cost`, the cost (in dollars per kg) that the plant incurs to process each kg of input.
|
||||
* `base capacity`, the amount of input (in kg) the plant can process when zero dollars are spent on expansion. If unlimited, this key may be omitted.
|
||||
* `max capacity`, the amount (in kg) the plant can process when the maximum amount of dollars are spent on expansion. If unlimited, this key may be omitted.
|
||||
* `expansion cost`, the cost (in dollars per kg) to increase the plant capacity beyond its base capacity. If zero, this key may be omitted.
|
||||
* `disposal`, a dictionary describing what products can be disposed locally at the plant.
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
| `latitude` | The latitude of the location, in degrees.
|
||||
| `longitude` | The longitude of the location, in degrees.
|
||||
| `opening cost` | The cost (in dollars) to open the plant.
|
||||
| `fixed operating cost` | The cost (in dollars) to keep the plant open, even if the plant doesn't process anything. Must be a timeseries.
|
||||
| `variable operating cost` | The cost (in dollars per tonnes) that the plant incurs to process each tonnes of input. Must be a timeseries.
|
||||
| `base capacity` | The amount of input (in tonnes) the plant can process when zero dollars are spent on expansion. If unlimited, this key may be omitted.
|
||||
| `max capacity` | The amount (in tonnes) the plant can process when the maximum amount of dollars are spent on expansion. If unlimited, this key may be omitted.
|
||||
| `expansion cost` | The cost (in dollars per tonnes) to increase the plant capacity beyond its base capacity. If zero, this key may be omitted. Must be a timeseries.
|
||||
| `disposal` | A dictionary describing what products can be disposed locally at the plant.
|
||||
|
||||
The keys in the disposal dictionary should be the names of the products. The values are dictionaries with the following keys:
|
||||
|
||||
* `cost`, the cost (in dollars per kg) to dispose of the product.
|
||||
* `limit`, the maximum amount (in kg) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted.
|
||||
| Key | Description
|
||||
|:------------------------|---------------|
|
||||
| `cost` | The cost (in dollars per tonnes) to dispose of the product. Must be a timeseries.
|
||||
| `limit` | The maximum amount (in tonnes) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries.
|
||||
|
||||
### Optimizing
|
||||
|
||||
@@ -83,10 +101,12 @@ ReverseManufacturing.solve("/home/user/instance.json")
|
||||
|
||||
The optimal logistics plan will be printed to the screen.
|
||||
|
||||
Current Limitations
|
||||
-------------------
|
||||
* Each plant is only allowed exactly one product as input
|
||||
* No support for multi-period simulations
|
||||
Model Assumptions
|
||||
-----------------
|
||||
* 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.
|
||||
* Variable and fixed operating costs do not change according to plant size.
|
||||
* 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.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
@@ -1,68 +1,71 @@
|
||||
{
|
||||
"parameters": {
|
||||
"time periods": 2
|
||||
},
|
||||
"products": {
|
||||
"P1": {
|
||||
"transportation cost": 0.015,
|
||||
"transportation cost": [0.015, 0.015],
|
||||
"initial amounts": {
|
||||
"C1": {
|
||||
"latitude": 7.0,
|
||||
"longitude": 7.0,
|
||||
"amount": 934.56
|
||||
"amount": [934.56, 934.56]
|
||||
},
|
||||
"C2": {
|
||||
"latitude": 7.0,
|
||||
"longitude": 19.0,
|
||||
"amount": 198.95
|
||||
"amount": [198.95, 198.95]
|
||||
},
|
||||
"C3": {
|
||||
"latitude": 84.0,
|
||||
"longitude": 76.0,
|
||||
"amount": 212.97
|
||||
"amount": [212.97, 212.97]
|
||||
},
|
||||
"C4": {
|
||||
"latitude": 21.0,
|
||||
"longitude": 16.0,
|
||||
"amount": 352.19
|
||||
"amount": [352.19, 352.19]
|
||||
},
|
||||
"C5": {
|
||||
"latitude": 32.0,
|
||||
"longitude": 92.0,
|
||||
"amount": 510.33
|
||||
"amount": [510.33, 510.33]
|
||||
},
|
||||
"C6": {
|
||||
"latitude": 14.0,
|
||||
"longitude": 62.0,
|
||||
"amount": 471.66
|
||||
"amount": [471.66, 471.66]
|
||||
},
|
||||
"C7": {
|
||||
"latitude": 30.0,
|
||||
"longitude": 83.0,
|
||||
"amount": 785.21
|
||||
"amount": [785.21, 785.21]
|
||||
},
|
||||
"C8": {
|
||||
"latitude": 35.0,
|
||||
"longitude": 40.0,
|
||||
"amount": 706.17
|
||||
"amount": [706.17, 706.17]
|
||||
},
|
||||
"C9": {
|
||||
"latitude": 74.0,
|
||||
"longitude": 52.0,
|
||||
"amount": 30.08
|
||||
"amount": [30.08, 30.08]
|
||||
},
|
||||
"C10": {
|
||||
"latitude": 22.0,
|
||||
"longitude": 54.0,
|
||||
"amount": 536.52
|
||||
"amount": [536.52, 536.52]
|
||||
}
|
||||
}
|
||||
},
|
||||
"P2": {
|
||||
"transportation cost": 0.02
|
||||
"transportation cost": [0.02, 0.02]
|
||||
},
|
||||
"P3": {
|
||||
"transportation cost": 0.0125
|
||||
"transportation cost": [0.0125, 0.0125]
|
||||
},
|
||||
"P4": {
|
||||
"transportation cost": 0.0175
|
||||
"transportation cost": [0.0175, 0.0175]
|
||||
}
|
||||
},
|
||||
"plants": {
|
||||
@@ -76,32 +79,32 @@
|
||||
"L1": {
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0,
|
||||
"opening cost": 500,
|
||||
"opening cost": [500, 500],
|
||||
"base capacity": 250.0,
|
||||
"max capacity": 1000.0,
|
||||
"expansion cost": 1.0,
|
||||
"fixed operating cost": 30.0,
|
||||
"variable operating cost": 30.0,
|
||||
"expansion cost": [1.0, 1.0],
|
||||
"fixed operating cost": [30.0, 30.0],
|
||||
"variable operating cost": [30.0, 30.0],
|
||||
"disposal": {
|
||||
"P2": {
|
||||
"cost": -10.0,
|
||||
"limit": 1.0
|
||||
"cost": [-10.0, -10.0],
|
||||
"limit": [1.0, 1.0]
|
||||
},
|
||||
"P3": {
|
||||
"cost": -10.0,
|
||||
"limit": 1.0
|
||||
"cost": [-10.0, -10.0],
|
||||
"limit": [1.0, 1.0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"L2": {
|
||||
"latitude": 0.5,
|
||||
"longitude": 0.5,
|
||||
"opening cost": 1000,
|
||||
"opening cost": [1000, 1000],
|
||||
"base capacity": 0.0,
|
||||
"max capacity": 10000.0,
|
||||
"expansion cost": 1.0,
|
||||
"fixed operating cost": 50.0,
|
||||
"variable operating cost": 50.0
|
||||
"expansion cost": [1.0, 1.0],
|
||||
"fixed operating cost": [50.0, 50.0],
|
||||
"variable operating cost": [50.0, 50.0]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -116,17 +119,17 @@
|
||||
"latitude": 25.0,
|
||||
"longitude": 65.0,
|
||||
"capacity": 1000,
|
||||
"opening cost": 3000,
|
||||
"fixed operating cost": 50.0,
|
||||
"variable operating cost": 50.0
|
||||
"opening cost": [3000, 3000],
|
||||
"fixed operating cost": [50.0, 50.0],
|
||||
"variable operating cost": [50.0, 50.0]
|
||||
},
|
||||
"L4": {
|
||||
"latitude": 0.75,
|
||||
"longitude": 0.20,
|
||||
"processing cost": 250.0,
|
||||
"opening cost": 3000,
|
||||
"fixed operating cost": 50.0,
|
||||
"variable operating cost": 50.0
|
||||
"opening cost": [3000, 3000],
|
||||
"fixed operating cost": [50.0, 50.0],
|
||||
"variable operating cost": [50.0, 50.0]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -136,9 +139,9 @@
|
||||
"L5": {
|
||||
"latitude": 100.0,
|
||||
"longitude": 100.0,
|
||||
"opening cost": 0.0,
|
||||
"fixed operating cost": 0.0,
|
||||
"variable operating cost": -15.0
|
||||
"opening cost": [0.0, 0.0],
|
||||
"fixed operating cost": [0.0, 0.0],
|
||||
"variable operating cost": [-15.0, -15.0]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -148,9 +151,9 @@
|
||||
"L6": {
|
||||
"latitude": 50.0,
|
||||
"longitude": 50.0,
|
||||
"opening cost": 0.0,
|
||||
"fixed operating cost": 0.0,
|
||||
"variable operating cost": -15.0
|
||||
"opening cost": [0.0, 0.0],
|
||||
"fixed operating cost": [0.0, 0.0],
|
||||
"variable operating cost": [-15.0, -15.0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ end
|
||||
|
||||
mutable struct ProcessNode <: Node
|
||||
index::Int
|
||||
plant::Plant
|
||||
location::Plant
|
||||
incoming_arcs::Array{Arc}
|
||||
outgoing_arcs::Array{Arc}
|
||||
end
|
||||
@@ -79,8 +79,8 @@ function build_graph(instance::Instance)::Graph
|
||||
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)
|
||||
dest.location.latitude,
|
||||
dest.location.longitude)
|
||||
values = Dict("distance" => distance)
|
||||
arc = Arc(source, dest, values)
|
||||
push!(source.outgoing_arcs, arc)
|
||||
@@ -91,7 +91,7 @@ function build_graph(instance::Instance)::Graph
|
||||
|
||||
# Build arcs from process nodes to shipping nodes within a plant
|
||||
for source in process_nodes
|
||||
plant = source.plant
|
||||
plant = source.location
|
||||
for dest in shipping_nodes_by_plant[plant]
|
||||
weight = plant.output[dest.product]
|
||||
values = Dict("weight" => weight)
|
||||
|
||||
@@ -6,38 +6,41 @@ using JSON, JSONSchema
|
||||
|
||||
mutable struct Product
|
||||
name::String
|
||||
transportation_cost::Float64
|
||||
transportation_cost::Array{Float64}
|
||||
end
|
||||
|
||||
|
||||
mutable struct CollectionCenter
|
||||
index::Int64
|
||||
name::String
|
||||
latitude::Float64
|
||||
longitude::Float64
|
||||
product::Product
|
||||
amount::Float64
|
||||
amount::Array{Float64}
|
||||
end
|
||||
|
||||
|
||||
mutable struct Plant
|
||||
index::Int64
|
||||
plant_name::String
|
||||
location_name::String
|
||||
input::Product
|
||||
output::Dict{Product, Float64}
|
||||
latitude::Float64
|
||||
longitude::Float64
|
||||
variable_operating_cost::Float64
|
||||
fixed_operating_cost::Float64
|
||||
opening_cost::Float64
|
||||
variable_operating_cost::Array{Float64}
|
||||
fixed_operating_cost::Array{Float64}
|
||||
opening_cost::Array{Float64}
|
||||
base_capacity::Float64
|
||||
max_capacity::Float64
|
||||
expansion_cost::Float64
|
||||
disposal_limit::Dict{Product, Float64}
|
||||
disposal_cost::Dict{Product, Float64}
|
||||
expansion_cost::Array{Float64}
|
||||
disposal_limit::Dict{Product, Array{Float64}}
|
||||
disposal_cost::Dict{Product, Array{Float64}}
|
||||
end
|
||||
|
||||
|
||||
mutable struct Instance
|
||||
time::Int64
|
||||
products::Array{Product, 1}
|
||||
collection_centers::Array{CollectionCenter, 1}
|
||||
plants::Array{Plant, 1}
|
||||
@@ -49,16 +52,21 @@ function load(path::String)::Instance
|
||||
json = JSON.parsefile(path)
|
||||
schema = Schema(JSON.parsefile("$basedir/schemas/input.json"))
|
||||
|
||||
validation_results = JSONSchema.validate(json, schema)
|
||||
if validation_results !== nothing
|
||||
println(validation_results)
|
||||
throw("Invalid input file")
|
||||
result = JSONSchema.validate(json, schema)
|
||||
if result !== nothing
|
||||
if result isa JSONSchema.SingleIssue
|
||||
path = join(result.path, " → ")
|
||||
msg = "$(result.x) $(result.msg) in $(path)"
|
||||
else
|
||||
msg = convert(String, result)
|
||||
end
|
||||
throw(msg)
|
||||
end
|
||||
|
||||
T = json["parameters"]["time periods"]
|
||||
plants = Plant[]
|
||||
products = Product[]
|
||||
collection_centers = CollectionCenter[]
|
||||
plants = Plant[]
|
||||
|
||||
product_name_to_product = Dict{String, Product}()
|
||||
|
||||
# Create products
|
||||
@@ -70,7 +78,8 @@ function load(path::String)::Instance
|
||||
# Create collection centers
|
||||
if "initial amounts" in keys(product_dict)
|
||||
for (center_name, center_dict) in product_dict["initial amounts"]
|
||||
center = CollectionCenter(center_name,
|
||||
center = CollectionCenter(length(collection_centers) + 1,
|
||||
center_name,
|
||||
center_dict["latitude"],
|
||||
center_dict["longitude"],
|
||||
product,
|
||||
@@ -93,8 +102,8 @@ function load(path::String)::Instance
|
||||
end
|
||||
|
||||
for (location_name, location_dict) in plant_dict["locations"]
|
||||
disposal_limit = Dict(p => 0.0 for p in keys(output))
|
||||
disposal_cost = Dict(p => 0.0 for p in keys(output))
|
||||
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
|
||||
if "disposal" in keys(location_dict)
|
||||
@@ -106,7 +115,7 @@ function load(path::String)::Instance
|
||||
|
||||
base_capacity = 1e8
|
||||
max_capacity = 1e8
|
||||
expansion_cost = 0
|
||||
expansion_cost = [0.0 for t in 1:T]
|
||||
|
||||
if "base capacity" in keys(location_dict)
|
||||
base_capacity = location_dict["base capacity"]
|
||||
@@ -120,7 +129,8 @@ function load(path::String)::Instance
|
||||
expansion_cost = location_dict["expansion cost"]
|
||||
end
|
||||
|
||||
plant = Plant(plant_name,
|
||||
plant = Plant(length(plants) + 1,
|
||||
plant_name,
|
||||
location_name,
|
||||
input,
|
||||
output,
|
||||
@@ -138,5 +148,5 @@ function load(path::String)::Instance
|
||||
end
|
||||
end
|
||||
|
||||
return Instance(products, collection_centers, plants)
|
||||
return Instance(T, products, collection_centers, plants)
|
||||
end
|
||||
|
||||
251
src/model.jl
251
src/model.jl
@@ -23,60 +23,76 @@ end
|
||||
|
||||
|
||||
function create_vars!(model::ManufacturingModel)
|
||||
mip, vars, graph = model.mip, model.vars, model.graph
|
||||
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
|
||||
|
||||
vars.flow = Dict(a => @variable(mip, lower_bound=0)
|
||||
for a in graph.arcs)
|
||||
vars.flow = Dict((a, t) => @variable(mip,
|
||||
lower_bound=0,
|
||||
base_name="flow($(a.source.location.index),$(a.dest.location.index),$t)")
|
||||
for a in graph.arcs, t in 1:T)
|
||||
|
||||
vars.dispose = Dict(n => @variable(mip,
|
||||
lower_bound = 0,
|
||||
upper_bound = n.location.disposal_limit[n.product])
|
||||
for n in values(graph.plant_shipping_nodes))
|
||||
vars.dispose = Dict((n, t) => @variable(mip,
|
||||
lower_bound=0,
|
||||
upper_bound=n.location.disposal_limit[n.product][t],
|
||||
base_name="dispose($(n.location.index),$(n.product.name),$t)")
|
||||
for n in values(graph.plant_shipping_nodes), t in 1:T)
|
||||
|
||||
vars.open_plant = Dict(n => @variable(mip, binary=true)
|
||||
for n in values(graph.process_nodes))
|
||||
vars.open_plant = Dict((n, t) => @variable(mip,
|
||||
binary=true,
|
||||
base_name="open_plant($(n.location.index),$t)")
|
||||
for n in values(graph.process_nodes), t in 1:T)
|
||||
|
||||
vars.capacity = Dict(n => @variable(mip,
|
||||
lower_bound = 0,
|
||||
upper_bound = n.plant.max_capacity)
|
||||
for n in values(graph.process_nodes))
|
||||
vars.is_open = Dict((n, t) => @variable(mip,
|
||||
binary=true,
|
||||
base_name="is_open($(n.location.index),$t)")
|
||||
for n in values(graph.process_nodes), t in 1:T)
|
||||
|
||||
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))
|
||||
vars.capacity = Dict((n, t) => @variable(mip,
|
||||
lower_bound = 0,
|
||||
upper_bound = n.location.max_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),
|
||||
base_name="expansion($(n.location.index),$t)")
|
||||
for n in values(graph.process_nodes), t in 1:T)
|
||||
end
|
||||
|
||||
|
||||
function create_objective_function!(model::ManufacturingModel)
|
||||
mip, vars, graph = model.mip, model.vars, model.graph
|
||||
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
|
||||
obj = @expression(mip, 0 * @variable(mip))
|
||||
|
||||
# Process node costs
|
||||
for n in values(graph.process_nodes)
|
||||
for n in values(graph.process_nodes), t in 1:T
|
||||
|
||||
# Transportation and variable operating costs
|
||||
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])
|
||||
c = n.location.input.transportation_cost[t] * a.values["distance"]
|
||||
c += n.location.variable_operating_cost[t]
|
||||
add_to_expression!(obj, c, vars.flow[a, t])
|
||||
end
|
||||
|
||||
# Fixed and opening costss
|
||||
add_to_expression!(obj,
|
||||
n.plant.fixed_operating_cost + n.plant.opening_cost,
|
||||
vars.open_plant[n])
|
||||
# Opening costs
|
||||
add_to_expression!(obj, n.location.opening_cost[t], vars.open_plant[n, t])
|
||||
|
||||
# Fixed operating costss
|
||||
add_to_expression!(obj, n.location.fixed_operating_cost[t], vars.is_open[n, t])
|
||||
|
||||
# Expansion costs
|
||||
add_to_expression!(obj, n.plant.expansion_cost,
|
||||
vars.expansion[n])
|
||||
if t < T
|
||||
add_to_expression!(obj,
|
||||
n.location.expansion_cost[t] - n.location.expansion_cost[t + 1],
|
||||
vars.expansion[n, t])
|
||||
else
|
||||
add_to_expression!(obj, n.location.expansion_cost[t], vars.expansion[n, t])
|
||||
end
|
||||
end
|
||||
|
||||
# Disposal costs
|
||||
for n in values(graph.plant_shipping_nodes)
|
||||
add_to_expression!(obj,
|
||||
n.location.disposal_cost[n.product],
|
||||
vars.dispose[n])
|
||||
for n in values(graph.plant_shipping_nodes), t in 1:T
|
||||
add_to_expression!(obj, n.location.disposal_cost[n.product][t], vars.dispose[n, t])
|
||||
end
|
||||
|
||||
@objective(mip, Min, obj)
|
||||
@@ -84,41 +100,56 @@ end
|
||||
|
||||
|
||||
function create_shipping_node_constraints!(model::ManufacturingModel)
|
||||
mip, vars, graph = model.mip, model.vars, model.graph
|
||||
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
|
||||
|
||||
# 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
|
||||
for t in 1:T
|
||||
# Collection centers
|
||||
for n in graph.collection_shipping_nodes
|
||||
@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,
|
||||
sum(vars.flow[a] for a in n.incoming_arcs) ==
|
||||
sum(vars.flow[a] for a in n.outgoing_arcs) + vars.dispose[n])
|
||||
# Plants
|
||||
for n in graph.plant_shipping_nodes
|
||||
@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])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function create_process_node_constraints!(model::ManufacturingModel)
|
||||
mip, vars, graph = model.mip, model.vars, model.graph
|
||||
|
||||
for n in graph.process_nodes
|
||||
mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time
|
||||
|
||||
for n in graph.process_nodes, t in 1:T
|
||||
# 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, t] for a in n.incoming_arcs)
|
||||
for a in n.outgoing_arcs
|
||||
@constraint(mip, vars.flow[a] == a.values["weight"] * input_sum)
|
||||
@constraint(mip, vars.flow[a, t] == a.values["weight"] * input_sum)
|
||||
end
|
||||
|
||||
# If plant is closed, capacity is zero
|
||||
@constraint(mip, vars.capacity[n] <= n.plant.max_capacity * vars.open_plant[n])
|
||||
@constraint(mip, vars.capacity[n, t] <= n.location.max_capacity * vars.is_open[n, t])
|
||||
|
||||
# Capacity is linked to expansion
|
||||
@constraint(mip, vars.capacity[n] <= n.plant.base_capacity + vars.expansion[n])
|
||||
@constraint(mip, vars.capacity[n, t] <= n.location.base_capacity + vars.expansion[n, t])
|
||||
|
||||
# Input sum must be smaller than capacity
|
||||
@constraint(mip, input_sum <= vars.capacity[n])
|
||||
@constraint(mip, input_sum <= vars.capacity[n, t])
|
||||
|
||||
if t > 1
|
||||
# Plant capacity can only increase over time
|
||||
@constraint(mip, vars.capacity[n, t] >= vars.capacity[n, t-1])
|
||||
@constraint(mip, vars.expansion[n, t] >= vars.expansion[n, t-1])
|
||||
end
|
||||
|
||||
# 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])
|
||||
else
|
||||
@constraint(mip, vars.is_open[n, t] == vars.open_plant[n, t])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -141,19 +172,22 @@ end
|
||||
|
||||
function get_solution(model::ManufacturingModel)
|
||||
mip, vars, graph, instance = model.mip, model.vars, model.graph, model.instance
|
||||
T = instance.time
|
||||
|
||||
output = Dict(
|
||||
"plants" => Dict(),
|
||||
"costs" => Dict(
|
||||
"fixed" => 0.0,
|
||||
"variable" => 0.0,
|
||||
"transportation" => 0.0,
|
||||
"disposal" => 0.0,
|
||||
"total" => 0.0,
|
||||
"expansion" => 0.0,
|
||||
"fixed operating" => zeros(T),
|
||||
"variable operating" => zeros(T),
|
||||
"opening" => zeros(T),
|
||||
"transportation" => zeros(T),
|
||||
"disposal" => zeros(T),
|
||||
"expansion" => zeros(T),
|
||||
"total" => zeros(T),
|
||||
)
|
||||
)
|
||||
|
||||
plant_to_process_node = Dict(n.plant => n for n in graph.process_nodes)
|
||||
plant_to_process_node = Dict(n.location => n for n in graph.process_nodes)
|
||||
plant_to_shipping_nodes = Dict()
|
||||
for p in instance.plants
|
||||
plant_to_shipping_nodes[p] = []
|
||||
@@ -163,7 +197,7 @@ function get_solution(model::ManufacturingModel)
|
||||
end
|
||||
|
||||
for plant in instance.plants
|
||||
skip_plant = true
|
||||
skip_plant = false
|
||||
process_node = plant_to_process_node[plant]
|
||||
plant_dict = Dict{Any, Any}(
|
||||
"input" => Dict(),
|
||||
@@ -171,31 +205,44 @@ function get_solution(model::ManufacturingModel)
|
||||
"send" => Dict(),
|
||||
"dispose" => Dict(),
|
||||
),
|
||||
"total input" => 0.0,
|
||||
"total input" => [0.0 for t in 1:T],
|
||||
"total output" => Dict(),
|
||||
"latitude" => plant.latitude,
|
||||
"longitude" => plant.longitude,
|
||||
"capacity" => JuMP.value(vars.capacity[process_node]),
|
||||
"fixed cost" => JuMP.value(vars.open_plant[process_node]) * (plant.opening_cost + plant.fixed_operating_cost),
|
||||
"expansion cost" => JuMP.value(vars.expansion[process_node]) * plant.expansion_cost,
|
||||
"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]
|
||||
for t in 1:T],
|
||||
"fixed operating cost" => [JuMP.value(vars.is_open[process_node, t]) * plant.fixed_operating_cost[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])
|
||||
else
|
||||
JuMP.value(vars.expansion[process_node, t])
|
||||
end)
|
||||
for t in 1:T],
|
||||
)
|
||||
output["costs"]["fixed"] += plant_dict["fixed cost"]
|
||||
output["costs"]["fixed operating"] += plant_dict["fixed operating cost"]
|
||||
output["costs"]["opening"] += plant_dict["opening cost"]
|
||||
output["costs"]["expansion"] += plant_dict["expansion cost"]
|
||||
|
||||
# Inputs
|
||||
for a in process_node.incoming_arcs
|
||||
val = JuMP.value(vars.flow[a])
|
||||
if val <= 1e-3
|
||||
vals = [JuMP.value(vars.flow[a, t]) for t in 1:T]
|
||||
if sum(vals) <= 1e-3
|
||||
continue
|
||||
end
|
||||
skip_plant = false
|
||||
dict = Dict{Any, Any}(
|
||||
"amount" => val,
|
||||
"amount" => vals,
|
||||
"distance" => a.values["distance"],
|
||||
"latitude" => a.source.location.latitude,
|
||||
"longitude" => a.source.location.longitude,
|
||||
"transportation cost" => a.source.product.transportation_cost * val,
|
||||
"variable operating cost" => plant.variable_operating_cost * val,
|
||||
"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]
|
||||
for t in 1:T],
|
||||
)
|
||||
if a.source.location isa CollectionCenter
|
||||
plant_name = "Origin"
|
||||
@@ -209,45 +256,45 @@ function get_solution(model::ManufacturingModel)
|
||||
plant_dict["input"][plant_name] = Dict()
|
||||
end
|
||||
plant_dict["input"][plant_name][location_name] = dict
|
||||
plant_dict["total input"] += val
|
||||
plant_dict["total input"] += vals
|
||||
output["costs"]["transportation"] += dict["transportation cost"]
|
||||
output["costs"]["variable"] += dict["variable operating cost"]
|
||||
output["costs"]["variable operating"] += dict["variable operating cost"]
|
||||
end
|
||||
|
||||
# Outputs
|
||||
for shipping_node in plant_to_shipping_nodes[plant]
|
||||
product_name = shipping_node.product.name
|
||||
plant_dict["total output"][product_name] = 0.0
|
||||
plant_dict["output"]["send"][product_name] = product_dict = Dict()
|
||||
# # Outputs
|
||||
# for shipping_node in plant_to_shipping_nodes[plant]
|
||||
# product_name = shipping_node.product.name
|
||||
# plant_dict["total output"][product_name] = 0.0
|
||||
# plant_dict["output"]["send"][product_name] = product_dict = Dict()
|
||||
|
||||
disposal_amount = JuMP.value(vars.dispose[shipping_node])
|
||||
if disposal_amount > 1e-5
|
||||
plant_dict["output"]["dispose"][product_name] = disposal_dict = Dict()
|
||||
disposal_dict["amount"] = JuMP.value(model.vars.dispose[shipping_node])
|
||||
disposal_dict["cost"] = disposal_dict["amount"] * plant.disposal_cost[shipping_node.product]
|
||||
plant_dict["total output"][product_name] += disposal_amount
|
||||
output["costs"]["disposal"] += disposal_dict["cost"]
|
||||
end
|
||||
# disposal_amount = JuMP.value(vars.dispose[shipping_node])
|
||||
# if disposal_amount > 1e-5
|
||||
# plant_dict["output"]["dispose"][product_name] = disposal_dict = Dict()
|
||||
# disposal_dict["amount"] = JuMP.value(model.vars.dispose[shipping_node])
|
||||
# disposal_dict["cost"] = disposal_dict["amount"] * plant.disposal_cost[shipping_node.product]
|
||||
# plant_dict["total output"][product_name] += disposal_amount
|
||||
# output["costs"]["disposal"] += disposal_dict["cost"]
|
||||
# end
|
||||
|
||||
for a in shipping_node.outgoing_arcs
|
||||
val = JuMP.value(vars.flow[a])
|
||||
if val <= 1e-3
|
||||
continue
|
||||
end
|
||||
skip_plant = false
|
||||
dict = Dict(
|
||||
"amount" => val,
|
||||
"distance" => a.values["distance"],
|
||||
"latitude" => a.dest.plant.latitude,
|
||||
"longitude" => a.dest.plant.longitude,
|
||||
)
|
||||
if a.dest.plant.plant_name ∉ keys(product_dict)
|
||||
product_dict[a.dest.plant.plant_name] = Dict()
|
||||
end
|
||||
product_dict[a.dest.plant.plant_name][a.dest.plant.location_name] = dict
|
||||
plant_dict["total output"][product_name] += val
|
||||
end
|
||||
end
|
||||
# for a in shipping_node.outgoing_arcs
|
||||
# val = JuMP.value(vars.flow[a])
|
||||
# if val <= 1e-3
|
||||
# continue
|
||||
# end
|
||||
# skip_plant = false
|
||||
# dict = Dict(
|
||||
# "amount" => val,
|
||||
# "distance" => a.values["distance"],
|
||||
# "latitude" => a.dest.location.latitude,
|
||||
# "longitude" => a.dest.location.longitude,
|
||||
# )
|
||||
# if a.dest.location.plant_name ∉ keys(product_dict)
|
||||
# product_dict[a.dest.location.plant_name] = Dict()
|
||||
# end
|
||||
# product_dict[a.dest.location.plant_name][a.dest.location.location_name] = dict
|
||||
# plant_dict["total output"][product_name] += val
|
||||
# end
|
||||
# end
|
||||
|
||||
if !skip_plant
|
||||
if plant.plant_name ∉ keys(output["plants"])
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
"$id": "https://axavier.org/ReverseManufacturing/input/schema",
|
||||
"title": "Schema for ReverseManufacturing Input File",
|
||||
"definitions": {
|
||||
"TimeSeries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"Parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time": { "type": "number" }
|
||||
},
|
||||
"required": [
|
||||
"time periods"
|
||||
]
|
||||
},
|
||||
"Plant": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -28,19 +43,19 @@
|
||||
"properties": {
|
||||
"latitude": { "type": "number" },
|
||||
"longitude": { "type": "number" },
|
||||
"variable operating cost": { "type": "number" },
|
||||
"fixed operating cost": { "type": "number" },
|
||||
"opening cost": { "type": "number" },
|
||||
"variable operating cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"fixed operating cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"opening cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"base capacity": { "type": "number" },
|
||||
"max capacity": { "type": "number" },
|
||||
"expansion cost": { "type": "number" },
|
||||
"expansion cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"disposal": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cost": { "type": "number" },
|
||||
"limit": { "type": "number" }
|
||||
"cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"limit": { "$ref": "#/definitions/TimeSeries" }
|
||||
},
|
||||
"required": [
|
||||
"cost"
|
||||
@@ -64,7 +79,7 @@
|
||||
"properties": {
|
||||
"latitude": { "type": "number" },
|
||||
"longitude": { "type": "number" },
|
||||
"amount": { "type": "number" }
|
||||
"amount": { "$ref": "#/definitions/TimeSeries" }
|
||||
},
|
||||
"required": [
|
||||
"latitude",
|
||||
@@ -78,7 +93,7 @@
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"transportation cost": { "type": "number" },
|
||||
"transportation cost": { "$ref": "#/definitions/TimeSeries" },
|
||||
"initial amounts": { "$ref": "#/definitions/InitialAmount" }
|
||||
},
|
||||
"required": [
|
||||
@@ -89,6 +104,7 @@
|
||||
},
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parameters": { "$ref": "#/definitions/Parameters" },
|
||||
"plants": { "$ref": "#/definitions/Plant" },
|
||||
"products": { "$ref": "#/definitions/Product" }
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ using ReverseManufacturing
|
||||
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
|
||||
process_node_by_location_name = Dict(n.location.location_name => n
|
||||
for n in graph.process_nodes)
|
||||
|
||||
@test length(graph.plant_shipping_nodes) == 8
|
||||
@@ -20,19 +20,19 @@ using ReverseManufacturing
|
||||
@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].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.plant.plant_name == "F1"
|
||||
@test node.plant.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.plant.plant_name == "F2"
|
||||
@test node.plant.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
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ using ReverseManufacturing
|
||||
@test centers[1].latitude == 7
|
||||
@test centers[1].latitude == 7
|
||||
@test centers[1].longitude == 7
|
||||
@test centers[1].amount == 934.56
|
||||
@test centers[1].amount == [934.56, 934.56]
|
||||
@test centers[1].product.name == "P1"
|
||||
|
||||
@test length(plants) == 6
|
||||
@@ -32,40 +32,40 @@ using ReverseManufacturing
|
||||
@test plant.input.name == "P1"
|
||||
@test plant.latitude == 0
|
||||
@test plant.longitude == 0
|
||||
@test plant.opening_cost == 500
|
||||
@test plant.fixed_operating_cost == 30
|
||||
@test plant.variable_operating_cost == 30
|
||||
@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
|
||||
@test plant.expansion_cost == [1, 1]
|
||||
|
||||
p2 = product_name_to_product["P2"]
|
||||
p3 = product_name_to_product["P3"]
|
||||
@test length(plant.output) == 2
|
||||
@test plant.output[p2] == 0.2
|
||||
@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 plant.disposal_limit[p2] == [1, 1]
|
||||
@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 plant.opening_cost == 3000
|
||||
@test plant.fixed_operating_cost == 50
|
||||
@test plant.variable_operating_cost == 50
|
||||
@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
|
||||
@test plant.expansion_cost == [0, 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
|
||||
@test plant.disposal_limit[p3] == [0, 0]
|
||||
@test plant.disposal_limit[p4] == [0, 0]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2020 Argonne National Laboratory
|
||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||
|
||||
using ReverseManufacturing, Cbc, JuMP, Printf, JSON
|
||||
using ReverseManufacturing, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
||||
|
||||
@testset "Model" begin
|
||||
@testset "build" begin
|
||||
@@ -10,42 +10,46 @@ using ReverseManufacturing, Cbc, JuMP, Printf, JSON
|
||||
graph = ReverseManufacturing.build_graph(instance)
|
||||
model = ReverseManufacturing.build_model(instance, graph, Cbc.Optimizer)
|
||||
|
||||
process_node_by_location_name = Dict(n.plant.location_name => n
|
||||
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)
|
||||
|
||||
|
||||
@test length(model.vars.flow) == 38
|
||||
@test length(model.vars.dispose) == 8
|
||||
@test length(model.vars.open_plant) == 6
|
||||
@test length(model.vars.capacity) == 6
|
||||
@test length(model.vars.expansion) == 6
|
||||
@test length(model.vars.flow) == 76
|
||||
@test length(model.vars.dispose) == 16
|
||||
@test length(model.vars.open_plant) == 12
|
||||
@test length(model.vars.capacity) == 12
|
||||
@test length(model.vars.expansion) == 12
|
||||
|
||||
l1 = process_node_by_location_name["L1"]
|
||||
v = model.vars.capacity[l1]
|
||||
v = model.vars.capacity[l1, 1]
|
||||
@test lower_bound(v) == 0.0
|
||||
@test upper_bound(v) == 1000.0
|
||||
|
||||
v = model.vars.expansion[l1]
|
||||
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"]]
|
||||
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")
|
||||
|
||||
end
|
||||
|
||||
@testset "solve" begin
|
||||
solution = ReverseManufacturing.solve("$(pwd())/../instances/samples/s1.json")
|
||||
JSON.print(stdout, solution, 4)
|
||||
#JSON.print(stdout, solution, 4)
|
||||
|
||||
@test "costs" in keys(solution)
|
||||
@test "fixed" in keys(solution["costs"])
|
||||
@test "fixed operating" in keys(solution["costs"])
|
||||
@test "transportation" in keys(solution["costs"])
|
||||
@test "variable" in keys(solution["costs"])
|
||||
@test "variable operating" in keys(solution["costs"])
|
||||
@test "total" in keys(solution["costs"])
|
||||
|
||||
@test "plants" in keys(solution)
|
||||
@@ -53,10 +57,6 @@ using ReverseManufacturing, Cbc, JuMP, Printf, JSON
|
||||
@test "F2" in keys(solution["plants"])
|
||||
@test "F3" in keys(solution["plants"])
|
||||
@test "F4" in keys(solution["plants"])
|
||||
# @test "L2" in keys(solution["plants"]["F1"])
|
||||
# @test "total output" in keys(solution["plants"]["F1"]["L2"])
|
||||
|
||||
# @test "capacity" in keys(solution["plants"]["F1"]["L1"])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user