Performance Summary

Baseline Cost (No Optimisation)
Direct point-to-point routing
Optimised Cost (OR-Tools CVRP)
Buses Deployed
CO₂ Emissions Saved
kg CO₂ saved this shift @ 0.67 kg/km diesel

Route Visualisation Loading...

Real road-traced routes via OSRM. The depot is shown in orange. Passenger stops are shown in blue. Each coloured line represents a unique bus route.

Depot Stops Optimised Routes

Optimisation Methodology

This system solves a real-world logistics problem using classical Operations Research, powered by Google OR-Tools. Below is a full walkthrough of the model formulation, solver technique, and constraints applied.

Step 1

Problem Formulation — Capacitated Vehicle Routing Problem (CVRP)

The transport scheduling problem is modelled as a Capacitated Vehicle Routing Problem (CVRP) — one of the most well-studied problems in combinatorial optimisation and Operations Research.

🏭
Single Depot
All buses originate from and return to the MAS Controline Pallekele factory. This is the depot node in the graph.
📍
99 Passenger Stops
Each stop has a fixed passenger demand (workers to collect or drop). The solver must decide which bus visits which stops.
🚌
Homogeneous Fleet
All buses are identical — same seat capacity. The solver allocates the minimum number of buses needed to cover all stops.
🎯
Objective Function
Minimise the total distance (km) travelled by the entire fleet across all routes combined for a single shift.
Minimise: kij dij · xijk where dij = real road distance between stop i and j, xijk = 1 if bus k travels from i to j
Step 2

Real Road Distance Matrix via OSRM

Straight-line (Haversine) distances are fundamentally inaccurate for Sri Lanka's mountainous terrain. The Kandy district has steep hills and winding roads where a 5 km straight line can be a 15 km road journey.

🗺️
OSRM Table API
The Open Source Routing Machine (OSRM) is called with all 100 coordinates (1 depot + 99 stops) to produce a 100×100 pairwise road distance matrix in a single API call.
🛣️
OpenStreetMap Data
OSRM uses real OpenStreetMap road network data — actual driving routes, road types, and speeds. All distances are in kilometres.
LRU Caching
The distance matrix is cached in memory. Re-running the optimiser with different fleet/capacity/cost parameters reuses the same matrix — no repeated API calls.
🔍
Pre-filtering
Before solving, any stop whose road distance from the depot exceeds the user-set one-way limit is excluded before entering the solver — reducing problem size.
Step 3

Constraints Applied to the Model

Three hard constraints are enforced. No solution that violates any of these is accepted by the solver.

C1
Seat Capacity Constraint

The total passenger demand assigned to any single bus cannot exceed its seat capacity. Formally: ∑ demand(i) ≤ capacity for all stops i on that bus's route. This is enforced using OR-Tools' AddDimensionWithVehicleCapacity.

C2
Maximum Route Distance Constraint

Each bus route has a total distance cap of 3× the one-way limit. This models a real-world commute time ceiling — workers should not be on a bus for an unreasonable duration. Enforced via OR-Tools' Distance Dimension.

C3
Fleet Size Constraint

The number of active routes cannot exceed the user-specified fleet size. Rather than silently adding more buses, the solver uses AddDisjunction to make stops optional with a high penalty — dropping the least-accessible stops when the fleet is insufficient instead of exceeding the limit.

Step 4

Solver Engine — Google OR-Tools Routing API

The CVRP is NP-Hard — meaning an exact optimal solution for 99 stops is computationally infeasible in real time. OR-Tools uses a two-phase heuristic + metaheuristic strategy to find a near-optimal solution within a fixed time limit.

Phase 1
Constructive Heuristic — Path Cheapest Arc

Builds an initial feasible solution by always extending the current partial route to the nearest unvisited stop. Fast and greedy — guarantees a valid starting point but not optimality. Acts as the seed for Phase 2.

Phase 2
Metaheuristic — Guided Local Search (GLS)

Iteratively improves the Phase 1 solution by exploring neighbouring solutions (swap stops between routes, re-order within routes). GLS uses penalty terms to escape local optima — it penalises frequently visited solution features to force exploration of new areas of the search space. Runs for 4 seconds.

💡 Why GLS over simpler local search? Simple hill-climbing gets trapped in local optima (a good solution that cannot be improved by small changes, but is not globally optimal). GLS's penalty mechanism forces the solver to explore beyond local optima, consistently producing better final solutions.
Step 5

