Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ae84696
Move the list of non-dominated labels to a Bucket class.
legraina Mar 6, 2026
1e06699
Code running correctly, but still some bugs on the dominance and the …
legraina Mar 9, 2026
8ab3791
Working Buckets.
legraina Mar 10, 2026
8beb230
Moved print labels to TRACE level.
legraina Mar 10, 2026
722c718
Change ResourceType to ContainerResourceType.
legraina Mar 10, 2026
969e51a
Fix all the errors with LabelContainerType to make it work for vrp an…
legraina Mar 10, 2026
ef27efc
Resolve copilot comments.
legraina Mar 10, 2026
0f50c64
Fix format for fast_domiance_check
legraina Mar 10, 2026
6cf7447
Fix adjency matrix in connectivity.
legraina Mar 10, 2026
917211e
Fix some bug in greedy. Also fix make_resource() and reset().
legraina Apr 22, 2026
6a2e782
Change make_resource to create_resource
legraina Apr 22, 2026
aac8b21
Merged
legraina Apr 23, 2026
f467784
Add the storage of the best bound, and it's pruning based on it.
legraina Apr 23, 2026
3e20952
Refactor: BacktrackingDiveAlgorithm + TabuList; add TabuSearchAlgorithm
legraina Apr 29, 2026
46ccc24
Add a timeout on the algorithms, add a leq comparison for the contain…
legraina May 5, 2026
dca8801
feat: path vectors, CI Python matrix, and py.typed stubs
legraina May 25, 2026
b4c97c1
perf: LabelBuckets — vector + binary search, O(1) erase, symmetric stats
legraina May 25, 2026
ff7b940
test+bench: LabelBuckets tests and BucketS/BucketP benchmark columns
legraina May 25, 2026
109e34e
Has implemented some improvements for bucket
legraina May 25, 2026
abe93e7
feat: unified benchmark solve with cross-checking, optional Boost, fo…
legraina May 25, 2026
1cde86d
feat: parent-pointer path reconstruction + AlgorithmBaseParams::with_…
legraina May 25, 2026
7ead13d
refactor: simplify benchmark labels vector construction
legraina May 25, 2026
2f4934a
feat: benchmark improvements — cost/BKS table, large instances, CI wo…
legraina May 25, 2026
339d523
Avoid copying solution vectors in collect_solutions
Copilot May 27, 2026
c5c3f4c
feat: memory-limit and pressure-pruning for label algorithms
legraina May 27, 2026
e74e7a5
Improve _add_rows_bulk: batch inserts per arc using sorted arc_id runs
legraina May 27, 2026
fb467e5
Demote resolve() memory-limit log from INFO to DEBUG
legraina May 27, 2026
20c254c
Add process RSS to memory-limit debug log
legraina May 27, 2026
1dae09f
merge: integrate tight-lb into quick-improvements
legraina May 27, 2026
b442c47
Merge branch 'quick-improvements' of github.com:lab-core/rcspp into q…
legraina May 27, 2026
6bcd31c
Clean files and minor fix.
legraina May 27, 2026
30cc88d
feat: add SolveResult, AlgorithmStatus, timeout, and release_after_so…
legraina May 28, 2026
f58ae8a
chore: update .gitignore and remove tracked build artifacts
legraina May 28, 2026
057cd1c
Add memory helpers in python.
legraina May 28, 2026
ecb81ad
Enforce timeout_s in TabuSearchAlgorithm::main_loop
legraina May 28, 2026
a4c3e9b
feat: add AStarDominanceAlgorithm with backward Bellman-Ford heuristic
legraina May 28, 2026
2193a5d
feat: use CostResourceType reduced costs for A* heuristic
legraina May 28, 2026
56c7f9b
fix: use AStarAlgoBound in VRP benchmarks
legraina May 28, 2026
ad00540
feat: add ImprovingTabuSearch, ConstructiveTabu/Tabu heuristics, --as…
legraina May 28, 2026
326e3b5
refactor: replace ImprovingTabuSearch with proper classical improving TS
legraina May 28, 2026
0a3cc8a
chore: use should_stop() consistently in tabu search algorithms
legraina May 29, 2026
08086ef
Ensure should_stop() is always used.
legraina May 29, 2026
c63b923
Merge branch 'main' into quick-improvements
legraina Jun 8, 2026
be5bc16
Removed idea files
legraina Jun 8, 2026
455047e
Fix include order
pamun1 Jun 8, 2026
77917a2
Merge branch 'quick-improvements' of https://github.com/lab-core/rcsp…
pamun1 Jun 8, 2026
0c0157f
fix: correct label ref-count release
pamun1 Jun 8, 2026
5cce1e4
fix: protect Windows include order from clang-format; silence shm tra…
legraina Jun 9, 2026
187c519
feat: ng-route unreachable-set dominance augmentation
legraina Jun 10, 2026
d74b9af
fix(benchmark): measure pricing labels from CG; cap labels/columns fo…
legraina Jun 10, 2026
1088b50
Add the possibility to chose which solomon family to run in the bench…
legraina Jun 10, 2026
8f515d2
fix: add missing headers and update structured bindings for CGSolveRe…
legraina Jun 10, 2026
bca081b
feat(benchmark): SLURM array + Python generator for per-instance ng runs
legraina Jun 10, 2026
14377ba
feat(benchmark): report CG wall-clock time and time-drop% in the table
legraina Jun 10, 2026
544b6e8
fix: suppress pytest-internal sqlite3 ResourceWarning in conftest
legraina Jun 10, 2026
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
80 changes: 80 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Benchmark

