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 Solution quality across process counts

-## 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