From c084cc5b3151d329bfd7f1ab4885bae16411d6cd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 6 Jul 2026 15:04:27 +0000
Subject: [PATCH 1/3] Initial plan
From 8cec48333dd7a230d0e017a782e09ff0776c010c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 6 Jul 2026 15:10:57 +0000
Subject: [PATCH 2/3] feat: add runtime CLI, CI smoke workflow, and python
plotting
---
.github/workflows/ci.yml | 29 ++
.gitignore | 2 +
CONTRIBUTING.md | 6 +-
Makefile | 10 +-
README.md | 60 +++-
script/plot_results.py | 128 ++++++++
script/tsp_mpi.c | 629 ++++++++++++++++++++++++++++++---------
7 files changed, 710 insertions(+), 154 deletions(-)
create mode 100644 .github/workflows/ci.yml
create mode 100644 script/plot_results.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..84017fd
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ build-and-smoke:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y openmpi-bin libopenmpi-dev python3 python3-matplotlib
+
+ - name: Build
+ run: make
+
+ - name: Smoke test (deterministic)
+ run: |
+ mpirun --allow-run-as-root -np 2 bin/tsp_solver data/berlin52.tsp --replicas 4 --steps 10 --deterministic --swap-interval 2
+
+ - name: Run plotting script
+ run: |
+ python3 script/plot_results.py --solution solution.txt --route my_route.txt --tsp data/berlin52.tsp --output figures/route_comparison_python.png
diff --git a/.gitignore b/.gitignore
index 1e83101..6bd8e92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,12 +3,14 @@
*.out
*.exe
tsp_solver
+bin/tsp_solver
# Generated benchmark output
*.log
scaling_results.csv
solution.txt
my_route.txt
+figures/route_comparison_python.png
# Editors and operating systems
.vscode/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 05aa216..a3cf73c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,11 @@ Before opening a pull request:
2. Keep changes limited to one topic.
3. Compile the solver with `make`.
4. Run at least one Berlin52 case and check that the output is sensible.
-5. Note the compiler, MPI implementation, and process count used for testing.
+5. Run the local CI smoke test:
+ - `mpirun --allow-run-as-root -np 2 bin/tsp_solver data/berlin52.tsp --replicas 4 --steps 10 --deterministic --swap-interval 2`
+ - `python3 script/plot_results.py --solution solution.txt --route my_route.txt --tsp data/berlin52.tsp --output figures/route_comparison_python.png`
+6. Mention whether CI passed (GitHub Actions workflow in `.github/workflows/ci.yml`).
+7. Note the compiler, MPI implementation, and process count used for testing.
For C and MPI changes, use four-space indentation, free allocated memory, and add comments only where the communication or algorithmic logic is not obvious.
diff --git a/Makefile b/Makefile
index 780b50c..2b760c5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,21 @@
CC = mpicc
CFLAGS = -O3 -std=c99 -Wall -Wextra
LDLIBS = -lm
-TARGET = tsp_solver
+BIN_DIR := bin
+TARGET = $(BIN_DIR)/tsp_solver
SOURCE = script/tsp_mpi.c
-.PHONY: all clean
+.PHONY: all clean run-example
all: $(TARGET)
$(TARGET): $(SOURCE)
+ mkdir -p $(BIN_DIR)
$(CC) $(CFLAGS) $(SOURCE) -o $(TARGET) $(LDLIBS)
+run-example: $(TARGET)
+ @echo "Example:"
+ @echo " mpirun --allow-run-as-root -np 2 $(TARGET) data/berlin52.tsp --replicas 4 --steps 10 --deterministic --swap-interval 2"
+
clean:
rm -f $(TARGET) *.o
diff --git a/README.md b/README.md
index 90c0192..b1ac583 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ The project uses parallel tempering to study how a stochastic optimization metho
- Deadlock-free communication between neighboring MPI ranks
- Strong-scaling measurements across several process counts
- Octave scripts for plotting runtime, efficiency, and route quality
+- Python plotting script (`script/plot_results.py`) for route visualization without MATLAB/Octave
## How the solver works
@@ -72,35 +73,84 @@ For the tested Berlin52 configuration, the parallel version reached a reported s
-## Running the code
+## Getting started
On Ubuntu or Debian, install the main dependencies with:
```bash
-sudo apt install build-essential openmpi-bin libopenmpi-dev octave
+sudo apt install build-essential openmpi-bin libopenmpi-dev python3 python3-matplotlib octave
```
-Build and run the included Berlin52 case:
+Build the solver:
```bash
git clone https://github.com/Kandil2001/Distributed-TSP-Solver.git
cd Distributed-TSP-Solver
make
-mpirun -np 4 ./tsp_solver data/berlin52.tsp
```
-The cluster scaling study can be submitted with:
+The binary is generated at `bin/tsp_solver`.
+
+## How to run
+
+Run with defaults (`data/berlin52.tsp`, 24 replicas, 260000 steps):
+
+```bash
+mpirun --allow-run-as-root -np 4 bin/tsp_solver
+```
+
+Run with explicit options (short deterministic smoke run):
+
+```bash
+mpirun --allow-run-as-root -np 2 bin/tsp_solver data/berlin52.tsp \
+ --replicas 4 --steps 10 --swap-interval 2 --deterministic
+```
+
+Available CLI flags:
+
+- `--replicas N`
+- `--steps N`
+- `--swap-interval N`
+- `--initial-temp X`
+- `--temp-decay X`
+- `--seed N`
+- `--deterministic`
+- `--help`
+
+Important: replicas must divide evenly across MPI processes (`replicas % np == 0`).
+
+The solver writes:
+
+- `solution.txt` (best distance)
+- `my_route.txt` (city index route for plotting)
+
+Plot results without MATLAB:
+
+```bash
+python3 script/plot_results.py --solution solution.txt --route my_route.txt --tsp data/berlin52.tsp
+```
+
+The cluster scaling study can still be submitted with:
```bash
sbatch script/scaling_test.sh
```
+For local CI-equivalent smoke testing:
+
+```bash
+make
+mpirun --allow-run-as-root -np 2 bin/tsp_solver data/berlin52.tsp --replicas 4 --steps 10 --deterministic --swap-interval 2
+python3 script/plot_results.py --solution solution.txt --route my_route.txt --tsp data/berlin52.tsp --output figures/route_comparison_python.png
+```
+
## Repository structure
```text
script/tsp_mpi.c MPI solver
script/scaling_test.sh cluster scaling study
script/plot_*.m Octave plotting scripts
+script/plot_results.py Python plotting script (no MATLAB required)
data/berlin52.tsp included benchmark case
figures/ selected published plots
Makefile local build configuration
diff --git a/script/plot_results.py b/script/plot_results.py
new file mode 100644
index 0000000..10d3225
--- /dev/null
+++ b/script/plot_results.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+import argparse
+import pathlib
+import sys
+
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+
+
+def read_solution(path: pathlib.Path) -> float | None:
+ if not path.exists():
+ return None
+ text = path.read_text(encoding="utf-8").strip()
+ if not text:
+ return None
+ try:
+ return float(text.splitlines()[0].strip())
+ except ValueError:
+ return None
+
+
+def read_route(path: pathlib.Path) -> list[int]:
+ route: list[int] = []
+ for line in path.read_text(encoding="utf-8").splitlines():
+ stripped = line.strip()
+ if not stripped:
+ continue
+ route.append(int(stripped))
+ return route
+
+
+def read_coords(tsplib_path: pathlib.Path) -> list[tuple[float, float]]:
+ coords: list[tuple[float, float]] = []
+ in_section = False
+ for line in tsplib_path.read_text(encoding="utf-8").splitlines():
+ stripped = line.strip()
+ if not stripped:
+ continue
+ if stripped.startswith("NODE_COORD_SECTION"):
+ in_section = True
+ continue
+ if stripped.startswith("EOF"):
+ break
+ if in_section:
+ parts = stripped.split()
+ if len(parts) >= 3:
+ coords.append((float(parts[1]), float(parts[2])))
+ return coords
+
+
+def close_route(route: list[int]) -> list[int]:
+ if not route:
+ return route
+ if route[0] == route[-1]:
+ return route
+ return route + [route[0]]
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Plot TSP route output using matplotlib.")
+ parser.add_argument("--solution", default="solution.txt", help="Path to solution.txt")
+ parser.add_argument("--route", default="my_route.txt", help="Path to my_route.txt")
+ parser.add_argument("--tsp", default="data/berlin52.tsp", help="Optional TSPLIB file")
+ parser.add_argument("--output", default="figures/route_comparison_python.png", help="Output image path")
+ args = parser.parse_args()
+
+ route_path = pathlib.Path(args.route)
+ if not route_path.exists():
+ print(f"Error: route file not found: {route_path}", file=sys.stderr)
+ return 1
+
+ tsp_path = pathlib.Path(args.tsp)
+ if not tsp_path.exists():
+ print(f"Error: TSPLIB file not found: {tsp_path}", file=sys.stderr)
+ return 1
+
+ route = read_route(route_path)
+ coords = read_coords(tsp_path)
+
+ if not route:
+ print("Error: route file is empty.", file=sys.stderr)
+ return 1
+ if not coords:
+ print("Error: could not parse coordinates from TSPLIB file.", file=sys.stderr)
+ return 1
+
+ if max(route) >= len(coords) or min(route) < 0:
+ print("Error: route indices are out of bounds for TSPLIB coordinates.", file=sys.stderr)
+ return 1
+
+ route_closed = close_route(route)
+ ref_route = list(range(len(coords)))
+ ref_closed = close_route(ref_route)
+
+ x_ref = [coords[i][0] for i in ref_closed]
+ y_ref = [coords[i][1] for i in ref_closed]
+ x_sol = [coords[i][0] for i in route_closed]
+ y_sol = [coords[i][1] for i in route_closed]
+
+ best_dist = read_solution(pathlib.Path(args.solution))
+
+ fig, axes = plt.subplots(1, 2, figsize=(12, 5), constrained_layout=True)
+
+ axes[0].plot(x_ref, y_ref, "o-", linewidth=1.0, markersize=3)
+ axes[0].set_title("Reference order route")
+ axes[0].set_aspect("equal", adjustable="box")
+
+ axes[1].plot(x_sol, y_sol, "o-", linewidth=1.0, markersize=3, color="tab:orange")
+ if best_dist is None:
+ axes[1].set_title("Optimized route")
+ else:
+ axes[1].set_title(f"Optimized route (distance={best_dist:.2f})")
+ axes[1].set_aspect("equal", adjustable="box")
+
+ for ax in axes:
+ ax.set_xlabel("x")
+ ax.set_ylabel("y")
+
+ out_path = pathlib.Path(args.output)
+ out_path.parent.mkdir(parents=True, exist_ok=True)
+ fig.savefig(out_path, dpi=160)
+ print(f"Saved plot: {out_path}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/script/tsp_mpi.c b/script/tsp_mpi.c
index c5eac8e..8f245b1 100644
--- a/script/tsp_mpi.c
+++ b/script/tsp_mpi.c
@@ -13,28 +13,169 @@
#include
#include
#include
-
-// --- LAB 2 PARAMETERS [cite: 87-90] ---
-#define TOTAL_GLOBAL_REPLICAS 24
-#define INITIAL_TEMP 150.0
-#define TEMP_DECAY 0.80
-#define SWAP_INTERVAL 52
-#define TOTAL_SWAP_ATTEMPTS 5000
-#define TOTAL_STEPS (SWAP_INTERVAL * TOTAL_SWAP_ATTEMPTS)
+#include
+#include
+
+#define DEFAULT_TOTAL_GLOBAL_REPLICAS 24
+#define DEFAULT_INITIAL_TEMP 150.0
+#define DEFAULT_TEMP_DECAY 0.80
+#define DEFAULT_SWAP_INTERVAL 52
+#define DEFAULT_TOTAL_SWAP_ATTEMPTS 5000
+#define DEFAULT_TOTAL_STEPS (DEFAULT_SWAP_INTERVAL * DEFAULT_TOTAL_SWAP_ATTEMPTS)
+#define DEFAULT_TSP_FILE "data/berlin52.tsp"
+#define DETERMINISTIC_SEED 1337UL
typedef struct { double x, y; } City;
-// Globals
+typedef struct {
+ int total_global_replicas;
+ long total_steps;
+ int swap_interval;
+ double initial_temp;
+ double temp_decay;
+ int deterministic;
+ unsigned long seed;
+ int seed_set;
+ const char *filename;
+} SolverConfig;
+
+/* Globals */
int N_CITIES = 0;
City *cities = NULL;
-double *dist_matrix = NULL;
+double *dist_matrix = NULL;
+
+static void print_usage(const char *prog) {
+ printf("Usage: %s [OPTIONS] [TSPLIB_FILE]\n", prog);
+ printf("\nOptions:\n");
+ printf(" --replicas N Total number of global replicas (default: %d)\n", DEFAULT_TOTAL_GLOBAL_REPLICAS);
+ printf(" --steps N Total optimization steps (default: %d)\n", DEFAULT_TOTAL_STEPS);
+ printf(" --swap-interval N Swap interval in steps (default: %d)\n", DEFAULT_SWAP_INTERVAL);
+ printf(" --initial-temp X Initial temperature (default: %.1f)\n", DEFAULT_INITIAL_TEMP);
+ printf(" --temp-decay X Temperature decay factor (default: %.2f)\n", DEFAULT_TEMP_DECAY);
+ printf(" --seed N Base random seed (rank offset is added)\n");
+ printf(" --deterministic Use fixed deterministic base seed (%lu)\n", DETERMINISTIC_SEED);
+ printf(" --help Show this help and exit\n");
+ printf("\nTSPLIB_FILE defaults to %s\n", DEFAULT_TSP_FILE);
+}
+
+static int parse_long_value(const char *text, long *out) {
+ char *endptr = NULL;
+ errno = 0;
+ long val = strtol(text, &endptr, 10);
+ if (errno != 0 || endptr == text || *endptr != '\0') return 0;
+ *out = val;
+ return 1;
+}
+
+static int parse_int_value(const char *text, int *out) {
+ long val = 0;
+ if (!parse_long_value(text, &val)) return 0;
+ if (val < 1 || val > 2147483647L) return 0;
+ *out = (int)val;
+ return 1;
+}
+
+static int parse_double_value(const char *text, double *out) {
+ char *endptr = NULL;
+ errno = 0;
+ double val = strtod(text, &endptr);
+ if (errno != 0 || endptr == text || *endptr != '\0') return 0;
+ *out = val;
+ return 1;
+}
+
+static int parse_seed_value(const char *text, unsigned long *out) {
+ char *endptr = NULL;
+ errno = 0;
+ unsigned long val = strtoul(text, &endptr, 10);
+ if (errno != 0 || endptr == text || *endptr != '\0') return 0;
+ *out = val;
+ return 1;
+}
+
+static int parse_args(int argc, char **argv, SolverConfig *cfg) {
+ static struct option long_options[] = {
+ {"replicas", required_argument, 0, 1},
+ {"steps", required_argument, 0, 2},
+ {"swap-interval", required_argument, 0, 3},
+ {"initial-temp", required_argument, 0, 4},
+ {"temp-decay", required_argument, 0, 5},
+ {"seed", required_argument, 0, 6},
+ {"deterministic", no_argument, 0, 7},
+ {"help", no_argument, 0, 8},
+ {0, 0, 0, 0}
+ };
+
+ int option_index = 0;
+ int c;
+
+ opterr = 0;
+ while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+ switch (c) {
+ case 1:
+ if (!parse_int_value(optarg, &cfg->total_global_replicas)) {
+ fprintf(stderr, "Invalid --replicas value: %s\n", optarg);
+ return -1;
+ }
+ break;
+ case 2:
+ if (!parse_long_value(optarg, &cfg->total_steps) || cfg->total_steps <= 0) {
+ fprintf(stderr, "Invalid --steps value: %s\n", optarg);
+ return -1;
+ }
+ break;
+ case 3:
+ if (!parse_int_value(optarg, &cfg->swap_interval)) {
+ fprintf(stderr, "Invalid --swap-interval value: %s\n", optarg);
+ return -1;
+ }
+ break;
+ case 4:
+ if (!parse_double_value(optarg, &cfg->initial_temp)) {
+ fprintf(stderr, "Invalid --initial-temp value: %s\n", optarg);
+ return -1;
+ }
+ break;
+ case 5:
+ if (!parse_double_value(optarg, &cfg->temp_decay)) {
+ fprintf(stderr, "Invalid --temp-decay value: %s\n", optarg);
+ return -1;
+ }
+ break;
+ case 6:
+ if (!parse_seed_value(optarg, &cfg->seed)) {
+ fprintf(stderr, "Invalid --seed value: %s\n", optarg);
+ return -1;
+ }
+ cfg->seed_set = 1;
+ break;
+ case 7:
+ cfg->deterministic = 1;
+ break;
+ case 8:
+ print_usage(argv[0]);
+ return 1;
+ case '?':
+ default:
+ fprintf(stderr, "Unknown option. Use --help for usage.\n");
+ return -1;
+ }
+ }
+
+ if (optind < argc) {
+ cfg->filename = argv[optind];
+ }
-// --- HELPER FUNCTIONS ---
+ return 0;
+}
-// Read TSPLIB format (Rank 0 only)
-int read_tsp_file(const char *filename) {
+/* Read TSPLIB format (Rank 0 only) */
+static int read_tsp_file(const char *filename) {
FILE *f = fopen(filename, "r");
- if (!f) return 0;
+ if (!f) {
+ fprintf(stderr, "Error: failed to open '%s': %s\n", filename, strerror(errno));
+ return 0;
+ }
char line[256];
int reading_coords = 0;
@@ -44,17 +185,23 @@ int read_tsp_file(const char *filename) {
if (strncmp(line, "DIMENSION", 9) == 0) {
char *p = strchr(line, ':');
if (p) N_CITIES = atoi(p + 1);
- }
- else if (strncmp(line, "NODE_COORD_SECTION", 18) == 0) {
+ } else if (strncmp(line, "NODE_COORD_SECTION", 18) == 0) {
reading_coords = 1;
- if (N_CITIES == 0) N_CITIES = 52;
- cities = (City*)malloc(N_CITIES * sizeof(City));
+ if (N_CITIES == 0) N_CITIES = 52;
+ cities = (City *)malloc((size_t)N_CITIES * sizeof(City));
+ if (!cities) {
+ fprintf(stderr, "Error: failed to allocate city buffer for %d cities.\n", N_CITIES);
+ fclose(f);
+ return 0;
+ }
continue;
+ } else if (strncmp(line, "EOF", 3) == 0) {
+ break;
}
- else if (strncmp(line, "EOF", 3) == 0) break;
if (reading_coords && idx < N_CITIES) {
- int id; double x, y;
+ int id;
+ double x, y;
if (sscanf(line, "%d %lf %lf", &id, &x, &y) == 3) {
cities[idx].x = x;
cities[idx].y = y;
@@ -62,27 +209,47 @@ int read_tsp_file(const char *filename) {
}
}
}
+
fclose(f);
- return (idx == N_CITIES);
+
+ if (!cities) {
+ fprintf(stderr, "Error: '%s' does not contain NODE_COORD_SECTION.\n", filename);
+ return 0;
+ }
+
+ if (idx != N_CITIES) {
+ fprintf(stderr, "Error: expected %d coordinates in '%s', but read %d.\n", N_CITIES, filename, idx);
+ free(cities);
+ cities = NULL;
+ return 0;
+ }
+
+ return 1;
}
-void init_dist_matrix() {
- dist_matrix = (double*)malloc(N_CITIES * N_CITIES * sizeof(double));
- for(int i=0; i b) { int t = a; a = b; b = t; }
+ if (a > b) {
+ int t = a;
+ a = b;
+ b = t;
+ }
int cA = route[a];
- int cA_next = route[(a+1)%N_CITIES];
+ int cA_next = route[(a + 1) % N_CITIES];
int cB = route[b];
- int cB_next = route[(b+1)%N_CITIES];
-
- // 2-opt Delta Calculation using Lookup Matrix
- double delta = (get_dist(cA, cB) + get_dist(cA_next, cB_next)) -
+ int cB_next = route[(b + 1) % N_CITIES];
+
+ /* 2-opt Delta Calculation using Lookup Matrix */
+ double delta = (get_dist(cA, cB) + get_dist(cA_next, cB_next)) -
(get_dist(cA, cA_next) + get_dist(cB, cB_next));
- if (delta < 0 || (T > 1e-9 && ((double)rand() / RAND_MAX) < exp(-delta / T))) {
+ if (delta < 0 || (T > 1e-9 && ((double)rand() / (double)RAND_MAX) < exp(-delta / T))) {
int l = a + 1, h = b;
while (l < h) {
- int temp = route[l]; route[l] = route[h]; route[h] = temp;
- l++; h--;
+ int temp = route[l];
+ route[l] = route[h];
+ route[h] = temp;
+ l++;
+ h--;
}
}
}
-int main(int argc, char** argv) {
+static void log_mpi_error(int rank, const char *context, int err_code) {
+ char err_str[MPI_MAX_ERROR_STRING];
+ int err_len = 0;
+ MPI_Error_string(err_code, err_str, &err_len);
+ fprintf(stderr, "Rank %d: MPI swap error in %s: %s\n", rank, context, err_str);
+}
+
+int main(int argc, char **argv) {
+ SolverConfig cfg;
+ cfg.total_global_replicas = DEFAULT_TOTAL_GLOBAL_REPLICAS;
+ cfg.total_steps = DEFAULT_TOTAL_STEPS;
+ cfg.swap_interval = DEFAULT_SWAP_INTERVAL;
+ cfg.initial_temp = DEFAULT_INITIAL_TEMP;
+ cfg.temp_decay = DEFAULT_TEMP_DECAY;
+ cfg.deterministic = 0;
+ cfg.seed = 0;
+ cfg.seed_set = 0;
+ cfg.filename = DEFAULT_TSP_FILE;
+
+ int parse_status = parse_args(argc, argv, &cfg);
+ if (parse_status == 1) {
+ return 0;
+ }
+ if (parse_status != 0) {
+ return 1;
+ }
+
MPI_Init(&argc, &argv);
+
int rank, size;
+ int exit_code = 0;
+ int abort_code = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
-
- // --- 1. FILE READING (Rank 0) ---
+
+ int *routes_flat = NULL;
+ int **routes = NULL;
+ double *temps = NULL;
+ int *my_best_route = NULL;
+ int *winner_route = NULL;
+ int *swap_tmp = NULL;
+ int *swap_buf = NULL;
+
+ if (cfg.total_global_replicas <= 0) {
+ if (rank == 0) fprintf(stderr, "Error: --replicas must be a positive integer.\n");
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ if (cfg.swap_interval <= 0) {
+ if (rank == 0) fprintf(stderr, "Error: --swap-interval must be a positive integer.\n");
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ if (cfg.total_steps <= 0) {
+ if (rank == 0) fprintf(stderr, "Error: --steps must be a positive integer.\n");
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ if (cfg.initial_temp < 0.0) {
+ if (rank == 0) fprintf(stderr, "Error: --initial-temp must be non-negative.\n");
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ if (cfg.temp_decay <= 0.0) {
+ if (rank == 0) fprintf(stderr, "Error: --temp-decay must be > 0.\n");
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ /* --- 1. FILE READING (Rank 0) --- */
if (rank == 0) {
- const char *fname = (argc > 1) ? argv[1] : "berlin52.tsp";
- if (!read_tsp_file(fname)) {
- fprintf(stderr, "Error: Could not read %s\n", fname);
- MPI_Abort(MPI_COMM_WORLD, 1);
+ if (!read_tsp_file(cfg.filename)) {
+ abort_code = 1;
+ goto cleanup;
}
}
-
- // Broadcast Map Data
+
MPI_Bcast(&N_CITIES, 1, MPI_INT, 0, MPI_COMM_WORLD);
- if (rank != 0) cities = (City*)malloc(N_CITIES * sizeof(City));
- MPI_Bcast(cities, N_CITIES * sizeof(City), MPI_BYTE, 0, MPI_COMM_WORLD);
-
- // Precompute Distances Locally
- init_dist_matrix();
- srand(time(NULL) + rank * 999);
-
- // --- 2. STRONG SCALING SETUP ---
- // Total work is 24 replicas. Split among P processors.
- if (TOTAL_GLOBAL_REPLICAS % size != 0) {
- if (rank==0) printf("Error: 24 Replicas must be divisible by %d processors.\n", size);
- MPI_Finalize(); return 1;
+
+ if (rank != 0) {
+ cities = (City *)malloc((size_t)N_CITIES * sizeof(City));
+ if (!cities) {
+ fprintf(stderr, "Rank %d: failed to allocate city buffer for %d cities.\n", rank, N_CITIES);
+ abort_code = 1;
+ goto cleanup;
+ }
}
-
- int my_replicas = TOTAL_GLOBAL_REPLICAS / size;
+
+ MPI_Bcast(cities, N_CITIES * (int)sizeof(City), MPI_BYTE, 0, MPI_COMM_WORLD);
+
+ if (!init_dist_matrix()) {
+ fprintf(stderr, "Rank %d: failed to allocate distance matrix for %d cities.\n", rank, N_CITIES);
+ abort_code = 1;
+ goto cleanup;
+ }
+
+ {
+ unsigned long base_seed;
+ if (cfg.deterministic) {
+ base_seed = DETERMINISTIC_SEED;
+ } else if (cfg.seed_set) {
+ base_seed = cfg.seed;
+ } else {
+ base_seed = (unsigned long)time(NULL) ^ (unsigned long)rank;
+ }
+ srand((unsigned int)(base_seed + (unsigned long)rank));
+ }
+
+ /* --- 2. STRONG SCALING SETUP --- */
+ if (cfg.total_global_replicas % size != 0) {
+ if (rank == 0) {
+ fprintf(stderr, "Error: replicas (%d) must be divisible by MPI processes (%d).\n",
+ cfg.total_global_replicas, size);
+ }
+ exit_code = 1;
+ goto cleanup;
+ }
+
+ int my_replicas = cfg.total_global_replicas / size;
int my_start_idx = rank * my_replicas;
-
- // Allocate Local Replicas
- int *routes_flat = (int*)malloc(my_replicas * N_CITIES * sizeof(int));
- int **routes = (int**)malloc(my_replicas * sizeof(int*));
- double *temps = (double*)malloc(my_replicas * sizeof(double));
-
- // Track Best Route Locally
- int *my_best_route = (int*)malloc(N_CITIES * sizeof(int));
+
+ routes_flat = (int *)malloc((size_t)my_replicas * (size_t)N_CITIES * sizeof(int));
+ routes = (int **)malloc((size_t)my_replicas * sizeof(int *));
+ temps = (double *)malloc((size_t)my_replicas * sizeof(double));
+ my_best_route = (int *)malloc((size_t)N_CITIES * sizeof(int));
+ winner_route = (int *)malloc((size_t)N_CITIES * sizeof(int));
+ swap_tmp = (int *)malloc((size_t)N_CITIES * sizeof(int));
+ swap_buf = (int *)malloc((size_t)N_CITIES * sizeof(int));
+
+ if (!routes_flat || !routes || !temps || !my_best_route || !winner_route || !swap_tmp || !swap_buf) {
+ fprintf(stderr, "Rank %d: memory allocation failed while creating solver buffers.\n", rank);
+ abort_code = 1;
+ goto cleanup;
+ }
+
double my_best_dist = 1e15;
for (int i = 0; i < my_replicas; i++) {
routes[i] = &routes_flat[i * N_CITIES];
- for (int c = 0; c < N_CITIES; c++) routes[i][c] = c;
-
- // Random Shuffle
- for(int k=0; k 1) {
- // Step 1: Handle LEFT Partner (Receive then Send)
+ int mpi_err = MPI_SUCCESS;
+
+ /* Step 1: Handle LEFT Partner (Receive then Send) */
if (rank > 0) {
int left = rank - 1;
- int left_last_g = (left+1)*my_replicas - 1;
+ int left_last_g = (left + 1) * my_replicas - 1;
int expect = 0;
- if ((step/SWAP_INTERVAL)%2 == 0 && left_last_g%2 == 0) expect = 1;
- if ((step/SWAP_INTERVAL)%2 != 0 && left_last_g%2 != 0) expect = 1;
+ if ((step / cfg.swap_interval) % 2 == 0 && left_last_g % 2 == 0) expect = 1;
+ if ((step / cfg.swap_interval) % 2 != 0 && left_last_g % 2 != 0) expect = 1;
if (expect) {
double my_E = calc_route_len(routes[0]);
double partner_E;
- MPI_Sendrecv(&my_E, 1, MPI_DOUBLE, left, 0, &partner_E, 1, MPI_DOUBLE, left, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-
+ mpi_err = MPI_Sendrecv(&my_E, 1, MPI_DOUBLE, left, 0,
+ &partner_E, 1, MPI_DOUBLE, left, 0,
+ MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "left energy exchange", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
+
int do_swap = 0;
- MPI_Recv(&do_swap, 1, MPI_INT, left, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-
+ mpi_err = MPI_Recv(&do_swap, 1, MPI_INT, left, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "left swap decision receive", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
+
if (do_swap) {
- int buf[N_CITIES];
- MPI_Sendrecv(routes[0], N_CITIES, MPI_INT, left, 2, buf, N_CITIES, MPI_INT, left, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- memcpy(routes[0], buf, N_CITIES*sizeof(int));
+ mpi_err = MPI_Sendrecv(routes[0], N_CITIES, MPI_INT, left, 2,
+ swap_buf, N_CITIES, MPI_INT, left, 2,
+ MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "left route swap", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
+ memcpy(routes[0], swap_buf, (size_t)N_CITIES * sizeof(int));
}
}
}
- // Step 2: Handle RIGHT Partner (Initiate Logic)
+ /* Step 2: Handle RIGHT Partner (Initiate Logic) */
if (rank < size - 1) {
int right = rank + 1;
int my_last = my_replicas - 1;
int my_last_g = my_start_idx + my_last;
-
+
int init = 0;
- if ((step/SWAP_INTERVAL)%2 == 0 && my_last_g%2 == 0) init = 1;
- if ((step/SWAP_INTERVAL)%2 != 0 && my_last_g%2 != 0) init = 1;
+ if ((step / cfg.swap_interval) % 2 == 0 && my_last_g % 2 == 0) init = 1;
+ if ((step / cfg.swap_interval) % 2 != 0 && my_last_g % 2 != 0) init = 1;
if (init) {
double my_E = calc_route_len(routes[my_last]);
double partner_E;
- MPI_Sendrecv(&my_E, 1, MPI_DOUBLE, right, 0, &partner_E, 1, MPI_DOUBLE, right, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-
+ mpi_err = MPI_Sendrecv(&my_E, 1, MPI_DOUBLE, right, 0,
+ &partner_E, 1, MPI_DOUBLE, right, 0,
+ MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "right energy exchange", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
+
int do_swap = 0;
double my_T = temps[my_last];
- double partner_T = (my_last_g+1 == TOTAL_GLOBAL_REPLICAS-1) ? 0.0 : INITIAL_TEMP * pow(TEMP_DECAY, my_last_g+1);
- double delta = (1.0/(my_T+1e-9) - 1.0/(partner_T+1e-9)) * (my_E - partner_E);
-
- if (((double)rand()/RAND_MAX) < exp(delta)) do_swap = 1;
+ double partner_T = (my_last_g + 1 == cfg.total_global_replicas - 1)
+ ? 0.0
+ : cfg.initial_temp * pow(cfg.temp_decay, my_last_g + 1);
+ double delta = (1.0 / (my_T + 1e-9) - 1.0 / (partner_T + 1e-9)) * (my_E - partner_E);
+
+ if (((double)rand() / (double)RAND_MAX) < exp(delta)) do_swap = 1;
+
+ mpi_err = MPI_Send(&do_swap, 1, MPI_INT, right, 1, MPI_COMM_WORLD);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "right swap decision send", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
- MPI_Send(&do_swap, 1, MPI_INT, right, 1, MPI_COMM_WORLD);
-
if (do_swap) {
- int buf[N_CITIES];
- MPI_Sendrecv(routes[my_last], N_CITIES, MPI_INT, right, 2, buf, N_CITIES, MPI_INT, right, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- memcpy(routes[my_last], buf, N_CITIES*sizeof(int));
+ mpi_err = MPI_Sendrecv(routes[my_last], N_CITIES, MPI_INT, right, 2,
+ swap_buf, N_CITIES, MPI_INT, right, 2,
+ MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+ if (mpi_err != MPI_SUCCESS) {
+ log_mpi_error(rank, "right route swap", mpi_err);
+ abort_code = 1;
+ goto cleanup;
+ }
+ memcpy(routes[my_last], swap_buf, (size_t)N_CITIES * sizeof(int));
}
}
}
@@ -278,35 +587,63 @@ int main(int argc, char** argv) {
}
}
- // --- 4. FINALIZE & SAVE ---
+ /* --- 4. FINALIZE & SAVE --- */
double end_time = MPI_Wtime();
-
- // Find Global Best
- struct { double val; int rank; } loc_data = {my_best_dist, rank}, glob_data;
+
+ /* Find Global Best */
+ struct {
+ double val;
+ int rank;
+ } loc_data = {my_best_dist, rank}, glob_data;
MPI_Allreduce(&loc_data, &glob_data, 1, MPI_DOUBLE_INT, MPI_MINLOC, MPI_COMM_WORLD);
- // Winner Rank sends the Route to Rank 0
- int *winner_route = (int*)malloc(N_CITIES * sizeof(int));
- if (rank == glob_data.rank) memcpy(winner_route, my_best_route, N_CITIES * sizeof(int));
-
- // Broadcast winning route to Rank 0 (and everyone else)
+ /* Winner Rank sends the Route to Rank 0 */
+ if (rank == glob_data.rank) {
+ memcpy(winner_route, my_best_route, (size_t)N_CITIES * sizeof(int));
+ }
+
+ /* Broadcast winning route to Rank 0 (and everyone else) */
MPI_Bcast(winner_route, N_CITIES, MPI_INT, glob_data.rank, MPI_COMM_WORLD);
if (rank == 0) {
- printf("Simulation Time: %.4f s | Global Best: %.4f (Found by Rank %d)\n",
+ printf("Simulation Time: %.4f s | Global Best: %.4f (Found by Rank %d)\n",
end_time - start_time, glob_data.val, glob_data.rank);
-
+
FILE *f = fopen("solution.txt", "w");
+ if (!f) {
+ fprintf(stderr, "Error: failed to write solution.txt: %s\n", strerror(errno));
+ exit_code = 1;
+ goto cleanup;
+ }
fprintf(f, "%.4f\n", glob_data.val);
fclose(f);
-
+
FILE *fr = fopen("my_route.txt", "w");
- for(int i=0; i
Date: Mon, 6 Jul 2026 15:12:31 +0000
Subject: [PATCH 3/3] ci: set minimal workflow token permissions
---
.github/workflows/ci.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 84017fd..6471a24 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,6 +4,9 @@ on:
push:
pull_request:
+permissions:
+ contents: read
+
jobs:
build-and-smoke:
runs-on: ubuntu-latest