This repository contains the algorithmic implementation and computational analysis for the Crowdshipping Vehicle Routing Problem integrated with public transportation networks.
The project aims to replicate and analyze findings from the literature by comparing an exact approach based on Mixed-Integer Linear Programming (MILP) against a meta-heuristic approach based on Adaptive Large Neighborhood Search (ALNS). Three MILP variants are proposed: (1) base model, (2) with static valid inequalities, (3) with dynamically generated cuts via callbacks. The study evaluates scalability, solution quality, and computational efficiency.
Reference Paper: Optimizing last-mile delivery through crowdshipping on public transportation networks (Gajda et al., Transportation Research Part C, 2025)
Test the entire pipeline on a small instance:
# 1. Install dependencies (one-time)
pip install -r requirements.txt
# 2. Run complete test (MILP + ALNS comparison)
python scripts/run_small_test.pyExpected: Solutions from 3 MILP variants and ALNS in few minutes.
What you'll see:
===========================================
MILP - Simple: Obj, Gap, Time (s)
MILP - Cuts: Obj, Gap, Time (s)
MILP - Callback: Obj, Gap, Time (s)
ALNS Heuristic: Obj, Gap, Time (s)
===========================================
The heuristic is within x% of the MILP optimum.
For scalability analysis and detailed usage, continue reading below.
- System Specifications
- Mathematical Formulation
- Repository Structure
- Installation
- Usage
- Troubleshooting
- References
All computational experiments and benchmarks presented in this project were conducted using the following hardware and software configuration. This information is provided to ensure reproducibility of computational times.
- CPU: 12th Gen Intel(R) Core(TM) i5-1235U
- Architecture: x64-based processor
- Cores: 10 physical cores, 12 logical processors
- Instruction Set: SSE2, AVX, AVX2
- OS: Windows 11 (Build 26100.2)
- Solver: Gurobi Optimizer 12.0.3 (Build v12.0.3rc0)
- Python: 3.8+
- Threading: Experiments constrained to 1-2 threads for consistency
The problem is modeled on a global delivery graph
The objective is to minimize a generalized cost function
where:
-
$z_{k}$ : binary variable for crowdshipper$k$ employment -
$s_{ip}$ : binary variable for activation of source station$i$ for parcel$p$ -
$e_i$ : binary variable for activation of source station$i$ -
$q_p$ : binary variable for activation of backup service for parcel$p$
Subject to constraints covering:
- Flow Conservation: network continuity for crowdshippers
- Capacity Constraints: APL (Automated Parcel Locker) limits at stations
-
Time Windows: service must occur within
$[1, T]$ - Synchronization: alignment between crowdshippers' schedules and parcel delivery
├── analysis/ # Post-processing and plotting scripts
│ ├── section1_milp.py # Performance analysis of MILP variants
│ ├── section2_ALNSrobust.py# Robustness analysis of heuristic (multiple runs)
│ └── section3_comparison.py# Gap analysis (MILP vs ALNS)
├── instances/ # Benchmark datasets (JSON format)
├── instances_scalability/ # Instance classes organized by size
├── outputs/ # Solution files, logs, and plots
├── scripts/ # Main executable pipelines
│ ├── main_generation.py # Synthetic instance generator
│ ├── run_scalability_milp.py # MILP scalability benchmarks
│ ├── run_scalability_ALNS.py # ALNS scalability benchmarks
│ └── run_small_test.py # Quick test (all methods, small instance)
├── tuning/ # Hyperparameter tuning (work in progress)
└── src/ # Core source code
├── data_generation/ # Graph topology and demand generation
├── heuristic/ # ALNS implementation (Destroy/Repair operators)
└── milp_model/ # MILP models (Simple, Cuts, Callback)
- Python 3.8+
- Gurobi License (Free for academic use: gurobi.com/academia)
- Optimization:
gurobipy(Requires valid Gurobi license) - Data Manipulation:
pandas,numpy - Spatial & Graph:
shapely,networkx - Visualization:
matplotlib,seaborn
- Clone the repository:
git clone https://github.com/Ludo476/Math_Opt_project.git
cd Math_Opt_project- Install dependencies:
pip install -r requirements.txt- Verify Gurobi license:
gurobi_cl --licenseIf this fails, see Troubleshooting.
Purpose: Validate installation and compare all methods on a small instance.
python scripts/run_small_test.py- Input: Toy instance (N=36, K=55, P=35)
- Output: Console summary
- Time: ~few minutes
- What it does: Solves with MILP (3 variants) + ALNS, prints comparison table
Purpose: Create synthetic datasets with different topologies and sizes.
python scripts/main_generation.py- Output:
instances_scalability/custom/ - Time: ~few seconds (depending on how many classes of instances to generate)
- Customization: Edit
src/data_generation/data_generator_scalability.pyto change N, K, P, parameters
Purpose: Test exact methods on increasing instance sizes to identify computational limits.
python scripts/run_scalability_milp.py- Input:
instances_scalability/(pre-generated instances) - Output:
outputs/scalability_milp/results.csv+ logs - Time: this can take a lot of time (depending on how many classes, families and instances you want to test)
- What it does:
- Solves instances with N=24, 36, 44, K=75→300
- Tests 3 MILP variants (Simple, Cuts, Callback)
- Logs gap, time, nodes explored for each
Purpose: Test heuristic on the same instances where MILP found a solution.
python scripts/run_scalability_ALNS.py- Input:
instances_scalability/ - Output:
outputs/scalability_alns/results.csv+ convergence plots - Time: depends on the number of instances tested (you can choose ALNS time limit)
- What it does:
- Runs ALNS with time_limit=600s per instance
- Multiple runs (5) for robustness analysis
- Saves best solutions + convergence history
Purpose: Create figures for presentation comparing methods.
# Performance comparison (MILP variants)
python analysis/section1_milp.py
# ALNS robustness (solution quality distribution)
python analysis/section2_ALNSrobust.py
# Gap analysis (MILP vs ALNS)
python analysis/section3_comparison.py- Output:
outputs/plots/(PNG/PDF) - Figures generated:
- Computational time vs instance size
- Optimality gap trends
- Solution quality plots
- Convergence curves
Example from outputs/scalability_milp/results.csv:
| N | K | P | Model | Gap | Time(s) | Obj | Nodes |
|---|---|---|---|---|---|---|---|
| 24 | 75 | 50 | Simple | 0.0% | 108 | 245.3 | 31 |
| 24 | 75 | 50 | Cuts | 0.0% | 52 | 245.3 | 3 |
| 24 | 75 | 50 | Callback | 0.0% | 44 | 245.3 | 1 |
| 36 | 100 | 75 | Simple | 15.2% | 3600 | 512.4 | 892 |
| 36 | 100 | 75 | Cuts | 8.3% | 3600 | 489.7 | 234 |
| ... | ... | ... | ... | ... | ... | ... | ... |
gurobipy.GurobiError: No Gurobi license found
Solution:
- Check license:
gurobi_cl --license - For academic use, register at gurobi.com/academia
- Install license file to:
- Linux/Mac:
~/gurobi.lic - Windows:
%USERPROFILE%\gurobi.lic
- Linux/Mac:
pip install gurobipyExpected behavior: Instances with K≥200 may not solve to optimality in 3600s.
Solutions:
- Reduce time limit in config:
# In scripts/run_scalability_milp.py, line 42:
model.setParam('TimeLimit', 1800) # Reduce from 3600s- Use ALNS for large instances instead
- Increase threads (may violate reproducibility):
model.setParam('Threads', 4)Possible causes:
- Too short time_limit: Increase to 1000-2000s for K≥200
- Bad initial solution: Check CIH quality
- Suboptimal hyperparameters: Run
tuning/tune_alns.py(experimental)
Quick fix:
# In scripts/run_scalability_ALNS.py, line 67:
solver.run(time_limit=600) # Increase from 3000s- Install dependencies (
pip install -r requirements.txt) - Verify Gurobi license (
gurobi_cl --license) - Run small test (
python scripts/run_small_test.py) - Run scalability benchmarks
- (Optional) Generate analysis plots
Estimated total time:
- Quick test: 15 minutes
- Full scalability analysis: many hours
@article{gajda2025crowdshipping,
title={Optimizing last-mile delivery through crowdshipping on public transportation networks},
author={Gajda, Mikele and Gallay, Olivier and Mansini, Renata and Ranza, Filippo},
journal={Transportation Research Part C: Emerging Technologies},
volume={179},
pages={105250},
year={2025},
publisher={Elsevier}
}