Skip to content

Commit d871c8e

Browse files
committed
feat: add Vehicle Routing Problem (VRP) to advertisement video
Added 10th NP-hard problem to the cinematic showcase: - VRP: 20 customers, 3 vehicles, depot-based routing - Visualizes multi-vehicle routes with capacity constraints - Shows colorful route lines (red/green/blue for each vehicle) - Capacity bars showing load vs capacity - Real-time route distance optimization Now showcases 10 different NP-hard problems across: - Graph theory (coloring, clique, MIS) - Constraint satisfaction (N-Queens, 3-SAT) - Scheduling/Logistics (TSP, VRP, Bin Packing) - Resource allocation (Knapsack, Number Partition) Total: 33 seconds, 1980 frames @ 60 FPS
1 parent c173305 commit d871c8e

1 file changed

Lines changed: 155 additions & 1 deletion

File tree

visualizations/generate_advertisement.py

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,158 @@ def energy(state):
881881

882882
self.save_frame()
883883

884+
def scene_vrp(self, duration_sec=3):
885+
"""Scene 10: Vehicle Routing Problem"""
886+
print("🚚 Rendering: Vehicle Routing Problem...")
887+
888+
frames = int(FPS * duration_sec)
889+
890+
# Setup VRP: Depot + 20 customers, 3 vehicles
891+
n_customers = 20
892+
n_vehicles = 3
893+
depot = (WIDTH // 2, HEIGHT // 2)
894+
895+
# Customer locations
896+
customers = [
897+
(random.randint(200, WIDTH - 200), random.randint(150, HEIGHT - 150))
898+
for _ in range(n_customers)
899+
]
900+
demands = [random.randint(5, 20) for _ in range(n_customers)]
901+
vehicle_capacity = 50
902+
903+
# Assignment: which vehicle serves which customer
904+
state = [random.randint(0, n_vehicles - 1) for _ in range(n_customers)]
905+
906+
def route_distance(assignment):
907+
"""Calculate total distance for all routes"""
908+
total_dist = 0
909+
910+
for v in range(n_vehicles):
911+
# Get customers for this vehicle
912+
vehicle_customers = [
913+
customers[i] for i, a in enumerate(assignment) if a == v
914+
]
915+
vehicle_demand = sum(
916+
demands[i] for i, a in enumerate(assignment) if a == v
917+
)
918+
919+
# Check capacity
920+
if vehicle_demand > vehicle_capacity:
921+
total_dist += 10000 # Heavy penalty
922+
923+
# TSP-like route for this vehicle (simplified)
924+
if vehicle_customers:
925+
# Nearest neighbor approximation
926+
route = [depot]
927+
unvisited = vehicle_customers.copy()
928+
929+
while unvisited:
930+
last = route[-1]
931+
nearest = min(unvisited, key=lambda c: math.dist(last, c))
932+
total_dist += math.dist(last, nearest)
933+
route.append(nearest)
934+
unvisited.remove(nearest)
935+
936+
# Return to depot
937+
total_dist += math.dist(route[-1], depot)
938+
939+
return total_dist
940+
941+
current = route_distance(state)
942+
best = current
943+
step = 0
944+
945+
vehicle_colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255)]
946+
947+
for frame in range(frames):
948+
self.screen.fill(BG_COLOR)
949+
950+
# Optimize
951+
for _ in range(15):
952+
idx = random.randint(0, n_customers - 1)
953+
new = state.copy()
954+
new[idx] = random.randint(0, n_vehicles - 1)
955+
new_dist = route_distance(new)
956+
if new_dist < current or random.random() < 0.2:
957+
state = new
958+
current = new_dist
959+
if current < best:
960+
best = current
961+
step += 1
962+
963+
# Draw depot
964+
pygame.draw.circle(self.screen, (255, 200, 100), depot, 25)
965+
pygame.draw.circle(self.screen, (255, 255, 255), depot, 18)
966+
depot_text = self.font_small.render("DEPOT", True, (0, 0, 0))
967+
depot_rect = depot_text.get_rect(center=depot)
968+
self.screen.blit(depot_text, depot_rect)
969+
970+
# Draw routes for each vehicle
971+
for v in range(n_vehicles):
972+
vehicle_customers = [
973+
(i, customers[i]) for i, a in enumerate(state) if a == v
974+
]
975+
vehicle_demand = sum(demands[i] for i, a in enumerate(state) if a == v)
976+
977+
if vehicle_customers:
978+
# Draw route
979+
points = [depot]
980+
for idx, pos in vehicle_customers:
981+
points.append(pos)
982+
points.append(depot)
983+
984+
# Draw lines with vehicle color
985+
for i in range(len(points) - 1):
986+
pygame.draw.line(
987+
self.screen, vehicle_colors[v], points[i], points[i + 1], 4
988+
)
989+
990+
# Draw customers
991+
for idx, pos in vehicle_customers:
992+
color = vehicle_colors[v]
993+
pygame.draw.circle(self.screen, color, pos, 12)
994+
pygame.draw.circle(self.screen, (255, 255, 255), pos, 8)
995+
# Demand text
996+
d_text = self.font_small.render(
997+
str(demands[idx]), True, (0, 0, 0)
998+
)
999+
d_rect = d_text.get_rect(center=pos)
1000+
self.screen.blit(d_text, d_rect)
1001+
1002+
# Capacity bars
1003+
bar_y = HEIGHT - 80
1004+
for v in range(n_vehicles):
1005+
load = sum(demands[i] for i, a in enumerate(state) if a == v)
1006+
bar_x = 300 + v * 400
1007+
bar_w = 200
1008+
bar_h = 25
1009+
1010+
# Background
1011+
pygame.draw.rect(
1012+
self.screen, (50, 50, 70), (bar_x, bar_y, bar_w, bar_h)
1013+
)
1014+
# Fill
1015+
fill_w = (load / vehicle_capacity) * bar_w
1016+
color = vehicle_colors[v] if load <= vehicle_capacity else (255, 50, 50)
1017+
pygame.draw.rect(self.screen, color, (bar_x, bar_y, fill_w, bar_h))
1018+
1019+
# Label
1020+
label = self.font_small.render(
1021+
f"Vehicle {v + 1}: {load}/{vehicle_capacity}",
1022+
True,
1023+
vehicle_colors[v],
1024+
)
1025+
self.screen.blit(label, (bar_x, bar_y - 25))
1026+
1027+
self.draw_stats_panel(
1028+
"Vehicle Routing", step, int(current / 100), int(best / 100), 100
1029+
)
1030+
1031+
title = self.font.render("Problem 10: Vehicle Routing", True, ACCENT2)
1032+
self.screen.blit(title, (50, 50))
1033+
1034+
self.save_frame()
1035+
8841036
def scene_final(self, duration_sec=3):
8851037
"""Scene 10: Final showcase with all stats"""
8861038
print("🏆 Rendering: Final showcase...")
@@ -897,13 +1049,14 @@ def scene_final(self, duration_sec=3):
8971049
("Max Clique", "35 nodes", "✓ Clique found"),
8981050
("Bin Packing", "40 items, 8 bins", "✓ Packed optimally"),
8991051
("Max Independent Set", "40 nodes", "✓ Set isolated"),
1052+
("Vehicle Routing", "20 customers, 3 vehicles", "✓ Routes optimized"),
9001053
]
9011054

9021055
for frame in range(frames):
9031056
self.screen.fill(BG_COLOR)
9041057

9051058
# Title
906-
title = self.font_large.render("BAHA: 9 Problems Conquered", True, ACCENT)
1059+
title = self.font_large.render("BAHA: 10 Problems Conquered", True, ACCENT)
9071060
title_rect = title.get_rect(center=(WIDTH // 2, 100))
9081061
self.screen.blit(title, title_rect)
9091062

@@ -978,6 +1131,7 @@ def generate(self):
9781131
self.scene_clique(duration_sec=2)
9791132
self.scene_binpacking(duration_sec=2)
9801133
self.scene_mis(duration_sec=2)
1134+
self.scene_vrp(duration_sec=3)
9811135

9821136
# Final showcase
9831137
self.scene_final(duration_sec=4)

0 commit comments

Comments
 (0)