diff --git a/CHANGELOG.md b/CHANGELOG.md index f3776ff..5f45517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ All notable changes to this project will be documented in this file. [semver]: https://semver.org/spec/v2.0.0.html [pkjjl]: https://pkgdocs.julialang.org/v1/compatibility/#compat-pre-1.0 +# [0.6.0] -- 2022-12-15 +### Changed +- Switch from Euclidean distance to approximate driving distance + ## [0.5.2] -- 2022-08-26 ### Changed - Update to JuMP 1.x diff --git a/Project.toml b/Project.toml index 6a4eab8..038f3c2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "RELOG" uuid = "a2afcdf7-cf04-4913-85f9-c0d81ddf2008" authors = ["Alinson S Xavier "] -version = "0.5.2" +version = "0.6.0" [deps] CRC = "44b605c4-b955-5f2b-9b6d-d2bd01d3d205" @@ -18,6 +18,7 @@ JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568" diff --git a/docs/src/usage.md b/docs/src/usage.md index 9c1e384..7a2fe25 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -7,7 +7,7 @@ To use RELOG, the first step is to install the [Julia programming language](http ```julia 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: diff --git a/src/RELOG.jl b/src/RELOG.jl index d8d4226..0f39e32 100644 --- a/src/RELOG.jl +++ b/src/RELOG.jl @@ -5,19 +5,19 @@ module RELOG include("instance/structs.jl") - include("graph/structs.jl") +include("instance/geodb.jl") +include("graph/dist.jl") include("graph/build.jl") include("graph/csv.jl") include("instance/compress.jl") -include("instance/geodb.jl") include("instance/parse.jl") include("instance/validate.jl") include("model/build.jl") include("model/getsol.jl") -include("model/solve.jl") include("model/resolve.jl") +include("model/solve.jl") include("reports/plant_emissions.jl") include("reports/plant_outputs.jl") include("reports/plants.jl") diff --git a/src/graph/build.jl b/src/graph/build.jl index dc274e7..12640d6 100644 --- a/src/graph/build.jl +++ b/src/graph/build.jl @@ -2,14 +2,6 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # 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 arcs = [] next_index = 0 @@ -48,13 +40,15 @@ function build_graph(instance::Instance)::Graph end # Build arcs from collection centers to plants, and from one plant to another + metric = _KnnDrivingDistance() for source in [collection_shipping_nodes; plant_shipping_nodes] for dest in process_nodes_by_input_product[source.product] - distance = calculate_distance( + distance = _calculate_distance( source.location.latitude, source.location.longitude, dest.location.latitude, dest.location.longitude, + metric, ) values = Dict("distance" => distance) arc = Arc(source, dest, values) diff --git a/src/graph/dist.jl b/src/graph/dist.jl new file mode 100644 index 0000000..abaecc0 --- /dev/null +++ b/src/graph/dist.jl @@ -0,0 +1,69 @@ +# 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 + +Base.@kwdef mutable struct _KnnDrivingDistance + tree = nothing + ratios = nothing +end + +mutable struct _EuclideanDistance end + +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)) + 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]) + return round(dist_euclidean * ratio_pred, digits = 3) +end diff --git a/test/graph/build_test.jl b/test/graph/build_test.jl index 1b52f58..9df3cab 100644 --- a/test/graph/build_test.jl +++ b/test/graph/build_test.jl @@ -21,7 +21,7 @@ using RELOG @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 + @test node.outgoing_arcs[1].values["distance"] == 1695.364 node = process_node_by_location_name["L1"] @test node.location.plant_name == "F1" diff --git a/test/graph/dist_test.jl b/test/graph/dist_test.jl new file mode 100644 index 0000000..cce8b6a --- /dev/null +++ b/test/graph/dist_test.jl @@ -0,0 +1,25 @@ +# 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 "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 diff --git a/test/runtests.jl b/test/runtests.jl index 3fcf070..1da75c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ using Test end @testset "Graph" begin include("graph/build_test.jl") + include("graph/dist_test.jl") end @testset "Model" begin include("model/build_test.jl")