This document provides detailed documentation for each metaheuristic algorithm implemented in the TOPTW package.
- Genetic Algorithm (GA)
- Simulated Annealing (SA)
- Tabu Search
- GRASP
- Ant Colony Optimization (ACO)
- Particle Swarm Optimization (PSO)
Genetic Algorithms are population-based evolutionary algorithms inspired by natural selection. They maintain a population of solutions that evolve over generations through selection, crossover, and mutation operators.
Why suitable for TOPTW: GAs can explore diverse regions of the solution space simultaneously through their population-based approach, and the crossover operator allows combining good partial solutions (routes) from different parents.
solution = [[tour_1], [tour_2], ..., [tour_v]]
Each solution is a list of v tours (one per vehicle), where each tour is a list of Node objects representing customers in visitation order.
- Method: Random greedy construction
- Process: For each tour, randomly select feasible customers until no more can be added
- Population size: 200 individuals (configurable)
- Method: Random selection (no fitness bias)
- Process: Two parents selected uniformly at random from population
Tour-level exchange crossover:
Input: Parent solutions P1 and P2
Process:
1. Select random tour index i from P1
2. Select random tour index j from P2
3. Swap P1[i] with P2[j]
4. Repair both solutions (remove duplicate customers)
Output: Two offspring solutions
1. Insertion Mutation (80% probability)
Input: Solution S, tour index i
Process:
1. Find all unvisited customers
2. For random positions in tour:
- Try inserting each unvisited customer
- Accept first feasible insertion
Output: Modified solution
2. Removal Mutation (20% probability)
Input: Solution S, tour index i
Process:
1. If tour has > 2 customers:
- Remove random customer from tour
Output: Modified solution
fitness(solution) = Σ (score of all customers in feasible tours)Only tours that satisfy all time window and time budget constraints contribute to fitness.
Elitist with random component:
- Combine parents and offspring (pool of ~400 solutions)
- Sort by fitness (descending)
- Select: 70% best solutions + 30% random from remainder
- Result: Next generation of 200 individuals
- Maximum generations: 500 (default)
- Time limit: Optional
| Parameter | Default | Description |
|---|---|---|
population_size |
200 | Number of individuals in population |
generations |
500 | Number of evolution iterations |
num_threads |
100 | Parallel threads for offspring generation |
elite_ratio |
0.7 | Fraction of best individuals always kept |
Algorithm: Genetic Algorithm for TOPTW
Input: Problem instance, parameters
Output: Best solution found
population ← GenerateRandomPopulation(population_size)
for generation = 1 to max_generations:
offspring ← []
parallel for i = 1 to num_threads:
parent1 ← RandomSelect(population)
parent2 ← RandomSelect(population)
child1, child2 ← Crossover(parent1, parent2)
child1 ← Mutate(child1)
child2 ← Mutate(child2)
offspring.add(child1, child2)
pool ← population ∪ offspring
Sort(pool, by=fitness, descending)
elite ← pool[0:elite_count]
random_selection ← RandomChoice(pool[elite_count:], k=random_count)
population ← elite ∪ random_selection
return BestSolution(population)
Simulated Annealing is a probabilistic local search algorithm inspired by the annealing process in metallurgy. It accepts worse solutions with a probability that decreases over time, allowing escape from local optima.
Why suitable for TOPTW: SA can escape local optima through its probabilistic acceptance, which is important for complex routing problems with many constraints.
Note: This algorithm is implemented in C++ for performance.
Three neighborhood operators are applied in sequence:
1. Swap Within Tour (swap_in_tour)
- Select random tour
- Swap two random positions within the tour
- Accept if feasible, retry up to 2 times
2. Swap Between Tours (swap_between_two_tour)
- Select two random tours
- Swap one node between them
- Accept if both tours remain feasible
- Retry up to 6 times
3. Local Search Insertion (local_search_insertion)
- For each position in each tour:
- Try inserting unvisited customers
- Accept first feasible insertion
- Method: Random construction
- Randomly select feasible customers until no more fit
Geometric cooling:
T_new = α × T_current
| Parameter | Value |
|---|---|
| Initial temperature (T₀) | 150 |
| Cooling rate (α) | 0.5 |
| Iterations before cooling | 1000 |
| Moves per iteration | 30 |
Metropolis criterion:
If new_score > current_score:
Accept (always)
Else:
Δ = current_score - new_score
p = exp(Δ / T)
Accept with probability p
| Parameter | Default | Description |
|---|---|---|
num_runs |
5 | Independent restarts |
time_limit_per_run |
60s | Time limit per run |
| Initial temperature | 150 | Fixed in C++ |
| Cooling rate | 0.5 | Fixed in C++ |
Algorithm: Simulated Annealing for TOPTW
Input: Problem instance
Output: Best solution found
for run = 1 to num_runs:
T ← 150
α ← 0.5
solution ← RandomInitialSolution()
best ← solution
while T > 0 and time < time_limit:
for j = 1 to 30:
candidate ← SwapInTour(solution)
candidate ← SwapBetweenTours(candidate)
candidate ← LocalSearchInsertion(candidate)
if Score(candidate) > Score(solution):
solution ← candidate
best ← candidate if Score(candidate) > Score(best)
else:
Δ ← Score(best) - Score(candidate)
if random() ≤ exp(Δ/T):
solution ← candidate
T ← α × T
return best
Tabu Search is a memory-based local search algorithm that maintains a list of recently visited solutions or moves to prevent cycling and promote exploration.
Why suitable for TOPTW: The tabu list prevents revisiting recent configurations, forcing exploration of new regions of the solution space.
Note: This algorithm is implemented in C++ for performance.
- What's stored: Route indices (not specific moves or solutions)
- Structure: Array of counters, one per route
- Mechanism: When a route is modified, its counter is set to
tabu_tenure; counters decrement each iteration
- Value: Equal to number of vehicles (team_number)
- Type: Fixed (not adaptive)
Not implemented: Tabu moves are never accepted, even if they would improve the best solution.
Same three operators as SA:
- Swap within tour (checks tabu status)
- Swap between tours (checks tabu status)
- Local search insertion (always available)
Not explicitly implemented: Relies on tabu list alone for diversification.
| Parameter | Default | Description |
|---|---|---|
num_runs |
5 | Independent restarts |
max_iterations |
5000 | Iterations per run |
| Tabu tenure | num_vehicles | Fixed in C++ |
| Swap attempts | 100 | Max attempts per operator |
Algorithm: Tabu Search for TOPTW
Input: Problem instance
Output: Best solution found
for run = 1 to num_runs:
solution ← RandomInitialSolution()
best ← solution
tabu_list ← [0, 0, ..., 0] // One per route
for iteration = 1 to max_iterations:
// Apply operators (skip if tabu)
if not IsTabu(tour_index):
candidate ← SwapInTour(solution, tour_index)
SetTabu(tour_index, tabu_tenure)
candidate ← SwapBetweenTours(candidate)
candidate ← LocalSearchInsertion(candidate)
if Score(candidate) > Score(best):
solution ← candidate
best ← candidate
// Decay tabu counters
for i in range(num_routes):
if tabu_list[i] > 0:
tabu_list[i] -= 1
return best
GRASP (Greedy Randomized Adaptive Search Procedure) is a multi-start metaheuristic that combines greedy construction with local search. Each iteration constructs a solution using a randomized greedy approach, then improves it with local search.
Why suitable for TOPTW: The greedy construction respects time windows naturally, and the randomization ensures diverse starting points for local search.
Note: This algorithm is implemented in C++ for performance.
Greedy function:
ratio(candidate) = score² / distance_to_current
Restricted Candidate List (RCL):
- Size: 7 candidates
- Selection: Random from top 7 by greedy ratio
Construction Process:
For each vehicle:
current ← depot
tour ← []
while candidates exist:
Sort candidates by greedy ratio
Select random from top 7
If feasible: add to tour, update current
Else: retry (up to 6 times)
Insertion improvement (3 iterations):
For each tour position:
For each unvisited customer:
If insertion is feasible:
Insert customer
Break
| Parameter | Default | Description |
|---|---|---|
num_runs |
5 | Independent runs |
max_iterations |
100,000 | Constructions per run |
time_limit_per_run |
30s | Time limit per run |
| RCL size | 7 | Fixed in C++ |
| Local search iterations | 3 | Fixed in C++ |
Algorithm: GRASP for TOPTW
Input: Problem instance
Output: Best solution found
for run = 1 to num_runs:
best ← null
best_score ← 0
while time < time_limit and iterations < max_iterations:
// Construction phase
solution ← GreedyRandomizedConstruction()
// Local search phase (3 iterations)
for i = 1 to 3:
solution ← InsertionLocalSearch(solution)
if Score(solution) > best_score:
best ← solution
best_score ← Score(solution)
return best
ACO is a swarm intelligence algorithm inspired by ant foraging behavior. Artificial ants construct solutions probabilistically using pheromone trails that are updated based on solution quality.
Why suitable for TOPTW: The pheromone mechanism can learn which edges (customer pairs) are frequently used in good solutions, guiding future constructions.
- Structure: 2D matrix τ[i][j] for each pair of nodes
- Initial value: τ₀ = 1.0
- Updates: During construction and after evaporation
η(i, j) = score_j / (max(distance, time_gap) × time_window_duration + 1)Where:
time_gap= open_time_j - current_time - service_time_itime_window_duration= close_time_j - current_time - service_time_i - distance
Selection probability:
p(i, j) = (η(i,j) × τ[i][j]) / Σₖ(η(i,k) × τ[i][k])
Process:
- Find all feasible next customers
- Calculate η×τ for each
- Select customer with maximum probability (or random if all zero)
- Update pheromone on selected edge
During construction:
τ[i][j] ← τ[i][j] + ψ × τ₀
After each iteration (evaporation):
τ[i][j] ← (1 - ρ) × τ[i][j] for all i, j
Same insertion improvement as GRASP, applied twice per iteration.
| Parameter | Default | Description |
|---|---|---|
rho (ρ) |
0.1 | Evaporation rate |
psi (ψ) |
0.1 | Pheromone deposit rate |
tau_zero (τ₀) |
1.0 | Initial pheromone value |
time_limit_per_run |
180s | Time limit per run |
Algorithm: Ant Colony Optimization for TOPTW
Input: Problem instance
Output: Best solution found
τ ← Initialize(n × n matrix, τ₀)
best ← null
best_score ← 0
while time < time_limit:
// Construct solution
solution ← []
for vehicle = 1 to v:
tour ← ConstructTour(τ, η)
solution.add(tour)
// Local search (twice)
solution ← InsertionLocalSearch(solution)
solution ← InsertionLocalSearch(solution)
if Score(solution) > best_score:
best ← solution
best_score ← Score(solution)
// Evaporation
τ ← (1 - ρ) × τ
return best
PSO is a swarm intelligence algorithm where particles (solutions) move through the solution space influenced by their personal best position and the global best position of the swarm.
Why suitable for TOPTW: PSO balances exploration and exploitation through the interaction between personal experience and swarm knowledge.
- Particles: 100 complete solutions
- Personal best (p_best): Best solution found by each particle
- Global best (g_best): Best solution found by any particle
Mutation operators applied to each particle:
- Swap mutation: Swap two nodes within a tour (if feasible)
- Insert mutation (80%): Insert unvisited customer
- Remove mutation (20%): Remove random customer
Position update via tour exchange:
-
Exchange with p_best (10% of tours):
- Swap random tours between current and personal best
- Repair duplicates
-
Exchange with g_best (30% of tours):
- Swap random tours between current and global best
- Repair duplicates
| Parameter | Default | Description |
|---|---|---|
population_size |
100 | Number of particles |
max_iterations |
5000 | Iterations per run |
p_best_weight |
10 | % tours exchanged with p_best |
g_best_weight |
30 | % tours exchanged with g_best |
num_runs |
3 | Independent runs |
Algorithm: Particle Swarm Optimization for TOPTW
Input: Problem instance
Output: Best solution found
for run = 1 to num_runs:
population ← RandomInitialize(population_size)
p_best ← copy(population)
g_best ← BestOf(population)
for iteration = 1 to max_iterations:
for each particle j:
// Apply mutations
particle[j] ← MutationSwap(particle[j])
particle[j] ← MutationInsertOrRemove(particle[j])
// Exchange with personal best
particle[j] ← ExchangeTours(particle[j], p_best[j], p_best_weight)
// Exchange with global best
particle[j] ← ExchangeTours(particle[j], g_best, g_best_weight)
// Update bests
if Score(particle[j]) > Score(p_best[j]):
p_best[j] ← particle[j]
if Score(particle[j]) > Score(g_best):
g_best ← particle[j]
return g_best
| Feature | GA | SA | Tabu | GRASP | ACO | PSO |
|---|---|---|---|---|---|---|
| Type | Population | Single solution | Single solution | Multi-start | Population | Population |
| Memory | No | No | Tabu list | No | Pheromone | Personal/Global best |
| Language | Python | C++ | C++ | C++ | Python | Python |
| Parallelization | Yes (threads) | No | No | No | No | No |
| Escape mechanism | Crossover | Accept worse | Tabu restriction | Restart | Pheromone diversity | Swarm influence |
For quick results: GRASP or SA (fast construction/iteration)
For best quality (with more time): GA or ACO
For balance: Tabu Search or PSO
For large instances: C++ implementations (SA, Tabu, GRASP) are faster