9 Commits

16 changed files with 103 additions and 34 deletions

28
.zenodo.json Normal file
View File

@@ -0,0 +1,28 @@
{
"creators": [
{
"orcid": "0000-0002-5022-9802",
"affiliation": "Argonne National Laboratory",
"name": "Santos Xavier, Alinson"
},
{
"orcid": "0000-0002-3426-9425",
"affiliation": "Argonne National Laboratory",
"name": "Iloeje, Chukwunwike"
},
{
"affiliation": "Argonne National Laboratory",
"name": "Atkins, John"
},
{
"affiliation": "Argonne National Laboratory",
"name": "Sun, Kyle"
},
{
"affiliation": "Argonne National Laboratory",
"name": "Gallier, Audrey"
}
],
"title": "RELOG: Reverse Logistics Optimization",
"description": "<b>RELOG</b> 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."
}

View File

@@ -11,6 +11,29 @@ 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.7.2] -- 2023-03-10
### Fixed
- Core: Fixed modeling issue with collection disposal
- Core: Fix column names in products CSV file
## [0.7.1] -- 2023-03-08
### Added
- Core: Add `write_reports` function
### Changed
- Web UI: Disable usage of heuristic method
### Fixed
- Core: Prevent plants from sending products to themselves
- Core: Enforce constraint that, if plant is closed, storage cannot be used
- Web UI: Fix parsing bug in disposal limit
## [0.7.0] -- 2023-02-23 ## [0.7.0] -- 2023-02-23
### Added ### Added

View File

@@ -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.7.0" version = "0.7.2"
[deps] [deps]
CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205" CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205"

View File

@@ -235,7 +235,7 @@ const InputPage = () => {
"disposal limit (tonne)", "disposal limit (tonne)",
].forEach((key) => { ].forEach((key) => {
newData.plants[plantName][key] = { ...newData.plants[plantName][key] }; newData.plants[plantName][key] = { ...newData.plants[plantName][key] };
newData.plants[plantName][key][productName] = 0; newData.plants[plantName][key][productName] = "0";
}); });
save(newData); save(newData);
return newData; return newData;

View File

