Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
581 changes: 581 additions & 0 deletions Manifest.toml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[deps]
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Hygese = "0672f53d-ad77-409a-bbb3-4bc94f101f4b"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LKH = "294deba0-74fa-42c9-af84-c3834b01fefc"
Metaheuristics = "bcdb8e00-2c21-11e9-3065-2b553b22f898"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548"
TravelingSalesmanHeuristics = "8c8f4381-2cdd-507c-846c-be2bcff6f45f"
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,26 @@ The [Travelling Salesman Problem (TSP)](https://en.m.wikipedia.org/wiki/Travelli
* An efficient solver, which uses the [Miller-Tucker-Zemlin Encoding](https://phabe.ch/2021/09/19/tsp-subtour-elimination-by-miller-tucker-zemlin-constraint/) to translate TSP into an Integer Programming problem and solves that with the commercial state-of-the-art solver Gurobi
* A standalone solver which implements the [Held-Karp algorithm](https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm) to solve TSP with dynamic programming
* A simple branch-and-bound solver for educational purposes, who can prune backtracking branches as soon as their lower bound is bigger than the current maximum
* [Concorde](http://www.math.uwaterloo.ca/tsp/concorde.html) State-of-the-art exact TSP solver using cutting planes
* [LKH-3](http://webhotel4.ruc.dk/~keld/research/LKH-3/) Lin-Kernighan Heuristic, one of the best TSP heuristics
* [Hygese](https://github.com/chkwon/Hygese.jl) Hybrid Genetic Search for CVRP/TSP

## Quick Start

```bash
# 1. Run setup (installs all Julia packages)
julia setup.jl

# 2. Run the benchmark
julia --project=. main.jl
```

## Requirements

- **Julia 1.9+** (tested with 1.12)
- **Concorde binary** (for optimal solutions on larger instances)


Using `benchmark()`, one can compare the performances on the [TSPLIB95 Dataset](http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/). `benchmark.log` also provides pre-recorded results measured with an Intel i9-10980HK and 32 GB of memory.

(c) Mia Müßig
(c) Mia Müßig
482 changes: 482 additions & 0 deletions benchmark.extended.log

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions concorde-solver.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Concorde TSP Solver - CLI Wrapper
# Calls the system-installed Concorde binary directly

"""
solve_concorde(tsp, timeout)

Solves a TSPLIB instance using the Concorde solver via command-line interface.

This wrapper calls the Concorde binary directly, avoiding the need for the
Concorde.jl package to build its own copy.

Requires: Concorde binary installed (e.g., at ~/.local/bin/concorde)

# Arguments
- `tsp`: the TSPLIB instance (from TSPLIB.jl)
- `timeout`: solver timeout in seconds
"""
function solve_concorde(tsp, timeout::Int=60)
# Find concorde binary
concorde_path = get(ENV, "CONCORDE_PATH", expanduser("~/.local/bin/concorde"))

if !isfile(concorde_path)
# Try to find in PATH
try
concorde_path = strip(read(`which concorde`, String))
catch
concorde_path = ""
end
if isempty(concorde_path) || !isfile(concorde_path)
println("Concorde binary not found. Install from http://www.math.uwaterloo.ca/tsp/concorde/")
return nothing, nothing
end
end

n = tsp.dimension

# Create temporary directory for working files
mktempdir() do tmpdir
# Write distance matrix in TSPLIB format
tsp_file = joinpath(tmpdir, "problem.tsp")
sol_file = joinpath(tmpdir, "problem.sol")

open(tsp_file, "w") do f
println(f, "NAME: problem")
println(f, "TYPE: TSP")
println(f, "DIMENSION: $n")
println(f, "EDGE_WEIGHT_TYPE: EXPLICIT")
println(f, "EDGE_WEIGHT_FORMAT: FULL_MATRIX")
println(f, "EDGE_WEIGHT_SECTION")

for i in 1:n
row = String[]
for j in 1:n
push!(row, string(round(Int, tsp.weights[i, j])))
end
println(f, join(row, " "))
end
println(f, "EOF")
end

try
# Run concorde FROM the temp directory so it writes files there
# Use -x to clean up intermediate files
cmd = Cmd(`$concorde_path -x -o problem.sol problem.tsp`, dir=tmpdir)

t_start = time()
proc = run(pipeline(cmd, stdout=devnull, stderr=devnull), wait=false)

deadline = time() + timeout
while process_running(proc) && time() < deadline
sleep(0.1)
end

if process_running(proc)
kill(proc)
return nothing, nothing
end

elapsed = time() - t_start

if !isfile(sol_file)
println("Concorde did not produce a solution file")
return nothing, nothing
end

# Parse solution file to get tour
lines = readlines(sol_file)
if isempty(lines)
return nothing, nothing
end

# First line is the number of nodes
# Remaining lines contain the tour (0-indexed node indices)
tour_nodes = Int[]
for line in lines[2:end]
for s in split(strip(line))
push!(tour_nodes, parse(Int, s) + 1) # Convert to 1-indexed
end
end

# Calculate tour cost
if length(tour_nodes) >= n
cost = 0.0
for i in 1:n
from = tour_nodes[i]
to = tour_nodes[mod1(i + 1, n)]
cost += tsp.weights[from, to]
end
return cost, elapsed
else
return nothing, nothing
end

catch e
println("Concorde error: $e")
return nothing, nothing
end
end
end
Loading