From d0bd316575fbd88a9e21bf216335bdf9653697b7 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 00:58:05 -0500 Subject: [PATCH 01/12] using TSPLIB --- Project.toml | 2 ++ deps/build.jl | 3 +++ src/Concorde.jl | 9 +++++++-- test/runtests.jl | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 8be8a7e..76fa596 100644 --- a/Project.toml +++ b/Project.toml @@ -6,7 +6,9 @@ version = "0.1.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" [compat] julia = "1" diff --git a/deps/build.jl b/deps/build.jl index 10015ff..c7961a4 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,5 +1,8 @@ import Libdl +import Pkg +Pkg.add(url="https://github.com/matago/TSPLIB.jl") + const QSOPT_LOCATION = Dict( "Darwin" => [ "https://www.math.uwaterloo.ca/~bico/qsopt/beta/codes/mac64/qsopt.a", diff --git a/src/Concorde.jl b/src/Concorde.jl index a3c397c..9e42cdf 100644 --- a/src/Concorde.jl +++ b/src/Concorde.jl @@ -1,6 +1,7 @@ module Concorde using Random, LinearAlgebra +using TSPLIB include("../deps/deps.jl") include("dist.jl") @@ -29,7 +30,7 @@ end function cleanup(name) - exts = ["mas", "pul", "sav", "sol", "tsp"] + exts = ["mas", "pul", "sav", "sol", "tsp", "res"] for ext in exts file = "$(name).$(ext)" rm(file, force=true) @@ -142,8 +143,12 @@ function solve_tsp(tsp_file::String) sol_filepath = name * ".sol" opt_tour = read_solution(sol_filepath) - opt_len = - 1 # Need to implement the calculation of the obj function + # opt_len = - 1 # Need to implement the calculation of the obj function + tsp = TSPLIB.readTSP(filepath) + M = Int.(tsp.weights) + opt_len = tour_length(opt_tour, M) + cleanup(name) return opt_tour, opt_len diff --git a/test/runtests.jl b/test/runtests.jl index 75c9edd..9d51024 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -36,7 +36,7 @@ using Test @testset "Input File" begin opt_tour, opt_len = solve_tsp("gr17.tsp") - @test opt_len == -1 - # @test opt_len == 2085 + # @test opt_len == -1 + @test opt_len == 2085 end end From 9547903aa5c966106bf60ac355444c4643bee6f0 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 01:19:15 -0500 Subject: [PATCH 02/12] tsplib ci --- .github/workflows/ci.yml | 2 ++ Project.toml | 1 - deps/build.jl | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e70f435..d44d15e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- + - run: | + julia -e 'import Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl")' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/Project.toml b/Project.toml index 76fa596..6da9669 100644 --- a/Project.toml +++ b/Project.toml @@ -6,7 +6,6 @@ version = "0.1.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" diff --git a/deps/build.jl b/deps/build.jl index c7961a4..3024cec 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,7 +1,5 @@ import Libdl -import Pkg -Pkg.add(url="https://github.com/matago/TSPLIB.jl") const QSOPT_LOCATION = Dict( "Darwin" => [ From 6a7704b0ea0659c278bee1dda0758f8b956d76c3 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 01:23:33 -0500 Subject: [PATCH 03/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d44d15e..f4536cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - run: | - julia -e 'import Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl")' + julia -e 'import Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl"); Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From 0cccfb6c2b93bee7303f44317bcb5f8b7fe5a6af Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 01:32:47 -0500 Subject: [PATCH 04/12] copying codes from TSPLIB (temporarily) --- .github/workflows/ci.yml | 2 - Project.toml | 3 +- src/Concorde.jl | 8 +- src/tsplib.jl | 327 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 6 deletions(-) create mode 100644 src/tsplib.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4536cf..e70f435 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,6 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- - - run: | - julia -e 'import Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl"); Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/Project.toml b/Project.toml index 6da9669..493873d 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,11 @@ authors = ["Changhyun Kwon and contributors"] version = "0.1.0" [deps] +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Match = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" [compat] julia = "1" diff --git a/src/Concorde.jl b/src/Concorde.jl index 9e42cdf..d6e3736 100644 --- a/src/Concorde.jl +++ b/src/Concorde.jl @@ -1,11 +1,13 @@ module Concorde using Random, LinearAlgebra -using TSPLIB include("../deps/deps.jl") include("dist.jl") -# Write your package code here. + +# using TSPLIB +using Match, DataStructures +include("tsplib.jl") function read_solution(filepath) sol = readlines(filepath) @@ -145,7 +147,7 @@ function solve_tsp(tsp_file::String) opt_tour = read_solution(sol_filepath) # opt_len = - 1 # Need to implement the calculation of the obj function - tsp = TSPLIB.readTSP(filepath) + tsp = readTSP(filepath) M = Int.(tsp.weights) opt_len = tour_length(opt_tour, M) diff --git a/src/tsplib.jl b/src/tsplib.jl new file mode 100644 index 0000000..b64128b --- /dev/null +++ b/src/tsplib.jl @@ -0,0 +1,327 @@ + struct TSP + name::AbstractString + dimension::Integer + weight_type::AbstractString + weights::Matrix + nodes::Matrix + Dnodes::Bool + ffx::Function + pfx::Function + optimal::Float64 + end + + const tsp_keys = ["NAME", + "TYPE", + "COMMENT", + "DIMENSION", + "EDGE_WEIGHT_TYPE", + "EDGE_WEIGHT_FORMAT", + "EDGE_DATA_FORMAT", + "NODE_COORD_TYPE", + "DISPLAY_DATA_TYPE", + "NODE_COORD_SECTION", + "DEPOT_SECTION", + "DEMAND_SECTION", + "EDGE_DATA_SECTION", + "FIXED_EDGES_SECTION", + "DISPLAY_DATA_SECTION", + "TOUR_SECTION", + "EDGE_WEIGHT_SECTION", + "EOF"] + + function readTSP(path::AbstractString) + raw = read(path, String) + checkEOF(raw) + return _generateTSP(raw) + end + + readTSPLIB(instance::Symbol) = readTSP(joinpath(TSPLIB95_path,string(instance)*".tsp")) + + function _generateTSP(raw::AbstractString) + _dict = keyextract(raw, tsp_keys) + name = _dict["NAME"] + dimension = parse(Int,_dict["DIMENSION"]) + weight_type = _dict["EDGE_WEIGHT_TYPE"] + dxp = false + + if weight_type == "EXPLICIT" && haskey(_dict,"EDGE_WEIGHT_SECTION") + explicits = parse.(Float64, split(_dict["EDGE_WEIGHT_SECTION"])) + weights = explicit_weights(_dict["EDGE_WEIGHT_FORMAT"],explicits) + #Push display data to nodes if possible + if haskey(_dict,"DISPLAY_DATA_SECTION") + coords = parse.(Float64, split(_dict["DISPLAY_DATA_SECTION"])) + n_r = convert(Integer,length(coords)/dimension) + nodes = reshape(coords,(n_r,dimension))'[:,2:end] + dxp = true + else + nodes = zeros(dimension,2) + end + elseif haskey(_dict,"NODE_COORD_SECTION") + coords = parse.(Float64, split(_dict["NODE_COORD_SECTION"])) + n_r = convert(Integer,length(coords)/dimension) + nodes = reshape(coords,(n_r,dimension))'[:,2:end] + weights = calc_weights(_dict["EDGE_WEIGHT_TYPE"],nodes) + end + + fFX = fullFit(weights) + pFX = partFit(weights) + optimal = Optimals[Symbol(name)] + + TSP(name,dimension,weight_type,weights,nodes,dxp,fFX,pFX,optimal) + end + + function keyextract(raw::T,ks::Array{T}) where T<:AbstractString + pq = PriorityQueue{T,Tuple{Integer,Integer}}() + vals = Dict{T,T}() + for k in ks + idx = findfirst(k,raw) + idx != nothing && enqueue!(pq,k,extrema(idx)) + end + while length(pq) > 1 + s_key, s_pts = peek(pq) + dequeue!(pq) + f_key, f_pts = peek(pq) + rng = (s_pts[2]+1):(f_pts[1]-1) + vals[s_key] = strip(replace(raw[rng],":"=>"")) + end + return vals + end + + + function explicit_weights(key::AbstractString,data::Vector{Float64}) + w = @match key begin + "UPPER_DIAG_ROW" => vec2UDTbyRow(data) + "LOWER_DIAG_ROW" => vec2LDTbyRow(data) + "UPPER_DIAG_COL" => vec2UDTbyCol(data) + "LOWER_DIAG_COL" => vec2LDTbyCol(data) + "UPPER_ROW" => vec2UTbyRow(data) + "FULL_MATRIX" => vec2FMbyRow(data) + end + if !in(key,["FULL_MATRIX"]) + w.+=w' + end + return w + end + + function calc_weights(key::AbstractString,data::Matrix) + w = @match key begin + "EUC_2D" => euclidian(data[:,1], data[:,2]) + "GEO" => geo(data[:,1], data[:,2]) + "ATT" => att_euclidian(data[:,1], data[:,2]) + "CEIL_2D" => ceil_euclidian(data[:,1], data[:,2]) + end + + return w + end + + function checkEOF(raw::AbstractString) + n = findlast("EOF",raw) + if n == nothing + throw("EOF not found") + end + return + end + + + function vec2LDTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Integer,(sqrt(8n+1)-1)/2) + s*(s+1)/2 == n || error("vec2LTbyRow: length of vector is not triangular") + k=0 + [i<=j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' +end + +function vec2UDTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Integer,(sqrt(8n+1)-1)/2) + s*(s+1)/2 == n || error("vec2UTbyRow: length of vector is not triangular") + k=0 + [i>=j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' +end + +function vec2LDTbyCol(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Integer,(sqrt(8n+1)-1)/2) + s*(s+1)/2 == n || error("vec2LTbyCol: length of vector is not triangular") + k=0 + [i>=j ? (k+=1; v[k]) : z for i=1:s, j=1:s] +end + +function vec2UDTbyCol(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Integer,(sqrt(8n+1)-1)/2) + s*(s+1)/2 == n || error("vec2UTbyCol: length of vector is not triangular") + k=0 + [i<=j ? (k+=1; v[k]) : z for i=1:s, j=1:s] +end + +function vec2UTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Integer,((sqrt(8n+1)-1)/2)+1) + (s*(s+1)/2)-s == n || error("vec2UTbyRow: length of vector is not triangular") + k=0 + [i>j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' +end + +function vec2FMbyRow(v::AbstractVector{T}, z::T=zero(T)) where T + n = length(v) + s = round(Int,sqrt(n)) + s^2 == n || error("vec2FMbyRow: length of vector is not square") + k=0 + [(k+=1; v[k]) for i=1:s, j=1:s] +end + +function findTSP(path::AbstractString) + if isdir(path) + syms = [Symbol(split(file,".")[1]) for file in readdir(path) if (split(file,".")[end] == "tsp")] + else + error("Not a valid directory") + end + return syms +end + + +#=Generator function for TSP that takes the weight Matrix +and returns a function that evaluates the fitness of a single path=# + + function fullFit(costs::AbstractMatrix{Float64}) + N = size(costs,1) + function fFit(tour::Vector{T}) where T<:Integer + @assert length(tour) == N "Tour must be of length $N" + @assert isperm(tour) "Not a valid tour, not a permutation" + #distance = weights[from,to] (from,to) in tour + distance = costs[tour[N],tour[1]] + for i in 1:N-1 + @inbounds distance += costs[tour[i],tour[i+1]] + end + return distance + end + return fFit + end + + function partFit(costs::AbstractMatrix{Float64}) + N = size(costs,1) + function pFit(tour::Vector{T}) where T<:Integer + n = length(tour) + #distance = weights[from,to] (from,to) in tour + distance = n == N ? costs[tour[N],tour[1]] : zero(Float64) + for i in 1:n-1 + @inbounds distance += costs[tour[i],tour[i+1]] + end + return distance + end + return pFit + end + + const Optimals = Dict{Symbol,Float64}( + [:a280 => 2579, + :ali535 => 202339, + :att48 => 10628, + :att532 => 27686, + :bayg29 => 1610, + :bays29 => 2020, + :berlin52 => 7542, + :bier127 => 118282, + :brazil58 => 25395, + :brd14051 => 469385, + :brg180 => 1950, + :burma14 => 3323, + :ch130 => 6110, + :ch150 => 6528, + :d198 => 15780, + :d493 => 35002, + :d657 => 48912, + :d1291 => 50801, + :d1655 => 62128, + :d2103 => 80450, + :d15112 => 1573084, + :d18512 => 645238, + :dantzig42 => 699, + :dsj1000 => 18659688, + :eil51 => 426, + :eil76 => 538, + :eil101 => 629, + :fl417 => 11861, + :fl1400 => 20127, + :fl1577 => 22249, + :fl3795 => 28772, + :fnl4461 => 182566, + :fri26 => 937, + :gil262 => 2378, + :gr17 => 2085, + :gr21 => 2707, + :gr24 => 1272, + :gr48 => 5046, + :gr96 => 55209, + :gr120 => 6942, + :gr137 => 69853, + :gr202 => 40160, + :gr229 => 134602, + :gr431 => 171414, + :gr666 => 294358, + :hk48 => 11461, + :kroA100 => 21282, + :kroB100 => 22141, + :kroC100 => 20749, + :kroD100 => 21294, + :kroE100 => 22068, + :kroA150 => 26524, + :kroB150 => 26130, + :kroA200 => 29368, + :kroB200 => 29437, + :lin105 => 14379, + :lin318 => 42029, + :linhp318 => 41345, + :nrw1379 => 56638, + :p654 => 34643, + :pa561 => 2763, + :pcb442 => 50778, + :pcb1173 => 56892, + :pcb3038 => 137694, + :pla7397 => 23260728, + :pla33810 => 66048945, + :pla85900 => 142382641, + :pr76 => 108159, + :pr107 => 44303, + :pr124 => 59030, + :pr136 => 96772, + :pr144 => 58537, + :pr152 => 73682, + :pr226 => 80369, + :pr264 => 49135, + :pr299 => 48191, + :pr439 => 107217, + :pr1002 => 259045, + :pr2392 => 378032, + :rat99 => 1211, + :rat195 => 2323, + :rat575 => 6773, + :rat783 => 8806, + :rd100 => 7910, + :rd400 => 15281, + :rl1304 => 252948, + :rl1323 => 270199, + :rl1889 => 316536, + :rl5915 => 565530, + :rl5934 => 556045, + :rl11849 => 923288, + :si175 => 21407, + :si535 => 48450, + :si1032 => 92650, + :st70 => 675, + :swiss42 => 1273, + :ts225 => 126643, + :tsp225 => 3916, + :u159 => 42080, + :u574 => 36905, + :u724 => 41910, + :u1060 => 224094, + :u1432 => 152970, + :u1817 => 57201, + :u2152 => 64253, + :u2319 => 234256, + :ulysses16 => 6859, + :ulysses22 => 7013, + :usa13509 => 19982859, + :vm1084 => 239297, + :vm1748 => 336556]) \ No newline at end of file From de04d1ff0ea3741a2e6d36145566c657cd848500 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 13:40:05 -0500 Subject: [PATCH 05/12] tsplib-ci --- .github/workflows/ci.yml | 2 + Project.toml | 1 + src/Concorde.jl | 6 +- src/tsplib.jl | 327 --------------------------------------- 4 files changed, 6 insertions(+), 330 deletions(-) delete mode 100644 src/tsplib.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e70f435..557a1ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- + - name: Install dependencies + run: julia --project=. -e 'using Pkg; pkg"add MyPackage#master"; Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/Project.toml b/Project.toml index 493873d..5a88f1b 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Match = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" [compat] julia = "1" diff --git a/src/Concorde.jl b/src/Concorde.jl index d6e3736..634cac6 100644 --- a/src/Concorde.jl +++ b/src/Concorde.jl @@ -5,9 +5,9 @@ using Random, LinearAlgebra include("../deps/deps.jl") include("dist.jl") -# using TSPLIB -using Match, DataStructures -include("tsplib.jl") +using TSPLIB +# using Match, DataStructures +# include("tsplib.jl") function read_solution(filepath) sol = readlines(filepath) diff --git a/src/tsplib.jl b/src/tsplib.jl deleted file mode 100644 index b64128b..0000000 --- a/src/tsplib.jl +++ /dev/null @@ -1,327 +0,0 @@ - struct TSP - name::AbstractString - dimension::Integer - weight_type::AbstractString - weights::Matrix - nodes::Matrix - Dnodes::Bool - ffx::Function - pfx::Function - optimal::Float64 - end - - const tsp_keys = ["NAME", - "TYPE", - "COMMENT", - "DIMENSION", - "EDGE_WEIGHT_TYPE", - "EDGE_WEIGHT_FORMAT", - "EDGE_DATA_FORMAT", - "NODE_COORD_TYPE", - "DISPLAY_DATA_TYPE", - "NODE_COORD_SECTION", - "DEPOT_SECTION", - "DEMAND_SECTION", - "EDGE_DATA_SECTION", - "FIXED_EDGES_SECTION", - "DISPLAY_DATA_SECTION", - "TOUR_SECTION", - "EDGE_WEIGHT_SECTION", - "EOF"] - - function readTSP(path::AbstractString) - raw = read(path, String) - checkEOF(raw) - return _generateTSP(raw) - end - - readTSPLIB(instance::Symbol) = readTSP(joinpath(TSPLIB95_path,string(instance)*".tsp")) - - function _generateTSP(raw::AbstractString) - _dict = keyextract(raw, tsp_keys) - name = _dict["NAME"] - dimension = parse(Int,_dict["DIMENSION"]) - weight_type = _dict["EDGE_WEIGHT_TYPE"] - dxp = false - - if weight_type == "EXPLICIT" && haskey(_dict,"EDGE_WEIGHT_SECTION") - explicits = parse.(Float64, split(_dict["EDGE_WEIGHT_SECTION"])) - weights = explicit_weights(_dict["EDGE_WEIGHT_FORMAT"],explicits) - #Push display data to nodes if possible - if haskey(_dict,"DISPLAY_DATA_SECTION") - coords = parse.(Float64, split(_dict["DISPLAY_DATA_SECTION"])) - n_r = convert(Integer,length(coords)/dimension) - nodes = reshape(coords,(n_r,dimension))'[:,2:end] - dxp = true - else - nodes = zeros(dimension,2) - end - elseif haskey(_dict,"NODE_COORD_SECTION") - coords = parse.(Float64, split(_dict["NODE_COORD_SECTION"])) - n_r = convert(Integer,length(coords)/dimension) - nodes = reshape(coords,(n_r,dimension))'[:,2:end] - weights = calc_weights(_dict["EDGE_WEIGHT_TYPE"],nodes) - end - - fFX = fullFit(weights) - pFX = partFit(weights) - optimal = Optimals[Symbol(name)] - - TSP(name,dimension,weight_type,weights,nodes,dxp,fFX,pFX,optimal) - end - - function keyextract(raw::T,ks::Array{T}) where T<:AbstractString - pq = PriorityQueue{T,Tuple{Integer,Integer}}() - vals = Dict{T,T}() - for k in ks - idx = findfirst(k,raw) - idx != nothing && enqueue!(pq,k,extrema(idx)) - end - while length(pq) > 1 - s_key, s_pts = peek(pq) - dequeue!(pq) - f_key, f_pts = peek(pq) - rng = (s_pts[2]+1):(f_pts[1]-1) - vals[s_key] = strip(replace(raw[rng],":"=>"")) - end - return vals - end - - - function explicit_weights(key::AbstractString,data::Vector{Float64}) - w = @match key begin - "UPPER_DIAG_ROW" => vec2UDTbyRow(data) - "LOWER_DIAG_ROW" => vec2LDTbyRow(data) - "UPPER_DIAG_COL" => vec2UDTbyCol(data) - "LOWER_DIAG_COL" => vec2LDTbyCol(data) - "UPPER_ROW" => vec2UTbyRow(data) - "FULL_MATRIX" => vec2FMbyRow(data) - end - if !in(key,["FULL_MATRIX"]) - w.+=w' - end - return w - end - - function calc_weights(key::AbstractString,data::Matrix) - w = @match key begin - "EUC_2D" => euclidian(data[:,1], data[:,2]) - "GEO" => geo(data[:,1], data[:,2]) - "ATT" => att_euclidian(data[:,1], data[:,2]) - "CEIL_2D" => ceil_euclidian(data[:,1], data[:,2]) - end - - return w - end - - function checkEOF(raw::AbstractString) - n = findlast("EOF",raw) - if n == nothing - throw("EOF not found") - end - return - end - - - function vec2LDTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Integer,(sqrt(8n+1)-1)/2) - s*(s+1)/2 == n || error("vec2LTbyRow: length of vector is not triangular") - k=0 - [i<=j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' -end - -function vec2UDTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Integer,(sqrt(8n+1)-1)/2) - s*(s+1)/2 == n || error("vec2UTbyRow: length of vector is not triangular") - k=0 - [i>=j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' -end - -function vec2LDTbyCol(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Integer,(sqrt(8n+1)-1)/2) - s*(s+1)/2 == n || error("vec2LTbyCol: length of vector is not triangular") - k=0 - [i>=j ? (k+=1; v[k]) : z for i=1:s, j=1:s] -end - -function vec2UDTbyCol(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Integer,(sqrt(8n+1)-1)/2) - s*(s+1)/2 == n || error("vec2UTbyCol: length of vector is not triangular") - k=0 - [i<=j ? (k+=1; v[k]) : z for i=1:s, j=1:s] -end - -function vec2UTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Integer,((sqrt(8n+1)-1)/2)+1) - (s*(s+1)/2)-s == n || error("vec2UTbyRow: length of vector is not triangular") - k=0 - [i>j ? (k+=1; v[k]) : z for i=1:s, j=1:s]' -end - -function vec2FMbyRow(v::AbstractVector{T}, z::T=zero(T)) where T - n = length(v) - s = round(Int,sqrt(n)) - s^2 == n || error("vec2FMbyRow: length of vector is not square") - k=0 - [(k+=1; v[k]) for i=1:s, j=1:s] -end - -function findTSP(path::AbstractString) - if isdir(path) - syms = [Symbol(split(file,".")[1]) for file in readdir(path) if (split(file,".")[end] == "tsp")] - else - error("Not a valid directory") - end - return syms -end - - -#=Generator function for TSP that takes the weight Matrix -and returns a function that evaluates the fitness of a single path=# - - function fullFit(costs::AbstractMatrix{Float64}) - N = size(costs,1) - function fFit(tour::Vector{T}) where T<:Integer - @assert length(tour) == N "Tour must be of length $N" - @assert isperm(tour) "Not a valid tour, not a permutation" - #distance = weights[from,to] (from,to) in tour - distance = costs[tour[N],tour[1]] - for i in 1:N-1 - @inbounds distance += costs[tour[i],tour[i+1]] - end - return distance - end - return fFit - end - - function partFit(costs::AbstractMatrix{Float64}) - N = size(costs,1) - function pFit(tour::Vector{T}) where T<:Integer - n = length(tour) - #distance = weights[from,to] (from,to) in tour - distance = n == N ? costs[tour[N],tour[1]] : zero(Float64) - for i in 1:n-1 - @inbounds distance += costs[tour[i],tour[i+1]] - end - return distance - end - return pFit - end - - const Optimals = Dict{Symbol,Float64}( - [:a280 => 2579, - :ali535 => 202339, - :att48 => 10628, - :att532 => 27686, - :bayg29 => 1610, - :bays29 => 2020, - :berlin52 => 7542, - :bier127 => 118282, - :brazil58 => 25395, - :brd14051 => 469385, - :brg180 => 1950, - :burma14 => 3323, - :ch130 => 6110, - :ch150 => 6528, - :d198 => 15780, - :d493 => 35002, - :d657 => 48912, - :d1291 => 50801, - :d1655 => 62128, - :d2103 => 80450, - :d15112 => 1573084, - :d18512 => 645238, - :dantzig42 => 699, - :dsj1000 => 18659688, - :eil51 => 426, - :eil76 => 538, - :eil101 => 629, - :fl417 => 11861, - :fl1400 => 20127, - :fl1577 => 22249, - :fl3795 => 28772, - :fnl4461 => 182566, - :fri26 => 937, - :gil262 => 2378, - :gr17 => 2085, - :gr21 => 2707, - :gr24 => 1272, - :gr48 => 5046, - :gr96 => 55209, - :gr120 => 6942, - :gr137 => 69853, - :gr202 => 40160, - :gr229 => 134602, - :gr431 => 171414, - :gr666 => 294358, - :hk48 => 11461, - :kroA100 => 21282, - :kroB100 => 22141, - :kroC100 => 20749, - :kroD100 => 21294, - :kroE100 => 22068, - :kroA150 => 26524, - :kroB150 => 26130, - :kroA200 => 29368, - :kroB200 => 29437, - :lin105 => 14379, - :lin318 => 42029, - :linhp318 => 41345, - :nrw1379 => 56638, - :p654 => 34643, - :pa561 => 2763, - :pcb442 => 50778, - :pcb1173 => 56892, - :pcb3038 => 137694, - :pla7397 => 23260728, - :pla33810 => 66048945, - :pla85900 => 142382641, - :pr76 => 108159, - :pr107 => 44303, - :pr124 => 59030, - :pr136 => 96772, - :pr144 => 58537, - :pr152 => 73682, - :pr226 => 80369, - :pr264 => 49135, - :pr299 => 48191, - :pr439 => 107217, - :pr1002 => 259045, - :pr2392 => 378032, - :rat99 => 1211, - :rat195 => 2323, - :rat575 => 6773, - :rat783 => 8806, - :rd100 => 7910, - :rd400 => 15281, - :rl1304 => 252948, - :rl1323 => 270199, - :rl1889 => 316536, - :rl5915 => 565530, - :rl5934 => 556045, - :rl11849 => 923288, - :si175 => 21407, - :si535 => 48450, - :si1032 => 92650, - :st70 => 675, - :swiss42 => 1273, - :ts225 => 126643, - :tsp225 => 3916, - :u159 => 42080, - :u574 => 36905, - :u724 => 41910, - :u1060 => 224094, - :u1432 => 152970, - :u1817 => 57201, - :u2152 => 64253, - :u2319 => 234256, - :ulysses16 => 6859, - :ulysses22 => 7013, - :usa13509 => 19982859, - :vm1084 => 239297, - :vm1748 => 336556]) \ No newline at end of file From 13436595e3ca4a0795d3a8c7d939bfff38f6eb0e Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 13:42:32 -0500 Subject: [PATCH 06/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 557a1ce..8f8eeff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies - run: julia --project=. -e 'using Pkg; pkg"add MyPackage#master"; Pkg.instantiate()' + run: julia --project=. -e 'using Pkg; pkg"add https://github.com/matago/TSPLIB.jl"; Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From 28c9e84149d6009927b3abaab483d8e9e46c15cb Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 13:44:46 -0500 Subject: [PATCH 07/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f8eeff..cef4d34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies - run: julia --project=. -e 'using Pkg; pkg"add https://github.com/matago/TSPLIB.jl"; Pkg.instantiate()' + run: julia --project=. -e 'using Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl")"; Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From 40344ed03109b824e1333e7aa72e9e49616ab6ab Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 13:47:58 -0500 Subject: [PATCH 08/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cef4d34..52eef0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies - run: julia --project=. -e 'using Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl")"; Pkg.instantiate()' + run: julia --project=. -e 'using Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl"); Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From f6080c236c44ca234321add115b45dd9fb9928d1 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 13:50:50 -0500 Subject: [PATCH 09/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52eef0c..dabc3de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies - run: julia --project=. -e 'using Pkg; Pkg.add(url="https://github.com/matago/TSPLIB.jl"); Pkg.instantiate()' + run: julia --project=. -e 'using Pkg; Pkg.add(url="https:\/\/github.com\/matago\/TSPLIB.jl"); Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From afec1fc4d1387fba42eda96cf5c4e42e7245257b Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 14:00:54 -0500 Subject: [PATCH 10/12] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dabc3de..1181bb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies - run: julia --project=. -e 'using Pkg; Pkg.add(url="https:\/\/github.com\/matago\/TSPLIB.jl"); Pkg.instantiate()' + run: julia --project=. -e 'using Pkg; Pkg.add(url="""https://github.com/matago/TSPLIB.jl"""); Pkg.instantiate()' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From 88aedf0e608e002e0e70ecfaf4e84adc6df333ff Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 15:32:01 -0500 Subject: [PATCH 11/12] Update Project.toml --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 5a88f1b..6da9669 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,8 @@ authors = ["Changhyun Kwon and contributors"] version = "0.1.0" [deps] -DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Match = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" From e2c43b846507aad4f11af0bb1802d0460a829bd6 Mon Sep 17 00:00:00 2001 From: Changhyun Kwon Date: Thu, 25 Feb 2021 16:19:09 -0500 Subject: [PATCH 12/12] refactoring functions --- src/Concorde.jl | 155 ++---------------------------------------------- src/dist.jl | 110 +++++++++++++++++----------------- src/solver.jl | 98 ++++++++++++++++++++++++++++++ src/util.jl | 31 ++++++++++ 4 files changed, 188 insertions(+), 206 deletions(-) create mode 100644 src/solver.jl create mode 100644 src/util.jl diff --git a/src/Concorde.jl b/src/Concorde.jl index 634cac6..811ac6f 100644 --- a/src/Concorde.jl +++ b/src/Concorde.jl @@ -1,160 +1,13 @@ module Concorde using Random, LinearAlgebra - -include("../deps/deps.jl") -include("dist.jl") - using TSPLIB -# using Match, DataStructures -# include("tsplib.jl") - -function read_solution(filepath) - sol = readlines(filepath) - n_nodes = sol[1] - tour = parse.(Int, split(join(sol[2:end]))) .+ 1 - return tour -end - -function tour_length(tour, M) - n_nodes = length(tour) - len = 0 - for i in 1:n_nodes - j = i + 1 - if i == n_nodes - j = 1 - end - - len += M[tour[i], tour[j]] - end - return len -end - - -function cleanup(name) - exts = ["mas", "pul", "sav", "sol", "tsp", "res"] - for ext in exts - file = "$(name).$(ext)" - rm(file, force=true) - file = "O$(name).$(ext)" - rm(file, force=true) - end -end - -function solve_tsp(dist_mtx::Matrix{Int}) - if !issymmetric(dist_mtx) - error("Asymmetric TSP is not supported.") - end - - n_nodes = size(dist_mtx, 1) - name = randstring(10) - filepath = name * ".tsp" - lower_diag_row = Int[] - for i in 1:n_nodes - for j in 1:i - push!(lower_diag_row, dist_mtx[i, j]) - end - end - buf = 10 - n_rows = length(lower_diag_row) / buf |> ceil |> Int - rows = String[] - for i in 1:n_rows - s = buf * (i-1) + 1 - t = min(buf * i, length(lower_diag_row)) - push!(rows, join(lower_diag_row[s:t], " ")) - end - - open(filepath, "w") do io - write(io, "NAME: $(name)\n") - write(io, "TYPE: TSP\n") - write(io, "COMMENT: $(name)\n") - write(io, "DIMENSION: $(n_nodes)\n") - write(io, "EDGE_WEIGHT_TYPE: EXPLICIT\n") - write(io, "EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW \n") - write(io, "EDGE_WEIGHT_SECTION\n") - for r in rows - write(io, "$r\n") - end - write(io, "EOF\n") - end - - status = run(`$(Concorde.CONCORDE_EXECUTABLE) $(filepath)`, wait=false) - while !success(status) - # - end - - sol_filepath = name * ".sol" - opt_tour = read_solution(sol_filepath) - opt_len = tour_length(opt_tour, dist_mtx) - - cleanup(name) - return opt_tour, opt_len -end - - -function solve_tsp(x::Vector{Float64}, y::Vector{Float64}; dist="EUC_2D") - n_nodes = length(x) - @assert length(x) == length(y) - - name = randstring(10) - filepath = name * ".tsp" - - open(filepath, "w") do io - write(io, "NAME: $(name)\n") - write(io, "TYPE: TSP\n") - write(io, "COMMENT: $(name)\n") - write(io, "DIMENSION: $(n_nodes)\n") - write(io, "EDGE_WEIGHT_TYPE: $(dist)\n") - write(io, "EDGE_WEIGHT_FORMAT: FUNCTION \n") - write(io, "NODE_COORD_TYPE: TWOD_COORDS \n") - write(io, "NODE_COORD_SECTION\n") - for i in 1:n_nodes - write(io, "$i $(x[i]) $(y[i])\n") - end - write(io, "EOF\n") - end - - status = run(`$(Concorde.CONCORDE_EXECUTABLE) $(filepath)`, wait=false) - while !success(status) - # - end - - sol_filepath = name * ".sol" - opt_tour = read_solution(sol_filepath) - opt_len = tour_length(opt_tour, dist_matrix(x, y, dist=dist)) - - cleanup(name) - - return opt_tour, opt_len -end -function solve_tsp(tsp_file::String) - if !isfile(tsp_file) - error("$(tsp_file) is not a file.") - end - - name = randstring(10) - filepath = name * ".tsp" - cp(tsp_file, filepath) - - status = run(`$(Concorde.CONCORDE_EXECUTABLE) $(filepath)`, wait=false) - while !success(status) - # - end - - sol_filepath = name * ".sol" - opt_tour = read_solution(sol_filepath) - # opt_len = - 1 # Need to implement the calculation of the obj function - - tsp = readTSP(filepath) - M = Int.(tsp.weights) - opt_len = tour_length(opt_tour, M) - - cleanup(name) - - return opt_tour, opt_len -end +include("../deps/deps.jl") +include("dist.jl") +include("util.jl") +include("solver.jl") export solve_tsp diff --git a/src/dist.jl b/src/dist.jl index a8b76b5..78bf9e7 100644 --- a/src/dist.jl +++ b/src/dist.jl @@ -1,63 +1,63 @@ -function nint(x::Float64) - return round(Int, x) -end +# function nint(x::Float64) +# return round(Int, x) +# end -function geo_coordinate(x, y) - PI = 3.141592 +# function geo_coordinate(x, y) +# PI = 3.141592 - deg = nint(x) - m = x - deg - latitude = PI * (deg + 5.0 * m / 3.0) / 180.0 +# deg = nint(x) +# m = x - deg +# latitude = PI * (deg + 5.0 * m / 3.0) / 180.0 - deg = nint(y) - m = y - deg - longitude = PI * (deg + 5.0 * m / 3.0) / 180.0 +# deg = nint(y) +# m = y - deg +# longitude = PI * (deg + 5.0 * m / 3.0) / 180.0 - return latitude, longitude -end +# return latitude, longitude +# end -function distance2D(xi, yi, xj, yj; dist="EUC_2D") - if dist == "EUC_2D" - xd = xi - xj - yd = yi - yj - return nint(sqrt(xd*xd + yd*yd)) - elseif dist == "MAN_2D" - xd = abs(xi - xj) - yd = abs(yi - yj) - return nint(xd + yd) - elseif dist == "MAX_2D" - xd = abs(xi - xj) - yd = abs(yi - yj) - return max(nint(xd), nint(yd)) - elseif dist == "GEO" - lat_i, long_i = geo_coordinate(xi, yi) - lat_j, long_j = geo_coordinate(xj, yj) - RRR = 6378.388 - q1 = cos(long_i - long_j) - q2 = cos(lat_i - lat_j) - q3 = cos(lat_i + lat_j) - dij = RRR * acos( 0.5*((1.0+q1)*q2 - (1.0-q1)*q3) ) + 1.0 - return floor(Int, dij) - else - error("Distance function $dist is not supported.") - end -end +# function distance2D(xi, yi, xj, yj; dist="EUC_2D") +# if dist == "EUC_2D" +# xd = xi - xj +# yd = yi - yj +# return nint(sqrt(xd*xd + yd*yd)) +# elseif dist == "MAN_2D" +# xd = abs(xi - xj) +# yd = abs(yi - yj) +# return nint(xd + yd) +# elseif dist == "MAX_2D" +# xd = abs(xi - xj) +# yd = abs(yi - yj) +# return max(nint(xd), nint(yd)) +# elseif dist == "GEO" +# lat_i, long_i = geo_coordinate(xi, yi) +# lat_j, long_j = geo_coordinate(xj, yj) +# RRR = 6378.388 +# q1 = cos(long_i - long_j) +# q2 = cos(lat_i - lat_j) +# q3 = cos(lat_i + lat_j) +# dij = RRR * acos( 0.5*((1.0+q1)*q2 - (1.0-q1)*q3) ) + 1.0 +# return floor(Int, dij) +# else +# error("Distance function $dist is not supported.") +# end +# end -function dist_matrix(x::Vector{Float64}, y::Vector{Float64}; dist="EUC_2D") - n_nodes = length(x) - @assert length(x) == length(y) +# function dist_matrix(x::Vector{Float64}, y::Vector{Float64}; dist="EUC_2D") +# n_nodes = length(x) +# @assert length(x) == length(y) - M = Matrix{Int}(undef, n_nodes, n_nodes) +# M = Matrix{Int}(undef, n_nodes, n_nodes) - for i in 1:n_nodes - for j in i:n_nodes - if i == j - M[i, j] = 0 - else - M[i, j] = distance2D(x[i], y[i], x[j], y[j]; dist=dist) - M[j, i] = M[i, j] - end - end - end - return M -end \ No newline at end of file +# for i in 1:n_nodes +# for j in i:n_nodes +# if i == j +# M[i, j] = 0 +# else +# M[i, j] = distance2D(x[i], y[i], x[j], y[j]; dist=dist) +# M[j, i] = M[i, j] +# end +# end +# end +# return M +# end \ No newline at end of file diff --git a/src/solver.jl b/src/solver.jl new file mode 100644 index 0000000..08267e7 --- /dev/null +++ b/src/solver.jl @@ -0,0 +1,98 @@ +function solve_tsp(dist_mtx::Matrix{Int}) + if !issymmetric(dist_mtx) + error("Asymmetric TSP is not supported.") + end + + n_nodes = size(dist_mtx, 1) + + lower_diag_row = Int[] + for i in 1:n_nodes + for j in 1:i + push!(lower_diag_row, dist_mtx[i, j]) + end + end + buf = 10 + n_rows = length(lower_diag_row) / buf |> ceil |> Int + rows = String[] + for i in 1:n_rows + s = buf * (i-1) + 1 + t = min(buf * i, length(lower_diag_row)) + push!(rows, join(lower_diag_row[s:t], " ")) + end + + name = randstring(10) + filename = name * ".tsp" + open(filename, "w") do io + write(io, "NAME: $(name)\n") + write(io, "TYPE: TSP\n") + write(io, "COMMENT: $(name)\n") + write(io, "DIMENSION: $(n_nodes)\n") + write(io, "EDGE_WEIGHT_TYPE: EXPLICIT\n") + write(io, "EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW \n") + write(io, "EDGE_WEIGHT_SECTION\n") + for r in rows + write(io, "$r\n") + end + write(io, "EOF\n") + end + + return __solve_tsp__(filename) +end + + +function solve_tsp(x::Vector{Float64}, y::Vector{Float64}; dist="EUC_2D") + n_nodes = length(x) + @assert length(x) == length(y) + + name = randstring(10) + filename = name * ".tsp" + + open(filename, "w") do io + write(io, "NAME: $(name)\n") + write(io, "TYPE: TSP\n") + write(io, "COMMENT: $(name)\n") + write(io, "DIMENSION: $(n_nodes)\n") + write(io, "EDGE_WEIGHT_TYPE: $(dist)\n") + write(io, "EDGE_WEIGHT_FORMAT: FUNCTION \n") + write(io, "NODE_COORD_TYPE: TWOD_COORDS \n") + write(io, "NODE_COORD_SECTION\n") + for i in 1:n_nodes + write(io, "$i $(x[i]) $(y[i])\n") + end + write(io, "EOF\n") + end + + return __solve_tsp__(filename) +end + +function solve_tsp(org_tsp_file::String) + if !isfile(org_tsp_file) + error("$(org_tsp_file) is not a file.") + end + + name = randstring(10) + filename = name * ".tsp" + cp(org_tsp_file, filename) + + return __solve_tsp__(filename) +end + + +function __solve_tsp__(tsp_file::String) + status = run(`$(Concorde.CONCORDE_EXECUTABLE) $(tsp_file)`, wait=false) + while !success(status) + # + end + + name = split(basename(tsp_file), ".")[1] + sol_filepath = name * ".sol" + opt_tour = read_solution(sol_filepath) + + tsp = readTSP(tsp_file) + M = Int.(tsp.weights) + opt_len = tour_length(opt_tour, M) + + cleanup(name) + + return opt_tour, opt_len +end \ No newline at end of file diff --git a/src/util.jl b/src/util.jl new file mode 100644 index 0000000..d531859 --- /dev/null +++ b/src/util.jl @@ -0,0 +1,31 @@ + +function read_solution(filepath) + sol = readlines(filepath) + n_nodes = sol[1] + tour = parse.(Int, split(join(sol[2:end]))) .+ 1 + return tour +end + +function tour_length(tour, M) + n_nodes = length(tour) + len = 0 + for i in 1:n_nodes + j = i + 1 + if i == n_nodes + j = 1 + end + + len += M[tour[i], tour[j]] + end + return len +end + +function cleanup(name) + exts = ["mas", "pul", "sav", "sol", "tsp", "res"] + for ext in exts + file = "$(name).$(ext)" + rm(file, force=true) + file = "O$(name).$(ext)" + rm(file, force=true) + end +end \ No newline at end of file