Decoders and comparison tools for the Fixed Permutation Splitting and Charging Problem(FPSCP) and Fixed Route Vehicle Charging Problem(FRVCP).
- C++ implementations for EVRP instances, solutions, validation, route costs, linear cargo splitting, battery decoding, and combined optimal decoding.
- Python bindings packaged as
evrp_decoder._evrp_core. - Loader utilities for
.evrpbenchmark files. - Adapters for external/reference decoders:
- BHGA focus enumeration
- BACO and CACO from CEVRP
frvcpyfixed-route charging station insertion
- Scripts for generating customer permutations and collecting decoder comparison data.
- Notebooks for data exploration, front-size analysis, and decoder comparison.
.
|-- evrp_decoder/ Python package, loaders, generators, frvcpy adapter
|-- src/evrp/ C++ EVRP core and decoders
|-- src/bindings/ pybind11 module definition
|-- scripts/ Experiment data-generation scripts
|-- notebooks/ Analysis notebooks
|-- tests/ pytest suite
|-- extern/ Git submodules used by comparison decoders
|-- CMakeLists.txt CMake/scikit-build configuration
`-- pyproject.toml Python package metadata and tooling
- Python 3.10+
- CMake 3.20+
- A C++ compiler with C++23 support
uvor another Python environment manager- Git submodules initialized under
extern/
The project is built through scikit-build-core, so installing the Python
package also compiles the C++ extension.
Clone with submodules, or initialize them after cloning:
git submodule update --init --recursiveCreate the environment and install the package. This compiles the C++ extension through scikit-build:
uv sync --devTests and scripts expect EVRP benchmark files in a top-level dataset/
directory:
dataset/
|-- E-n22-k4.evrp
|-- ...
`-- X-n1001-k43.evrp
The dataset directory is not created by the setup commands; add the benchmark files before running the full test suite or experiment scripts.
The loader accepts any directory containing .evrp files:
from evrp_decoder.loader import load_all, load_evrp
inst = load_evrp("dataset/E-n22-k4.evrp")
instances = load_all("dataset")from evrp_decoder._evrp_core import OptimalDecode, Solution, linear_split
from evrp_decoder.loader import load_evrp
inst = load_evrp("dataset/E-n22-k4.evrp")
# Customer IDs are 1..inst.customer_cnt(); node 0 is the depot.
customers = list(range(1, inst.customer_cnt() + 1))
# Combined decoder inserts depots and charging stations while preserving
# customer order.
decoder = OptimalDecode(inst)
decoded = decoder.decode(Solution(inst, customers), upper_bound=1e9)
print(decoded.routes)
print(decoded.cost())
print(decoded.is_valid())Solution routes are flat node-ID lists. Depot visits are represented by
0, customers by 1..customer_cnt, and charging stations by
customer_cnt + 1..node_cnt - 1.
| Decoder | Notes |
|---|---|
linear_split |
Adds depot separators to satisfy cargo capacity. |
OptimalDecode |
Combined cargo and battery decoder. |
BatteryDecode |
Inserts charging stations for fixed split routes. |
BhgaDecode |
Wraps the BHGA focus-enumeration implementation. |
BacoDecode |
Wraps the BACO implementation from CEVRP. |
CacoDecode |
Wraps the CACO implementation from CEVRP. |
FrvcpyDecode |
Wraps frvcpy. |
For two-phase decoders, call linear_split first and pass split.routes to
the charging-station decoder.
uv run task testor:
uv run pytest tests/ -vThe full suite requires:
- initialized submodules in
extern/ - the compiled Python extension
- a populated
dataset/directory
Generate random and k-nearest-neighbour customer permutations:
uv run python scripts/generate_permutations.py \
--dataset-dir dataset \
--output-dir .output \
--n-permutations 100 \
--seed 42 \
--k 3Collect decoder comparison data:
uv run python scripts/collect_data.py \
--dataset-dir dataset \
--permutations .output/permutations_random.json \
--output .output/decoder_results.json \
--upper-bound 1000000000The notebooks/ directory includes exploratory and analysis notebooks:
data_exploration.ipynbfront_size_analysis.ipynbdecoder_comparison.ipynb
Start Jupyter with:
uv run jupyter lab- CMake builds
cevrp_libas a shared library with hidden symbols because the CEVRP and BHGA submodules both define incompatible globalCaseclasses. evrp_decoder.frvcpy_decodeimportsextern/frvcpydirectly, so thefrvcpysubmodule must be present even if it is not installed separately.upper_boundarguments are pruning bounds for decoder search. Use a large value such as1e9when you do not want pruning to reject candidate solutions.