on:
workflow_dispatch:
inputs:
max_instances:
description: 'Instances per family for small benchmark (1–9; default 2)'
default: '2'
required: true
type: string
run_large:
description: 'Also run large benchmark (C2/R2/RC2)'
type: boolean
default: false
max_large_instances:
description: 'Instances per family for large benchmark (1–8; R2 up to 11)'
default: '2'
required: false
type: string

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

defaults:
run:
shell: bash

jobs:
benchmark:
name: Benchmark / ubuntu
# NOTE: Gurobi must be installed on the runner and GUROBI_HOME must be set.
# Use a self-hosted runner with Gurobi, or adjust the "Install Gurobi" step.
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
lfs: true

- name: Install build dependencies
run: sudo apt-get update -q && sudo apt-get install -y build-essential cmake

- name: Configure (Release, no Python, no tests)
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DUSE_PYTHON=OFF \
-DUSE_TESTS=OFF

- name: Build small benchmark
run: cmake --build build --target rcspp-vrp-benchmark --parallel

- name: Build large benchmark
if: inputs.run_large
run: cmake --build build --target rcspp-vrp-benchmark-large --parallel

- name: Run small benchmark (C1/R1/RC1)
run: |
./build/bin/rcspp-vrp-benchmark ${{ inputs.max_instances }} \
2>&1 | tee benchmark_small.txt

- name: Run large benchmark (C2/R2/RC2)
if: inputs.run_large
run: |
./build/bin/rcspp-vrp-benchmark-large ${{ inputs.max_large_instances }} \
2>&1 | tee benchmark_large.txt

- name: Upload small benchmark results
uses: actions/upload-artifact@v4
with:
name: benchmark-small-${{ github.sha }}
path: benchmark_small.txt

- name: Upload large benchmark results
if: inputs.run_large
uses: actions/upload-artifact@v4
with:
name: benchmark-large-${{ github.sha }}
path: benchmark_large.txt
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ defaults:

