mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 07:48:50 -06:00
Merge branch 'master' into relog-web
This commit is contained in:
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
|||||||
shell: julia --color=yes {0}
|
shell: julia --color=yes {0}
|
||||||
run: |
|
run: |
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.add(PackageSpec(name="JuliaFormatter", version="0.14.4"))
|
Pkg.add(PackageSpec(name="JuliaFormatter", version="1"))
|
||||||
using JuliaFormatter
|
using JuliaFormatter
|
||||||
format("src", verbose=true)
|
format("src", verbose=true)
|
||||||
format("test", verbose=true)
|
format("test/src", verbose=true)
|
||||||
out = String(read(Cmd(`git diff`)))
|
out = String(read(Cmd(`git diff`)))
|
||||||
if isempty(out)
|
if isempty(out)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|||||||
14
.github/workflows/test.yml
vendored
14
.github/workflows/test.yml
vendored
@@ -21,5 +21,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: ${{ matrix.version }}
|
version: ${{ matrix.version }}
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
- uses: julia-actions/julia-buildpkg@v1
|
- name: Run tests
|
||||||
- uses: julia-actions/julia-runtest@v1
|
shell: julia --color=yes --project=test {0}
|
||||||
|
run: |
|
||||||
|
using Pkg
|
||||||
|
Pkg.develop(path=".")
|
||||||
|
Pkg.update()
|
||||||
|
using RELOGT
|
||||||
|
try
|
||||||
|
runtests()
|
||||||
|
catch
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ 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
|
||||||
|
|
||||||
|
## [0.6.0] -- 2022-12-15
|
||||||
|
### Added
|
||||||
|
- Allow RELOG to calculate approximate driving distances, instead of just straight-line distances between points.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix bug that caused building period parameter to be ignored
|
||||||
|
|
||||||
## [0.5.2] -- 2022-08-26
|
## [0.5.2] -- 2022-08-26
|
||||||
### Changed
|
### Changed
|
||||||
- Update to JuMP 1.x
|
- Update to JuMP 1.x
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,4 +1,4 @@
|
|||||||
VERSION := 0.5
|
VERSION := 0.6
|
||||||
PKG := ghcr.io/anl-ceeesa/relog-web
|
PKG := ghcr.io/anl-ceeesa/relog-web
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name = "RELOG"
|
name = "RELOG"
|
||||||
uuid = "a2afcdf7-cf04-4913-85f9-c0d81ddf2008"
|
uuid = "a2afcdf7-cf04-4913-85f9-c0d81ddf2008"
|
||||||
authors = ["Alinson S Xavier <axavier@anl.gov>"]
|
authors = ["Alinson S Xavier <axavier@anl.gov>"]
|
||||||
version = "0.5.2"
|
version = "0.6.0"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205"
|
CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205"
|
||||||
@@ -19,7 +19,9 @@ JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
|||||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||||
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||||
|
NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
|
||||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
|
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
||||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
@@ -40,6 +42,7 @@ JSON = "0.21"
|
|||||||
JSONSchema = "1"
|
JSONSchema = "1"
|
||||||
JuMP = "1"
|
JuMP = "1"
|
||||||
MathOptInterface = "1"
|
MathOptInterface = "1"
|
||||||
|
NearestNeighbors = "0.4"
|
||||||
OrderedCollections = "1"
|
OrderedCollections = "1"
|
||||||
ProgressBars = "1"
|
ProgressBars = "1"
|
||||||
Shapefile = "0.8"
|
Shapefile = "0.8"
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -13,22 +13,24 @@
|
|||||||
|
|
||||||
**RELOG** is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.
|
**RELOG** is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.
|
||||||
|
|
||||||
<img src="https://anl-ceeesa.github.io/RELOG/0.5/assets/ex_transportation.png" width="1000px"/>
|
|
||||||
|
|
||||||
|
<img src="https://anl-ceeesa.github.io/RELOG/0.6/assets/ex_transportation.png" width="1000px"/>
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [Usage](https://anl-ceeesa.github.io/RELOG/0.5/usage)
|
* [Usage](https://anl-ceeesa.github.io/RELOG/0.6/usage)
|
||||||
- [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.5/format)
|
* [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.6/format)
|
||||||
- [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.5/reports)
|
* [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.6/reports)
|
||||||
- [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.5/model)
|
* [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.6/model)
|
||||||
|
|
||||||
### Authors
|
### Authors
|
||||||
|
|
||||||
- **Alinson S. Xavier** <<axavier@anl.gov>>
|
* **Alinson S. Xavier** <<axavier@anl.gov>>
|
||||||
- **Nwike Iloeje** <<ciloeje@anl.gov>>
|
* **Nwike Iloeje** <<ciloeje@anl.gov>>
|
||||||
- **John Atkins**
|
* **John Atkins**
|
||||||
- **Kyle Sun**
|
* **Kyle Sun**
|
||||||
- **Audrey Gallier**
|
* **Audrey Gallier**
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|||||||
5
deps/formatter/Project.toml
vendored
5
deps/formatter/Project.toml
vendored
@@ -1,5 +0,0 @@
|
|||||||
[deps]
|
|
||||||
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
|
|
||||||
|
|
||||||
[compat]
|
|
||||||
JuliaFormatter = "0.14.4"
|
|
||||||
8
deps/formatter/format.jl
vendored
8
deps/formatter/format.jl
vendored
@@ -1,8 +0,0 @@
|
|||||||
using JuliaFormatter
|
|
||||||
format(
|
|
||||||
[
|
|
||||||
"../../src",
|
|
||||||
"../../test",
|
|
||||||
],
|
|
||||||
verbose=true,
|
|
||||||
)
|
|
||||||
@@ -14,6 +14,7 @@ The **parameters** section describes details about the simulation itself.
|
|||||||
|:--------------------------|:---------------|
|
|:--------------------------|:---------------|
|
||||||
|`time horizon (years)` | Number of years in the simulation.
|
|`time horizon (years)` | Number of years in the simulation.
|
||||||
|`building period (years)` | List of years in which we are allowed to open new plants. For example, if this parameter is set to `[1,2,3]`, we can only open plants during the first three years. By default, this equals `[1]`; that is, plants can only be opened during the first year. |
|
|`building period (years)` | List of years in which we are allowed to open new plants. For example, if this parameter is set to `[1,2,3]`, we can only open plants during the first three years. By default, this equals `[1]`; that is, plants can only be opened during the first year. |
|
||||||
|
|`distance metric` | Metric used to compute distances between pairs of locations. Valid options are: `"Euclidean"`, for the straight-line distance between points; or `"driving"` for an approximated driving distance. If not specified, defaults to `"Euclidean"`.
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
@@ -21,7 +22,8 @@ The **parameters** section describes details about the simulation itself.
|
|||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"time horizon (years)": 2,
|
"time horizon (years)": 2,
|
||||||
"building period (years)": [1]
|
"building period (years)": [1],
|
||||||
|
"distance metric": "driving",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -36,6 +38,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 +77,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]
|
||||||
@@ -220,6 +226,7 @@ Database | Description | Examples
|
|||||||
* Plants can be expanded at any time, even long after they are open.
|
* Plants can be expanded at any time, even long after they are open.
|
||||||
* All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next.
|
* All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next.
|
||||||
* Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.
|
* Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.
|
||||||
|
* Accurate driving distances are only available for the continental United States.
|
||||||
|
|
||||||
## Output Data Format (JSON)
|
## Output Data Format (JSON)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ To use RELOG, the first step is to install the [Julia programming language](http
|
|||||||
|
|
||||||
```julia
|
```julia
|
||||||
using Pkg
|
using Pkg
|
||||||
Pkg.add(name="RELOG", version="0.5")
|
Pkg.add(name="RELOG", version="0.6")
|
||||||
```
|
|
||||||
|
|
||||||
After the package and all its dependencies have been installed, please run the RELOG test suite, as shown below, to make sure that the package has been correctly installed:
|
|
||||||
|
|
||||||
```julia
|
|
||||||
Pkg.test("RELOG")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Modeling the problem
|
## 2. Modeling the problem
|
||||||
|
|||||||
@@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
juliaw
75
juliaw
@@ -1,75 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
|
||||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
|
|
||||||
if [ ! -e Project.toml ]; then
|
|
||||||
echo "juliaw: Project.toml not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -e Manifest.toml ]; then
|
|
||||||
julia --project=. -e 'using Pkg; Pkg.instantiate()' || exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -e build/sysimage.so -o Project.toml -nt build/sysimage.so ]; then
|
|
||||||
echo "juliaw: rebuilding system image..."
|
|
||||||
|
|
||||||
# Generate temporary project folder
|
|
||||||
rm -rf $HOME/.juliaw
|
|
||||||
mkdir -p $HOME/.juliaw/src
|
|
||||||
cp Project.toml Manifest.toml $HOME/.juliaw
|
|
||||||
NAME=$(julia -e 'using TOML; toml = TOML.parsefile("Project.toml"); "name" in keys(toml) && print(toml["name"])')
|
|
||||||
if [ ! -z $NAME ]; then
|
|
||||||
cat > $HOME/.juliaw/src/$NAME.jl << EOF
|
|
||||||
module $NAME
|
|
||||||
end
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add PackageCompiler dependencies to temporary project
|
|
||||||
julia --project=$HOME/.juliaw -e 'using Pkg; Pkg.add(["PackageCompiler", "TOML", "Logging"])'
|
|
||||||
|
|
||||||
# Generate system image scripts
|
|
||||||
cat > $HOME/.juliaw/sysimage.jl << EOF
|
|
||||||
using PackageCompiler
|
|
||||||
using TOML
|
|
||||||
using Logging
|
|
||||||
|
|
||||||
Logging.disable_logging(Logging.Info)
|
|
||||||
mkpath("$PWD/build")
|
|
||||||
|
|
||||||
println("juliaw: generating precompilation statements...")
|
|
||||||
run(\`julia --project="$PWD" --trace-compile="$PWD"/build/precompile.jl \$(ARGS)\`)
|
|
||||||
|
|
||||||
println("juliaw: finding dependencies...")
|
|
||||||
project = TOML.parsefile("Project.toml")
|
|
||||||
manifest = TOML.parsefile("Manifest.toml")
|
|
||||||
deps = Symbol[]
|
|
||||||
for dep in keys(project["deps"])
|
|
||||||
if dep in keys(manifest)
|
|
||||||
# Up to Julia 1.6
|
|
||||||
dep_entry = manifest[dep][1]
|
|
||||||
else
|
|
||||||
# Julia 1.7+
|
|
||||||
dep_entry = manifest["deps"][dep][1]
|
|
||||||
end
|
|
||||||
if "path" in keys(dep_entry)
|
|
||||||
println(" - \$(dep) [skip]")
|
|
||||||
else
|
|
||||||
println(" - \$(dep)")
|
|
||||||
push!(deps, Symbol(dep))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
println("juliaw: building system image...")
|
|
||||||
create_sysimage(
|
|
||||||
deps,
|
|
||||||
precompile_statements_file = "$PWD/build/precompile.jl",
|
|
||||||
sysimage_path = "$PWD/build/sysimage.so",
|
|
||||||
)
|
|
||||||
EOF
|
|
||||||
julia --project=$HOME/.juliaw $HOME/.juliaw/sysimage.jl $*
|
|
||||||
else
|
|
||||||
julia --project=. --sysimage build/sysimage.so $*
|
|
||||||
fi
|
|
||||||
23
mkdocs.yml
23
mkdocs.yml
@@ -1,23 +0,0 @@
|
|||||||
site_name: RELOG
|
|
||||||
theme: cinder
|
|
||||||
copyright: "Copyright © 2020, UChicago Argonne, LLC. All Rights Reserved."
|
|
||||||
repo_url: https://github.com/ANL-CEEESA/RELOG
|
|
||||||
edit_uri: edit/master/src/docs/
|
|
||||||
nav:
|
|
||||||
- Home: index.md
|
|
||||||
- Usage: usage.md
|
|
||||||
- Data Format: format.md
|
|
||||||
- Reports: reports.md
|
|
||||||
- Optimization Model: model.md
|
|
||||||
plugins:
|
|
||||||
- search
|
|
||||||
markdown_extensions:
|
|
||||||
- admonition
|
|
||||||
- mdx_math
|
|
||||||
extra_javascript:
|
|
||||||
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML
|
|
||||||
- js/mathjax.js
|
|
||||||
docs_dir: src/docs
|
|
||||||
site_dir: docs
|
|
||||||
extra_css:
|
|
||||||
- "css/custom.css"
|
|
||||||
10
src/RELOG.jl
10
src/RELOG.jl
@@ -4,20 +4,24 @@
|
|||||||
|
|
||||||
module RELOG
|
module RELOG
|
||||||
|
|
||||||
include("instance/structs.jl")
|
using Pkg
|
||||||
|
|
||||||
|
version() = Pkg.dependencies()[Base.UUID("a2afcdf7-cf04-4913-85f9-c0d81ddf2008")].version
|
||||||
|
|
||||||
|
include("instance/structs.jl")
|
||||||
include("graph/structs.jl")
|
include("graph/structs.jl")
|
||||||
|
|
||||||
|
include("instance/geodb.jl")
|
||||||
|
include("graph/dist.jl")
|
||||||
include("graph/build.jl")
|
include("graph/build.jl")
|
||||||
include("graph/csv.jl")
|
include("graph/csv.jl")
|
||||||
include("instance/compress.jl")
|
include("instance/compress.jl")
|
||||||
include("instance/geodb.jl")
|
|
||||||
include("instance/parse.jl")
|
include("instance/parse.jl")
|
||||||
include("instance/validate.jl")
|
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/resolve.jl")
|
include("model/resolve.jl")
|
||||||
|
include("model/solve.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")
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
# 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 Geodesy
|
|
||||||
|
|
||||||
function calculate_distance(source_lat, source_lon, dest_lat, dest_lon)::Float64
|
|
||||||
x = LLA(source_lat, source_lon, 0.0)
|
|
||||||
y = LLA(dest_lat, dest_lon, 0.0)
|
|
||||||
return round(euclidean_distance(x, y) / 1000.0, digits = 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
function build_graph(instance::Instance)::Graph
|
function build_graph(instance::Instance)::Graph
|
||||||
arcs = []
|
arcs = []
|
||||||
next_index = 0
|
next_index = 0
|
||||||
@@ -18,6 +10,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 +20,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
|
||||||
|
|
||||||
@@ -50,11 +44,12 @@ function build_graph(instance::Instance)::Graph
|
|||||||
# Build arcs from collection centers to plants, and from one plant to another
|
# Build arcs from collection centers to plants, and from one plant to another
|
||||||
for source in [collection_shipping_nodes; plant_shipping_nodes]
|
for source in [collection_shipping_nodes; plant_shipping_nodes]
|
||||||
for dest in process_nodes_by_input_product[source.product]
|
for dest in process_nodes_by_input_product[source.product]
|
||||||
distance = calculate_distance(
|
distance = _calculate_distance(
|
||||||
source.location.latitude,
|
source.location.latitude,
|
||||||
source.location.longitude,
|
source.location.longitude,
|
||||||
dest.location.latitude,
|
dest.location.latitude,
|
||||||
dest.location.longitude,
|
dest.location.longitude,
|
||||||
|
instance.distance_metric,
|
||||||
)
|
)
|
||||||
values = Dict("distance" => distance)
|
values = Dict("distance" => distance)
|
||||||
arc = Arc(source, dest, values)
|
arc = Arc(source, dest, values)
|
||||||
@@ -83,6 +78,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
|
||||||
|
|
||||||
|
|||||||
60
src/graph/dist.jl
Normal file
60
src/graph/dist.jl
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# RELOG: Reverse Logistics Optimization
|
||||||
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using Geodesy
|
||||||
|
using NearestNeighbors
|
||||||
|
using DataFrames
|
||||||
|
|
||||||
|
function _calculate_distance(
|
||||||
|
source_lat,
|
||||||
|
source_lon,
|
||||||
|
dest_lat,
|
||||||
|
dest_lon,
|
||||||
|
::EuclideanDistance,
|
||||||
|
)::Float64
|
||||||
|
x = LLA(source_lat, source_lon, 0.0)
|
||||||
|
y = LLA(dest_lat, dest_lon, 0.0)
|
||||||
|
return round(euclidean_distance(x, y) / 1000.0, digits = 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _calculate_distance(
|
||||||
|
source_lat,
|
||||||
|
source_lon,
|
||||||
|
dest_lat,
|
||||||
|
dest_lon,
|
||||||
|
metric::KnnDrivingDistance,
|
||||||
|
)::Float64
|
||||||
|
if metric.tree === nothing
|
||||||
|
basedir = joinpath(dirname(@__FILE__), "..", "..", "data")
|
||||||
|
csv_filename = joinpath(basedir, "dist_driving.csv")
|
||||||
|
|
||||||
|
# Download pre-computed driving data
|
||||||
|
if !isfile(csv_filename)
|
||||||
|
_download_zip(
|
||||||
|
"https://axavier.org/RELOG/0.6/data/dist_driving_0b9a6ad6.zip",
|
||||||
|
basedir,
|
||||||
|
csv_filename,
|
||||||
|
0x0b9a6ad6,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fit kNN model
|
||||||
|
df = DataFrame(CSV.File(csv_filename, missingstring = "NaN"))
|
||||||
|
dropmissing!(df)
|
||||||
|
coords = Matrix(df[!, [:source_lat, :source_lon, :dest_lat, :dest_lon]])'
|
||||||
|
metric.ratios = Matrix(df[!, [:ratio]])
|
||||||
|
metric.tree = KDTree(coords)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compute Euclidean distance
|
||||||
|
dist_euclidean =
|
||||||
|
_calculate_distance(source_lat, source_lon, dest_lat, dest_lon, EuclideanDistance())
|
||||||
|
|
||||||
|
# Predict ratio
|
||||||
|
idxs, _ = knn(metric.tree, [source_lat, source_lon, dest_lat, dest_lon], 5)
|
||||||
|
ratio_pred = mean(metric.ratios[idxs])
|
||||||
|
dist_pred = round(dist_euclidean * ratio_pred, digits = 3)
|
||||||
|
isfinite(dist_pred) || error("non-finite distance detected: $dist_pred")
|
||||||
|
return dist_pred
|
||||||
|
end
|
||||||
@@ -33,6 +33,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)
|
||||||
|
|||||||
@@ -23,8 +23,20 @@ function parse(json)::Instance
|
|||||||
validate(json, Schema(json_schema))
|
validate(json, Schema(json_schema))
|
||||||
|
|
||||||
building_period = [1]
|
building_period = [1]
|
||||||
if "building period (years)" in keys(json)
|
if "building period (years)" in keys(json["parameters"])
|
||||||
building_period = json["building period (years)"]
|
building_period = json["parameters"]["building period (years)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
distance_metric = EuclideanDistance()
|
||||||
|
if "distance metric" in keys(json["parameters"])
|
||||||
|
metric_name = json["parameters"]["distance metric"]
|
||||||
|
if metric_name == "driving"
|
||||||
|
distance_metric = KnnDrivingDistance()
|
||||||
|
elseif metric_name == "Euclidean"
|
||||||
|
# nop
|
||||||
|
else
|
||||||
|
error("Unknown distance metric: $metric_name")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
plants = Plant[]
|
plants = Plant[]
|
||||||
@@ -37,6 +49,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 +60,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 +98,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
|
||||||
@@ -176,5 +209,12 @@ function parse(json)::Instance
|
|||||||
@info @sprintf("%12d collection centers", length(collection_centers))
|
@info @sprintf("%12d collection centers", length(collection_centers))
|
||||||
@info @sprintf("%12d candidate plant locations", length(plants))
|
@info @sprintf("%12d candidate plant locations", length(plants))
|
||||||
|
|
||||||
return Instance(T, products, collection_centers, plants, building_period)
|
return Instance(
|
||||||
|
T,
|
||||||
|
products,
|
||||||
|
collection_centers,
|
||||||
|
plants,
|
||||||
|
building_period,
|
||||||
|
distance_metric,
|
||||||
|
)
|
||||||
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
|
||||||
@@ -48,10 +51,21 @@ mutable struct Plant
|
|||||||
storage_cost::Vector{Float64}
|
storage_cost::Vector{Float64}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
abstract type DistanceMetric end
|
||||||
|
|
||||||
|
Base.@kwdef mutable struct KnnDrivingDistance <: DistanceMetric
|
||||||
|
tree = nothing
|
||||||
|
ratios = nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
mutable struct EuclideanDistance <: DistanceMetric end
|
||||||
|
|
||||||
mutable struct Instance
|
mutable struct Instance
|
||||||
time::Int64
|
time::Int64
|
||||||
products::Vector{Product}
|
products::Vector{Product}
|
||||||
collection_centers::Vector{CollectionCenter}
|
collection_centers::Vector{CollectionCenter}
|
||||||
plants::Vector{Plant}
|
plants::Vector{Plant}
|
||||||
building_period::Vector{Int64}
|
building_period::Vector{Int64}
|
||||||
|
distance_metric::DistanceMetric
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,13 +20,17 @@ function create_vars!(model::JuMP.Model)
|
|||||||
graph, T = model[:graph], model[:instance].time
|
graph, T = model[:graph], model[:instance].time
|
||||||
model[:flow] =
|
model[:flow] =
|
||||||
Dict((a, t) => @variable(model, lower_bound = 0) for a in graph.arcs, t = 1:T)
|
Dict((a, t) => @variable(model, lower_bound = 0) for a in graph.arcs, t = 1:T)
|
||||||
model[:dispose] = Dict(
|
model[:plant_dispose] = Dict(
|
||||||
(n, t) => @variable(
|
(n, t) => @variable(
|
||||||
model,
|
model,
|
||||||
lower_bound = 0,
|
lower_bound = 0,
|
||||||
upper_bound = n.location.disposal_limit[n.product][t]
|
upper_bound = n.location.disposal_limit[n.product][t]
|
||||||
) for n in values(graph.plant_shipping_nodes), t = 1:T
|
) for n in values(graph.plant_shipping_nodes), t = 1:T
|
||||||
)
|
)
|
||||||
|
model[:collection_dispose] = Dict(
|
||||||
|
(n, t) => @variable(model, lower_bound = 0,) for
|
||||||
|
n in values(graph.collection_shipping_nodes), t = 1:T
|
||||||
|
)
|
||||||
model[:store] = Dict(
|
model[:store] = Dict(
|
||||||
(n, t) =>
|
(n, t) =>
|
||||||
@variable(model, lower_bound = 0, upper_bound = n.location.storage_limit)
|
@variable(model, lower_bound = 0, upper_bound = n.location.storage_limit)
|
||||||
@@ -131,14 +135,25 @@ function create_objective_function!(model::JuMP.Model)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shipping node costs
|
# Plant shipping node costs
|
||||||
for n in values(graph.plant_shipping_nodes), t = 1:T
|
for n in values(graph.plant_shipping_nodes), t = 1:T
|
||||||
|
|
||||||
# Disposal costs
|
# Disposal costs
|
||||||
add_to_expression!(
|
add_to_expression!(
|
||||||
obj,
|
obj,
|
||||||
n.location.disposal_cost[n.product][t],
|
n.location.disposal_cost[n.product][t],
|
||||||
model[:dispose][n, t],
|
model[:plant_dispose][n, t],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Collection shipping node costs
|
||||||
|
for n in values(graph.collection_shipping_nodes), t = 1:T
|
||||||
|
|
||||||
|
# Disposal costs
|
||||||
|
add_to_expression!(
|
||||||
|
obj,
|
||||||
|
n.location.product.disposal_cost[t],
|
||||||
|
model[:collection_dispose][n, t],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -154,16 +169,29 @@ function create_shipping_node_constraints!(model::JuMP.Model)
|
|||||||
for n in graph.collection_shipping_nodes
|
for n in graph.collection_shipping_nodes
|
||||||
model[:eq_balance][n, t] = @constraint(
|
model[:eq_balance][n, t] = @constraint(
|
||||||
model,
|
model,
|
||||||
sum(model[:flow][a, t] for a in n.outgoing_arcs) == n.location.amount[t]
|
sum(model[:flow][a, t] for a in n.outgoing_arcs) ==
|
||||||
|
n.location.amount[t] + model[:collection_dispose][n, t]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
for prod in model[:instance].products
|
||||||
|
if isempty(prod.collection_centers)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
expr = AffExpr()
|
||||||
|
for center in prod.collection_centers
|
||||||
|
n = graph.collection_center_to_node[center]
|
||||||
|
add_to_expression!(expr, model[:collection_dispose][n, t])
|
||||||
|
end
|
||||||
|
@constraint(model, expr <= prod.disposal_limit[t])
|
||||||
|
end
|
||||||
|
|
||||||
# Plants
|
# Plants
|
||||||
for n in graph.plant_shipping_nodes
|
for n in graph.plant_shipping_nodes
|
||||||
@constraint(
|
@constraint(
|
||||||
model,
|
model,
|
||||||
sum(model[:flow][a, t] for a in n.incoming_arcs) ==
|
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]
|
sum(model[:flow][a, t] for a in n.outgoing_arcs) +
|
||||||
|
model[:plant_dispose][n, t]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,22 +39,25 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Products
|
# Products
|
||||||
if marginal_costs
|
|
||||||
for n in graph.collection_shipping_nodes
|
for n in graph.collection_shipping_nodes
|
||||||
location_dict = OrderedDict{Any,Any}(
|
location_dict = OrderedDict{Any,Any}(
|
||||||
"Marginal cost (\$/tonne)" => [
|
|
||||||
round(abs(JuMP.shadow_price(model[:eq_balance][n, t])), digits = 2) for t = 1:T
|
|
||||||
],
|
|
||||||
"Latitude (deg)" => n.location.latitude,
|
"Latitude (deg)" => n.location.latitude,
|
||||||
"Longitude (deg)" => n.location.longitude,
|
"Longitude (deg)" => n.location.longitude,
|
||||||
"Amount (tonne)" => n.location.amount,
|
"Amount (tonne)" => n.location.amount,
|
||||||
|
"Dispose (tonne)" =>
|
||||||
|
[JuMP.value(model[:collection_dispose][n, t]) for t = 1:T],
|
||||||
)
|
)
|
||||||
|
if marginal_costs
|
||||||
|
location_dict["Marginal cost (\$/tonne)"] = [
|
||||||
|
round(abs(JuMP.shadow_price(model[:eq_balance][n, t])), digits = 2) for
|
||||||
|
t = 1:T
|
||||||
|
]
|
||||||
|
end
|
||||||
if n.product.name ∉ keys(output["Products"])
|
if n.product.name ∉ keys(output["Products"])
|
||||||
output["Products"][n.product.name] = OrderedDict()
|
output["Products"][n.product.name] = OrderedDict()
|
||||||
end
|
end
|
||||||
output["Products"][n.product.name][n.location.name] = location_dict
|
output["Products"][n.product.name][n.location.name] = location_dict
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Plants
|
# Plants
|
||||||
for plant in instance.plants
|
for plant in instance.plants
|
||||||
@@ -178,13 +181,14 @@ function get_solution(model::JuMP.Model; marginal_costs = true)
|
|||||||
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 =
|
||||||
|
[JuMP.value(model[:plant_dispose][shipping_node, 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]
|
[JuMP.value(model[:plant_dispose][shipping_node, 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
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ function products_report(solution; marginal_costs = true)::DataFrame
|
|||||||
df."longitude (deg)" = Float64[]
|
df."longitude (deg)" = Float64[]
|
||||||
df."year" = Int[]
|
df."year" = Int[]
|
||||||
df."amount (tonne)" = Float64[]
|
df."amount (tonne)" = Float64[]
|
||||||
|
df."amount disposed (tonne)" = Float64[]
|
||||||
df."marginal cost (\$/tonne)" = Float64[]
|
df."marginal cost (\$/tonne)" = 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"]
|
||||||
@@ -22,6 +23,7 @@ function products_report(solution; marginal_costs = true)::DataFrame
|
|||||||
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]
|
||||||
push!(
|
push!(
|
||||||
df,
|
df,
|
||||||
[
|
[
|
||||||
@@ -32,6 +34,7 @@ function products_report(solution; marginal_costs = true)::DataFrame
|
|||||||
year,
|
year,
|
||||||
amount,
|
amount,
|
||||||
marginal_cost,
|
marginal_cost,
|
||||||
|
amount_disposed,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"time horizon (years)": {
|
"time horizon (years)": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"distance metric": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -169,6 +172,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": [
|
||||||
|
|||||||
19
test/Project.toml
Normal file
19
test/Project.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name = "RELOGT"
|
||||||
|
uuid = "a6dae211-05d8-42ed-9081-b88c982fc90a"
|
||||||
|
authors = ["Alinson S. Xavier <git@axavier.org>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
||||||
|
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
||||||
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
|
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
|
||||||
|
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||||
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
|
RELOG = "a2afcdf7-cf04-4913-85f9-c0d81ddf2008"
|
||||||
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
|
|
||||||
|
[compat]
|
||||||
|
JuliaFormatter = "1"
|
||||||
358
test/fixtures/s1.json
vendored
Normal file
358
test/fixtures/s1.json
vendored
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"time horizon (years)": 2,
|
||||||
|
"distance metric": "driving"
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
test/fixtures/s1.zip
vendored
Normal file
BIN
test/fixtures/s1.zip
vendored
Normal file
Binary file not shown.
@@ -1,39 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
@testset "build_graph" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
|
||||||
graph = RELOG.build_graph(instance)
|
|
||||||
process_node_by_location_name =
|
|
||||||
Dict(n.location.location_name => n for n in graph.process_nodes)
|
|
||||||
|
|
||||||
@test length(graph.plant_shipping_nodes) == 8
|
|
||||||
@test length(graph.collection_shipping_nodes) == 10
|
|
||||||
@test length(graph.process_nodes) == 6
|
|
||||||
|
|
||||||
node = graph.collection_shipping_nodes[1]
|
|
||||||
@test node.location.name == "C1"
|
|
||||||
@test length(node.incoming_arcs) == 0
|
|
||||||
@test length(node.outgoing_arcs) == 2
|
|
||||||
@test node.outgoing_arcs[1].source.location.name == "C1"
|
|
||||||
@test node.outgoing_arcs[1].dest.location.plant_name == "F1"
|
|
||||||
@test node.outgoing_arcs[1].dest.location.location_name == "L1"
|
|
||||||
@test node.outgoing_arcs[1].values["distance"] == 1095.62
|
|
||||||
|
|
||||||
node = process_node_by_location_name["L1"]
|
|
||||||
@test node.location.plant_name == "F1"
|
|
||||||
@test node.location.location_name == "L1"
|
|
||||||
@test length(node.incoming_arcs) == 10
|
|
||||||
@test length(node.outgoing_arcs) == 2
|
|
||||||
|
|
||||||
node = process_node_by_location_name["L3"]
|
|
||||||
@test node.location.plant_name == "F2"
|
|
||||||
@test node.location.location_name == "L3"
|
|
||||||
@test length(node.incoming_arcs) == 2
|
|
||||||
@test length(node.outgoing_arcs) == 2
|
|
||||||
|
|
||||||
@test length(graph.arcs) == 38
|
|
||||||
end
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
@testset "compress" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
|
||||||
compressed = RELOG._compress(instance)
|
|
||||||
|
|
||||||
product_name_to_product = Dict(p.name => p for p in compressed.products)
|
|
||||||
location_name_to_facility = Dict()
|
|
||||||
for p in compressed.plants
|
|
||||||
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
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# RELOG: Reverse Logistics Optimization
|
|
||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
@testset "geodb_query (2018-us-county)" begin
|
|
||||||
region = RELOG.geodb_query("2018-us-county:17043")
|
|
||||||
@test region.centroid.lat == 41.83956
|
|
||||||
@test region.centroid.lon == -88.08857
|
|
||||||
@test region.population == 922_921
|
|
||||||
end
|
|
||||||
|
|
||||||
# @testset "geodb_query (2018-us-zcta)" begin
|
|
||||||
# region = RELOG.geodb_query("2018-us-zcta:60439")
|
|
||||||
# @test region.centroid.lat == 41.68241
|
|
||||||
# @test region.centroid.lon == -87.98954
|
|
||||||
# end
|
|
||||||
|
|
||||||
@testset "geodb_query (us-state)" begin
|
|
||||||
region = RELOG.geodb_query("us-state:IL")
|
|
||||||
@test region.centroid.lat == 39.73939
|
|
||||||
@test region.centroid.lon == -89.50414
|
|
||||||
@test region.population == 12_671_821
|
|
||||||
end
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
@testset "parse" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
|
||||||
|
|
||||||
centers = instance.collection_centers
|
|
||||||
plants = instance.plants
|
|
||||||
products = instance.products
|
|
||||||
location_name_to_plant = Dict(p.location_name => p for p in plants)
|
|
||||||
product_name_to_product = Dict(p.name => p for p in products)
|
|
||||||
|
|
||||||
@test length(centers) == 10
|
|
||||||
@test centers[1].name == "C1"
|
|
||||||
@test centers[1].latitude == 7
|
|
||||||
@test centers[1].latitude == 7
|
|
||||||
@test centers[1].longitude == 7
|
|
||||||
@test centers[1].amount == [934.56, 934.56]
|
|
||||||
@test centers[1].product.name == "P1"
|
|
||||||
|
|
||||||
@test length(plants) == 6
|
|
||||||
|
|
||||||
plant = location_name_to_plant["L1"]
|
|
||||||
@test plant.plant_name == "F1"
|
|
||||||
@test plant.location_name == "L1"
|
|
||||||
@test plant.input.name == "P1"
|
|
||||||
@test plant.latitude == 0
|
|
||||||
@test plant.longitude == 0
|
|
||||||
|
|
||||||
@test length(plant.sizes) == 2
|
|
||||||
@test plant.sizes[1].capacity == 250
|
|
||||||
@test plant.sizes[1].opening_cost == [500, 500]
|
|
||||||
@test plant.sizes[1].fixed_operating_cost == [30, 30]
|
|
||||||
@test plant.sizes[1].variable_operating_cost == [30, 30]
|
|
||||||
@test plant.sizes[2].capacity == 1000
|
|
||||||
@test plant.sizes[2].opening_cost == [1250, 1250]
|
|
||||||
@test plant.sizes[2].fixed_operating_cost == [30, 30]
|
|
||||||
@test plant.sizes[2].variable_operating_cost == [30, 30]
|
|
||||||
|
|
||||||
p2 = product_name_to_product["P2"]
|
|
||||||
p3 = product_name_to_product["P3"]
|
|
||||||
@test length(plant.output) == 2
|
|
||||||
@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"]
|
|
||||||
@test plant.location_name == "L3"
|
|
||||||
@test plant.input.name == "P2"
|
|
||||||
@test plant.latitude == 25
|
|
||||||
@test plant.longitude == 65
|
|
||||||
|
|
||||||
@test length(plant.sizes) == 2
|
|
||||||
@test plant.sizes[1].capacity == 1000.0
|
|
||||||
@test plant.sizes[1].opening_cost == [3000, 3000]
|
|
||||||
@test plant.sizes[1].fixed_operating_cost == [50, 50]
|
|
||||||
@test plant.sizes[1].variable_operating_cost == [50, 50]
|
|
||||||
@test plant.sizes[1] == plant.sizes[2]
|
|
||||||
|
|
||||||
p4 = product_name_to_product["P4"]
|
|
||||||
@test plant.output[p3] == 0.05
|
|
||||||
@test plant.output[p4] == 0.8
|
|
||||||
@test plant.disposal_limit[p3] == [1e8, 1e8]
|
|
||||||
@test plant.disposal_limit[p4] == [0, 0]
|
|
||||||
end
|
|
||||||
|
|
||||||
@testset "parse (geodb)" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s2.json")
|
|
||||||
|
|
||||||
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
|
|
||||||
# basedir = dirname(@__FILE__)
|
|
||||||
# @test_throws ErrorException RELOG.parsefile("$basedir/../fixtures/s1-wrong-length.json")
|
|
||||||
# end
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG, HiGHS, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
|
||||||
|
|
||||||
@testset "build" begin
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
instance = RELOG.parsefile("$basedir/../../instances/s1.json")
|
|
||||||
graph = RELOG.build_graph(instance)
|
|
||||||
model = RELOG.build_model(instance, graph, HiGHS.Optimizer)
|
|
||||||
|
|
||||||
process_node_by_location_name =
|
|
||||||
Dict(n.location.location_name => n for n in graph.process_nodes)
|
|
||||||
|
|
||||||
shipping_node_by_loc_and_prod_names = Dict(
|
|
||||||
(n.location.location_name, n.product.name) => n for n in graph.plant_shipping_nodes
|
|
||||||
)
|
|
||||||
|
|
||||||
@test length(model[:flow]) == 76
|
|
||||||
@test length(model[:dispose]) == 16
|
|
||||||
@test length(model[:open_plant]) == 12
|
|
||||||
@test length(model[:capacity]) == 12
|
|
||||||
@test length(model[:expansion]) == 12
|
|
||||||
|
|
||||||
l1 = process_node_by_location_name["L1"]
|
|
||||||
v = model[:capacity][l1, 1]
|
|
||||||
@test lower_bound(v) == 0.0
|
|
||||||
@test upper_bound(v) == 1000.0
|
|
||||||
|
|
||||||
v = model[:expansion][l1, 1]
|
|
||||||
@test lower_bound(v) == 0.0
|
|
||||||
@test upper_bound(v) == 750.0
|
|
||||||
|
|
||||||
v = model[:dispose][shipping_node_by_loc_and_prod_names["L1", "P2"], 1]
|
|
||||||
@test lower_bound(v) == 0.0
|
|
||||||
@test upper_bound(v) == 1.0
|
|
||||||
end
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG
|
|
||||||
|
|
||||||
BASEDIR = dirname(@__FILE__)
|
|
||||||
|
|
||||||
@testset "Resolve" begin
|
|
||||||
# Shoud not crash
|
|
||||||
filename = joinpath(BASEDIR, "..", "..", "instances", "s1.json")
|
|
||||||
solution_old, model_old = RELOG.solve(filename, return_model = true)
|
|
||||||
solution_new = RELOG.resolve(model_old, filename)
|
|
||||||
end
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using RELOG, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
|
||||||
|
|
||||||
basedir = dirname(@__FILE__)
|
|
||||||
|
|
||||||
@testset "solve (exact)" begin
|
|
||||||
solution_filename_a = tempname()
|
|
||||||
solution_filename_b = tempname()
|
|
||||||
solution = RELOG.solve("$basedir/../../instances/s1.json", output = solution_filename_a)
|
|
||||||
|
|
||||||
@test isfile(solution_filename_a)
|
|
||||||
|
|
||||||
RELOG.write(solution, solution_filename_b)
|
|
||||||
@test isfile(solution_filename_b)
|
|
||||||
|
|
||||||
@test "Costs" in keys(solution)
|
|
||||||
@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 "Plants" in keys(solution)
|
|
||||||
@test "F1" in keys(solution["Plants"])
|
|
||||||
@test "F2" in keys(solution["Plants"])
|
|
||||||
@test "F3" in keys(solution["Plants"])
|
|
||||||
@test "F4" in keys(solution["Plants"])
|
|
||||||
end
|
|
||||||
|
|
||||||
@testset "solve (heuristic)" begin
|
|
||||||
# Should not crash
|
|
||||||
solution = RELOG.solve("$basedir/../../instances/s1.json", heuristic = true)
|
|
||||||
end
|
|
||||||
|
|
||||||
@testset "solve (infeasible)" begin
|
|
||||||
json = JSON.parsefile("$basedir/../../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
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# RELOG: Reverse Logistics Optimization
|
|
||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
|
|
||||||
using RELOG, JSON, GZip
|
|
||||||
|
|
||||||
BASEDIR = dirname(@__FILE__)
|
|
||||||
|
|
||||||
@testset "Reports" begin
|
|
||||||
@testset "from solve" begin
|
|
||||||
solution = RELOG.solve(joinpath(BASEDIR, "..", "instances", "s1.json"))
|
|
||||||
tmp_filename = tempname()
|
|
||||||
# The following should not crash
|
|
||||||
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
|
||||||
RELOG.write_plant_outputs_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
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Copyright (C) 2020 Argonne National Laboratory
|
|
||||||
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
|
||||||
|
|
||||||
using Test
|
|
||||||
|
|
||||||
@testset "RELOG" begin
|
|
||||||
@testset "Instance" begin
|
|
||||||
include("instance/compress_test.jl")
|
|
||||||
include("instance/geodb_test.jl")
|
|
||||||
include("instance/parse_test.jl")
|
|
||||||
end
|
|
||||||
@testset "Graph" begin
|
|
||||||
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
|
|
||||||
51
test/src/RELOGT.jl
Normal file
51
test/src/RELOGT.jl
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
module RELOGT
|
||||||
|
|
||||||
|
using Test
|
||||||
|
using JuliaFormatter
|
||||||
|
|
||||||
|
include("instance/compress_test.jl")
|
||||||
|
include("instance/geodb_test.jl")
|
||||||
|
include("instance/parse_test.jl")
|
||||||
|
include("graph/build_test.jl")
|
||||||
|
include("graph/dist_test.jl")
|
||||||
|
include("model/build_test.jl")
|
||||||
|
include("model/solve_test.jl")
|
||||||
|
include("model/resolve_test.jl")
|
||||||
|
include("reports_test.jl")
|
||||||
|
|
||||||
|
basedir = dirname(@__FILE__)
|
||||||
|
|
||||||
|
function fixture(path::String)::String
|
||||||
|
return "$basedir/../fixtures/$path"
|
||||||
|
end
|
||||||
|
|
||||||
|
function runtests()
|
||||||
|
@testset "RELOG" begin
|
||||||
|
@testset "instance" begin
|
||||||
|
instance_compress_test()
|
||||||
|
instance_geodb_test()
|
||||||
|
instance_parse_test()
|
||||||
|
end
|
||||||
|
@testset "graph" begin
|
||||||
|
graph_build_test()
|
||||||
|
graph_dist_test()
|
||||||
|
end
|
||||||
|
@testset "model" begin
|
||||||
|
model_build_test()
|
||||||
|
model_solve_test()
|
||||||
|
model_resolve_test()
|
||||||
|
end
|
||||||
|
reports_test()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
function format()
|
||||||
|
JuliaFormatter.format(basedir, verbose = true)
|
||||||
|
JuliaFormatter.format("$basedir/../../src", verbose = true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
export runtests, format
|
||||||
|
|
||||||
|
end # module RELOGT
|
||||||
40
test/src/graph/build_test.jl
Normal file
40
test/src/graph/build_test.jl
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function graph_build_test()
|
||||||
|
@testset "build_graph" begin
|
||||||
|
instance = RELOG.parsefile(fixture("s1.json"))
|
||||||
|
graph = RELOG.build_graph(instance)
|
||||||
|
process_node_by_location_name =
|
||||||
|
Dict(n.location.location_name => n for n in graph.process_nodes)
|
||||||
|
|
||||||
|
@test length(graph.plant_shipping_nodes) == 8
|
||||||
|
@test length(graph.collection_shipping_nodes) == 10
|
||||||
|
@test length(graph.process_nodes) == 6
|
||||||
|
|
||||||
|
node = graph.collection_shipping_nodes[1]
|
||||||
|
@test node.location.name == "C1"
|
||||||
|
@test length(node.incoming_arcs) == 0
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
@test node.outgoing_arcs[1].source.location.name == "C1"
|
||||||
|
@test node.outgoing_arcs[1].dest.location.plant_name == "F1"
|
||||||
|
@test node.outgoing_arcs[1].dest.location.location_name == "L1"
|
||||||
|
@test node.outgoing_arcs[1].values["distance"] == 1695.364
|
||||||
|
|
||||||
|
node = process_node_by_location_name["L1"]
|
||||||
|
@test node.location.plant_name == "F1"
|
||||||
|
@test node.location.location_name == "L1"
|
||||||
|
@test length(node.incoming_arcs) == 10
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
|
node = process_node_by_location_name["L3"]
|
||||||
|
@test node.location.plant_name == "F2"
|
||||||
|
@test node.location.location_name == "L3"
|
||||||
|
@test length(node.incoming_arcs) == 2
|
||||||
|
@test length(node.outgoing_arcs) == 2
|
||||||
|
|
||||||
|
@test length(graph.arcs) == 38
|
||||||
|
end
|
||||||
|
end
|
||||||
27
test/src/graph/dist_test.jl
Normal file
27
test/src/graph/dist_test.jl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# RELOG: Reverse Logistics Optimization
|
||||||
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function graph_dist_test()
|
||||||
|
@testset "KnnDrivingDistance" begin
|
||||||
|
# Euclidean distance between Chicago and Indianapolis
|
||||||
|
@test RELOG._calculate_distance(
|
||||||
|
41.866,
|
||||||
|
-87.656,
|
||||||
|
39.764,
|
||||||
|
-86.148,
|
||||||
|
RELOG.EuclideanDistance(),
|
||||||
|
) == 265.818
|
||||||
|
|
||||||
|
# Approximate driving distance between Chicago and Indianapolis
|
||||||
|
@test RELOG._calculate_distance(
|
||||||
|
41.866,
|
||||||
|
-87.656,
|
||||||
|
39.764,
|
||||||
|
-86.148,
|
||||||
|
RELOG.KnnDrivingDistance(),
|
||||||
|
) == 316.43
|
||||||
|
end
|
||||||
|
end
|
||||||
54
test/src/instance/compress_test.jl
Normal file
54
test/src/instance/compress_test.jl
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function instance_compress_test()
|
||||||
|
@testset "compress" begin
|
||||||
|
instance = RELOG.parsefile(fixture("s1.json"))
|
||||||
|
compressed = RELOG._compress(instance)
|
||||||
|
|
||||||
|
product_name_to_product = Dict(p.name => p for p in compressed.products)
|
||||||
|
location_name_to_facility = Dict()
|
||||||
|
for p in compressed.plants
|
||||||
|
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
|
||||||
27
test/src/instance/geodb_test.jl
Normal file
27
test/src/instance/geodb_test.jl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# RELOG: Reverse Logistics Optimization
|
||||||
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function instance_geodb_test()
|
||||||
|
@testset "geodb_query (2018-us-county)" begin
|
||||||
|
region = RELOG.geodb_query("2018-us-county:17043")
|
||||||
|
@test region.centroid.lat == 41.83956
|
||||||
|
@test region.centroid.lon == -88.08857
|
||||||
|
@test region.population == 922_921
|
||||||
|
end
|
||||||
|
|
||||||
|
# @testset "geodb_query (2018-us-zcta)" begin
|
||||||
|
# region = RELOG.geodb_query("2018-us-zcta:60439")
|
||||||
|
# @test region.centroid.lat == 41.68241
|
||||||
|
# @test region.centroid.lon == -87.98954
|
||||||
|
# end
|
||||||
|
|
||||||
|
@testset "geodb_query (us-state)" begin
|
||||||
|
region = RELOG.geodb_query("us-state:IL")
|
||||||
|
@test region.centroid.lat == 39.73939
|
||||||
|
@test region.centroid.lon == -89.50414
|
||||||
|
@test region.population == 12_671_821
|
||||||
|
end
|
||||||
|
end
|
||||||
87
test/src/instance/parse_test.jl
Normal file
87
test/src/instance/parse_test.jl
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function instance_parse_test()
|
||||||
|
@testset "parse" begin
|
||||||
|
instance = RELOG.parsefile(fixture("s1.json"))
|
||||||
|
|
||||||
|
centers = instance.collection_centers
|
||||||
|
plants = instance.plants
|
||||||
|
products = instance.products
|
||||||
|
location_name_to_plant = Dict(p.location_name => p for p in plants)
|
||||||
|
product_name_to_product = Dict(p.name => p for p in products)
|
||||||
|
|
||||||
|
@test length(centers) == 10
|
||||||
|
@test centers[1].name == "C1"
|
||||||
|
@test centers[1].latitude == 7
|
||||||
|
@test centers[1].latitude == 7
|
||||||
|
@test centers[1].longitude == 7
|
||||||
|
@test centers[1].amount == [934.56, 934.56]
|
||||||
|
@test centers[1].product.name == "P1"
|
||||||
|
|
||||||
|
@test length(plants) == 6
|
||||||
|
|
||||||
|
plant = location_name_to_plant["L1"]
|
||||||
|
@test plant.plant_name == "F1"
|
||||||
|
@test plant.location_name == "L1"
|
||||||
|
@test plant.input.name == "P1"
|
||||||
|
@test plant.latitude == 0
|
||||||
|
@test plant.longitude == 0
|
||||||
|
|
||||||
|
@test length(plant.sizes) == 2
|
||||||
|
@test plant.sizes[1].capacity == 250
|
||||||
|
@test plant.sizes[1].opening_cost == [500, 500]
|
||||||
|
@test plant.sizes[1].fixed_operating_cost == [30, 30]
|
||||||
|
@test plant.sizes[1].variable_operating_cost == [30, 30]
|
||||||
|
@test plant.sizes[2].capacity == 1000
|
||||||
|
@test plant.sizes[2].opening_cost == [1250, 1250]
|
||||||
|
@test plant.sizes[2].fixed_operating_cost == [30, 30]
|
||||||
|
@test plant.sizes[2].variable_operating_cost == [30, 30]
|
||||||
|
|
||||||
|
p1 = product_name_to_product["P1"]
|
||||||
|
@test p1.disposal_limit == [1.0, 1.0]
|
||||||
|
@test p1.disposal_cost == [-1000.0, -1000.0]
|
||||||
|
|
||||||
|
p2 = product_name_to_product["P2"]
|
||||||
|
@test p2.disposal_limit == [0.0, 0.0]
|
||||||
|
@test p2.disposal_cost == [0.0, 0.0]
|
||||||
|
|
||||||
|
p3 = product_name_to_product["P3"]
|
||||||
|
@test length(plant.output) == 2
|
||||||
|
@test plant.output[p2] == 0.2
|
||||||
|
@test plant.output[p3] == 0.5
|
||||||
|
@test plant.disposal_limit[p2] == [1, 1]
|
||||||
|
@test plant.disposal_limit[p3] == [1, 1]
|
||||||
|
@test plant.disposal_cost[p2] == [-10, -10]
|
||||||
|
@test plant.disposal_cost[p3] == [-10, -10]
|
||||||
|
|
||||||
|
plant = location_name_to_plant["L3"]
|
||||||
|
@test plant.location_name == "L3"
|
||||||
|
@test plant.input.name == "P2"
|
||||||
|
@test plant.latitude == 25
|
||||||
|
@test plant.longitude == 65
|
||||||
|
|
||||||
|
@test length(plant.sizes) == 2
|
||||||
|
@test plant.sizes[1].capacity == 1000.0
|
||||||
|
@test plant.sizes[1].opening_cost == [3000, 3000]
|
||||||
|
@test plant.sizes[1].fixed_operating_cost == [50, 50]
|
||||||
|
@test plant.sizes[1].variable_operating_cost == [50, 50]
|
||||||
|
@test plant.sizes[1] == plant.sizes[2]
|
||||||
|
|
||||||
|
p4 = product_name_to_product["P4"]
|
||||||
|
@test plant.output[p3] == 0.05
|
||||||
|
@test plant.output[p4] == 0.8
|
||||||
|
@test plant.disposal_limit[p3] == [1e8, 1e8]
|
||||||
|
@test plant.disposal_limit[p4] == [0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "parse (geodb)" begin
|
||||||
|
instance = RELOG.parsefile(fixture("s2.json"))
|
||||||
|
centers = instance.collection_centers
|
||||||
|
@test centers[1].name == "C1"
|
||||||
|
@test centers[1].latitude == 41.83956
|
||||||
|
@test centers[1].longitude == -88.08857
|
||||||
|
end
|
||||||
|
end
|
||||||
40
test/src/model/build_test.jl
Normal file
40
test/src/model/build_test.jl
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
||||||
|
|
||||||
|
function model_build_test()
|
||||||
|
@testset "build" begin
|
||||||
|
instance = RELOG.parsefile(fixture("s1.json"))
|
||||||
|
graph = RELOG.build_graph(instance)
|
||||||
|
model = RELOG.build_model(instance, graph, Cbc.Optimizer)
|
||||||
|
set_optimizer_attribute(model, "logLevel", 0)
|
||||||
|
|
||||||
|
process_node_by_location_name =
|
||||||
|
Dict(n.location.location_name => n for n in graph.process_nodes)
|
||||||
|
|
||||||
|
shipping_node_by_loc_and_prod_names = Dict(
|
||||||
|
(n.location.location_name, n.product.name) => n for
|
||||||
|
n in graph.plant_shipping_nodes
|
||||||
|
)
|
||||||
|
|
||||||
|
@test length(model[:flow]) == 76
|
||||||
|
@test length(model[:plant_dispose]) == 16
|
||||||
|
@test length(model[:open_plant]) == 12
|
||||||
|
@test length(model[:capacity]) == 12
|
||||||
|
@test length(model[:expansion]) == 12
|
||||||
|
|
||||||
|
l1 = process_node_by_location_name["L1"]
|
||||||
|
v = model[:capacity][l1, 1]
|
||||||
|
@test lower_bound(v) == 0.0
|
||||||
|
@test upper_bound(v) == 1000.0
|
||||||
|
|
||||||
|
v = model[:expansion][l1, 1]
|
||||||
|
@test lower_bound(v) == 0.0
|
||||||
|
@test upper_bound(v) == 750.0
|
||||||
|
|
||||||
|
v = model[:plant_dispose][shipping_node_by_loc_and_prod_names["L1", "P2"], 1]
|
||||||
|
@test lower_bound(v) == 0.0
|
||||||
|
@test upper_bound(v) == 1.0
|
||||||
|
end
|
||||||
|
end
|
||||||
13
test/src/model/resolve_test.jl
Normal file
13
test/src/model/resolve_test.jl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function model_resolve_test()
|
||||||
|
@testset "Resolve" begin
|
||||||
|
# Shoud not crash
|
||||||
|
filename = fixture("s1.json")
|
||||||
|
solution_old, model_old = RELOG.solve(filename, return_model = true)
|
||||||
|
solution_new = RELOG.resolve(model_old, filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
70
test/src/model/solve_test.jl
Normal file
70
test/src/model/solve_test.jl
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Copyright (C) 2020 Argonne National Laboratory
|
||||||
|
# Written by Alinson Santos Xavier <axavier@anl.gov>
|
||||||
|
|
||||||
|
using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
|
||||||
|
|
||||||
|
|
||||||
|
function model_solve_test()
|
||||||
|
@testset "solve (exact)" begin
|
||||||
|
solution_filename_a = tempname()
|
||||||
|
solution_filename_b = tempname()
|
||||||
|
solution = RELOG.solve(fixture("s1.json"), output = solution_filename_a)
|
||||||
|
|
||||||
|
@test isfile(solution_filename_a)
|
||||||
|
|
||||||
|
RELOG.write(solution, solution_filename_b)
|
||||||
|
@test isfile(solution_filename_b)
|
||||||
|
|
||||||
|
@test "Costs" in keys(solution)
|
||||||
|
@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 "Plants" in keys(solution)
|
||||||
|
@test "F1" in keys(solution["Plants"])
|
||||||
|
@test "F2" in keys(solution["Plants"])
|
||||||
|
@test "F3" in keys(solution["Plants"])
|
||||||
|
@test "F4" in keys(solution["Plants"])
|
||||||
|
|
||||||
|
@test "Products" in keys(solution)
|
||||||
|
@test "P1" in keys(solution["Products"])
|
||||||
|
@test "C1" in keys(solution["Products"]["P1"])
|
||||||
|
@test "Dispose (tonne)" in keys(solution["Products"]["P1"]["C1"])
|
||||||
|
|
||||||
|
total_disposal =
|
||||||
|
sum([loc["Dispose (tonne)"] for loc in values(solution["Products"]["P1"])])
|
||||||
|
@test total_disposal == [1.0, 1.0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "solve (heuristic)" begin
|
||||||
|
# Should not crash
|
||||||
|
solution = RELOG.solve(fixture("s1.json"), heuristic = true)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "solve (infeasible)" begin
|
||||||
|
json = JSON.parsefile(fixture("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
|
||||||
|
filename = fixture("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
|
||||||
21
test/src/reports_test.jl
Normal file
21
test/src/reports_test.jl
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# RELOG: Reverse Logistics Optimization
|
||||||
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
using RELOG, JSON, GZip
|
||||||
|
|
||||||
|
function reports_test()
|
||||||
|
@testset "reports" begin
|
||||||
|
@testset "from solve" begin
|
||||||
|
solution = RELOG.solve(fixture("s1.json"))
|
||||||
|
tmp_filename = tempname()
|
||||||
|
# The following should not crash
|
||||||
|
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
||||||
|
RELOG.write_plant_outputs_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
|
||||||
Reference in New Issue
Block a user