A distributed-memory Traveling Salesman Problem solver written in C and parallelized with MPI.
The project was developed for a High Performance Computing course at Bergische Universität Wuppertal. It uses parallel tempering to study how a stochastic optimization method behaves when multiple temperature replicas are distributed across MPI processes.
The solver follows the TSPLIB EUC_2D convention: each Euclidean edge length is rounded to the nearest integer before it contributes to the route weight.
The included official Berlin52 optimal tour is evaluated automatically and must produce:
7542
The current repeated benchmark uses this corrected objective. Older course figures and the previously reported 20.7× speedup were generated with raw floating-point Euclidean distances and are retained only as historical course artifacts.
- distributed optimization with MPI
- parallel tempering with 24 global temperature replicas
- Metropolis acceptance logic
- two-opt route updates
- TSPLIB-compatible
EUC_2Dedge weights - precomputed distance matrix for constant-time lookups
- deadlock-free communication between neighboring MPI ranks
- optional deterministic base seed for reproducible runs
- official Berlin52 optimal-tour regression check
- GitHub Actions production build and two-rank smoke execution
- repeated fixed-seed strong-scaling benchmark
Each MPI process stores one or more route replicas. The replicas use different temperatures, allowing colder replicas to focus on local improvement while hotter replicas explore the search space more freely.
During the search:
- a replica proposes a two-opt move
- the route-weight change is calculated from four distance-matrix lookups
- the move is accepted or rejected using the Metropolis criterion
- neighboring replicas periodically attempt to exchange routes
- the best route found across all processes is collected
The exchange step helps colder replicas leave local minima by receiving routes explored at higher temperatures.
The included instance declares:
EDGE_WEIGHT_TYPE: EUC_2D
For two cities separated by dx and dy, the solver calculates:
nint(sqrt(dx² + dy²))
where nint is the TSPLIB nearest-integer convention for non-negative distances. Unsupported edge-weight types are rejected instead of being interpreted silently.
City identifiers are validated when the instance is loaded, and the known optimal tour is stored in data/berlin52.opt.tour as a regression fixture.
On Ubuntu or Debian, install the main dependencies with:
sudo apt install build-essential openmpi-bin libopenmpi-dev octaveBuild the production solver:
git clone https://github.com/Kandil2001/Distributed-TSP-Solver.git
cd Distributed-TSP-Solver
makeRun the Berlin52 case with a reproducible seed:
mpirun -np 4 ./tsp_solver data/berlin52.tsp --seed 12345Without --seed, the solver uses the current time as the base seed.
Evaluate the official optimal tour without running the stochastic search:
mpirun -np 1 ./tsp_solver \
data/berlin52.tsp \
--evaluate-tour data/berlin52.opt.tourExpected output:
Tour weight (TSPLIB EUC_2D): 7542
The corrected solver was benchmarked with five fixed seeds (1001, 2002, 3003, 4004, and 5005) at 1, 2, 4, 8, 12, and 24 MPI processes. This produced 30 measured runs.
Every run reached the official Berlin52 optimum of 7542, giving a 0% optimality gap at every process count.
| MPI processes | Median runtime [s] | Runtime std. dev. [s] | Median speedup | Parallel efficiency |
|---|---|---|---|---|
| 1 | 6.8956 | 0.0310 | 1.000 | 100.0% |
| 2 | 3.5857 | 0.0097 | 1.923 | 96.2% |
| 4 | 1.9133 | 0.0047 | 3.604 | 90.1% |
| 8 | 1.0907 | 0.0009 | 6.322 | 79.0% |
| 12 | 0.8497 | 0.0020 | 8.116 | 67.6% |
| 24 | 0.6367 | 0.0080 | 10.830 | 45.1% |
The canonical raw and aggregated datasets are stored in results/stromboli_2026-07-20. The table uses median runtime for speedup because repeated stochastic runs are better represented by a robust central value than by one isolated timing.
The GitHub Actions workflow performs three checks:
- builds the unchanged production solver
- evaluates the official Berlin52 optimal tour and requires a weight of
7542 - builds a temporary reduced-iteration binary and runs a deterministic two-rank MPI smoke case
The smoke run verifies that MPI communication completes, the global reduction returns a finite positive route weight, and the saved route is a valid permutation of all 52 cities. The temporary iteration reduction is used only to keep CI fast and does not modify the production solver.
The original course study reported a speedup of 20.7× on 24 cores and approximately 86% parallel efficiency. That study used the earlier raw floating-point distance objective, so its figures must not be compared directly with the corrected TSPLIB-compatible benchmark above.
script/tsp_mpi.c MPI solver
script/scaling_test.sh cluster scaling study
script/plot_*.m Octave plotting scripts
data/berlin52.tsp included TSPLIB benchmark instance
data/berlin52.opt.tour official optimal-tour regression fixture
results/stromboli_2026-07-20/ corrected repeated benchmark data
figures/ historical course-study plots
.github/workflows/ci.yml build, validation, and MPI smoke test
Makefile local build configuration
This repository is an educational HPC project rather than a general-purpose TSP library. The current implementation is limited to TSPLIB EUC_2D coordinate instances, one MPI parallel-tempering method, 24 compile-time global replicas, and the Berlin52 benchmark.
The repeated benchmark improves confidence in the reported timing and route quality, but it still represents one problem instance and one cluster environment. Useful extensions include runtime configuration of the temperature schedule and replica count, additional TSPLIB datasets, hybrid MPI/OpenMP execution, and CUDA acceleration.
This project was developed for the High Performance Computing course at Bergische Universität Wuppertal.
Thanks to Dr. T. Korzec and Dr. J. Koponen for their supervision and guidance.
Ahmed Kandil — Portfolio · LinkedIn · ORCID
Released under the MIT License.