@@ -277,7 +277,7 @@ export const exportPlant = (original, parameters) => {
const v = exportValueAcf(dispCost, origDict); const v = exportValueAcf(dispCost, origDict);
if (v) { if (v) {
resDict.disposal[dispName] = { "cost ($/tonne)": v }; resDict.disposal[dispName] = { "cost ($/tonne)": v };
const limit = original["disposal limit (tonne)"][dispName]; const limit = String(original["disposal limit (tonne)"][dispName]);
if (limit.length > 0) { if (limit.length > 0) {
resDict.disposal[dispName]["limit (tonne)"] = exportValue( resDict.disposal[dispName]["limit (tonne)"] = exportValue(
limit, limit,

View File

@@ -213,7 +213,7 @@ const samplePlantsOriginal = [
}, },
"disposal limit (tonne)": { "disposal limit (tonne)": {
"Hydrogen gas": "10", "Hydrogen gas": "10",
"Carbon dioxide": "", "Carbon dioxide": 0,
Tar: "", Tar: "",
}, },
"emissions (tonne/tonne)": { "emissions (tonne/tonne)": {
@@ -406,6 +406,7 @@ const samplePlantsExported = [
}, },
"Carbon dioxide": { "Carbon dioxide": {
"cost ($/tonne)": [0, 0, 0], "cost ($/tonne)": [0, 0, 0],
"limit (tonne)": [0, 0, 0],
}, },
Tar: { Tar: {
"cost ($/tonne)": [200, 400, 800], "cost ($/tonne)": [200, 400, 800],
@@ -439,6 +440,7 @@ const samplePlantsExported = [
}, },
"Carbon dioxide": { "Carbon dioxide": {
"cost ($/tonne)": [0, 0, 0], "cost ($/tonne)": [0, 0, 0],
"limit (tonne)": [0, 0, 0],
}, },
Tar: { Tar: {
"cost ($/tonne)": [100, 200.0, 400], "cost ($/tonne)": [100, 200.0, 400],

View File

@@ -44,6 +44,7 @@ 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]
source.location != dest.location || continue
distance = _calculate_distance( distance = _calculate_distance(
source.location.latitude, source.location.latitude,
source.location.longitude, source.location.longitude,

View File

@@ -184,8 +184,8 @@ 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) == sum(model[:flow][a, t] for a in n.outgoing_arcs) +
n.location.amount[t] + model[:collection_dispose][n, t] model[:collection_dispose][n, t] == n.location.amount[t]
) )
end end
for prod in model[:instance].products for prod in model[:instance].products
@@ -237,6 +237,12 @@ function create_process_node_constraints!(model::JuMP.Model)
model[:capacity][n, t] <= n.location.sizes[2].capacity * model[:is_open][n, t] model[:capacity][n, t] <= n.location.sizes[2].capacity * model[:is_open][n, t]
) )
# If plant is closed, storage cannot be used
@constraint(
model,
model[:store][n, t] <= n.location.storage_limit * model[:is_open][n, t]
)
# If plant is open, capacity is greater than base # If plant is open, capacity is greater than base
@constraint( @constraint(
model, model,

View File

@@ -24,7 +24,7 @@ function plant_emissions_report(solution)::DataFrame
location_name, location_name,
year, year,
emission_name, emission_name,
round(emission_amount[year], digits = 2), round(emission_amount[year], digits = 6),
], ],
) )
end end

View File

@@ -30,7 +30,7 @@ function plant_outputs_report(solution)::DataFrame
end end
end end
end end
sent = round.(sent, digits = 2) sent = round.(sent, digits = 6)
disposal_amount = zeros(T) disposal_amount = zeros(T)
disposal_cost = zeros(T) disposal_cost = zeros(T)
@@ -38,8 +38,8 @@ function plant_outputs_report(solution)::DataFrame
disposal_amount += disposal_dict[product_name]["Amount (tonne)"] disposal_amount += disposal_dict[product_name]["Amount (tonne)"]
disposal_cost += disposal_dict[product_name]["Cost (\$)"] disposal_cost += disposal_dict[product_name]["Cost (\$)"]
end end
disposal_amount = round.(disposal_amount, digits = 2) disposal_amount = round.(disposal_amount, digits = 6)
disposal_cost = round.(disposal_cost, digits = 2) disposal_cost = round.(disposal_cost, digits = 6)
for year = 1:T for year = 1:T
push!( push!(
@@ -49,7 +49,7 @@ function plant_outputs_report(solution)::DataFrame
location_name, location_name,
year, year,
product_name, product_name,
round(amount_produced[year], digits = 2), round(amount_produced[year], digits = 6),
sent[year], sent[year],
disposal_amount[year], disposal_amount[year],
disposal_cost[year], disposal_cost[year],

View File

@@ -28,25 +28,25 @@ function plants_report(solution)::DataFrame
for (plant_name, plant_dict) in solution["Plants"] for (plant_name, plant_dict) in solution["Plants"]
for (location_name, location_dict) in plant_dict for (location_name, location_dict) in plant_dict
for year = 1:T for year = 1:T
capacity = round(location_dict["Capacity (tonne)"][year], digits = 2) capacity = round(location_dict["Capacity (tonne)"][year], digits = 6)
received = round(location_dict["Total input (tonne)"][year], digits = 2) received = round(location_dict["Total input (tonne)"][year], digits = 6)
processed = round(location_dict["Process (tonne)"][year], digits = 2) processed = round(location_dict["Process (tonne)"][year], digits = 6)
in_storage = round(location_dict["Storage (tonne)"][year], digits = 2) in_storage = round(location_dict["Storage (tonne)"][year], digits = 6)
utilization_factor = round(processed / capacity * 100.0, digits = 2) utilization_factor = round(processed / capacity * 100.0, digits = 6)
energy = round(location_dict["Energy (GJ)"][year], digits = 2) energy = round(location_dict["Energy (GJ)"][year], digits = 6)
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)
opening_cost = round(location_dict["Opening cost (\$)"][year], digits = 2) opening_cost = round(location_dict["Opening cost (\$)"][year], digits = 6)
expansion_cost = expansion_cost =
round(location_dict["Expansion cost (\$)"][year], digits = 2) round(location_dict["Expansion cost (\$)"][year], digits = 6)
fixed_cost = fixed_cost =
round(location_dict["Fixed operating cost (\$)"][year], digits = 2) round(location_dict["Fixed operating cost (\$)"][year], digits = 6)
var_cost = var_cost =
round(location_dict["Variable operating cost (\$)"][year], digits = 2) round(location_dict["Variable operating cost (\$)"][year], digits = 6)
storage_cost = round(location_dict["Storage cost (\$)"][year], digits = 2) storage_cost = round(location_dict["Storage cost (\$)"][year], digits = 6)
total_cost = round( total_cost = round(
opening_cost + expansion_cost + fixed_cost + var_cost + storage_cost, opening_cost + expansion_cost + fixed_cost + var_cost + storage_cost,
digits = 2, digits = 6,
) )
push!( push!(
df, df,

View File

@@ -37,8 +37,8 @@ function products_report(solution; marginal_costs = true)::DataFrame
longitude, longitude,
year, year,
amount, amount,
marginal_cost,
amount_disposed, amount_disposed,
marginal_cost,
acquisition_cost, acquisition_cost,
disposal_cost, disposal_cost,
], ],

View File

@@ -42,24 +42,24 @@ function transportation_report(solution)::DataFrame
round(dst_location_dict["Longitude (deg)"], digits = 6), round(dst_location_dict["Longitude (deg)"], digits = 6),
dst_location_dict["Input product"], dst_location_dict["Input product"],
year, year,
round(src_location_dict["Distance (km)"], digits = 2), round(src_location_dict["Distance (km)"], digits = 6),
round( round(
src_location_dict["Amount (tonne)"][year], src_location_dict["Amount (tonne)"][year],
digits = 2, digits = 6,
), ),
round( round(
src_location_dict["Amount (tonne)"][year] * src_location_dict["Amount (tonne)"][year] *
src_location_dict["Distance (km)"], src_location_dict["Distance (km)"],
digits = 2, digits = 6,
), ),
round( round(
src_location_dict["Transportation cost (\$)"][year], src_location_dict["Transportation cost (\$)"][year],
digits = 2, digits = 6,
), ),
round( round(
src_location_dict["Transportation energy (J)"][year] / src_location_dict["Transportation energy (J)"][year] /
1e9, 1e9,
digits = 2, digits = 6,
), ),
], ],
) )

View File

@@ -44,18 +44,18 @@ function transportation_emissions_report(solution)::DataFrame
round(dst_location_dict["Longitude (deg)"], digits = 6), round(dst_location_dict["Longitude (deg)"], digits = 6),
dst_location_dict["Input product"], dst_location_dict["Input product"],
year, year,
round(src_location_dict["Distance (km)"], digits = 2), round(src_location_dict["Distance (km)"], digits = 6),
round( round(
src_location_dict["Amount (tonne)"][year], src_location_dict["Amount (tonne)"][year],
digits = 2, digits = 6,
), ),
round( round(
src_location_dict["Amount (tonne)"][year] * src_location_dict["Amount (tonne)"][year] *
src_location_dict["Distance (km)"], src_location_dict["Distance (km)"],
digits = 2, digits = 6,
), ),
emission_name, emission_name,
round(emission_amount[year], digits = 2), round(emission_amount[year], digits = 6),
], ],
) )
end end

View File

@@ -12,3 +12,13 @@ function write(solution::AbstractDict, filename::AbstractString)
JSON.print(file, solution, 2) JSON.print(file, solution, 2)
end end
end end
function write_reports(solution::AbstractDict, basename::AbstractString)
RELOG.write_products_report(solution, "$(basename)_products.csv")
RELOG.write_plants_report(solution, "$(basename)_plants.csv")
RELOG.write_plant_outputs_report(solution, "$(basename)_plant_outputs.csv")
RELOG.write_plant_emissions_report(solution, "$(basename)_plant_emissions.csv")
RELOG.write_transportation_report(solution, "$(basename)_tr.csv")
RELOG.write_transportation_emissions_report(solution, "$(basename)_tr_emissions.csv")
return
end

View File

@@ -14,7 +14,6 @@ function solve(root, filename)
) )
ref_solution, ref_model = RELOG.solve( ref_solution, ref_model = RELOG.solve(
ref_file, ref_file,
heuristic = true,
optimizer = optimizer, optimizer = optimizer,
lp_optimizer = HiGHS.Optimizer, lp_optimizer = HiGHS.Optimizer,
return_model = true, return_model = true,