jobs:
test:
name: Test / ${{ matrix.os }} / ${{ matrix.build-type }}
name: Test / ${{ matrix.os }} / ${{ matrix.build-type }} / py${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
build-type: [Debug, Release]
python-version: ['3.11', '3.12', '3.13']
exclude:
# Debug builds don't run Python tests — only one Python version needed
- build-type: Debug
python-version: '3.11'
- build-type: Debug
python-version: '3.13'

steps:
- uses: actions/checkout@v4
Expand All @@ -42,7 +49,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: ${{ matrix.python-version }}

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DUSE_PYTHON=ON -DUSE_TESTS=ON
Expand All @@ -60,7 +67,7 @@ jobs:
- name: Python tests
if: matrix.build-type == 'Release'
working-directory: src/python
run: pytest -v
run: pytest -v --tb=short

build_wheels:
name: Wheels / ${{ matrix.os }}
Expand Down
31 changes: 30 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.vs/*
.cache/*
out/*
build/*
build/
build_*/
cmake-build-*/
**/__pycache__/**
*.dll
*.lib
*.exp
Expand All @@ -9,8 +13,23 @@ build/*
*.o
*.a
*.exe
*.so
*.dylib
instances/duals/

# Python
.venv/
.venv-*/
venv/
.pytest_cache/
__pycache__/
*.py[cod]
*.pyd
*.egg-info/
dist/
wheelhouse/
build-*/

# CMake / Ninja generated files
CMakeCache.txt
CMakeFiles/
Expand All @@ -37,3 +56,13 @@ INSTALL*
# Per-target/build subdirectories generated by CMake/VS
*.dir/
Testing/

# Claude Code project files
.claude/

# Clion
.idea/

# SLURM benchmark artifacts
scripts/ng_jobs.txt
logs/
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ repos:
hooks:
- id: markdownlint
files: .*\.md$
exclude: ^(build|build_[^/]+)/
args: ["--fix"]

# -------------------------
Expand Down
3 changes: 3 additions & 0 deletions instances/RC201_12.txt
Git LFS file not shown
114 changes: 114 additions & 0 deletions scripts/gen_ng_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Generate a job manifest for the ng-route benchmark SLURM array.

Each manifest line holds the CLI arguments for one ``rcspp-vrp-benchmark-ng``
run. By default there is one job per (instance, ng-size) pair so the SLURM
array runs every instance in parallel; ``--group-by family`` instead emits one
job per (family, ng-size). ``scripts/run_ng_benchmark.slurm`` reads the line
indexed by ``$SLURM_ARRAY_TASK_ID`` and runs it.

Example:
python scripts/gen_ng_jobs.py --ng 3 5 8 --max-index 9
sbatch --array=0-80 scripts/run_ng_benchmark.slurm
"""

from __future__ import annotations

import argparse
from pathlib import Path


def instance_names(families: list[str], max_index: int) -> list[str]:
"""Return Solomon instance names (e.g. C101) for the given families/indices.

Args:
families: Instance families to include (e.g. ["C", "R", "RC"]).
max_index: Highest instance index; expands to <family>10<1..max_index>.

Returns:
The list of instance names in (index, family) order.
"""
return [f"{family}10{i}" for i in range(1, max_index + 1) for family in families]


def build_jobs(args: argparse.Namespace) -> list[str]:
"""Build the manifest lines (one benchmark argument string per job).

Args:
args: Parsed command-line arguments.

Returns:
One argument string per job.
"""
common: list[str] = ["--max-labels", str(args.max_labels)]
if args.cols > 0:
common += ["--cols", str(args.cols)]

jobs: list[str] = []
if args.group_by == "instance":
for name in instance_names(args.families, args.max_index):
for ng in args.ng:
jobs.append(" ".join(["--instance", name, "--ng", str(ng), *common]))
else: # group_by == "family"
for family in args.families:
for ng in args.ng:
jobs.append(
" ".join([str(args.max_index), "--family", family, "--ng", str(ng), *common])
)
return jobs


def parse_args() -> argparse.Namespace:
"""Parse command-line arguments.

Returns:
The parsed arguments.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--ng", type=int, nargs="+", default=[3, 5, 8], help="ng-neighbourhood sizes"
)
parser.add_argument(
"--families",
nargs="+",
default=["C", "R", "RC"],
help="Solomon instance families",
)
parser.add_argument("--max-index", type=int, default=9, help="highest instance index (1..9)")
parser.add_argument("--max-labels", type=int, default=100, help="per-node label-expansion cap")
parser.add_argument(
"--cols", type=int, default=0, help="columns per CG iteration (0 => #customers)"
)
parser.add_argument(
"--group-by",
choices=["instance", "family"],
default="instance",
help="one job per instance (default) or per family",
)
parser.add_argument(
"--output",
type=Path,
default=Path("scripts/ng_jobs.txt"),
help="manifest output path",
)
return parser.parse_args()


