Inspiration
The logistics industry wastes billions annually on inefficient delivery routing and truck packing. A single delivery vehicle's utilization can drop 40% due to poor item placement and unloading accessibility. DAMM, Spain's leading beer distributor, faces this daily: 500+ stops per day across Barcelona, complex time windows, three vehicle types with different loading rules, and barrels that must respect weight distribution.
We wanted to solve the Vehicle Routing Problem + 3D bin packing simultaneously, not sequentially—because routing affects packing feasibility, and packing affects route cost. Simulated Annealing offered the right balance: practical convergence speed for a hackathon timeline, enough randomization to escape local optima, and elegant modularity.
What it does
DAMM-LIVERING is a two-phase logistics optimizer:
SA-Route: Solves vehicle routing with real distance/time matrices (OSRM), respects delivery time windows, balances load across three vehicle types (van ≤3 palets, truck-6 4–6 palets, truck-8 ≥7 palets).
SA-Distribution: Packs items into 3D truck layouts (6 floors × 5 rows × 2 columns per pallet), optimizing:
- Fragmentation: Keep products and customers cohesive → faster unloading
- Accessibility: Respect unloading order—don't block early stops with late deliveries
- Gravity & structure: Barrels above boxes forbidden, floors/pallets must fill sequentially
Real-time visualization: Interactive React frontend showing routes on OpenStreetMap, 3D pallet layouts with delivery timeline slider, KPI dashboards (distance, cost, utilization, service gaps).
Input: order CSVs + time windows + distance matrices.
Output: optimized routes per vehicle + 3D packing + delivery schedules.
How we built it
Stack: C++17 (optimization engine), Python 3.10+ (data pipeline), React + Vite + TypeScript (frontend).
// Two-phase SA template (codi/algo/sa/sa.cc)
template <typename Estado>
Estado simulated_annealing(
Estado inicial,
function<double(const Estado&)> costo, // fitness function
function<Estado(const Estado&, mt19937&)> vecino, // neighbor operator
ParametrosSA params,
mt19937& rng
) {
double T = params.T_init;
while (T > params.T_min) {
for (int i = 0; i < params.iter_por_temp; ++i) {
Estado candidato = vecino(actual, rng);
if (costo(candidato) < costo(actual) ||
uniform_real(0,1) < exp(-delta / T)) {
actual = candidato;
}
}
T *= params.cooling_rate;
}
return mejor;
}
Pipeline:
cargar_csv.py: Merge order CSVs + time windows + distance matrices → JSON problem definitiondamm --from-json: SA-Route optimizer (C++17)items_por_camion.py: Expand items per truck (barrels → 4 box-equivalents each)damm-dist: SA-Distribution (runs in parallel per truck)orquestador.py: Merge results, compute delivery schedules, generate final JSON- Frontend: Load JSON, render routes + 3D packing + KPIs
Key optimization heuristics:
double fitness(const Solucion& s, const vector<uint16_t>& orden_ruta) {
// F1: Product fragmentation (each material in one pallet-floor)
// F2: Customer fragmentation (each customer in one pallet-floor)
// F3: Accessibility penalties (unloading order, barrels above boxes)
return w1 * fragmentacion_producto(s)
+ w2 * fragmentacion_cliente(s)
+ w3 * accesibilidad(s, orden_ruta);
}
Data structures: Custom 4D layout for pallet state, incremental slot tracking (ocupados/vacios vectors), hash maps for product/customer indexing to avoid O(n) grid scans.
Challenges we ran into
Hard constraints vs soft penalties: Early SA accepted invalid solutions (floating items, incomplete floors). We pivoted to rejecting invalid neighbors—game-changer. Neighbor generation now verifies
es_valida()before accepting.Slot calculation bottleneck:
vecino_swapandvecino_moverscanned entire grid (480 cells) every call. With 100 retry attempts per iteration, this was killing performance. Solution: maintainvector<Pos> ocupados/vaciosincrementally in the state—drops scan cost to O(1).Vehicle type complexity: Three vehicle types (furgoneta/truck-6/truck-8) have different lateral access rules. Furgoneta accesses all pallets from back (row 4); even pallets access from front (row 0), odd from back. Camión 8 has full access to pallets 0–1. Required careful geometry modeling and state-specific validation logic.
Barrels as 4-box-equivalent items: Barrels are heavier but occupy single slots. We expand them at pipeline level (1 barrel → 4 identical items in
items_por_camion.py), keeping SA-distribution model simple while respecting weight constraints.Time window feasibility: Route quality depends on realistic time estimates. We integrated real OSRM matrices (meters, seconds) and computed arrival times with slack for delivery duration. Tight windows (<2h) sometimes become infeasible—flagged visually in frontend.
Accomplishments that we're proud of
Two-phase architecture: Routes and packing optimized together. Routes influence packing density; packing accessibility influences route viability. Clean separation without losing coupling.
Real-world data integration: OSRM distance/time matrices, Barcelona map coordinates, actual Damm depot location, real order data from test CSVs. Not synthetic benchmarks—production-ready data pipeline.
Hard constraints with high-performance neighbors: Validity-preserving operators (
vecino_swapalways preserves layout structure;vecino_moverwith retry loop) + incremental slot tracking = 100x faster neighbor generation than naive scanning.3D interactive visualization: React frontend with Leaflet route maps + Three.js 3D pallet viewer with delivery timeline slider. Users can inspect exact item placement, unloading order, and predicted schedules per stop.
Multi-vehicle fleet modeling: Supports three vehicle types with distinct geometry and access rules. Fleet composition configurable (vans, truck-6, truck-8 counts).
Experimental framework (
codi/experimentos.cc): 7 parameterized experiments (operator comparison, initialization strategies, grid-search SA tuning, scalability) generating CSV data. Python analysis script produces comparative plots.
What we learned
State representation matters: Compact state (4D layout + maps) is far better than flattened. Incremental indexing (por_material, por_cliente) is essential for fast heuristic evaluation.
Validity constraints are hard constraints: Soft penalty functions allowed SA to accept invalid solutions transiently. Hard rejection (immediate discarding) + retry-based neighbor generation is simpler and more robust.
Simulated Annealing's simplicity is deceptive: No complex data structures, no branch-and-bound, no LP relaxations—just random walks with temperature decay. Yet with good neighborhood operators and validity preservation, it converges well to practical solutions.
Two-phase decomposition has limits: Routes and packing interact. A slightly longer route might pack 20% more efficiently. True joint optimization would require a unified state (routes + full 3D) and massive state space. Phase separation is pragmatic.
Visualization changes perception: Seeing items physically stacked reveals why accessibility matters. A 50-point reduction in fitness looks good numerically; seeing barrels placed carelessly on boxes makes it visceral.
What's next for DAMM-LIVERING
Real-time re-optimization: Traffic incidents, order cancellations, new urgent orders → re-solve only affected vehicles dynamically. Currently offline; integrating live vehicle telemetry and ETA feedback.
Joint optimization: Unified SA state with routes + packing. State space explosion, but modern machines + parallel search might make it feasible.
Multi-objective Pareto: Not just minimize cost; explore trade-offs between distance, time windows compliance, and sustainability (CO2 per delivery, cold-chain violations).
Driver behavior modeling: Account for real driver preferences (avoid tight alleys, prefer main roads). Currently assumes neutral routing.
International expansion: Generalize from Barcelona to Spanish national routes + EU logistics. Integrate local constraints (truck size regulations, congestion zones, driver rest hours).
ML warm-start: Train neural network to predict good SA initial solutions faster than greedy construction.
Live dashboard for dispatch: Real-time vehicle tracking, dynamic priority updates, one-click what-if simulations ("what if we add this rush order?").
Code repo: github.com/ROCU08/Damm-boicot-al-euro
Try it: make pipeline JUEGO=test/juego_lunes.csv OUT_DIR=out/ && cd frontend && npm run dev
Log in or sign up for Devpost to join the conversation.