diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9d05c10 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,566 @@ +# CLAUDE.md — orienteering-cpp + +This file is a guide for AI assistants working on this codebase. Read it before making any changes. + +--- + +## 1. Project Purpose and Scope + +`orienteering-cpp` is a C++17 research library for solving the **Orienteering Problem (OP)** and 8 of its variants using heuristic and metaheuristic algorithms. The OP asks: given a graph of nodes with rewards and a time budget, find a route starting and ending at depots that maximizes collected reward without exceeding the budget. + +Supported variants: OP, OPTW, TOP, TOPTW, TDOP, TDOPTW, MCTOPMTW, SingleSat, TTDP. + +This is **not** an application or service. It is a static library (`orienteeringLib`) with example binaries for benchmarking and a test suite. There is no UI, no server, no database. + +--- + +## 2. Repository Structure + +``` +orienteering-cpp/ +├── CMakeLists.txt # Root: C++17, FetchContent for GoogleTest, subdirs +├── include/ # All public headers (library API lives here) +│ ├── core/ # Primitive types, constants, RNG, routing utils +│ │ ├── types.h # NodeId, Reward, Time, TimeWindow, enums +│ │ ├── constants.h # DEFAULT_SEED=42, DEFAULT_TIMELIMIT_SECONDS, etc. +│ │ ├── random.h # oplib::utils::Random (mt19937 wrapper) +│ │ └── routing_utils.h # oplib::utils::RoutingUtils (time-dependent helpers) +│ ├── io/ # Parser headers (one per variant) +│ │ ├── instance_parser.h # Abstract base: read(filepath) -> unique_ptr +│ │ └── *_parser.h # OPParser, TOPParser, TOPTWParser, TDOPParser, ... +│ ├── model/ # Problem domain types +│ │ ├── node.h # Node struct: id, x, y, reward, service_time, tw, neighbors +│ │ ├── problem.h # Abstract Problem base class +│ │ ├── solution.h # Solution: vector> + metadata +│ │ ├── graph.h # OPGraph: arc/detour preprocessing (legacy, not used by current solvers) +│ │ └── variants/ # Concrete problem classes (9 total) +│ │ ├── op.h # OPProblem (root of variant hierarchy) +│ │ ├── optw.h # OPTWProblem extends OPProblem +│ │ ├── top.h # TOPProblem extends OPProblem +│ │ ├── toptw.h # TOPTWProblem extends OPTWProblem +│ │ ├── tdop.h # TDOPProblem extends OPProblem +│ │ ├── tdoptw.h # TDOPTWProblem extends OPTWProblem +│ │ ├── mctopmtw.h # MCTOPMTWProblem extends TOPTWProblem +│ │ ├── singlesat.h # SingleSatProblem extends OPTWProblem +│ │ └── ttdp.h # TTDPProblem extends TOPTWProblem +│ └── solver/ +│ ├── solver.h # Abstract Solver + SolverConfig +│ ├── constructive/ +│ │ ├── move_evaluator.h # Abstract MoveEvaluator + concrete impls +│ │ ├── greedy.h # GreedySolver + RouteContext + InfeasibilityCache +│ │ └── randomized_greedy.h # RandomizedGreedySolver (RCL-based) +│ └── local_search/ +│ ├── LNS.h # LNSSolver (destroy + repair) +│ └── grasp.h # GraspSolver (multi-iteration metaheuristic) +├── src/ # Implementation files mirroring include/ +│ ├── CMakeLists.txt # Builds orienteeringLib (STATIC), optional Boost +│ ├── io/ # 8 parser .cpp files +│ ├── model/graph.cpp +│ └── solver/ +│ ├── constructive/ # greedy.cpp, randomized_greedy.cpp, move_evaluator.cpp +│ └── local_search/ # LNS.cpp, grasp.cpp +├── examples/ # 6 standalone executables (not part of the library) +│ ├── benchmark_utils.h # Shared parsing/CSV helpers for all benchmarks +│ ├── solve_op.cpp # CLI: -file, -seed, -timeout; uses LNSSolver +│ ├── benchmark_greedy.cpp +│ ├── benchmark_randomized_greedy.cpp +│ ├── benchmark_lns.cpp +│ ├── benchmark_grasp.cpp +│ └── check_dummy.cpp +└── tests/ # GoogleTest suite + ├── main.cpp + ├── test_greedy_solver.cpp # Unit + integration tests for GreedySolver + ├── test_io_parsers.cpp # Parser tests (require data/ folder with instances) + ├── test_model_extensions.cpp + └── test_ds.cpp # Minimal data-structure sanity checks +``` + +--- + +## 3. Build and Test Commands + +### Configure and Build + +```bash +# Standard Release build (Linux/macOS) +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build + +# Debug build +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build + +# Disable test compilation (faster CI or library-only builds) +cmake -S . -B build -DPACKAGE_TESTS=OFF +cmake --build build + +# Parallel build +cmake --build build -j$(nproc) +``` + +### Run Tests + +```bash +# Via CTest (preferred) +ctest --test-dir build + +# Directly (shows individual test names and failure messages) +./build/tests/test_orienteeringLib + +# Run tests matching a name pattern +./build/tests/test_orienteeringLib --gtest_filter="GreedySolverTest.*" +``` + +### Run Example Executables + +```bash +# Solve a single OP instance with LNS +./build/examples/solve_op -file data/op/ChaoSet_64/set_64_1_15.txt -seed 42 -timeout 60.0 + +# Benchmark GRASP over a dataset +./build/examples/benchmark_grasp -dataset data/ -mode quick -alpha 0.3 -lns_iters 10 -timelimit 30 + +# Other benchmarks: benchmark_greedy, benchmark_randomized_greedy, benchmark_lns +``` + +### Windows (MSYS2/GCC) + +```powershell +$env:PATH = "C:\msys64\mingw64\bin;C:\msys64\usr\bin;$env:PATH" +cmake -S . -B build -G"Unix Makefiles" +cmake --build build -j4 +.\build\tests\test_orienteeringLib.exe +``` + +### Windows (MSVC + vcpkg for Boost) + +```powershell +cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="C:\vcpkg\scripts\buildsystems\vcpkg.cmake" +cmake --build build --config Release -j4 +``` + +### Important Notes + +- GoogleTest v1.15.2 is auto-downloaded via `FetchContent` on first configure — requires internet access. +- Boost serialization is **optional**. If not found, a `[WARNING]` is printed but the build succeeds. +- The `data/` folder is **not** in the repository (listed in `.gitignore`). Integration tests that load real instance files will fail or be skipped without it. Unit tests using synthetic problems always pass. +- All build artifacts go in `build/` (out-of-source). Never commit `build/`. + +--- + +## 4. Key Architecture Concepts + +### 4.1 Namespace Layout + +``` +oplib:: — root (types, constants) +oplib::constants:: — compile-time constants +oplib::model:: — Node, Problem, Solution, OPGraph +oplib::model::variants:: — 9 concrete problem classes +oplib::solver:: — Solver (abstract), SolverConfig +oplib::solver::constructive:: — GreedySolver, RandomizedGreedySolver, MoveEvaluator +oplib::solver::local_search:: — LNSSolver, GraspSolver +oplib::io:: — InstanceParser (abstract), all parsers +oplib::utils:: — Random, RoutingUtils +``` + +### 4.2 Core Types (`include/core/types.h`, `include/core/constants.h`) + +```cpp +using NodeId = int32_t; +using Reward = double; +using Time = double; // used for both travel time and time budget + +struct TimeWindow { + Time opening = 0.0; + Time closing = 1e18; // default = "no constraint" + bool is_within(Time t) const; +}; + +enum class ScalingMode { RAW, SCALED_INTEGER }; +enum class InsertionStrategyMode { CUSTOMER_WISE, VEHICLE_WISE }; +enum class MoveEvaluatorType { REWARD, REWARD_PER_TIME, REWARD_PER_DISTANCE, SQUARED_REWARD_PER_DISTANCE }; +``` + +Constants (from `constants.h`): `DEFAULT_SEED = 42`, `DEFAULT_TIMELIMIT_SECONDS`, `DEFAULT_MAX_ITERATIONS`. + +### 4.3 Node Layout Convention + +Every problem instance uses **index 0 as source depot** and **index (n-1) as sink depot**. Customers occupy indices 1 through n-2. Always call `get_source_depot()` and `get_sink_depot()` — never hardcode 0 or n-1. + +Routes in a `Solution` always begin with `source_depot` and end with `sink_depot`. + +### 4.4 Problem Variant Hierarchy + +``` +Problem (abstract) +└── OPProblem — single vehicle, Euclidean distances, budget constraint + ├── OPTWProblem — adds per-node time windows + service times + │ ├── TOPTWProblem — adds multiple vehicles + │ │ ├── MCTOPMTWProblem — adds multi-commodity knapsack constraints + │ │ └── TTDPProblem — multi-day variant (num_days = num_vehicles) + │ ├── TDOPTWProblem — time-dependent travel + time windows + │ └── SingleSatProblem — satellite scheduling (domain extension) + ├── TOPProblem — multiple vehicles, no time windows + └── TDOPProblem — time-dependent travel, no time windows +``` + +Capability flags (virtual methods on `Problem`): +- `has_time_windows()` — nodes have `[opening, closing]` constraints +- `is_time_dependent()` — `get_travel_time(i, j, departure_time)` varies with time +- `is_multi_vehicle()` — `get_num_vehicles() > 1` + +### 4.5 Problem Lifecycle + +Every problem goes through three stages before solving: + +```cpp +auto problem = parser.read("instance.txt"); // 1. Parse: allocate nodes, set budget +problem->finalize(); // 2. Compute Euclidean distance matrix O(n²) +problem->preprocessing(); // 3. Prune infeasible arcs, sort neighbors by heuristic +``` + +`preprocessing()` populates `nodes[i].neighbors` — a sorted list of reachable successors. The greedy solver iterates over `neighbors` to find insertions. **Without calling `preprocessing()`, `neighbors` is empty and no customers are ever inserted.** + +`OPParser` calls `finalize()` internally but NOT `preprocessing()`. Always check which stages a specific parser performs, and call the missing ones explicitly. + +### 4.6 Solution Structure + +```cpp +// routes[v] = [source_depot, c1, c2, ..., sink_depot] +vector> routes; +Reward total_reward = 0.0; +Time total_travel_time = 0.0; +``` + +- Each route starts with `source_depot` and ends with `sink_depot`. +- No customer node appears in more than one route. +- `total_reward` and `total_travel_time` are maintained incrementally by the solver — do not recompute unless validating. + +### 4.7 Greedy Solver Internals + +`GreedySolver` is the central building block used by all other solvers. + +**`RouteContext`** — per-vehicle state updated incrementally on each insertion: +```cpp +struct RouteContext { + vector