def main() -> None:
"""Generate the manifest and print the matching sbatch command."""
args = parse_args()
jobs = build_jobs(args)

args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text("\n".join(jobs) + "\n")

print(f"Wrote {len(jobs)} jobs to {args.output}")
print("Submit with:")
print(
f" MANIFEST={args.output} sbatch --array=0-{len(jobs) - 1} "
f"scripts/run_ng_benchmark.slurm"
)


if __name__ == "__main__":
main()
54 changes: 54 additions & 0 deletions scripts/run_ng_benchmark.slurm
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash
# SLURM array runner for the ng-route augmentation benchmark.
#
# It executes one job from a manifest generated by scripts/gen_ng_jobs.py:
# array task N runs the (N+1)-th manifest line through rcspp-vrp-benchmark-ng.
# By default the manifest has one line per (instance, ng-size), so every
# instance runs in parallel.
#
# Usage (submit from the repository root; the binary locates instances/ relative
# to the source tree baked in at compile time):
# python scripts/gen_ng_jobs.py --ng 3 5 8 --max-index 9
# MANIFEST=scripts/ng_jobs.txt sbatch --array=0-80 scripts/run_ng_benchmark.slurm
#
# The --array range must equal the number of manifest lines (printed by the
# generator). Override MANIFEST / BIN via --export, e.g.:
# sbatch --array=0-26 --export=ALL,MANIFEST=scripts/ng_jobs.txt scripts/run_ng_benchmark.slurm

#SBATCH --job-name=ng-bench
#SBATCH --array=0-80
#SBATCH --cpus-per-task=1
#SBATCH --mem=8G
#SBATCH --time=12:00:00
#SBATCH --output=logs/ng_%A_%a.out
#SBATCH --error=logs/ng_%A_%a.err

set -euo pipefail

MANIFEST="${MANIFEST:-scripts/ng_jobs.txt}"
BIN="${BIN:-./build/bin/rcspp-vrp-benchmark-ng}"
task="${SLURM_ARRAY_TASK_ID:-0}"

if [[ ! -f "${MANIFEST}" ]]; then
echo "Manifest not found: ${MANIFEST} (run scripts/gen_ng_jobs.py first)" >&2
exit 1
fi
if [[ ! -x "${BIN}" ]]; then
echo "Benchmark binary not found/executable: ${BIN}" >&2
echo "Build it first, e.g.: cmake --build build --target rcspp-vrp-benchmark-ng" >&2
exit 1
fi

# Manifest is 0-indexed by array task; sed is 1-indexed.
line="$(sed -n "$(( task + 1 ))p" "${MANIFEST}")"
if [[ -z "${line}" ]]; then
echo "No manifest line for array task ${task} in ${MANIFEST}" >&2
echo "Set --array to match the number of lines ($(wc -l < "${MANIFEST}"))." >&2
exit 1
fi

mkdir -p logs
read -r -a job_args <<< "${line}"
echo "[$(date '+%F %T')] task=${task} args: ${line}"

srun "${BIN}" "${job_args[@]}"
7 changes: 7 additions & 0 deletions src/python/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ def pytest_configure(config):
message=".*SolverGurobi.*",
category=pytest.PytestUnraisableExceptionWarning,
)
# pytest's internal cache plugin leaves sqlite3 connections open; suppress the
# resulting ResourceWarning so it doesn't pollute test output.
warnings.filterwarnings(
"ignore",
message=".*unclosed.*<sqlite3.Connection.*",
category=ResourceWarning,
)
Loading