Skip to content

Commit

Permalink
Merge pull request #1 from chkwon/macos
Browse files Browse the repository at this point in the history
using binary executable to solve TSP
  • Loading branch information
chkwon authored Feb 24, 2021
2 parents c56a9e7 + f4ee268 commit bff4436
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
version: ['1'] # Test against LTS and current minor release
os: [ubuntu-latest] # macOS-latest, windows-latest
os: [ubuntu-latest, macOS-latest] # macOS-latest, windows-latest
arch: [x64]
steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.1.0"

[deps]
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
julia = "1"
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Concorde
# Concorde.jl


[![Build Status](https://github.com/chkwon/Concorde.jl/workflows/CI/badge.svg?branch=master)](https://github.com/chkwon/Concorde.jl/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/chkwon/Concorde.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/chkwon/Concorde.jl)
100 changes: 49 additions & 51 deletions deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,15 @@ const QSOPT_LOCATION = Dict(
const CONCORDE_SRC = "http://www.math.uwaterloo.ca/tsp/concorde/downloads/codes/src/co031219.tgz"


function _prefix_suffix(str)
if Sys.islinux() && Sys.ARCH == :x86_64
return "lib$(str).so"
elseif Sys.isapple()
return "lib$(str).dylib"
end
# elseif Sys.iswindows()
# return "$(str).dll"
# end
error(
"Unsupported operating system. Only 64-bit linux and macOS " *
"are supported."
)
end

function build_concorde_linux()
function _build_concorde()
# Download qsopt
qsopt_dir = joinpath(@__DIR__, "qsopt")
if !isdir(qsopt_dir)
mkdir(qsopt_dir)
end
download(QSOPT_LOCATION["Linux"][1], joinpath(qsopt_dir, "qsopt.a"))
download(QSOPT_LOCATION["Linux"][2], joinpath(qsopt_dir, "qsopt.h"))
sys_type = Sys.isapple() ? "Darwin" : "Linux"
download(QSOPT_LOCATION[sys_type][1], joinpath(qsopt_dir, "qsopt.a"))
download(QSOPT_LOCATION[sys_type][2], joinpath(qsopt_dir, "qsopt.h"))

# Download concorde
concorde_tarball = download(CONCORDE_SRC)
Expand All @@ -45,41 +31,52 @@ function build_concorde_linux()
# Build
concorde_src_dir = joinpath(@__DIR__, "concorde")
cd(concorde_src_dir)
run(`bash -c "CFLAGS='-fPIC -O2 -g' ./configure --with-qsopt=$(qsopt_dir)"`)

macflag = Sys.isapple() ? "--host=darwin" : ""
cflags = "-fPIC -O2 -g"
run(`bash -c "CFLAGS='$(cflags)' ./configure --with-qsopt=$(qsopt_dir) $(macflag)"`)
run(`make clean`)
run(`make`)
lib_dir = joinpath(@__DIR__, "lib")
if !isdir(lib_dir)
mkdir(lib_dir)
end
executable = joinpath(concorde_src_dir, "TSP", "concorde")

return executable

# Build a shared library
for ext in ["a", "h"]
cp("concorde.$ext", joinpath(lib_dir, "concorde.$ext"), force=true)
end
cd(lib_dir)
run(`ar -x concorde.a`)
object_files = String[]
for f in readdir()
if f != "concorde.a" && f != "concorde.h"
push!(object_files, f)
end
end
of = join(object_files, " ")
# # Build a shared library from the static library
# for ext in ["a", "h"]
# cp("concorde.$ext", joinpath(lib_dir, "concorde.$ext"), force=true)
# end
# cd(lib_dir)
# run(`ar -x concorde.a`)

run(`bash -c "gcc -shared *.o -o libconcorde.so"`)
run(`bash -c "rm -rf *.o"`)

cd(@__DIR__)
return joinpath(lib_dir, "libconcorde.so")
# shared_lib_filename = "libconcorde.so"
# if Sys.islinux()
# shared_lib_filename = "libconcorde.so"
# run(`bash -c "gcc -shared *.o -o $(shared_lib_filename)"`)
# elseif Sys.isapple()
# shared_lib_filename = "libconcorde.dylib"
# run(`bash -c "gcc -dynamiclib *.o -o $(shared_lib_filename)"`)
# else
# error(
# "Unsupported operating system. Only 64-bit linux and macOS " *
# "are supported."
# )
# end

# shared_lib = joinpath(lib_dir, shared_lib_filename)
# run(`bash -c "rm -rf *.o"`)

# return shared_lib, executable
end

function build_concorde()
if Sys.islinux() && Sys.ARCH == :x86_64
return build_concorde_linux()
if Sys.islinux() && Sys.ARCH == :x86_64
return _build_concorde()
elseif Sys.isapple()
return nothing
return build_concorde_linux()
return _build_concorde()
end
# elseif Sys.iswindows()
# return "$(str).dll"
Expand All @@ -88,24 +85,25 @@ function build_concorde()
"Unsupported operating system. Only 64-bit linux and macOS " *
"are supported."
)
return nothing
return nothing, nothing
end

function install_concorde()
concorde_lib_filename = get(ENV, "CONCORDE_JL_LOCATION", nothing)
if !haskey(ENV, "CONCORDE_JL_LOCATION")
concorde_lib_filename = build_concorde()
ENV["CONCORDE_JL_LOCATION"] = concorde_lib_filename
executable = get(ENV, "CONCORDE_EXECUTABLE", nothing)
if !haskey(ENV, "CONCORDE_EXECUTABLE")
executable = build_concorde()
ENV["CONCORDE_EXECUTABLE"] = executable
end

if concorde_lib_filename === nothing
error("Environment variable `CONCORDE_JL_LOCATION` not found.")
elseif Libdl.dlopen(concorde_lib_filename) == C_NULL
error("Unable to open the concorde library $(concorde_lib_filename).")
if executable === nothing
error("Environment variable `CONCORDE_EXECUTABLE` not found.")
else
gr17tsp = joinpath(@__DIR__, "../test/gr17.tsp")
run(`$(executable) $(gr17tsp)`)
end

open(joinpath(@__DIR__, "deps.jl"), "w") do io
write(io, "const LIB_CONCORDE = \"$(escape_string(concorde_lib_filename))\"\n")
write(io, "const CONCORDE_EXECUTABLE = \"$(escape_string(executable))\"\n")
end
end

Expand Down
77 changes: 77 additions & 0 deletions src/Concorde.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,83 @@
module Concorde

using Random

include("../deps/deps.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 solve_tsp(dist_mtx::Matrix{Int})
n_nodes = size(dist_mtx, 1)
name = randstring(10)
filepath = joinpath(pwd(), 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

run(`$(Concorde.CONCORDE_EXECUTABLE) $(filepath)`)

sol_filepath = joinpath(pwd(), name * ".sol")
opt_tour = read_solution(sol_filepath)
opt_len = tour_length(opt_tour, dist_mtx)

exts = ["mas", "pul", "sav", "sol", "tsp"]
for ext in exts
file = joinpath(pwd(), "$(name).$(ext)")
rm(file, force=true)
file = joinpath(pwd(), "O$(name).$(ext)")
rm(file, force=true)
end
return opt_tour, opt_len
end




export solve_tsp

end
21 changes: 21 additions & 0 deletions test/gr17.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
NAME: gr17
TYPE: TSP
COMMENT: 17-city problem (Groetschel)
DIMENSION: 17
EDGE_WEIGHT_TYPE: EXPLICIT
EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW
EDGE_WEIGHT_SECTION
0 633 0 257 390 0 91 661 228 0 412 227
169 383 0 150 488 112 120 267 0 80 572 196
77 351 63 0 134 530 154 105 309 34 29 0
259 555 372 175 338 264 232 249 0 505 289 262
476 196 360 444 402 495 0 353 282 110 324 61
208 292 250 352 154 0 324 638 437 240 421 329
297 314 95 578 435 0 70 567 191 27 346 83
47 68 189 439 287 254 0 211 466 74 182 243
105 150 108 326 336 184 391 145 0 268 420 53
239 199 123 207 165 383 240 140 448 202 57 0
246 745 472 237 528 364 332 349 202 685 542 157
289 426 483 0 121 518 142 84 297 35 29 36
236 390 238 301 55 96 153 336 0
EOF
23 changes: 19 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
using Concorde
using Test

# @testset "Concorde.jl" begin
# A = Cint.([4, 2, 5, 7, 2, 1, 3, 5])
# ccall((:CCutil_int_array_quicksort, Concorde.LIB_CONCORDE), Cvoid, (Ref{Cint}, Cint), A, length(A))
# @test A[1] == minimum(A)
# @test A[end] == maximum(A)
# end


@testset "Concorde.jl" begin
A = Cint.([4, 2, 5, 7, 2, 1, 3, 5])
ccall((:CCutil_int_array_quicksort, Concorde.LIB_CONCORDE), Cvoid, (Ref{Cint}, Cint), A, length(A))
@test A[1] == minimum(A)
@test A[end] == maximum(A)
M = [
0 16 7 14
16 0 3 5
7 3 0 16
14 5 16 0
]
opt_tour, opt_len = solve_tsp(M)
@show opt_tour, opt_len
# display(M)

@test opt_len == 29
end

0 comments on commit bff4436

Please sign in to comment.