diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e70f435..1181bb2 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(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 8be8a7e..6da9669 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.1.0" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 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..3024cec 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,5 +1,6 @@ import Libdl + 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..811ac6f 100644 --- a/src/Concorde.jl +++ b/src/Concorde.jl @@ -1,153 +1,13 @@ module Concorde using Random, LinearAlgebra +using TSPLIB + include("../deps/deps.jl") include("dist.jl") -# Write your package code here. - -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"] - 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 - - cleanup(name) - - return opt_tour, opt_len -end +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 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