Baseline Comparison — Measuring the Saving

To quantify how much the optimiser saves, we compare against a naive baseline: sending one dedicated bus directly from the depot to each stop and back — no shared routes, no grouping. This is the worst-case scenario that represents an unoptimised operation.

📏
Baseline Distance
Sum of all individual round trips: ∑ (depot→stop + stop→depot) for every active stop. Uses real OSRM road distances.
Optimised Distance
Total kilometres driven by the entire fleet under the CVRP solution — buses share routes and collect multiple stops in one trip.
💰
Financial Saving
(Baseline − Optimised) × Operating Cost/km. Reported in LKR per shift. Directly represents daily fuel and operational savings.
🌱
CO₂ Saving
Saved km × 0.67 kg CO₂/km (standard diesel bus emission factor). Quantifies the environmental benefit of route consolidation.

Synthetic Data Generation Pipeline

Since real employee home-address data is confidential, this system generates a realistic synthetic dataset that mirrors actual factory transport patterns. The pipeline has four stages.

Stage 1

Coordinate Generation — Random Sampling Within Radius

Random geographic coordinates are generated uniformly within a user-configurable recruitment radius (default 40 km) around the MAS Controline Pallekele factory (7.2842°N, 80.7061°E). A bounding box is sampled first, then a Haversine distance filter discards points outside the circular radius — ensuring all stops are realistically reachable from the factory.

📐 Why uniform distribution? Workers in a real factory live spread across the surrounding area without a strong geographic bias. A uniform distribution across the radius approximates this spread. A pool of 5× the required stops is generated to ensure enough valid candidates survive the road-snapping step.
Stage 2

Road Snapping — OSRM Nearest API (Parallel)

Raw random coordinates often land in fields, rivers, or buildings — locations that cannot be reached by road. Each coordinate is submitted to the OSRM Nearest API, which snaps it to the closest drivable road point on the OpenStreetMap network.

🔀
40 Parallel Threads
All road-snap requests are fired simultaneously using Python's ThreadPoolExecutor. This reduces dataset generation from ~2 minutes (sequential) to ~8–12 seconds.
📌
100% Routable Stops
Only road-snapped coordinates are accepted. Any snapped point that exceeds the radius limit is discarded. Fallback coordinates near the depot pad the dataset if too few candidates survive.
🗺️
OpenStreetMap Network
OSRM uses the Sri Lanka road network from OSM — including A-grade, B-grade, and local roads in the Kandy district. All snapped stops are reachable by standard bus.
🏷️
Area Name Labelling
Each accepted stop is assigned a real Kandy district area name (99 names pre-loaded) for realistic readability, shuffled randomly using the seed.
Stage 3

Demand Generation — Shift-Consistent Passenger Counts

For each stop, passenger demand is generated independently for two worker groups — morning shift and afternoon shift — using a Discrete Uniform Distribution U[5, 25]. The drop demands are then derived (not independently randomised) to enforce real-world logical consistency.

G1
Morning Shift Group (randomly generated)

Demand_10AM_Collect ~ U[5, 25] — buses collect these workers from home at 10 AM.
Demand_2PM_Drop = Demand_10AM_Collect — the same workers are dropped home at 2 PM. No independent value is generated.

G2
Afternoon Shift Group (randomly generated)

Demand_2PM_Collect ~ U[5, 25] — buses collect these workers from home at 2 PM.
Demand_10PM_Drop = Demand_2PM_Collect — the same workers are dropped home at 10 PM. No independent value is generated.

🎲 Seed behaviour: The first page load uses seed 42 (reproducible, consistent demo). Each click of "Regenerate Dataset" uses a time-based seed — producing genuinely different passenger distributions to simulate day-to-day variation in attendance.
Stage 4

Shift Structure — Four Optimisable Transport Events per Day

10:00 AM
COLLECT ↑
Morning shift workers picked up from home stops and transported to factory
2:00 PM
DROP ↓
Same morning shift workers dropped back to their home stops from factory
2:00 PM
COLLECT ↑
Afternoon shift workers picked up from home stops and transported to factory
10:00 PM
DROP ↓
Same afternoon shift workers dropped back to their home stops from factory
⚙️ Each of the four shift events is independently optimisable — select any shift in the sidebar to run the CVRP solver for that specific transport event. Route plans, bus deployments, and cost savings are computed per-shift.

Passenger Dataset

All 99 destination stops within the selected radius with simulated daily passenger demand per shift.

Loading dataset...