mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-05 23:38:52 -06:00
Compare commits
11 Commits
feature/Ca
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
8231f9da32
|
|||
|
48ccf0d180
|
|||
|
7f475a0914
|
|||
|
4b0fc7327c
|
|||
|
dde0d40282
|
|||
|
74606897cd
|
|||
|
07ca3abb4f
|
|||
| e915a57e58 | |||
| 57b7d09c08 | |||
| a03b9169fd | |||
| ee58af73f0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ Manifest.toml
|
|||||||
data
|
data
|
||||||
build
|
build
|
||||||
benchmark
|
benchmark
|
||||||
|
**/*.log
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
[semver]: https://semver.org/spec/v2.0.0.html
|
[semver]: https://semver.org/spec/v2.0.0.html
|
||||||
[pkjjl]: https://pkgdocs.julialang.org/v1/compatibility/#compat-pre-1.0
|
[pkjjl]: https://pkgdocs.julialang.org/v1/compatibility/#compat-pre-1.0
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Allow product disposal at collection centers
|
||||||
|
- Implement stochastic optimization
|
||||||
|
|
||||||
## [0.5.2] -- 2022-08-26
|
## [0.5.2] -- 2022-08-26
|
||||||
### Changed
|
### Changed
|
||||||
- Update to JuMP 1.x
|
- Update to JuMP 1.x
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ version = "0.5.2"
|
|||||||
[deps]
|
[deps]
|
||||||
CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205"
|
CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205"
|
||||||
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
|
||||||
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
|
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
|
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
|
||||||
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
||||||
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
|
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
|
||||||
|
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
|
||||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
||||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
@@ -21,16 +20,16 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
|||||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
||||||
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
|
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
|
||||||
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
||||||
|
StochasticPrograms = "8b8459f2-c380-502b-8633-9aed2d6c2b35"
|
||||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
|
ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
|
||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
CRC = "4"
|
CRC = "4"
|
||||||
CSV = "0.10"
|
CSV = "0.10"
|
||||||
Cbc = "1"
|
|
||||||
Clp = "1"
|
|
||||||
DataFrames = "1"
|
DataFrames = "1"
|
||||||
DataStructures = "0.18"
|
DataStructures = "0.18"
|
||||||
GZip = "0.5"
|
GZip = "0.5"
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ The **products** section describes all products and subproducts in the simulatio
|
|||||||
|`transportation energy (J/km/tonne)` | The energy required to transport this product. Must be a time series. Optional.
|
|`transportation energy (J/km/tonne)` | The energy required to transport this product. Must be a time series. Optional.
|
||||||
|`transportation emissions (tonne/km/tonne)` | A dictionary mapping the name of each greenhouse gas, produced to transport one tonne of this product along one kilometer, to the amount of gas produced (in tonnes). Must be a time series. Optional.
|
|`transportation emissions (tonne/km/tonne)` | A dictionary mapping the name of each greenhouse gas, produced to transport one tonne of this product along one kilometer, to the amount of gas produced (in tonnes). Must be a time series. Optional.
|
||||||
|`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 time series.
|
|`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 time series.
|
||||||
|
| `disposal limit (tonne)` | Total amount of product that can be disposed of across all collection centers. If omitted, all product must be processed. This parameter has no effect on product disposal at plants.
|
||||||
|
| `disposal cost ($/tonne)` | Cost of disposing one tonne of this product at a collection center. If omitted, defaults to zero. This parameter has no effect on product disposal costs at plants.
|
||||||
|
|
||||||
Each product may have some amount available at the beginning of each time period. In this case, the key `initial amounts` maps to a dictionary with the following keys:
|
Each product may have some amount available at the beginning of each time period. In this case, the key `initial amounts` maps to a dictionary with the following keys:
|
||||||
|
|
||||||
@@ -73,7 +75,9 @@ Each product may have some amount available at the beginning of each time period
|
|||||||
"transportation emissions (tonne/km/tonne)": {
|
"transportation emissions (tonne/km/tonne)": {
|
||||||
"CO2": [0.052, 0.050],
|
"CO2": [0.052, 0.050],
|
||||||
"CH4": [0.003, 0.002]
|
"CH4": [0.003, 0.002]
|
||||||
}
|
},
|
||||||
|
"disposal cost ($/tonne)": [-10.0, -12.0],
|
||||||
|
"disposal limit (tonne)": [1.0, 1.0],
|
||||||
},
|
},
|
||||||
"P2": {
|
"P2": {
|
||||||
"transportation cost ($/km/tonne)": [0.022, 0.020]
|
"transportation cost ($/km/tonne)": [0.022, 0.020]
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ Report showing primary product amounts, locations and marginal costs. Generated
|
|||||||
| `longitude (deg)` | Longitude of the collection center.
|
| `longitude (deg)` | Longitude of the collection center.
|
||||||
| `year` | What year this row corresponds to. This reports includes one row for each year.
|
| `year` | What year this row corresponds to. This reports includes one row for each year.
|
||||||
| `amount (tonne)` | Amount of product available at this collection center.
|
| `amount (tonne)` | Amount of product available at this collection center.
|
||||||
|
| `amount disposed (tonne)` | Amount of product disposed of at this collection center.
|
||||||
| `marginal cost ($/tonne)` | Cost to process one additional tonne of this product coming from this collection center.
|
| `marginal cost ($/tonne)` | Cost to process one additional tonne of this product coming from this collection center.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"time horizon (years)": 2
|
|
||||||
},
|
|
||||||
"products": {
|
|
||||||
"P1": {
|
|
||||||
"transportation cost ($/km/tonne)": [0.015, 0.015],
|
|
||||||
"transportation energy (J/km/tonne)": [0.12, 0.11],
|
|
||||||
"transportation emissions (tonne/km/tonne)": {
|
|
||||||
"CO2": [0.052, 0.050],
|
|
||||||
"CH4": [0.003, 0.002]
|
|
||||||
},
|
|
||||||
"initial amounts": {
|
|
||||||
"C1": {
|
|
||||||
"latitude (deg)": 7.0,
|
|
||||||
"longitude (deg)": 7.0,
|
|
||||||
"amount (tonne)": [934.56, 934.56]
|
|
||||||
},
|
|
||||||
"C2": {
|
|
||||||
"latitude (deg)": 7.0,
|
|
||||||
"longitude (deg)": 19.0,
|
|
||||||
"amount (tonne)": [198.95, 198.95]
|
|
||||||
},
|
|
||||||
"C3": {
|
|
||||||
"latitude (deg)": 84.0,
|
|
||||||
"longitude (deg)": 76.0,
|
|
||||||
"amount (tonne)": [212.97, 212.97]
|
|
||||||
},
|
|
||||||
"C4": {
|
|
||||||
"latitude (deg)": 21.0,
|
|
||||||
"longitude (deg)": 16.0,
|
|
||||||
"amount (tonne)": [352.19, 352.19]
|
|
||||||
},
|
|
||||||
"C5": {
|
|
||||||
"latitude (deg)": 32.0,
|
|
||||||
"longitude (deg)": 92.0,
|
|
||||||
"amount (tonne)": [510.33, 510.33]
|
|
||||||
},
|
|
||||||
"C6": {
|
|
||||||
"latitude (deg)": 14.0,
|
|
||||||
"longitude (deg)": 62.0,
|
|
||||||
"amount (tonne)": [471.66, 471.66]
|
|
||||||
},
|
|
||||||
"C7": {
|
|
||||||
"latitude (deg)": 30.0,
|
|
||||||
"longitude (deg)": 83.0,
|
|
||||||
"amount (tonne)": [785.21, 785.21]
|
|
||||||
},
|
|
||||||
"C8": {
|
|
||||||
"latitude (deg)": 35.0,
|
|
||||||
"longitude (deg)": 40.0,
|
|
||||||
"amount (tonne)": [706.17, 706.17]
|
|
||||||
},
|
|
||||||
"C9": {
|
|
||||||
"latitude (deg)": 74.0,
|
|
||||||
"longitude (deg)": 52.0,
|
|
||||||
"amount (tonne)": [30.08, 30.08]
|
|
||||||
},
|
|
||||||
"C10": {
|
|
||||||
"latitude (deg)": 22.0,
|
|
||||||
"longitude (deg)": 54.0,
|
|
||||||
"amount (tonne)": [536.52, 536.52]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"P2": {
|
|
||||||
"transportation cost ($/km/tonne)": [0.02, 0.02]
|
|
||||||
},
|
|
||||||
"P3": {
|
|
||||||
"transportation cost ($/km/tonne)": [0.0125, 0.0125]
|
|
||||||
},
|
|
||||||
"P4": {
|
|
||||||
"transportation cost ($/km/tonne)": [0.0175, 0.0175]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"plants": {
|
|
||||||
"F1": {
|
|
||||||
"input": "P1",
|
|
||||||
"outputs (tonne/tonne)": {
|
|
||||||
"P2": 0.2,
|
|
||||||
"P3": 0.5
|
|
||||||
},
|
|
||||||
"energy (GJ/tonne)": [0.12, 0.11],
|
|
||||||
"emissions (tonne/tonne)": {
|
|
||||||
"CO2": [0.052, 0.050],
|
|
||||||
"CH4": [0.003, 0.002]
|
|
||||||
},
|
|
||||||
"locations": {
|
|
||||||
"L1": {
|
|
||||||
"latitude (deg)": 0.0,
|
|
||||||
"longitude (deg)": 0.0,
|
|
||||||
"disposal": {
|
|
||||||
"P2": {
|
|
||||||
"cost ($/tonne)": [-10.0, -10.0],
|
|
||||||
"limit (tonne)": [1.0, 1.0]
|
|
||||||
},
|
|
||||||
"P3": {
|
|
||||||
"cost ($/tonne)": [-10.0, -10.0],
|
|
||||||
"limit (tonne)": [1.0, 1.0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"250.0": {
|
|
||||||
"opening cost ($)": [500.0, 500.0],
|
|
||||||
"fixed operating cost ($)": [30.0, 30.0],
|
|
||||||
"variable operating cost ($/tonne)": [30.0, 30.0]
|
|
||||||
},
|
|
||||||
"1000.0": {
|
|
||||||
"opening cost ($)": [1250.0, 1250.0],
|
|
||||||
"fixed operating cost ($)": [30.0, 30.0],
|
|
||||||
"variable operating cost ($/tonne)": [30.0, 30.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"L2": {
|
|
||||||
"latitude (deg)": 0.5,
|
|
||||||
"longitude (deg)": 0.5,
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"0.0": {
|
|
||||||
"opening cost ($)": [1000, 1000],
|
|
||||||
"fixed operating cost ($)": [50.0, 50.0],
|
|
||||||
"variable operating cost ($/tonne)": [50.0, 50.0]
|
|
||||||
},
|
|
||||||
"10000.0": {
|
|
||||||
"opening cost ($)": [10000, 10000],
|
|
||||||
"fixed operating cost ($)": [50.0, 50.0],
|
|
||||||
"variable operating cost ($/tonne)": [50.0, 50.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"F2": {
|
|
||||||
"input": "P2",
|
|
||||||
"outputs (tonne/tonne)": {
|
|
||||||
"P3": 0.05,
|
|
||||||
"P4": 0.80
|
|
||||||
},
|
|
||||||
"locations": {
|
|
||||||
"L3": {
|
|
||||||
"latitude (deg)": 25.0,
|
|
||||||
"longitude (deg)": 65.0,
|
|
||||||
"disposal": {
|
|
||||||
"P3": {
|
|
||||||
"cost ($/tonne)": [100.0, 100.0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"1000.0": {
|
|
||||||
"opening cost ($)": [3000, 3000],
|
|
||||||
"fixed operating cost ($)": [50.0, 50.0],
|
|
||||||
"variable operating cost ($/tonne)": [50.0, 50.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"L4": {
|
|
||||||
"latitude (deg)": 0.75,
|
|
||||||
"longitude (deg)": 0.20,
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"10000": {
|
|
||||||
"opening cost ($)": [3000, 3000],
|
|
||||||
"fixed operating cost ($)": [50.0, 50.0],
|
|
||||||
"variable operating cost ($/tonne)": [50.0, 50.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"F3": {
|
|
||||||
"input": "P4",
|
|
||||||
"locations": {
|
|
||||||
"L5": {
|
|
||||||
"latitude (deg)": 100.0,
|
|
||||||
"longitude (deg)": 100.0,
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"15000": {
|
|
||||||
"opening cost ($)": [0.0, 0.0],
|
|
||||||
"fixed operating cost ($)": [0.0, 0.0],
|
|
||||||
"variable operating cost ($/tonne)": [-15.0, -15.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"F4": {
|
|
||||||
"input": "P3",
|
|
||||||
"locations": {
|
|
||||||
"L6": {
|
|
||||||
"latitude (deg)": 50.0,
|
|
||||||
"longitude (deg)": 50.0,
|
|
||||||
"capacities (tonne)": {
|
|
||||||
"10000": {
|
|
||||||
"opening cost ($)": [0.0, 0.0],
|
|
||||||
"fixed operating cost ($)": [0.0, 0.0],
|
|
||||||
"variable operating cost ($/tonne)": [-15.0, -15.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[ Info: Reading s1.json...
|
|
||||||
[ Info: Building graph...
|
|
||||||
[ Info: 2 time periods
|
|
||||||
[ Info: 6 process nodes
|
|
||||||
[ Info: 8 shipping nodes (plant)
|
|
||||||
[ Info: 10 shipping nodes (collection)
|
|
||||||
[ Info: 38 arcs
|
|
||||||
[ Info: Building optimization model...
|
|
||||||
[ Info: Optimizing MILP...
|
|
||||||
[ Info: Re-optimizing with integer variables fixed...
|
|
||||||
[ Info: Extracting solution...
|
|
||||||
@@ -17,7 +17,6 @@ include("instance/validate.jl")
|
|||||||
include("model/build.jl")
|
include("model/build.jl")
|
||||||
include("model/getsol.jl")
|
include("model/getsol.jl")
|
||||||
include("model/solve.jl")
|
include("model/solve.jl")
|
||||||
include("model/resolve.jl")
|
|
||||||
include("reports/plant_emissions.jl")
|
include("reports/plant_emissions.jl")
|
||||||
include("reports/plant_outputs.jl")
|
include("reports/plant_outputs.jl")
|
||||||
include("reports/plants.jl")
|
include("reports/plants.jl")
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function build_graph(instance::Instance)::Graph
|
|||||||
collection_shipping_nodes = ShippingNode[]
|
collection_shipping_nodes = ShippingNode[]
|
||||||
|
|
||||||
name_to_process_node_map = Dict{Tuple{AbstractString,AbstractString},ProcessNode}()
|
name_to_process_node_map = Dict{Tuple{AbstractString,AbstractString},ProcessNode}()
|
||||||
|
collection_center_to_node = Dict()
|
||||||
|
|
||||||
process_nodes_by_input_product =
|
process_nodes_by_input_product =
|
||||||
Dict(product => ProcessNode[] for product in instance.products)
|
Dict(product => ProcessNode[] for product in instance.products)
|
||||||
@@ -27,6 +28,7 @@ function build_graph(instance::Instance)::Graph
|
|||||||
for center in instance.collection_centers
|
for center in instance.collection_centers
|
||||||
node = ShippingNode(next_index, center, center.product, [], [])
|
node = ShippingNode(next_index, center, center.product, [], [])
|
||||||
next_index += 1
|
next_index += 1
|
||||||
|
collection_center_to_node[center] = node
|
||||||
push!(collection_shipping_nodes, node)
|
push!(collection_shipping_nodes, node)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ function build_graph(instance::Instance)::Graph
|
|||||||
dest.location.longitude,
|
dest.location.longitude,
|
||||||
)
|
)
|
||||||
values = Dict("distance" => distance)
|
values = Dict("distance" => distance)
|
||||||
arc = Arc(source, dest, values)
|
arc = Arc(length(arcs) + 1, source, dest, values)
|
||||||
push!(source.outgoing_arcs, arc)
|
push!(source.outgoing_arcs, arc)
|
||||||
push!(dest.incoming_arcs, arc)
|
push!(dest.incoming_arcs, arc)
|
||||||
push!(arcs, arc)
|
push!(arcs, arc)
|
||||||
@@ -70,7 +72,7 @@ function build_graph(instance::Instance)::Graph
|
|||||||
for dest in shipping_nodes_by_plant[plant]
|
for dest in shipping_nodes_by_plant[plant]
|
||||||
weight = plant.output[dest.product]
|
weight = plant.output[dest.product]
|
||||||
values = Dict("weight" => weight)
|
values = Dict("weight" => weight)
|
||||||
arc = Arc(source, dest, values)
|
arc = Arc(length(arcs) + 1, source, dest, values)
|
||||||
push!(source.outgoing_arcs, arc)
|
push!(source.outgoing_arcs, arc)
|
||||||
push!(dest.incoming_arcs, arc)
|
push!(dest.incoming_arcs, arc)
|
||||||
push!(arcs, arc)
|
push!(arcs, arc)
|
||||||
@@ -83,6 +85,7 @@ function build_graph(instance::Instance)::Graph
|
|||||||
collection_shipping_nodes,
|
collection_shipping_nodes,
|
||||||
arcs,
|
arcs,
|
||||||
name_to_process_node_map,
|
name_to_process_node_map,
|
||||||
|
collection_center_to_node,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Geodesy
|
|||||||
abstract type Node end
|
abstract type Node end
|
||||||
|
|
||||||
mutable struct Arc
|
mutable struct Arc
|
||||||
|
index::Int
|
||||||
source::Node
|
source::Node
|
||||||
dest::Node
|
dest::Node
|
||||||
values::Dict{String,Float64}
|
values::Dict{String,Float64}
|
||||||
@@ -33,6 +34,7 @@ mutable struct Graph
|
|||||||
collection_shipping_nodes::Vector{ShippingNode}
|
collection_shipping_nodes::Vector{ShippingNode}
|
||||||
arcs::Vector{Arc}
|
arcs::Vector{Arc}
|
||||||
name_to_process_node_map::Dict{Tuple{AbstractString,AbstractString},ProcessNode}
|
name_to_process_node_map::Dict{Tuple{AbstractString,AbstractString},ProcessNode}
|
||||||
|
collection_center_to_node::Dict{CollectionCenter,ShippingNode}
|
||||||
end
|
end
|
||||||
|
|
||||||
function Base.show(io::IO, instance::Graph)
|
function Base.show(io::IO, instance::Graph)
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ function _compress(instance::Instance)::Instance
|
|||||||
for (emission_name, emission_value) in p.transportation_emissions
|
for (emission_name, emission_value) in p.transportation_emissions
|
||||||
p.transportation_emissions[emission_name] = [mean(emission_value)]
|
p.transportation_emissions[emission_name] = [mean(emission_value)]
|
||||||
end
|
end
|
||||||
|
p.disposal_limit = [maximum(p.disposal_limit) * T]
|
||||||
|
p.disposal_cost = [mean(p.disposal_cost)]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Compress collection centers
|
# Compress collection centers
|
||||||
@@ -58,3 +60,42 @@ function _compress(instance::Instance)::Instance
|
|||||||
|
|
||||||
return compressed
|
return compressed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function _slice(instance::Instance, T::UnitRange)::Instance
|
||||||
|
sliced = deepcopy(instance)
|
||||||
|
sliced.time = length(T)
|
||||||
|
|
||||||
|
for p in sliced.products
|
||||||
|
p.transportation_cost = p.transportation_cost[T]
|
||||||
|
p.transportation_energy = p.transportation_energy[T]
|
||||||
|
for (emission_name, emission_value) in p.transportation_emissions
|
||||||
|
p.transportation_emissions[emission_name] = emission_value[T]
|
||||||
|
end
|
||||||
|
p.disposal_limit = p.disposal_limit[T]
|
||||||
|
p.disposal_cost = p.disposal_cost[T]
|
||||||
|
end
|
||||||
|
|
||||||
|
for c in sliced.collection_centers
|
||||||
|
c.amount = c.amount[T]
|
||||||
|
end
|
||||||
|
|
||||||
|
for plant in sliced.plants
|
||||||
|
plant.energy = plant.energy[T]
|
||||||
|
for (emission_name, emission_value) in plant.emissions
|
||||||
|
plant.emissions[emission_name] = emission_value[T]
|
||||||
|
end
|
||||||
|
for s in plant.sizes
|
||||||
|
s.variable_operating_cost = s.variable_operating_cost[T]
|
||||||
|
s.opening_cost = s.opening_cost[T]
|
||||||
|
s.fixed_operating_cost = s.fixed_operating_cost[T]
|
||||||
|
end
|
||||||
|
for (prod_name, disp_limit) in plant.disposal_limit
|
||||||
|
plant.disposal_limit[prod_name] = disp_limit[T]
|
||||||
|
end
|
||||||
|
for (prod_name, disp_cost) in plant.disposal_cost
|
||||||
|
plant.disposal_cost[prod_name] = disp_cost[T]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return sliced
|
||||||
|
end
|
||||||
@@ -37,6 +37,8 @@ function parse(json)::Instance
|
|||||||
cost = product_dict["transportation cost (\$/km/tonne)"]
|
cost = product_dict["transportation cost (\$/km/tonne)"]
|
||||||
energy = zeros(T)
|
energy = zeros(T)
|
||||||
emissions = Dict()
|
emissions = Dict()
|
||||||
|
disposal_limit = zeros(T)
|
||||||
|
disposal_cost = zeros(T)
|
||||||
|
|
||||||
if "transportation energy (J/km/tonne)" in keys(product_dict)
|
if "transportation energy (J/km/tonne)" in keys(product_dict)
|
||||||
energy = product_dict["transportation energy (J/km/tonne)"]
|
energy = product_dict["transportation energy (J/km/tonne)"]
|
||||||
@@ -46,7 +48,25 @@ function parse(json)::Instance
|
|||||||
emissions = product_dict["transportation emissions (tonne/km/tonne)"]
|
emissions = product_dict["transportation emissions (tonne/km/tonne)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
product = Product(product_name, cost, energy, emissions)
|
if "disposal limit (tonne)" in keys(product_dict)
|
||||||
|
disposal_limit = product_dict["disposal limit (tonne)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
if "disposal cost (\$/tonne)" in keys(product_dict)
|
||||||
|
disposal_cost = product_dict["disposal cost (\$/tonne)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
prod_centers = []
|
||||||
|
|
||||||
|
product = Product(
|
||||||
|
product_name,
|
||||||
|
cost,
|
||||||
|
energy,
|
||||||
|
emissions,
|
||||||
|
disposal_limit,
|
||||||
|
disposal_cost,
|
||||||
|
prod_centers,
|
||||||
|
)
|
||||||
push!(products, product)
|
push!(products, product)
|
||||||
prod_name_to_product[product_name] = product
|
prod_name_to_product[product_name] = product
|
||||||
|
|
||||||
@@ -66,6 +86,7 @@ function parse(json)::Instance
|
|||||||
product,
|
product,
|
||||||
center_dict["amount (tonne)"],
|
center_dict["amount (tonne)"],
|
||||||
)
|
)
|
||||||
|
push!(prod_centers, center)
|
||||||
push!(collection_centers, center)
|
push!(collection_centers, center)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ mutable struct Product
|
|||||||
transportation_cost::Vector{Float64}
|
transportation_cost::Vector{Float64}
|
||||||
transportation_energy::Vector{Float64}
|
transportation_energy::Vector{Float64}
|
||||||
transportation_emissions::Dict{String,Vector{Float64}}
|
transportation_emissions::Dict{String,Vector{Float64}}
|
||||||
|
disposal_limit::Vector{Float64}
|
||||||
|
disposal_cost::Vector{Float64}
|
||||||
|
collection_centers::Vector
|
||||||
end
|
end
|
||||||
|
|
||||||
mutable struct CollectionCenter
|
mutable struct CollectionCenter
|
||||||
|
|||||||
@@ -2,62 +2,346 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures
|
using JuMP, LinearAlgebra, Geodesy, ProgressBars, Printf, DataStructures, StochasticPrograms
|
||||||
|
|
||||||
function build_model(instance::Instance, graph::Graph, optimizer)::JuMP.Model
|
function build_model(
|
||||||
model = Model(optimizer)
|
instance::Instance,
|
||||||
model[:instance] = instance
|
graph::Graph,
|
||||||
model[:graph] = graph
|
optimizer,
|
||||||
create_vars!(model)
|
)
|
||||||
create_objective_function!(model)
|
return build_model(
|
||||||
create_shipping_node_constraints!(model)
|
instance,
|
||||||
create_process_node_constraints!(model)
|
[graph],
|
||||||
return model
|
[1.0],
|
||||||
|
optimizer=optimizer,
|
||||||
|
method=:ef,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function build_model(
|
||||||
|
instance::Instance,
|
||||||
|
graphs::Vector{Graph},
|
||||||
|
probs::Vector{Float64};
|
||||||
|
optimizer,
|
||||||
|
method=:ef,
|
||||||
|
tol=0.1,
|
||||||
|
)
|
||||||
|
T = instance.time
|
||||||
|
|
||||||
function create_vars!(model::JuMP.Model)
|
@stochastic_model model begin
|
||||||
graph, T = model[:graph], model[:instance].time
|
# Stage 1: Build plants
|
||||||
model[:flow] =
|
# =====================================================================
|
||||||
Dict((a, t) => @variable(model, lower_bound = 0) for a in graph.arcs, t = 1:T)
|
@stage 1 begin
|
||||||
model[:dispose] = Dict(
|
pn = graphs[1].process_nodes
|
||||||
(n, t) => @variable(
|
PN = length(pn)
|
||||||
model,
|
|
||||||
lower_bound = 0,
|
# Var: open_plant
|
||||||
upper_bound = n.location.disposal_limit[n.product][t]
|
@decision(
|
||||||
) for n in values(graph.plant_shipping_nodes), t = 1:T
|
model,
|
||||||
)
|
open_plant[n in 1:PN, t in 1:T],
|
||||||
model[:store] = Dict(
|
binary = true,
|
||||||
(n, t) =>
|
)
|
||||||
@variable(model, lower_bound = 0, upper_bound = n.location.storage_limit)
|
|
||||||
for n in values(graph.process_nodes), t = 1:T
|
# Var: is_open
|
||||||
)
|
@decision(
|
||||||
model[:process] = Dict(
|
model,
|
||||||
(n, t) => @variable(model, lower_bound = 0) for
|
is_open[n in 1:PN, t in 1:T],
|
||||||
n in values(graph.process_nodes), t = 1:T
|
binary = true,
|
||||||
)
|
)
|
||||||
model[:open_plant] = Dict(
|
|
||||||
(n, t) => @variable(model, binary = true) for n in values(graph.process_nodes),
|
# Objective function
|
||||||
t = 1:T
|
@objective(
|
||||||
)
|
model,
|
||||||
model[:is_open] = Dict(
|
Min,
|
||||||
(n, t) => @variable(model, binary = true) for n in values(graph.process_nodes),
|
|
||||||
t = 1:T
|
# Opening, fixed operating costs
|
||||||
)
|
sum(
|
||||||
model[:capacity] = Dict(
|
pn[n].location.sizes[1].opening_cost[t] * open_plant[n, t] +
|
||||||
(n, t) => @variable(
|
pn[n].location.sizes[1].fixed_operating_cost[t] * is_open[n, t]
|
||||||
model,
|
for n in 1:PN
|
||||||
lower_bound = 0,
|
for t in 1:T
|
||||||
upper_bound = n.location.sizes[2].capacity
|
),
|
||||||
) for n in values(graph.process_nodes), t = 1:T
|
)
|
||||||
)
|
|
||||||
model[:expansion] = Dict(
|
for t = 1:T, n in 1:PN
|
||||||
(n, t) => @variable(
|
# Plant is currently open if it was already open in the previous time period or
|
||||||
model,
|
# if it was built just now
|
||||||
lower_bound = 0,
|
if t > 1
|
||||||
upper_bound = n.location.sizes[2].capacity - n.location.sizes[1].capacity
|
@constraint(
|
||||||
) for n in values(graph.process_nodes), t = 1:T
|
model,
|
||||||
)
|
is_open[n, t] == is_open[n, t-1] + open_plant[n, t]
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@constraint(model, is_open[n, t] == open_plant[n, t])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Plant can only be opened during building period
|
||||||
|
if t ∉ instance.building_period
|
||||||
|
@constraint(model, open_plant[n, t] == 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stage 2: Flows, disposal, capacity & storage
|
||||||
|
# =====================================================================
|
||||||
|
@stage 2 begin
|
||||||
|
@uncertain graph
|
||||||
|
pn = graph.process_nodes
|
||||||
|
psn = graph.plant_shipping_nodes
|
||||||
|
csn = graph.collection_shipping_nodes
|
||||||
|
arcs = graph.arcs
|
||||||
|
|
||||||
|
A = length(arcs)
|
||||||
|
PN = length(pn)
|
||||||
|
CSN = length(csn)
|
||||||
|
PSN = length(psn)
|
||||||
|
|
||||||
|
# Var: flow
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
flow[a in 1:A, t in 1:T],
|
||||||
|
lower_bound = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: plant_dispose
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
plant_dispose[n in 1:PSN, t in 1:T],
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = psn[n].location.disposal_limit[psn[n].product][t],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: collection_dispose
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
collection_dispose[n in 1:CSN, t in 1:T],
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = graph.collection_shipping_nodes[n].location.amount[t],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: collection_shortfall
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
collection_shortfall[n in 1:CSN, t in 1:T],
|
||||||
|
lower_bound = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: store
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
store[
|
||||||
|
n in 1:PN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = pn[n].location.storage_limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: process
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
process[
|
||||||
|
n in 1:PN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
lower_bound = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: capacity
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
capacity[
|
||||||
|
n in 1:PN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = pn[n].location.sizes[2].capacity,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Var: expansion
|
||||||
|
@recourse(
|
||||||
|
model,
|
||||||
|
expansion[
|
||||||
|
n in 1:PN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
lower_bound = 0,
|
||||||
|
upper_bound = (
|
||||||
|
pn[n].location.sizes[2].capacity -
|
||||||
|
pn[n].location.sizes[1].capacity
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Objective function
|
||||||
|
@objective(
|
||||||
|
model,
|
||||||
|
Min,
|
||||||
|
sum(
|
||||||
|
# Transportation costs
|
||||||
|
pn[n].location.input.transportation_cost[t] *
|
||||||
|
a.values["distance"] *
|
||||||
|
flow[a.index,t]
|
||||||
|
|
||||||
|
for n in 1:PN
|
||||||
|
for a in pn[n].incoming_arcs
|
||||||
|
for t in 1:T
|
||||||
|
) + sum(
|
||||||
|
# Fixed operating costs (expansion)
|
||||||
|
slope_fix_oper_cost(pn[n].location, t) * expansion[n, t] +
|
||||||
|
|
||||||
|
# Processing costs
|
||||||
|
pn[n].location.sizes[1].variable_operating_cost[t] * process[n, t] +
|
||||||
|
|
||||||
|
# Storage costs
|
||||||
|
pn[n].location.storage_cost[t] * store[n, t] +
|
||||||
|
|
||||||
|
# Expansion costs
|
||||||
|
(
|
||||||
|
t < T ? (
|
||||||
|
(
|
||||||
|
slope_open(pn[n].location, t) -
|
||||||
|
slope_open(pn[n].location, t + 1)
|
||||||
|
) * expansion[n, t]
|
||||||
|
) : slope_open(pn[n].location, t) * expansion[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
for n in 1:PN
|
||||||
|
for t in 1:T
|
||||||
|
) + sum(
|
||||||
|
# Disposal costs (plants)
|
||||||
|
psn[n].location.disposal_cost[psn[n].product][t] * plant_dispose[n, t]
|
||||||
|
for n in 1:PSN
|
||||||
|
for t in 1:T
|
||||||
|
) + sum(
|
||||||
|
# Disposal costs (collection centers)
|
||||||
|
csn[n].location.product.disposal_cost[t] * collection_dispose[n, t]
|
||||||
|
for n in 1:CSN
|
||||||
|
for t in 1:T
|
||||||
|
) + sum(
|
||||||
|
# Collection shortfall
|
||||||
|
1e4 * collection_shortfall[n, t]
|
||||||
|
for n in 1:CSN
|
||||||
|
for t in 1:T
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process node constraints
|
||||||
|
for t = 1:T, n in 1:PN
|
||||||
|
node = pn[n]
|
||||||
|
|
||||||
|
# Output amount is implied by amount processed
|
||||||
|
for arc in node.outgoing_arcs
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
flow[arc.index, t] == arc.values["weight"] * process[n, t]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If plant is closed, capacity is zero
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
capacity[n, t] <= node.location.sizes[2].capacity * is_open[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
# If plant is open, capacity is greater than base
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
capacity[n, t] >= node.location.sizes[1].capacity * is_open[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Capacity is linked to expansion
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
capacity[n, t] <=
|
||||||
|
node.location.sizes[1].capacity + expansion[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Can only process up to capacity
|
||||||
|
@constraint(model, process[n, t] <= capacity[n, t])
|
||||||
|
|
||||||
|
if t > 1
|
||||||
|
# Plant capacity can only increase over time
|
||||||
|
@constraint(model, capacity[n, t] >= capacity[n, t-1])
|
||||||
|
@constraint(model, expansion[n, t] >= expansion[n, t-1])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Amount received equals amount processed plus stored
|
||||||
|
store_in = 0
|
||||||
|
if t > 1
|
||||||
|
store_in = store[n, t-1]
|
||||||
|
end
|
||||||
|
if t == T
|
||||||
|
@constraint(model, store[n, t] == 0)
|
||||||
|
end
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
sum(
|
||||||
|
flow[arc.index, t]
|
||||||
|
for arc in node.incoming_arcs
|
||||||
|
) + store_in == store[n, t] + process[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Material flow at collection shipping nodes
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
eq_balance_centers[
|
||||||
|
n in 1:CSN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
sum(
|
||||||
|
flow[arc.index, t]
|
||||||
|
for arc in csn[n].outgoing_arcs
|
||||||
|
) == csn[n].location.amount[t] - collection_dispose[n, t] - collection_shortfall[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Material flow at plant shipping nodes
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
eq_balance_plant[
|
||||||
|
n in 1:PSN,
|
||||||
|
t in 1:T,
|
||||||
|
],
|
||||||
|
sum(flow[a.index, t] for a in psn[n].incoming_arcs) ==
|
||||||
|
sum(flow[a.index, t] for a in psn[n].outgoing_arcs) +
|
||||||
|
plant_dispose[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enforce product disposal limit at collection centers
|
||||||
|
for t in 1:T, prod in instance.products
|
||||||
|
if isempty(prod.collection_centers)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
@constraint(
|
||||||
|
model,
|
||||||
|
sum(
|
||||||
|
collection_dispose[n, t]
|
||||||
|
for n in 1:CSN
|
||||||
|
if csn[n].product.name == prod.name
|
||||||
|
) <= prod.disposal_limit[t]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ξ = [
|
||||||
|
@scenario graph = graphs[i] probability = probs[i]
|
||||||
|
for i in 1:length(graphs)
|
||||||
|
]
|
||||||
|
|
||||||
|
if method == :ef
|
||||||
|
sp = instantiate(model, ξ; optimizer=optimizer)
|
||||||
|
elseif method == :lshaped
|
||||||
|
sp = instantiate(model, ξ; optimizer=LShaped.Optimizer)
|
||||||
|
set_optimizer_attribute(sp, MasterOptimizer(), optimizer)
|
||||||
|
set_optimizer_attribute(sp, SubProblemOptimizer(), optimizer)
|
||||||
|
set_optimizer_attribute(sp, RelativeTolerance(), tol)
|
||||||
|
else
|
||||||
|
error("unknown method: $method")
|
||||||
|
end
|
||||||
|
|
||||||
|
return sp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -78,172 +362,3 @@ function slope_fix_oper_cost(plant, t)
|
|||||||
(plant.sizes[2].capacity - plant.sizes[1].capacity)
|
(plant.sizes[2].capacity - plant.sizes[1].capacity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_objective_function!(model::JuMP.Model)
|
|
||||||
graph, T = model[:graph], model[:instance].time
|
|
||||||
obj = AffExpr(0.0)
|
|
||||||
|
|
||||||
# Process node costs
|
|
||||||
for n in values(graph.process_nodes), t = 1:T
|
|
||||||
|
|
||||||
# Transportation and variable operating costs
|
|
||||||
for a in n.incoming_arcs
|
|
||||||
c = n.location.input.transportation_cost[t] * a.values["distance"]
|
|
||||||
add_to_expression!(obj, c, model[:flow][a, t])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Opening costs
|
|
||||||
add_to_expression!(
|
|
||||||
obj,
|
|
||||||
n.location.sizes[1].opening_cost[t],
|
|
||||||
model[:open_plant][n, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fixed operating costs (base)
|
|
||||||
add_to_expression!(
|
|
||||||
obj,
|
|
||||||
n.location.sizes[1].fixed_operating_cost[t],
|
|
||||||
model[:is_open][n, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fixed operating costs (expansion)
|
|
||||||
add_to_expression!(obj, slope_fix_oper_cost(n.location, t), model[:expansion][n, t])
|
|
||||||
|
|
||||||
# Processing costs
|
|
||||||
add_to_expression!(
|
|
||||||
obj,
|
|
||||||
n.location.sizes[1].variable_operating_cost[t],
|
|
||||||
model[:process][n, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Storage costs
|
|
||||||
add_to_expression!(obj, n.location.storage_cost[t], model[:store][n, t])
|
|
||||||
|
|
||||||
# Expansion costs
|
|
||||||
if t < T
|
|
||||||
add_to_expression!(
|
|
||||||
obj,
|
|
||||||
slope_open(n.location, t) - slope_open(n.location, t + 1),
|
|
||||||
model[:expansion][n, t],
|
|
||||||
)
|
|
||||||
else
|
|
||||||
add_to_expression!(obj, slope_open(n.location, t), model[:expansion][n, t])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Shipping node costs
|
|
||||||
for n in values(graph.plant_shipping_nodes), t = 1:T
|
|
||||||
|
|
||||||
# Disposal costs
|
|
||||||
add_to_expression!(
|
|
||||||
obj,
|
|
||||||
n.location.disposal_cost[n.product][t],
|
|
||||||
model[:dispose][n, t],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@objective(model, Min, obj)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function create_shipping_node_constraints!(model::JuMP.Model)
|
|
||||||
graph, T = model[:graph], model[:instance].time
|
|
||||||
model[:eq_balance] = OrderedDict()
|
|
||||||
for t = 1:T
|
|
||||||
# Collection centers
|
|
||||||
for n in graph.collection_shipping_nodes
|
|
||||||
model[:eq_balance][n, t] = @constraint(
|
|
||||||
model,
|
|
||||||
sum(model[:flow][a, t] for a in n.outgoing_arcs) == n.location.amount[t]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Plants
|
|
||||||
for n in graph.plant_shipping_nodes
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
sum(model[:flow][a, t] for a in n.incoming_arcs) ==
|
|
||||||
sum(model[:flow][a, t] for a in n.outgoing_arcs) + model[:dispose][n, t]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function create_process_node_constraints!(model::JuMP.Model)
|
|
||||||
graph, T = model[:graph], model[:instance].time
|
|
||||||
|
|
||||||
for t = 1:T, n in graph.process_nodes
|
|
||||||
input_sum = AffExpr(0.0)
|
|
||||||
for a in n.incoming_arcs
|
|
||||||
add_to_expression!(input_sum, 1.0, model[:flow][a, t])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Output amount is implied by amount processed
|
|
||||||
for a in n.outgoing_arcs
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
model[:flow][a, t] == a.values["weight"] * model[:process][n, t]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# If plant is closed, capacity is zero
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
model[:capacity][n, t] <= n.location.sizes[2].capacity * model[:is_open][n, t]
|
|
||||||
)
|
|
||||||
|
|
||||||
# If plant is open, capacity is greater than base
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
model[:capacity][n, t] >= n.location.sizes[1].capacity * model[:is_open][n, t]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Capacity is linked to expansion
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
model[:capacity][n, t] <=
|
|
||||||
n.location.sizes[1].capacity + model[:expansion][n, t]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Can only process up to capacity
|
|
||||||
@constraint(model, model[:process][n, t] <= model[:capacity][n, t])
|
|
||||||
|
|
||||||
if t > 1
|
|
||||||
# Plant capacity can only increase over time
|
|
||||||
@constraint(model, model[:capacity][n, t] >= model[:capacity][n, t-1])
|
|
||||||
@constraint(model, model[:expansion][n, t] >= model[:expansion][n, t-1])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Amount received equals amount processed plus stored
|
|
||||||
store_in = 0
|
|
||||||
if t > 1
|
|
||||||
store_in = model[:store][n, t-1]
|
|
||||||
end
|
|
||||||
if t == T
|
|
||||||
@constraint(model, model[:store][n, t] == 0)
|
|
||||||
end
|
|
||||||
@constraint(
|
|
||||||
model,
|
|
||||||
input_sum + store_in == model[:store][n, t] + model[:process][n, t]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# 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(
|
|
||||||
model,
|
|
||||||
model[:is_open][n, t] == model[:is_open][n, t-1] + model[:open_plant][n, t]
|
|
||||||
)
|
|
||||||
else
|
|
||||||
@constraint(model, model[:is_open][n, t] == model[:open_plant][n, t])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Plant can only be opened during building period
|
|
||||||
if t ∉ model[:instance].building_period
|
|
||||||
@constraint(model, model[:open_plant][n, t] == 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -2,12 +2,33 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures
|
using JuMP, LinearAlgebra, Geodesy, ProgressBars, Printf, DataStructures
|
||||||
|
|
||||||
|
function get_solution(
|
||||||
|
instance,
|
||||||
|
graph,
|
||||||
|
model,
|
||||||
|
scenario_index::Int=1;
|
||||||
|
marginal_costs=false,
|
||||||
|
)
|
||||||
|
value(x) = StochasticPrograms.value(x, scenario_index)
|
||||||
|
ivalue(x) = StochasticPrograms.value(x)
|
||||||
|
shadow_price(x) = StochasticPrograms.shadow_price(x, scenario_index)
|
||||||
|
|
||||||
function get_solution(model::JuMP.Model; marginal_costs = true)
|
|
||||||
graph, instance = model[:graph], model[:instance]
|
|
||||||
T = instance.time
|
T = instance.time
|
||||||
|
|
||||||
|
pn = graph.process_nodes
|
||||||
|
psn = graph.plant_shipping_nodes
|
||||||
|
csn = graph.collection_shipping_nodes
|
||||||
|
arcs = graph.arcs
|
||||||
|
|
||||||
|
A = length(arcs)
|
||||||
|
PN = length(pn)
|
||||||
|
CSN = length(csn)
|
||||||
|
PSN = length(psn)
|
||||||
|
|
||||||
|
flow = model[2, :flow]
|
||||||
|
|
||||||
output = OrderedDict(
|
output = OrderedDict(
|
||||||
"Plants" => OrderedDict(),
|
"Plants" => OrderedDict(),
|
||||||
"Products" => OrderedDict(),
|
"Products" => OrderedDict(),
|
||||||
@@ -29,37 +50,52 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
plant_to_process_node = OrderedDict(n.location => n for n in graph.process_nodes)
|
pn = graph.process_nodes
|
||||||
plant_to_shipping_nodes = OrderedDict()
|
psn = graph.plant_shipping_nodes
|
||||||
for p in instance.plants
|
|
||||||
plant_to_shipping_nodes[p] = []
|
plant_to_process_node_index = OrderedDict(
|
||||||
for a in plant_to_process_node[p].outgoing_arcs
|
pn[n].location => n
|
||||||
push!(plant_to_shipping_nodes[p], a.dest)
|
for n in 1:length(pn)
|
||||||
end
|
)
|
||||||
|
|
||||||
|
plant_to_shipping_node_indices = OrderedDict(p => [] for p in instance.plants)
|
||||||
|
for n in 1:length(psn)
|
||||||
|
push!(plant_to_shipping_node_indices[psn[n].location], n)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Products
|
# Products
|
||||||
if marginal_costs
|
for n in 1:CSN
|
||||||
for n in graph.collection_shipping_nodes
|
node = csn[n]
|
||||||
location_dict = OrderedDict{Any,Any}(
|
location_dict = OrderedDict{Any,Any}(
|
||||||
"Marginal cost (\$/tonne)" => [
|
"Latitude (deg)" => node.location.latitude,
|
||||||
round(abs(JuMP.shadow_price(model[:eq_balance][n, t])), digits = 2) for t = 1:T
|
"Longitude (deg)" => node.location.longitude,
|
||||||
],
|
"Amount (tonne)" => node.location.amount,
|
||||||
"Latitude (deg)" => n.location.latitude,
|
"Dispose (tonne)" => [
|
||||||
"Longitude (deg)" => n.location.longitude,
|
value(model[2, :collection_dispose][n, t])
|
||||||
"Amount (tonne)" => n.location.amount,
|
for t = 1:T
|
||||||
)
|
],
|
||||||
if n.product.name ∉ keys(output["Products"])
|
"Disposal cost (\$)" => [
|
||||||
output["Products"][n.product.name] = OrderedDict()
|
value(model[2, :collection_dispose][n, t]) *
|
||||||
end
|
node.location.product.disposal_cost[t]
|
||||||
output["Products"][n.product.name][n.location.name] = location_dict
|
for t = 1:T
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if marginal_costs
|
||||||
|
location_dict["Marginal cost (\$/tonne)"] = [
|
||||||
|
round(abs(shadow_price(model[2, :eq_balance_centers][n, t])), digits=2) for t = 1:T
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
if node.product.name ∉ keys(output["Products"])
|
||||||
|
output["Products"][node.product.name] = OrderedDict()
|
||||||
|
end
|
||||||
|
output["Products"][node.product.name][node.location.name] = location_dict
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plants
|
# Plants
|
||||||
for plant in instance.plants
|
for plant in instance.plants
|
||||||
skip_plant = true
|
skip_plant = true
|
||||||
process_node = plant_to_process_node[plant]
|
n = plant_to_process_node_index[plant]
|
||||||
|
process_node = pn[n]
|
||||||
plant_dict = OrderedDict{Any,Any}(
|
plant_dict = OrderedDict{Any,Any}(
|
||||||
"Input" => OrderedDict(),
|
"Input" => OrderedDict(),
|
||||||
"Output" =>
|
"Output" =>
|
||||||
@@ -70,39 +106,39 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
"Latitude (deg)" => plant.latitude,
|
"Latitude (deg)" => plant.latitude,
|
||||||
"Longitude (deg)" => plant.longitude,
|
"Longitude (deg)" => plant.longitude,
|
||||||
"Capacity (tonne)" =>
|
"Capacity (tonne)" =>
|
||||||
[JuMP.value(model[:capacity][process_node, t]) for t = 1:T],
|
[value(model[2, :capacity][n, t]) for t = 1:T],
|
||||||
"Opening cost (\$)" => [
|
"Opening cost (\$)" => [
|
||||||
JuMP.value(model[:open_plant][process_node, t]) *
|
ivalue(model[1, :open_plant][n, t]) *
|
||||||
plant.sizes[1].opening_cost[t] for t = 1:T
|
plant.sizes[1].opening_cost[t] for t = 1:T
|
||||||
],
|
],
|
||||||
"Fixed operating cost (\$)" => [
|
"Fixed operating cost (\$)" => [
|
||||||
JuMP.value(model[:is_open][process_node, t]) *
|
ivalue(model[1, :is_open][n, t]) *
|
||||||
plant.sizes[1].fixed_operating_cost[t] +
|
plant.sizes[1].fixed_operating_cost[t] +
|
||||||
JuMP.value(model[:expansion][process_node, t]) *
|
value(model[2, :expansion][n, t]) *
|
||||||
slope_fix_oper_cost(plant, t) for t = 1:T
|
slope_fix_oper_cost(plant, t) for t = 1:T
|
||||||
],
|
],
|
||||||
"Expansion cost (\$)" => [
|
"Expansion cost (\$)" => [
|
||||||
(
|
(
|
||||||
if t == 1
|
if t == 1
|
||||||
slope_open(plant, t) * JuMP.value(model[:expansion][process_node, t])
|
slope_open(plant, t) * value(model[2, :expansion][n, t])
|
||||||
else
|
else
|
||||||
slope_open(plant, t) * (
|
slope_open(plant, t) * (
|
||||||
JuMP.value(model[:expansion][process_node, t]) -
|
value(model[2, :expansion][n, t]) -
|
||||||
JuMP.value(model[:expansion][process_node, t-1])
|
value(model[2, :expansion][n, t-1])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
) for t = 1:T
|
) for t = 1:T
|
||||||
],
|
],
|
||||||
"Process (tonne)" =>
|
"Process (tonne)" =>
|
||||||
[JuMP.value(model[:process][process_node, t]) for t = 1:T],
|
[value(model[2, :process][n, t]) for t = 1:T],
|
||||||
"Variable operating cost (\$)" => [
|
"Variable operating cost (\$)" => [
|
||||||
JuMP.value(model[:process][process_node, t]) *
|
value(model[2, :process][n, t]) *
|
||||||
plant.sizes[1].variable_operating_cost[t] for t = 1:T
|
plant.sizes[1].variable_operating_cost[t] for t = 1:T
|
||||||
],
|
],
|
||||||
"Storage (tonne)" =>
|
"Storage (tonne)" =>
|
||||||
[JuMP.value(model[:store][process_node, t]) for t = 1:T],
|
[value(model[2, :store][n, t]) for t = 1:T],
|
||||||
"Storage cost (\$)" => [
|
"Storage cost (\$)" => [
|
||||||
JuMP.value(model[:store][process_node, t]) * plant.storage_cost[t]
|
value(model[2, :store][n, t]) * plant.storage_cost[t]
|
||||||
for t = 1:T
|
for t = 1:T
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -115,7 +151,7 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
for a in process_node.incoming_arcs
|
for a in process_node.incoming_arcs
|
||||||
vals = [JuMP.value(model[:flow][a, t]) for t = 1:T]
|
vals = [value(flow[a.index, t]) for t = 1:T]
|
||||||
if sum(vals) <= 1e-3
|
if sum(vals) <= 1e-3
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
@@ -173,18 +209,20 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
for shipping_node in plant_to_shipping_nodes[plant]
|
for n2 in plant_to_shipping_node_indices[plant]
|
||||||
|
shipping_node = psn[n2]
|
||||||
product_name = shipping_node.product.name
|
product_name = shipping_node.product.name
|
||||||
plant_dict["Total output"][product_name] = zeros(T)
|
plant_dict["Total output"][product_name] = zeros(T)
|
||||||
plant_dict["Output"]["Send"][product_name] = product_dict = OrderedDict()
|
plant_dict["Output"]["Send"][product_name] = product_dict = OrderedDict()
|
||||||
|
|
||||||
disposal_amount = [JuMP.value(model[:dispose][shipping_node, t]) for t = 1:T]
|
disposal_amount =
|
||||||
|
[value(model[2, :plant_dispose][n2, t]) for t = 1:T]
|
||||||
if sum(disposal_amount) > 1e-5
|
if sum(disposal_amount) > 1e-5
|
||||||
skip_plant = false
|
skip_plant = false
|
||||||
plant_dict["Output"]["Dispose"][product_name] =
|
plant_dict["Output"]["Dispose"][product_name] =
|
||||||
disposal_dict = OrderedDict()
|
disposal_dict = OrderedDict()
|
||||||
disposal_dict["Amount (tonne)"] =
|
disposal_dict["Amount (tonne)"] =
|
||||||
[JuMP.value(model[:dispose][shipping_node, t]) for t = 1:T]
|
[value(model[2, :plant_dispose][n2, t]) for t = 1:T]
|
||||||
disposal_dict["Cost (\$)"] = [
|
disposal_dict["Cost (\$)"] = [
|
||||||
disposal_dict["Amount (tonne)"][t] *
|
disposal_dict["Amount (tonne)"][t] *
|
||||||
plant.disposal_cost[shipping_node.product][t] for t = 1:T
|
plant.disposal_cost[shipping_node.product][t] for t = 1:T
|
||||||
@@ -194,7 +232,7 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for a in shipping_node.outgoing_arcs
|
for a in shipping_node.outgoing_arcs
|
||||||
vals = [JuMP.value(model[:flow][a, t]) for t = 1:T]
|
vals = [value(flow[a.index, t]) for t = 1:T]
|
||||||
if sum(vals) <= 1e-3
|
if sum(vals) <= 1e-3
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
# RELOG: Reverse Logistics Optimization
|
|
||||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
|
|
||||||
using JuMP
|
|
||||||
|
|
||||||
function resolve(model_old, filename::AbstractString; kwargs...)::OrderedDict
|
|
||||||
@info "Reading $filename..."
|
|
||||||
instance = RELOG.parsefile(filename)
|
|
||||||
return resolve(model_old, instance; kwargs...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function resolve(model_old, instance::Instance; optimizer = nothing)::OrderedDict
|
|
||||||
milp_optimizer = lp_optimizer = optimizer
|
|
||||||
if optimizer === nothing
|
|
||||||
milp_optimizer = _get_default_milp_optimizer()
|
|
||||||
lp_optimizer = _get_default_lp_optimizer()
|
|
||||||
end
|
|
||||||
|
|
||||||
@info "Building new graph..."
|
|
||||||
graph = build_graph(instance)
|
|
||||||
_print_graph_stats(instance, graph)
|
|
||||||
|
|
||||||
@info "Building new optimization model..."
|
|
||||||
model_new = RELOG.build_model(instance, graph, milp_optimizer)
|
|
||||||
|
|
||||||
@info "Fixing decision variables..."
|
|
||||||
_fix_plants!(model_old, model_new)
|
|
||||||
JuMP.set_optimizer(model_new, lp_optimizer)
|
|
||||||
|
|
||||||
@info "Optimizing MILP..."
|
|
||||||
JuMP.optimize!(model_new)
|
|
||||||
|
|
||||||
if !has_values(model_new)
|
|
||||||
@warn("No solution available")
|
|
||||||
return OrderedDict()
|
|
||||||
end
|
|
||||||
|
|
||||||
@info "Extracting solution..."
|
|
||||||
solution = get_solution(model_new, marginal_costs = true)
|
|
||||||
|
|
||||||
return solution
|
|
||||||
end
|
|
||||||
|
|
||||||
function _fix_plants!(model_old, model_new)::Nothing
|
|
||||||
T = model_new[:instance].time
|
|
||||||
|
|
||||||
# Fix open_plant variables
|
|
||||||
for ((node_old, t), var_old) in model_old[:open_plant]
|
|
||||||
value_old = JuMP.value(var_old)
|
|
||||||
node_new = model_new[:graph].name_to_process_node_map[(
|
|
||||||
node_old.location.plant_name,
|
|
||||||
node_old.location.location_name,
|
|
||||||
)]
|
|
||||||
var_new = model_new[:open_plant][node_new, t]
|
|
||||||
JuMP.unset_binary(var_new)
|
|
||||||
JuMP.fix(var_new, value_old)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fix is_open variables
|
|
||||||
for ((node_old, t), var_old) in model_old[:is_open]
|
|
||||||
value_old = JuMP.value(var_old)
|
|
||||||
node_new = model_new[:graph].name_to_process_node_map[(
|
|
||||||
node_old.location.plant_name,
|
|
||||||
node_old.location.location_name,
|
|
||||||
)]
|
|
||||||
var_new = model_new[:is_open][node_new, t]
|
|
||||||
JuMP.unset_binary(var_new)
|
|
||||||
JuMP.fix(var_new, value_old)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fix plant capacities
|
|
||||||
for ((node_old, t), var_old) in model_old[:capacity]
|
|
||||||
value_old = JuMP.value(var_old)
|
|
||||||
node_new = model_new[:graph].name_to_process_node_map[(
|
|
||||||
node_old.location.plant_name,
|
|
||||||
node_old.location.location_name,
|
|
||||||
)]
|
|
||||||
var_new = model_new[:capacity][node_new, t]
|
|
||||||
JuMP.delete_lower_bound(var_new)
|
|
||||||
JuMP.delete_upper_bound(var_new)
|
|
||||||
JuMP.fix(var_new, value_old)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fix plant expansion
|
|
||||||
for ((node_old, t), var_old) in model_old[:expansion]
|
|
||||||
value_old = JuMP.value(var_old)
|
|
||||||
node_new = model_new[:graph].name_to_process_node_map[(
|
|
||||||
node_old.location.plant_name,
|
|
||||||
node_old.location.location_name,
|
|
||||||
)]
|
|
||||||
var_new = model_new[:expansion][node_new, t]
|
|
||||||
JuMP.delete_lower_bound(var_new)
|
|
||||||
JuMP.delete_upper_bound(var_new)
|
|
||||||
JuMP.fix(var_new, value_old)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars, Printf, DataStructures
|
using JuMP, LinearAlgebra, Geodesy, HiGHS, ProgressBars, Printf, DataStructures
|
||||||
|
|
||||||
function _get_default_milp_optimizer()
|
function _get_default_milp_optimizer()
|
||||||
return optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
|
return optimizer_with_attributes(HiGHS.Optimizer)
|
||||||
end
|
end
|
||||||
|
|
||||||
function _get_default_lp_optimizer()
|
function _get_default_lp_optimizer()
|
||||||
return optimizer_with_attributes(Clp.Optimizer, "LogLevel" => 0)
|
return optimizer_with_attributes(HiGHS.Optimizer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -25,53 +25,81 @@ function _print_graph_stats(instance::Instance, graph::Graph)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function solve_stochastic(;
|
||||||
|
scenarios::Vector{String},
|
||||||
|
probs::Vector{Float64},
|
||||||
|
optimizer,
|
||||||
|
method=:ef,
|
||||||
|
tol=0.1,
|
||||||
|
)
|
||||||
|
@info "Reading instance files..."
|
||||||
|
instances = [parsefile(sc) for sc in scenarios]
|
||||||
|
|
||||||
|
@info "Building graphs..."
|
||||||
|
graphs = [build_graph(inst) for inst in instances]
|
||||||
|
|
||||||
|
@info "Building stochastic model..."
|
||||||
|
sp = RELOG.build_model(instances[1], graphs, probs; optimizer, method, tol)
|
||||||
|
|
||||||
|
@info "Optimizing stochastic model..."
|
||||||
|
optimize!(sp)
|
||||||
|
|
||||||
|
@info "Extracting solution..."
|
||||||
|
solutions = [
|
||||||
|
get_solution(instances[i], graphs[i], sp, i)
|
||||||
|
for i in 1:length(instances)
|
||||||
|
]
|
||||||
|
|
||||||
|
return solutions
|
||||||
|
end
|
||||||
|
|
||||||
function solve(
|
function solve(
|
||||||
instance::Instance;
|
instance::Instance;
|
||||||
optimizer = nothing,
|
optimizer=HiGHS.Optimizer,
|
||||||
output = nothing,
|
marginal_costs=true,
|
||||||
marginal_costs = true,
|
return_model=false
|
||||||
return_model = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
milp_optimizer = lp_optimizer = optimizer
|
|
||||||
if optimizer == nothing
|
|
||||||
milp_optimizer = _get_default_milp_optimizer()
|
|
||||||
lp_optimizer = _get_default_lp_optimizer()
|
|
||||||
end
|
|
||||||
|
|
||||||
@info "Building graph..."
|
@info "Building graph..."
|
||||||
graph = RELOG.build_graph(instance)
|
graph = RELOG.build_graph(instance)
|
||||||
_print_graph_stats(instance, graph)
|
_print_graph_stats(instance, graph)
|
||||||
|
|
||||||
@info "Building optimization model..."
|
@info "Building model..."
|
||||||
model = RELOG.build_model(instance, graph, milp_optimizer)
|
model = RELOG.build_model(instance, [graph], [1.0]; optimizer)
|
||||||
|
|
||||||
@info "Optimizing MILP..."
|
|
||||||
JuMP.optimize!(model)
|
|
||||||
|
|
||||||
|
@info "Optimizing model..."
|
||||||
|
optimize!(model)
|
||||||
if !has_values(model)
|
if !has_values(model)
|
||||||
error("No solution available")
|
error("No solution available")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@info "Extracting solution..."
|
||||||
|
solution = get_solution(instance, graph, model, 1)
|
||||||
|
|
||||||
if marginal_costs
|
if marginal_costs
|
||||||
@info "Re-optimizing with integer variables fixed..."
|
@info "Re-optimizing with integer variables fixed..."
|
||||||
all_vars = JuMP.all_variables(model)
|
open_plant_vals = value.(model[1, :open_plant])
|
||||||
vals = OrderedDict(var => JuMP.value(var) for var in all_vars)
|
is_open_vals = value.(model[1, :is_open])
|
||||||
JuMP.set_optimizer(model, lp_optimizer)
|
|
||||||
for var in all_vars
|
for n in 1:length(graph.process_nodes), t in 1:instance.time
|
||||||
if JuMP.is_binary(var)
|
unset_binary(model[1, :open_plant][n, t])
|
||||||
JuMP.unset_binary(var)
|
unset_binary(model[1, :is_open][n, t])
|
||||||
JuMP.fix(var, vals[var])
|
fix(
|
||||||
end
|
model[1, :open_plant][n, t],
|
||||||
|
open_plant_vals[n, t]
|
||||||
|
)
|
||||||
|
fix(
|
||||||
|
model[1, :is_open][n, t],
|
||||||
|
is_open_vals[n, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
optimize!(model)
|
||||||
|
if has_values(model)
|
||||||
|
@info "Extracting solution..."
|
||||||
|
solution = get_solution(instance, graph, model, 1, marginal_costs=true)
|
||||||
|
else
|
||||||
|
@warn "Error computing marginal costs. Ignoring."
|
||||||
end
|
end
|
||||||
JuMP.optimize!(model)
|
|
||||||
end
|
|
||||||
|
|
||||||
@info "Extracting solution..."
|
|
||||||
solution = get_solution(model, marginal_costs = marginal_costs)
|
|
||||||
|
|
||||||
if output != nothing
|
|
||||||
write(solution, output)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if return_model
|
if return_model
|
||||||
@@ -81,13 +109,13 @@ function solve(
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function solve(filename::AbstractString; heuristic = false, kwargs...)
|
function solve(filename::AbstractString; heuristic=false, kwargs...)
|
||||||
@info "Reading $filename..."
|
@info "Reading $filename..."
|
||||||
instance = RELOG.parsefile(filename)
|
instance = RELOG.parsefile(filename)
|
||||||
if heuristic && instance.time > 1
|
if heuristic && instance.time > 1
|
||||||
@info "Solving single-period version..."
|
@info "Solving single-period version..."
|
||||||
compressed = _compress(instance)
|
compressed = _compress(instance)
|
||||||
csol = solve(compressed; output = nothing, marginal_costs = false, kwargs...)
|
csol, model = solve(compressed; marginal_costs=false, return_model=true, kwargs...)
|
||||||
@info "Filtering candidate locations..."
|
@info "Filtering candidate locations..."
|
||||||
selected_pairs = []
|
selected_pairs = []
|
||||||
for (plant_name, plant_dict) in csol["Plants"]
|
for (plant_name, plant_dict) in csol["Plants"]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
using DataFrames
|
using DataFrames
|
||||||
using CSV
|
using CSV
|
||||||
|
|
||||||
function products_report(solution; marginal_costs = true)::DataFrame
|
function products_report(solution)::DataFrame
|
||||||
df = DataFrame()
|
df = DataFrame()
|
||||||
df."product name" = String[]
|
df."product name" = String[]
|
||||||
df."location name" = String[]
|
df."location name" = String[]
|
||||||
@@ -14,14 +14,21 @@ function products_report(solution; marginal_costs = true)::DataFrame
|
|||||||
df."year" = Int[]
|
df."year" = Int[]
|
||||||
df."amount (tonne)" = Float64[]
|
df."amount (tonne)" = Float64[]
|
||||||
df."marginal cost (\$/tonne)" = Float64[]
|
df."marginal cost (\$/tonne)" = Float64[]
|
||||||
|
df."amount disposed (tonne)" = Float64[]
|
||||||
|
df."disposal cost (\$)" = Float64[]
|
||||||
T = length(solution["Energy"]["Plants (GJ)"])
|
T = length(solution["Energy"]["Plants (GJ)"])
|
||||||
for (prod_name, prod_dict) in solution["Products"]
|
for (prod_name, prod_dict) in solution["Products"]
|
||||||
for (location_name, location_dict) in prod_dict
|
for (location_name, location_dict) in prod_dict
|
||||||
for year = 1:T
|
for year = 1:T
|
||||||
marginal_cost = location_dict["Marginal cost (\$/tonne)"][year]
|
marginal_cost = NaN
|
||||||
|
if "Marginal cost (\$/tonne)" in keys(location_dict)
|
||||||
|
marginal_cost = location_dict["Marginal cost (\$/tonne)"][year]
|
||||||
|
end
|
||||||
latitude = round(location_dict["Latitude (deg)"], digits = 6)
|
latitude = round(location_dict["Latitude (deg)"], digits = 6)
|
||||||
longitude = round(location_dict["Longitude (deg)"], digits = 6)
|
longitude = round(location_dict["Longitude (deg)"], digits = 6)
|
||||||
amount = location_dict["Amount (tonne)"][year]
|
amount = location_dict["Amount (tonne)"][year]
|
||||||
|
amount_disposed = location_dict["Dispose (tonne)"][year]
|
||||||
|
disposal_cost = location_dict["Disposal cost (\$)"][year]
|
||||||
push!(
|
push!(
|
||||||
df,
|
df,
|
||||||
[
|
[
|
||||||
@@ -32,6 +39,8 @@ function products_report(solution; marginal_costs = true)::DataFrame
|
|||||||
year,
|
year,
|
||||||
amount,
|
amount,
|
||||||
marginal_cost,
|
marginal_cost,
|
||||||
|
amount_disposed,
|
||||||
|
disposal_cost,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -169,6 +169,12 @@
|
|||||||
},
|
},
|
||||||
"initial amounts": {
|
"initial amounts": {
|
||||||
"$ref": "#/definitions/InitialAmount"
|
"$ref": "#/definitions/InitialAmount"
|
||||||
|
},
|
||||||
|
"disposal limit (tonne)": {
|
||||||
|
"$ref": "#/definitions/TimeSeries"
|
||||||
|
},
|
||||||
|
"disposal cost ($/tonne)": {
|
||||||
|
"$ref": "#/definitions/TimeSeries"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
using PackageCompiler
|
using PackageCompiler
|
||||||
|
using TOML
|
||||||
|
using Logging
|
||||||
|
|
||||||
using Cbc
|
Logging.disable_logging(Logging.Info)
|
||||||
using Clp
|
|
||||||
using Geodesy
|
|
||||||
using JSON
|
|
||||||
using JSONSchema
|
|
||||||
using JuMP
|
|
||||||
using MathOptInterface
|
|
||||||
using ProgressBars
|
|
||||||
|
|
||||||
pkg = [:Cbc, :Clp, :Geodesy, :JSON, :JSONSchema, :JuMP, :MathOptInterface, :ProgressBars]
|
mkpath("build")
|
||||||
|
|
||||||
@info "Building system image..."
|
printstyled("Generating precompilation statements...\n", color = :light_green)
|
||||||
create_sysimage(pkg, sysimage_path = "build/sysimage.so")
|
run(`julia --project=. --trace-compile=build/precompile.jl $ARGS`)
|
||||||
|
|
||||||
|
printstyled("Finding dependencies...\n", color = :light_green)
|
||||||
|
project = TOML.parsefile("Project.toml")
|
||||||
|
manifest = TOML.parsefile("Manifest.toml")
|
||||||
|
deps = Symbol[]
|
||||||
|
for dep in keys(project["deps"])
|
||||||
|
if "path" in keys(manifest[dep][1])
|
||||||
|
printstyled(" skip $(dep)\n", color = :light_black)
|
||||||
|
else
|
||||||
|
println(" add $(dep)")
|
||||||
|
push!(deps, Symbol(dep))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
printstyled("Building system image...\n", color = :light_green)
|
||||||
|
create_sysimage(
|
||||||
|
deps,
|
||||||
|
precompile_statements_file = "build/precompile.jl",
|
||||||
|
sysimage_path = "build/sysimage.so",
|
||||||
|
)
|
||||||
|
|||||||
4406
test/fixtures/instances/case3_p010_s1.00.json
vendored
Normal file
4406
test/fixtures/instances/case3_p010_s1.00.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4406
test/fixtures/instances/case3_p010_s1.25.json
vendored
Normal file
4406
test/fixtures/instances/case3_p010_s1.25.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
357
test/fixtures/instances/s1.json
vendored
Normal file
357
test/fixtures/instances/s1.json
vendored
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"time horizon (years)": 2
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"P1": {
|
||||||
|
"transportation cost ($/km/tonne)": [
|
||||||
|
0.015,
|
||||||
|
0.015
|
||||||
|
],
|
||||||
|
"transportation energy (J/km/tonne)": [
|
||||||
|
0.12,
|
||||||
|
0.11
|
||||||
|
],
|
||||||
|
"transportation emissions (tonne/km/tonne)": {
|
||||||
|
"CO2": [
|
||||||
|
0.052,
|
||||||
|
0.050
|
||||||
|
],
|
||||||
|
"CH4": [
|
||||||
|
0.003,
|
||||||
|
0.002
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"initial amounts": {
|
||||||
|
"C1": {
|
||||||
|
"latitude (deg)": 7.0,
|
||||||
|
"longitude (deg)": 7.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
934.56,
|
||||||
|
934.56
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C2": {
|
||||||
|
"latitude (deg)": 7.0,
|
||||||
|
"longitude (deg)": 19.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
198.95,
|
||||||
|
198.95
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C3": {
|
||||||
|
"latitude (deg)": 84.0,
|
||||||
|
"longitude (deg)": 76.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
212.97,
|
||||||
|
212.97
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C4": {
|
||||||
|
"latitude (deg)": 21.0,
|
||||||
|
"longitude (deg)": 16.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
352.19,
|
||||||
|
352.19
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C5": {
|
||||||
|
"latitude (deg)": 32.0,
|
||||||
|
"longitude (deg)": 92.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
510.33,
|
||||||
|
510.33
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C6": {
|
||||||
|
"latitude (deg)": 14.0,
|
||||||
|
"longitude (deg)": 62.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
471.66,
|
||||||
|
471.66
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C7": {
|
||||||
|
"latitude (deg)": 30.0,
|
||||||
|
"longitude (deg)": 83.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
785.21,
|
||||||
|
785.21
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C8": {
|
||||||
|
"latitude (deg)": 35.0,
|
||||||
|
"longitude (deg)": 40.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
706.17,
|
||||||
|
706.17
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C9": {
|
||||||
|
"latitude (deg)": 74.0,
|
||||||
|
"longitude (deg)": 52.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
30.08,
|
||||||
|
30.08
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"C10": {
|
||||||
|
"latitude (deg)": 22.0,
|
||||||
|
"longitude (deg)": 54.0,
|
||||||
|
"amount (tonne)": [
|
||||||
|
536.52,
|
||||||
|
536.52
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disposal limit (tonne)": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"disposal cost ($/tonne)": [
|
||||||
|
-1000,
|
||||||
|
-1000
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"P2": {
|
||||||
|
"transportation cost ($/km/tonne)": [
|
||||||
|
0.02,
|
||||||
|
0.02
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"P3": {
|
||||||
|
"transportation cost ($/km/tonne)": [
|
||||||
|
0.0125,
|
||||||
|
0.0125
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"P4": {
|
||||||
|
"transportation cost ($/km/tonne)": [
|
||||||
|
0.0175,
|
||||||
|
0.0175
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plants": {
|
||||||
|
"F1": {
|
||||||
|
"input": "P1",
|
||||||
|
"outputs (tonne/tonne)": {
|
||||||
|
"P2": 0.2,
|
||||||
|
"P3": 0.5
|
||||||
|
},
|
||||||
|
"energy (GJ/tonne)": [
|
||||||
|
0.12,
|
||||||
|
0.11
|
||||||
|
],
|
||||||
|
"emissions (tonne/tonne)": {
|
||||||
|
"CO2": [
|
||||||
|
0.052,
|
||||||
|
0.050
|
||||||
|
],
|
||||||
|
"CH4": [
|
||||||
|
0.003,
|
||||||
|
0.002
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"L1": {
|
||||||
|
"latitude (deg)": 0.0,
|
||||||
|
"longitude (deg)": 0.0,
|
||||||
|
"disposal": {
|
||||||
|
"P2": {
|
||||||
|
"cost ($/tonne)": [
|
||||||
|
-10.0,
|
||||||
|
-10.0
|
||||||
|
],
|
||||||
|
"limit (tonne)": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"P3": {
|
||||||
|
"cost ($/tonne)": [
|
||||||
|
-10.0,
|
||||||
|
-10.0
|
||||||
|
],
|
||||||
|
"limit (tonne)": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"250.0": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
500.0,
|
||||||
|
500.0
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
30.0,
|
||||||
|
30.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
30.0,
|
||||||
|
30.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"1000.0": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
1250.0,
|
||||||
|
1250.0
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
30.0,
|
||||||
|
30.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
30.0,
|
||||||
|
30.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"L2": {
|
||||||
|
"latitude (deg)": 0.5,
|
||||||
|
"longitude (deg)": 0.5,
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"0.0": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
1000,
|
||||||
|
1000
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"10000.0": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
10000,
|
||||||
|
10000
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"F2": {
|
||||||
|
"input": "P2",
|
||||||
|
"outputs (tonne/tonne)": {
|
||||||
|
"P3": 0.05,
|
||||||
|
"P4": 0.80
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"L3": {
|
||||||
|
"latitude (deg)": 25.0,
|
||||||
|
"longitude (deg)": 65.0,
|
||||||
|
"disposal": {
|
||||||
|
"P3": {
|
||||||
|
"cost ($/tonne)": [
|
||||||
|
100.0,
|
||||||
|
100.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"1000.0": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
3000,
|
||||||
|
3000
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"L4": {
|
||||||
|
"latitude (deg)": 0.75,
|
||||||
|
"longitude (deg)": 0.20,
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"10000": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
3000,
|
||||||
|
3000
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
50.0,
|
||||||
|
50.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"F3": {
|
||||||
|
"input": "P4",
|
||||||
|
"locations": {
|
||||||
|
"L5": {
|
||||||
|
"latitude (deg)": 100.0,
|
||||||
|
"longitude (deg)": 100.0,
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"15000": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
-15.0,
|
||||||
|
-15.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"F4": {
|
||||||
|
"input": "P3",
|
||||||
|
"locations": {
|
||||||
|
"L6": {
|
||||||
|
"latitude (deg)": 50.0,
|
||||||
|
"longitude (deg)": 50.0,
|
||||||
|
"capacities (tonne)": {
|
||||||
|
"10000": {
|
||||||
|
"opening cost ($)": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"fixed operating cost ($)": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"variable operating cost ($/tonne)": [
|
||||||
|
-15.0,
|
||||||
|
-15.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,37 +3,38 @@
|
|||||||
|
|
||||||
using RELOG
|
using RELOG
|
||||||
|
|
||||||
@testset "build_graph" begin
|
function graph_build_test()
|
||||||
basedir = dirname(@__FILE__)
|
@testset "build_graph" begin
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
instance = RELOG.parsefile(fixture("instances/s1.json"))
|
||||||
graph = RELOG.build_graph(instance)
|
graph = RELOG.build_graph(instance)
|
||||||
process_node_by_location_name =
|
process_node_by_location_name =
|
||||||
Dict(n.location.location_name => n for n in graph.process_nodes)
|
Dict(n.location.location_name => n for n in graph.process_nodes)
|
||||||
|
|
||||||
@test length(graph.plant_shipping_nodes) == 8
|
@test length(graph.plant_shipping_nodes) == 8
|
||||||
@test length(graph.collection_shipping_nodes) == 10
|
@test length(graph.collection_shipping_nodes) == 10
|
||||||
@test length(graph.process_nodes) == 6
|
@test length(graph.process_nodes) == 6
|
||||||
|
|
||||||
node = graph.collection_shipping_nodes[1]
|
node = graph.collection_shipping_nodes[1]
|
||||||
@test node.location.name == "C1"
|
@test node.location.name == "C1"
|
||||||
@test length(node.incoming_arcs) == 0
|
@test length(node.incoming_arcs) == 0
|
||||||
@test length(node.outgoing_arcs) == 2
|
@test length(node.outgoing_arcs) == 2
|
||||||
@test node.outgoing_arcs[1].source.location.name == "C1"
|
@test node.outgoing_arcs[1].source.location.name == "C1"
|
||||||
@test node.outgoing_arcs[1].dest.location.plant_name == "F1"
|
@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].dest.location.location_name == "L1"
|
||||||
@test node.outgoing_arcs[1].values["distance"] == 1095.62
|
@test node.outgoing_arcs[1].values["distance"] == 1095.62
|
||||||
|
|
||||||
node = process_node_by_location_name["L1"]
|
node = process_node_by_location_name["L1"]
|
||||||
@test node.location.plant_name == "F1"
|
@test node.location.plant_name == "F1"
|
||||||
@test node.location.location_name == "L1"
|
@test node.location.location_name == "L1"
|
||||||
@test length(node.incoming_arcs) == 10
|
@test length(node.incoming_arcs) == 10
|
||||||
@test length(node.outgoing_arcs) == 2
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
node = process_node_by_location_name["L3"]
|
node = process_node_by_location_name["L3"]
|
||||||
@test node.location.plant_name == "F2"
|
@test node.location.plant_name == "F2"
|
||||||
@test node.location.location_name == "L3"
|
@test node.location.location_name == "L3"
|
||||||
@test length(node.incoming_arcs) == 2
|
@test length(node.incoming_arcs) == 2
|
||||||
@test length(node.outgoing_arcs) == 2
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
@test length(graph.arcs) == 38
|
@test length(graph.arcs) == 38
|
||||||
end
|
end
|
||||||
|
end
|
||||||
@@ -3,51 +3,52 @@
|
|||||||
|
|
||||||
using RELOG
|
using RELOG
|
||||||
|
|
||||||
@testset "compress" begin
|
function compress_test()
|
||||||
basedir = dirname(@__FILE__)
|
@testset "compress" begin
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
instance = RELOG.parsefile(fixture("instances/s1.json"))
|
||||||
compressed = RELOG._compress(instance)
|
compressed = RELOG._compress(instance)
|
||||||
|
|
||||||
product_name_to_product = Dict(p.name => p for p in compressed.products)
|
product_name_to_product = Dict(p.name => p for p in compressed.products)
|
||||||
location_name_to_facility = Dict()
|
location_name_to_facility = Dict()
|
||||||
for p in compressed.plants
|
for p in compressed.plants
|
||||||
location_name_to_facility[p.location_name] = p
|
location_name_to_facility[p.location_name] = p
|
||||||
|
end
|
||||||
|
for c in compressed.collection_centers
|
||||||
|
location_name_to_facility[c.name] = c
|
||||||
|
end
|
||||||
|
|
||||||
|
p1 = product_name_to_product["P1"]
|
||||||
|
p2 = product_name_to_product["P2"]
|
||||||
|
p3 = product_name_to_product["P3"]
|
||||||
|
c1 = location_name_to_facility["C1"]
|
||||||
|
l1 = location_name_to_facility["L1"]
|
||||||
|
|
||||||
|
@test compressed.time == 1
|
||||||
|
@test compressed.building_period == [1]
|
||||||
|
|
||||||
|
@test p1.name == "P1"
|
||||||
|
@test p1.transportation_cost ≈ [0.015]
|
||||||
|
@test p1.transportation_energy ≈ [0.115]
|
||||||
|
@test p1.transportation_emissions["CO2"] ≈ [0.051]
|
||||||
|
@test p1.transportation_emissions["CH4"] ≈ [0.0025]
|
||||||
|
|
||||||
|
@test c1.name == "C1"
|
||||||
|
@test c1.amount ≈ [1869.12]
|
||||||
|
|
||||||
|
@test l1.plant_name == "F1"
|
||||||
|
@test l1.location_name == "L1"
|
||||||
|
@test l1.energy ≈ [0.115]
|
||||||
|
@test l1.emissions["CO2"] ≈ [0.051]
|
||||||
|
@test l1.emissions["CH4"] ≈ [0.0025]
|
||||||
|
@test l1.sizes[1].opening_cost ≈ [500]
|
||||||
|
@test l1.sizes[2].opening_cost ≈ [1250]
|
||||||
|
@test l1.sizes[1].fixed_operating_cost ≈ [60]
|
||||||
|
@test l1.sizes[2].fixed_operating_cost ≈ [60]
|
||||||
|
@test l1.sizes[1].variable_operating_cost ≈ [30]
|
||||||
|
@test l1.sizes[2].variable_operating_cost ≈ [30]
|
||||||
|
@test l1.disposal_limit[p2] ≈ [2.0]
|
||||||
|
@test l1.disposal_limit[p3] ≈ [2.0]
|
||||||
|
@test l1.disposal_cost[p2] ≈ [-10.0]
|
||||||
|
@test l1.disposal_cost[p3] ≈ [-10.0]
|
||||||
end
|
end
|
||||||
for c in compressed.collection_centers
|
end
|
||||||
location_name_to_facility[c.name] = c
|
|
||||||
end
|
|
||||||
|
|
||||||
p1 = product_name_to_product["P1"]
|
|
||||||
p2 = product_name_to_product["P2"]
|
|
||||||
p3 = product_name_to_product["P3"]
|
|
||||||
c1 = location_name_to_facility["C1"]
|
|
||||||
l1 = location_name_to_facility["L1"]
|
|
||||||
|
|
||||||
@test compressed.time == 1
|
|
||||||
@test compressed.building_period == [1]
|
|
||||||
|
|
||||||
@test p1.name == "P1"
|
|
||||||
@test p1.transportation_cost ≈ [0.015]
|
|
||||||
@test p1.transportation_energy ≈ [0.115]
|
|
||||||
@test p1.transportation_emissions["CO2"] ≈ [0.051]
|
|
||||||
@test p1.transportation_emissions["CH4"] ≈ [0.0025]
|
|
||||||
|
|
||||||
@test c1.name == "C1"
|
|
||||||
@test c1.amount ≈ [1869.12]
|
|
||||||
|
|
||||||
@test l1.plant_name == "F1"
|
|
||||||
@test l1.location_name == "L1"
|
|
||||||
@test l1.energy ≈ [0.115]
|
|
||||||
@test l1.emissions["CO2"] ≈ [0.051]
|
|
||||||
@test l1.emissions["CH4"] ≈ [0.0025]
|
|
||||||
@test l1.sizes[1].opening_cost ≈ [500]
|
|
||||||
@test l1.sizes[2].opening_cost ≈ [1250]
|
|
||||||
@test l1.sizes[1].fixed_operating_cost ≈ [60]
|
|
||||||
@test l1.sizes[2].fixed_operating_cost ≈ [60]
|
|
||||||
@test l1.sizes[1].variable_operating_cost ≈ [30]
|
|
||||||
@test l1.sizes[2].variable_operating_cost ≈ [30]
|
|
||||||
@test l1.disposal_limit[p2] ≈ [2.0]
|
|
||||||
@test l1.disposal_limit[p3] ≈ [2.0]
|
|
||||||
@test l1.disposal_cost[p2] ≈ [-10.0]
|
|
||||||
@test l1.disposal_cost[p3] ≈ [-10.0]
|
|
||||||
end
|
|
||||||
@@ -4,22 +4,24 @@
|
|||||||
|
|
||||||
using RELOG
|
using RELOG
|
||||||
|
|
||||||
@testset "geodb_query (2018-us-county)" begin
|
function geodb_test()
|
||||||
region = RELOG.geodb_query("2018-us-county:17043")
|
@testset "geodb_query (2018-us-county)" begin
|
||||||
@test region.centroid.lat == 41.83956
|
region = RELOG.geodb_query("2018-us-county:17043")
|
||||||
@test region.centroid.lon == -88.08857
|
@test region.centroid.lat == 41.83956
|
||||||
@test region.population == 922_921
|
@test region.centroid.lon == -88.08857
|
||||||
end
|
@test region.population == 922_921
|
||||||
|
end
|
||||||
|
|
||||||
# @testset "geodb_query (2018-us-zcta)" begin
|
# @testset "geodb_query (2018-us-zcta)" begin
|
||||||
# region = RELOG.geodb_query("2018-us-zcta:60439")
|
# region = RELOG.geodb_query("2018-us-zcta:60439")
|
||||||
# @test region.centroid.lat == 41.68241
|
# @test region.centroid.lat == 41.68241
|
||||||
# @test region.centroid.lon == -87.98954
|
# @test region.centroid.lon == -87.98954
|
||||||
# end
|
# end
|
||||||
|
|
||||||
@testset "geodb_query (us-state)" begin
|
@testset "geodb_query (us-state)" begin
|
||||||
region = RELOG.geodb_query("us-state:IL")
|
region = RELOG.geodb_query("us-state:IL")
|
||||||
@test region.centroid.lat == 39.73939
|
@test region.centroid.lat == 39.73939
|
||||||
@test region.centroid.lon == -89.50414
|
@test region.centroid.lon == -89.50414
|
||||||
@test region.population == 12_671_821
|
@test region.population == 12_671_821
|
||||||
end
|
end
|
||||||
|
end
|
||||||
@@ -3,84 +3,90 @@
|
|||||||
|
|
||||||
using RELOG
|
using RELOG
|
||||||
|
|
||||||
@testset "parse" begin
|
function parse_test()
|
||||||
basedir = dirname(@__FILE__)
|
@testset "parse" begin
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
instance = RELOG.parsefile(fixture("instances/s1.json"))
|
||||||
|
|
||||||
centers = instance.collection_centers
|
centers = instance.collection_centers
|
||||||
plants = instance.plants
|
plants = instance.plants
|
||||||
products = instance.products
|
products = instance.products
|
||||||
location_name_to_plant = Dict(p.location_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)
|
||||||
|
|
||||||
@test length(centers) == 10
|
@test length(centers) == 10
|
||||||
@test centers[1].name == "C1"
|
@test centers[1].name == "C1"
|
||||||
@test centers[1].latitude == 7
|
@test centers[1].latitude == 7
|
||||||
@test centers[1].latitude == 7
|
@test centers[1].latitude == 7
|
||||||
@test centers[1].longitude == 7
|
@test centers[1].longitude == 7
|
||||||
@test centers[1].amount == [934.56, 934.56]
|
@test centers[1].amount == [934.56, 934.56]
|
||||||
@test centers[1].product.name == "P1"
|
@test centers[1].product.name == "P1"
|
||||||
|
|
||||||
@test length(plants) == 6
|
@test length(plants) == 6
|
||||||
|
|
||||||
plant = location_name_to_plant["L1"]
|
plant = location_name_to_plant["L1"]
|
||||||
@test plant.plant_name == "F1"
|
@test plant.plant_name == "F1"
|
||||||
@test plant.location_name == "L1"
|
@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
|
||||||
|
|
||||||
@test length(plant.sizes) == 2
|
@test length(plant.sizes) == 2
|
||||||
@test plant.sizes[1].capacity == 250
|
@test plant.sizes[1].capacity == 250
|
||||||
@test plant.sizes[1].opening_cost == [500, 500]
|
@test plant.sizes[1].opening_cost == [500, 500]
|
||||||
@test plant.sizes[1].fixed_operating_cost == [30, 30]
|
@test plant.sizes[1].fixed_operating_cost == [30, 30]
|
||||||
@test plant.sizes[1].variable_operating_cost == [30, 30]
|
@test plant.sizes[1].variable_operating_cost == [30, 30]
|
||||||
@test plant.sizes[2].capacity == 1000
|
@test plant.sizes[2].capacity == 1000
|
||||||
@test plant.sizes[2].opening_cost == [1250, 1250]
|
@test plant.sizes[2].opening_cost == [1250, 1250]
|
||||||
@test plant.sizes[2].fixed_operating_cost == [30, 30]
|
@test plant.sizes[2].fixed_operating_cost == [30, 30]
|
||||||
@test plant.sizes[2].variable_operating_cost == [30, 30]
|
@test plant.sizes[2].variable_operating_cost == [30, 30]
|
||||||
|
|
||||||
p2 = product_name_to_product["P2"]
|
p1 = product_name_to_product["P1"]
|
||||||
p3 = product_name_to_product["P3"]
|
@test p1.disposal_limit == [1.0, 1.0]
|
||||||
@test length(plant.output) == 2
|
@test p1.disposal_cost == [-1000.0, -1000.0]
|
||||||
@test plant.output[p2] == 0.2
|
|
||||||
@test plant.output[p3] == 0.5
|
|
||||||
@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"]
|
p2 = product_name_to_product["P2"]
|
||||||
@test plant.location_name == "L3"
|
@test p2.disposal_limit == [0.0, 0.0]
|
||||||
@test plant.input.name == "P2"
|
@test p2.disposal_cost == [0.0, 0.0]
|
||||||
@test plant.latitude == 25
|
|
||||||
@test plant.longitude == 65
|
|
||||||
|
|
||||||
@test length(plant.sizes) == 2
|
p3 = product_name_to_product["P3"]
|
||||||
@test plant.sizes[1].capacity == 1000.0
|
@test length(plant.output) == 2
|
||||||
@test plant.sizes[1].opening_cost == [3000, 3000]
|
@test plant.output[p2] == 0.2
|
||||||
@test plant.sizes[1].fixed_operating_cost == [50, 50]
|
@test plant.output[p3] == 0.5
|
||||||
@test plant.sizes[1].variable_operating_cost == [50, 50]
|
@test plant.disposal_limit[p2] == [1, 1]
|
||||||
@test plant.sizes[1] == plant.sizes[2]
|
@test plant.disposal_limit[p3] == [1, 1]
|
||||||
|
@test plant.disposal_cost[p2] == [-10, -10]
|
||||||
|
@test plant.disposal_cost[p3] == [-10, -10]
|
||||||
|
|
||||||
p4 = product_name_to_product["P4"]
|
plant = location_name_to_plant["L3"]
|
||||||
@test plant.output[p3] == 0.05
|
@test plant.location_name == "L3"
|
||||||
@test plant.output[p4] == 0.8
|
@test plant.input.name == "P2"
|
||||||
@test plant.disposal_limit[p3] == [1e8, 1e8]
|
@test plant.latitude == 25
|
||||||
@test plant.disposal_limit[p4] == [0, 0]
|
@test plant.longitude == 65
|
||||||
end
|
|
||||||
|
|
||||||
@testset "parse (geodb)" begin
|
@test length(plant.sizes) == 2
|
||||||
basedir = dirname(@__FILE__)
|
@test plant.sizes[1].capacity == 1000.0
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s2.json")
|
@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]
|
||||||
|
|
||||||
centers = instance.collection_centers
|
p4 = product_name_to_product["P4"]
|
||||||
@test centers[1].name == "C1"
|
@test plant.output[p3] == 0.05
|
||||||
@test centers[1].latitude == 41.83956
|
@test plant.output[p4] == 0.8
|
||||||
@test centers[1].longitude == -88.08857
|
@test plant.disposal_limit[p3] == [1e8, 1e8]
|
||||||
end
|
@test plant.disposal_limit[p4] == [0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
# @testset "parse (invalid)" begin
|
@testset "parse (geodb)" begin
|
||||||
# basedir = dirname(@__FILE__)
|
instance = RELOG.parsefile(fixture("instances/s2.json"))
|
||||||
# @test_throws ErrorException RELOG.parsefile("$basedir/../fixtures/s1-wrong-length.json")
|
|
||||||
# end
|
centers = instance.collection_centers
|
||||||
|
@test centers[1].name == "C1"
|
||||||
|
@test centers[1].latitude == 41.83956
|
||||||
|
@test centers[1].longitude == -88.08857
|
||||||
|
end
|
||||||
|
|
||||||
|
# @testset "parse (invalid)" begin
|
||||||
|
# @test_throws ErrorException RELOG.parsefile(fixture("s1-wrong-length.json"))
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
using RELOG, HiGHS, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
||||||
|
|
||||||
@testset "build" begin
|
function model_build_test()
|
||||||
basedir = dirname(@__FILE__)
|
@testset "build" begin
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
instance = RELOG.parsefile(fixture("instances/s1.json"))
|
||||||
graph = RELOG.build_graph(instance)
|
graph = RELOG.build_graph(instance)
|
||||||
model = RELOG.build_model(instance, graph, Cbc.Optimizer)
|
model = RELOG.build_model(instance, graph, HiGHS.Optimizer)
|
||||||
set_optimizer_attribute(model, "logLevel", 0)
|
|
||||||
|
|
||||||
process_node_by_location_name =
|
process_node_by_location_name =
|
||||||
Dict(n.location.location_name => n for n in graph.process_nodes)
|
Dict(n.location.location_name => n for n in graph.process_nodes)
|
||||||
|
|
||||||
shipping_node_by_loc_and_prod_names = Dict(
|
shipping_node_by_loc_and_prod_names = Dict(
|
||||||
(n.location.location_name, n.product.name) => n for n in graph.plant_shipping_nodes
|
(n.location.location_name, n.product.name) => n for n in graph.plant_shipping_nodes
|
||||||
)
|
)
|
||||||
|
|
||||||
@test length(model[:flow]) == 76
|
@test length(model[1, :open_plant]) == 12
|
||||||
@test length(model[:dispose]) == 16
|
@test length(model[2, :flow]) == 76
|
||||||
@test length(model[:open_plant]) == 12
|
@test length(model[2, :plant_dispose]) == 16
|
||||||
@test length(model[:capacity]) == 12
|
@test length(model[2, :capacity]) == 12
|
||||||
@test length(model[:expansion]) == 12
|
@test length(model[2, :expansion]) == 12
|
||||||
|
|
||||||
l1 = process_node_by_location_name["L1"]
|
# l1 = process_node_by_location_name["L1"]
|
||||||
v = model[:capacity][l1, 1]
|
# v = model[2, :capacity][l1.index, 1]
|
||||||
@test lower_bound(v) == 0.0
|
# @test lower_bound(v) == 0.0
|
||||||
@test upper_bound(v) == 1000.0
|
# @test upper_bound(v) == 1000.0
|
||||||
|
|
||||||
v = model[:expansion][l1, 1]
|
# v = model[2, :expansion][l1.index, 1]
|
||||||
@test lower_bound(v) == 0.0
|
# @test lower_bound(v) == 0.0
|
||||||
@test upper_bound(v) == 750.0
|
# @test upper_bound(v) == 750.0
|
||||||
|
|
||||||
v = model[:dispose][shipping_node_by_loc_and_prod_names["L1", "P2"], 1]
|
# v = model[2, :plant_dispose][shipping_node_by_loc_and_prod_names["L1", "P2"].index, 1]
|
||||||
@test lower_bound(v) == 0.0
|
# @test lower_bound(v) == 0.0
|
||||||
@test upper_bound(v) == 1.0
|
# @test upper_bound(v) == 1.0
|
||||||
end
|
end
|
||||||
|
end
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
@testset "Resolve" begin
|
|
||||||
# Shoud not crash
|
|
||||||
filename = "$(pwd())/../instances/s1.json"
|
|
||||||
solution_old, model_old = RELOG.solve(filename, return_model = true)
|
|
||||||
solution_new = RELOG.resolve(model_old, filename)
|
|
||||||
end
|
|
||||||
@@ -1,61 +1,85 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
using RELOG, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
||||||
|
|
||||||
basedir = dirname(@__FILE__)
|
basedir = dirname(@__FILE__)
|
||||||
|
|
||||||
@testset "solve (exact)" begin
|
function model_solve_test()
|
||||||
solution_filename_a = tempname()
|
@testset "solve (exact)" begin
|
||||||
solution_filename_b = tempname()
|
solution = RELOG.solve(fixture("instances/s1.json"))
|
||||||
solution = RELOG.solve("$basedir/../../instances/s1.json", output = solution_filename_a)
|
|
||||||
|
|
||||||
@test isfile(solution_filename_a)
|
solution_filename = tempname()
|
||||||
|
RELOG.write(solution, solution_filename)
|
||||||
|
@test isfile(solution_filename)
|
||||||
|
|
||||||
RELOG.write(solution, solution_filename_b)
|
@test "Costs" in keys(solution)
|
||||||
@test isfile(solution_filename_b)
|
@test "Fixed operating (\$)" in keys(solution["Costs"])
|
||||||
|
@test "Transportation (\$)" in keys(solution["Costs"])
|
||||||
|
@test "Variable operating (\$)" in keys(solution["Costs"])
|
||||||
|
@test "Total (\$)" in keys(solution["Costs"])
|
||||||
|
|
||||||
@test "Costs" in keys(solution)
|
@test "Plants" in keys(solution)
|
||||||
@test "Fixed operating (\$)" in keys(solution["Costs"])
|
@test "F1" in keys(solution["Plants"])
|
||||||
@test "Transportation (\$)" in keys(solution["Costs"])
|
@test "F2" in keys(solution["Plants"])
|
||||||
@test "Variable operating (\$)" in keys(solution["Costs"])
|
@test "F3" in keys(solution["Plants"])
|
||||||
@test "Total (\$)" in keys(solution["Costs"])
|
@test "F4" in keys(solution["Plants"])
|
||||||
|
|
||||||
@test "Plants" in keys(solution)
|
@test "Products" in keys(solution)
|
||||||
@test "F1" in keys(solution["Plants"])
|
@test "P1" in keys(solution["Products"])
|
||||||
@test "F2" in keys(solution["Plants"])
|
@test "C1" in keys(solution["Products"]["P1"])
|
||||||
@test "F3" in keys(solution["Plants"])
|
@test "Dispose (tonne)" in keys(solution["Products"]["P1"]["C1"])
|
||||||
@test "F4" in keys(solution["Plants"])
|
|
||||||
end
|
|
||||||
|
|
||||||
@testset "solve (heuristic)" begin
|
total_disposal =
|
||||||
# Should not crash
|
sum([loc["Dispose (tonne)"] for loc in values(solution["Products"]["P1"])])
|
||||||
solution = RELOG.solve("$basedir/../../instances/s1.json", heuristic = true)
|
@test total_disposal == [1.0, 1.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "solve (infeasible)" begin
|
@testset "solve (heuristic)" begin
|
||||||
json = JSON.parsefile("$basedir/../../instances/s1.json")
|
# Should not crash
|
||||||
for (location_name, location_dict) in json["products"]["P1"]["initial amounts"]
|
solution = RELOG.solve(fixture("instances/s1.json"), heuristic = true)
|
||||||
location_dict["amount (tonne)"] *= 1000
|
end
|
||||||
|
|
||||||
|
# @testset "solve (infeasible)" begin
|
||||||
|
# json = JSON.parsefile(fixture("instances/s1.json"))
|
||||||
|
# for (location_name, location_dict) in json["products"]["P1"]["initial amounts"]
|
||||||
|
# location_dict["amount (tonne)"] *= 1000
|
||||||
|
# end
|
||||||
|
# @test_throws ErrorException("No solution available") RELOG.solve(RELOG.parse(json))
|
||||||
|
# end
|
||||||
|
|
||||||
|
@testset "solve (with storage)" begin
|
||||||
|
basedir = dirname(@__FILE__)
|
||||||
|
filename = "$basedir/../fixtures/storage.json"
|
||||||
|
instance = RELOG.parsefile(filename)
|
||||||
|
@test instance.plants[1].storage_limit == 50.0
|
||||||
|
@test instance.plants[1].storage_cost == [2.0, 1.5, 1.0]
|
||||||
|
|
||||||
|
solution = RELOG.solve(filename)
|
||||||
|
plant_dict = solution["Plants"]["mega plant"]["Chicago"]
|
||||||
|
@test plant_dict["Variable operating cost (\$)"] == [500.0, 0.0, 100.0]
|
||||||
|
@test plant_dict["Process (tonne)"] == [50.0, 0.0, 50.0]
|
||||||
|
@test plant_dict["Storage (tonne)"] == [50.0, 50.0, 0.0]
|
||||||
|
@test plant_dict["Storage cost (\$)"] == [100.0, 75.0, 0.0]
|
||||||
|
|
||||||
|
@test solution["Costs"]["Variable operating (\$)"] == [500.0, 0.0, 100.0]
|
||||||
|
@test solution["Costs"]["Storage (\$)"] == [100.0, 75.0, 0.0]
|
||||||
|
@test solution["Costs"]["Total (\$)"] == [600.0, 75.0, 100.0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "solve (stochastic)" begin
|
||||||
|
# Should not crash
|
||||||
|
solutions = RELOG.solve_stochastic(
|
||||||
|
scenarios=[
|
||||||
|
fixture("instances/case3_p010_s1.00.json"),
|
||||||
|
fixture("instances/case3_p010_s1.25.json"),
|
||||||
|
],
|
||||||
|
probs=[0.5, 0.5],
|
||||||
|
optimizer=optimizer_with_attributes(
|
||||||
|
HiGHS.Optimizer,
|
||||||
|
"log_to_console" => false,
|
||||||
|
),
|
||||||
|
method=:lshaped,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
@test_throws ErrorException("No solution available") RELOG.solve(RELOG.parse(json))
|
|
||||||
end
|
|
||||||
|
|
||||||
@testset "solve (with storage)" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
filename = "$basedir/../fixtures/storage.json"
|
|
||||||
instance = RELOG.parsefile(filename)
|
|
||||||
@test instance.plants[1].storage_limit == 50.0
|
|
||||||
@test instance.plants[1].storage_cost == [2.0, 1.5, 1.0]
|
|
||||||
|
|
||||||
solution = RELOG.solve(filename)
|
|
||||||
plant_dict = solution["Plants"]["mega plant"]["Chicago"]
|
|
||||||
@test plant_dict["Variable operating cost (\$)"] == [500.0, 0.0, 100.0]
|
|
||||||
@test plant_dict["Process (tonne)"] == [50.0, 0.0, 50.0]
|
|
||||||
@test plant_dict["Storage (tonne)"] == [50.0, 50.0, 0.0]
|
|
||||||
@test plant_dict["Storage cost (\$)"] == [100.0, 75.0, 0.0]
|
|
||||||
|
|
||||||
@test solution["Costs"]["Variable operating (\$)"] == [500.0, 0.0, 100.0]
|
|
||||||
@test solution["Costs"]["Storage (\$)"] == [100.0, 75.0, 0.0]
|
|
||||||
@test solution["Costs"]["Total (\$)"] == [600.0, 75.0, 100.0]
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,16 +4,20 @@
|
|||||||
|
|
||||||
using RELOG, JSON, GZip
|
using RELOG, JSON, GZip
|
||||||
|
|
||||||
@testset "Reports" begin
|
basedir = @__DIR__
|
||||||
@testset "from solve" begin
|
|
||||||
solution = RELOG.solve("$(pwd())/../instances/s1.json")
|
function reports_test()
|
||||||
tmp_filename = tempname()
|
@testset "Reports" begin
|
||||||
# The following should not crash
|
@testset "from solve" begin
|
||||||
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
solution = RELOG.solve(fixture("instances/s1.json"))
|
||||||
RELOG.write_plant_outputs_report(solution, tmp_filename)
|
tmp_filename = tempname()
|
||||||
RELOG.write_plants_report(solution, tmp_filename)
|
# The following should not crash
|
||||||
RELOG.write_products_report(solution, tmp_filename)
|
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
||||||
RELOG.write_transportation_emissions_report(solution, tmp_filename)
|
RELOG.write_plant_outputs_report(solution, tmp_filename)
|
||||||
RELOG.write_transportation_report(solution, tmp_filename)
|
RELOG.write_plants_report(solution, tmp_filename)
|
||||||
|
RELOG.write_products_report(solution, tmp_filename)
|
||||||
|
RELOG.write_transportation_emissions_report(solution, tmp_filename)
|
||||||
|
RELOG.write_transportation_report(solution, tmp_filename)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2,20 +2,46 @@
|
|||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
using Test
|
using Test
|
||||||
|
using RELOG
|
||||||
|
using Revise
|
||||||
|
|
||||||
@testset "RELOG" begin
|
includet("instance/compress_test.jl")
|
||||||
@testset "Instance" begin
|
includet("instance/geodb_test.jl")
|
||||||
include("instance/compress_test.jl")
|
includet("instance/parse_test.jl")
|
||||||
include("instance/geodb_test.jl")
|
includet("graph/build_test.jl")
|
||||||
include("instance/parse_test.jl")
|
includet("model/build_test.jl")
|
||||||
|
includet("model/solve_test.jl")
|
||||||
|
includet("reports_test.jl")
|
||||||
|
|
||||||
|
function fixture(path)
|
||||||
|
for candidate in [
|
||||||
|
"fixtures/$path",
|
||||||
|
"test/fixtures/$path"
|
||||||
|
]
|
||||||
|
if isfile(candidate)
|
||||||
|
return candidate
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@testset "Graph" begin
|
error("Fixture not found: $path")
|
||||||
include("graph/build_test.jl")
|
|
||||||
end
|
|
||||||
@testset "Model" begin
|
|
||||||
include("model/build_test.jl")
|
|
||||||
include("model/solve_test.jl")
|
|
||||||
include("model/resolve_test.jl")
|
|
||||||
end
|
|
||||||
include("reports_test.jl")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function runtests()
|
||||||
|
@testset "RELOG" begin
|
||||||
|
@testset "Instance" begin
|
||||||
|
compress_test()
|
||||||
|
geodb_test()
|
||||||
|
parse_test()
|
||||||
|
end
|
||||||
|
@testset "Graph" begin
|
||||||
|
graph_build_test()
|
||||||
|
end
|
||||||
|
@testset "Model" begin
|
||||||
|
model_build_test()
|
||||||
|
model_solve_test()
|
||||||
|
end
|
||||||
|
reports_test()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
runtests()
|
||||||
|
|||||||
Reference in New Issue
Block a user