-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsa_intersection.py
More file actions
1222 lines (981 loc) · 41.8 KB
/
Copy pathsa_intersection.py
File metadata and controls
1222 lines (981 loc) · 41.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Simulated Annealing for Multi-Vehicle Intersection Optimization
Milestone 3 - Due Oct 23, 2025
Integrates with metaheuristic_intersection.py
Uses FIXED Constraint 6B (conflict-point based, allows simultaneous crossing)
"""
import numpy as np
import matplotlib.pyplot as plt
from typing import Dict
from metaheuristic_intersection import (
ProblemParameters, objective_function, feasibility_check,
generate_random_solution, simulate_vehicle_trajectory
)
# ============================================================================
# REPAIR OPERATOR
# ============================================================================
# max_iter = 6000
def repair_solution(x_infeasible: np.ndarray,
violations: Dict,
params,
x0: np.ndarray,
v0: np.ndarray,
t0: np.ndarray) -> np.ndarray:
"""
IMPROVED repair operator with targeted fixes
Strategy:
1. Velocity violations → smooth accelerations to stay within bounds
2. Collision violations → create time gaps by adjusting speeds
3. Reaching violations → ensure sufficient forward acceleration
"""
from metaheuristic_intersection import feasibility_check
x_repaired = x_infeasible.copy()
N, K = params.N, params.K
# Extract components
u_profiles = x_repaired[:N*K].reshape(N, K)
Z_binary = x_repaired[N*K:] if len(x_repaired) > N*K else np.array([])
# Count violation types
has_velocity_viol = False
has_collision_viol = False
has_reaching_viol = False
# Parse violations
for constraint_type, result in violations.items():
if not result.get('satisfied', True):
if 'velocity' in constraint_type.lower():
has_velocity_viol = True
elif 'collision' in constraint_type.lower() or 'lateral' in constraint_type.lower():
has_collision_viol = True
elif 'reaching' in constraint_type.lower():
has_reaching_viol = True
# REPAIR STRATEGY 1: Fix velocity violations by smoothing
if has_velocity_viol:
for i in range(N):
# Apply moving average filter to smooth accelerations
window = 5
u_smooth = np.convolve(u_profiles[i], np.ones(window)/window, mode='same')
u_profiles[i] = u_smooth * 0.7 # Scale down to stay in bounds
u_profiles[i] = np.clip(u_profiles[i], params.u_min, params.u_max)
# REPAIR STRATEGY 2: Fix collisions by creating time separation
if has_collision_viol:
# Slow down some vehicles, speed up others to create gaps
for i in range(N):
if i % 2 == 0:
# Even vehicles: slow down in merge zone
merge_start_idx = int((params.L - params.S) / (v0[i] * params.dt))
merge_start_idx = max(0, min(merge_start_idx, K-10))
u_profiles[i, merge_start_idx:merge_start_idx+10] *= 0.5
else:
# Odd vehicles: speed up before merge zone
pre_merge_idx = max(0, int((params.L - params.S - 20) / (v0[i] * params.dt)))
pre_merge_idx = min(pre_merge_idx, K-10)
u_profiles[i, pre_merge_idx:pre_merge_idx+5] *= 1.3
# Clip to bounds
u_profiles = np.clip(u_profiles, params.u_min, params.u_max)
# Also flip priorities to change crossing order
if len(Z_binary) > 0:
flip_idx = np.random.randint(0, len(Z_binary))
Z_binary[flip_idx] = 1.0 - Z_binary[flip_idx]
# REPAIR STRATEGY 3: Fix reaching violations by boosting forward motion
if has_reaching_viol:
for i in range(N):
# Increase all accelerations to ensure reaching goal
u_profiles[i] *= 1.5
u_profiles[i] = np.clip(u_profiles[i], 0.0, params.u_max) # Only positive
# Reconstruct solution
if len(Z_binary) > 0:
x_repaired = np.concatenate([u_profiles.flatten(), Z_binary])
else:
x_repaired = u_profiles.flatten()
# Verify repair worked (at least partially)
is_feas_after, _ = feasibility_check(x_repaired, x0, v0, t0, params)
if not is_feas_after:
# If still infeasible, try more aggressive repair
# Strategy: blend with known-good conservative solution
u_conservative = np.ones((N, K)) * 0.3 # Very gentle acceleration
u_conservative = np.clip(u_conservative, params.u_min, params.u_max)
# Blend 70% conservative, 30% current
u_blended = 0.7 * u_conservative + 0.3 * u_profiles
u_blended = np.clip(u_blended, params.u_min, params.u_max)
if len(Z_binary) > 0:
x_repaired = np.concatenate([u_blended.flatten(), Z_binary])
else:
x_repaired = u_blended.flatten()
return x_repaired
def generate_initial_feasible_solution(params, x0, v0, t0, max_attempts=1000):
"""
Generate initial FEASIBLE solution using conservative heuristic
Strategy: Start with gentle accelerations (more likely feasible)
"""
from metaheuristic_intersection import feasibility_check
for attempt in range(max_attempts):
# Generate solution with bias toward gentle accelerations
u_profiles = np.random.normal(0, 0.5, (params.N, params.K))
u_profiles = np.clip(u_profiles, params.u_min, params.u_max)
# Random priority variables
n_binary = 4 # Z_02, Z_03, Z_12, Z_13
Z_binary = np.random.randint(0, 2, n_binary).astype(float)
# Combine
x = np.concatenate([u_profiles.flatten(), Z_binary])
# Check feasibility
is_feas, _ = feasibility_check(x, x0, v0, t0, params)
if is_feas:
print(f"✅ Found initial feasible solution on attempt {attempt + 1}")
return x
# If failed, return VERY conservative solution
print("⚠️ Using ultra-conservative fallback initial solution")
# Strategy: Very gentle constant acceleration to slowly reach goal
# Each vehicle accelerates gently, no conflicts
u_conservative = np.zeros((params.N, params.K))
for i in range(params.N):
# Gentle acceleration for first 20 steps to reach decent speed
u_conservative[i, :20] = 0.5 # Gentle accel
# Then coast (zero acceleration)
u_conservative[i, 20:] = 0.0
u_conservative = np.clip(u_conservative, params.u_min, params.u_max)
# Sequential priorities (vehicles go one by one)
Z_conservative = np.array([1.0, 1.0, 0.0, 0.0])
x_conservative = np.concatenate([u_conservative.flatten(), Z_conservative])
# VERIFY this is actually feasible
from metaheuristic_intersection import feasibility_check
is_feas, viols = feasibility_check(x_conservative, x0, v0, t0, params)
if is_feas:
print("✅ Conservative solution is feasible")
else:
print("❌ WARNING: Even conservative solution is infeasible!")
violated_constraints = [k for k, v in viols.items() if not v['satisfied']]
print(f" Violations: {violated_constraints}")
return x_conservative
# ============================================================================
# NEIGHBOR GENERATION
# ============================================================================
def generate_neighbor(x_current, params, T, T_init):
"""
Generate neighbor solution with adaptive step size
Strategy:
- Perturb ~10% of continuous variables
- Step size decreases with temperature
- Flip binary variables with 30% probability
"""
x_neighbor = x_current.copy()
N, K = params.N, params.K
# Adaptive step size
step_scale = np.sqrt(T / T_init)
u_step = 1.0 * step_scale
# Perturb random 10% of acceleration values
n_perturb = max(1, int(N * K * 0.1))
perturb_indices = np.random.choice(N * K, n_perturb, replace=False)
for idx in perturb_indices:
delta = np.random.uniform(-u_step, u_step)
x_neighbor[idx] += delta
x_neighbor[idx] = np.clip(x_neighbor[idx], params.u_min, params.u_max)
# Flip binary variables with 30% chance
n_binary = len(x_current) - N * K
if n_binary > 0 and np.random.random() < 0.3:
flip_idx = np.random.randint(0, n_binary)
binary_idx = N * K + flip_idx
x_neighbor[binary_idx] = 1.0 - x_neighbor[binary_idx]
return x_neighbor
# ============================================================================
# SIMULATED ANNEALING MAIN LOOP
# ============================================================================
def simulated_annealing(params, x0, v0, t0,
T_init=100.0,
T_final=0.01,
max_iter=5,
seed=42):
"""
Simulated Annealing with THREE FIXES:
1. Variable time horizon (adaptive K)
2. Repair operator (guaranteed feasibility)
3. Linear cooling (matches lecture)
Args:
params: Problem parameters
x0, v0, t0: Initial conditions
T_init: Initial temperature
T_final: Final temperature
max_iter: Maximum iterations
seed: Random seed
Returns:
x_best: Best feasible solution found
f_best: Best objective value
history: Convergence history
"""
from metaheuristic_intersection import objective_function, feasibility_check
np.random.seed(seed)
# Calculate LINEAR cooling rate (matches lecture)
beta = (T_init - T_final) / max_iter
# Initialize with FEASIBLE solution
print("\n" + "="*80)
print("SIMULATED ANNEALING - MILESTONE 3 (CORRECTED)")
print("="*80)
print("Generating initial feasible solution...")
x_current = generate_initial_feasible_solution(params, x0, v0, t0)
f_current, f_time_current, f_energy_current, _ = objective_function(
x_current, x0, v0, t0, params
)
# Track best
x_best = x_current.copy()
f_best = f_current
# History
history = {
'iteration': [],
'f_best': [],
'f_current': [],
'T': [],
'acceptance_rate': [],
'repair_rate': [],
'skip_rate': []
}
# Counters
n_accepted = 0
n_repaired = 0
n_skipped = 0
T = T_init
print(f"\n✅ Initial feasible solution found")
print(f" Fitness: {f_current:.2f} (time={f_time_current:.2f}, energy={f_energy_current:.2f})")
print(f"\nSA Parameters:")
print(f" T_init = {T_init}, T_final = {T_final}")
print(f" Cooling: LINEAR with β = {beta:.6f}")
print(f" Max iterations: {max_iter}")
print("="*80 + "\n")
# Main SA loop
for iteration in range(max_iter):
# Generate neighbor
x_neighbor = generate_neighbor(x_current, params, T, T_init)
# Check feasibility
is_feas, violations = feasibility_check(x_neighbor, x0, v0, t0, params)
# Repair if infeasible
if not is_feas:
x_neighbor = repair_solution(x_neighbor, violations, params, x0, v0, t0)
n_repaired += 1
# Re-check after repair
is_feas, violations = feasibility_check(x_neighbor, x0, v0, t0, params)
# If still infeasible after repair, skip this iteration
if not is_feas:
n_skipped += 1
# DEBUG: Print why it's failing (only first 10 times)
if n_skipped <= 10:
violated_constraints = [k for k, v in violations.items() if not v['satisfied']]
print(f" [Iter {iteration}] Skipped - still infeasible after repair")
print(f" Violations: {violated_constraints}")
# Still cool the temperature
T = T_init - beta * iteration
# Update history with current values
history['iteration'].append(iteration)
history['f_best'].append(f_best)
history['f_current'].append(f_current)
history['T'].append(T)
history['acceptance_rate'].append(n_accepted / (iteration + 1))
history['repair_rate'].append(n_repaired / (iteration + 1))
history['skip_rate'].append(n_skipped / (iteration + 1))
continue
# Evaluate feasible neighbor (NO PENALTIES, pure objective)
f_neighbor, f_time_neighbor, f_energy_neighbor, _ = objective_function(
x_neighbor, x0, v0, t0, params
)
# Acceptance criterion (standard Metropolis)
delta_f = f_neighbor - f_current
if delta_f < 0:
# Always accept improvement
accept = True
else:
# Accept worse solution with probability exp(-ΔE/T)
prob_accept = np.exp(-delta_f / T)
accept = (np.random.random() < prob_accept)
# Update current solution
if accept:
x_current = x_neighbor.copy()
f_current = f_neighbor
n_accepted += 1
# Update best solution
if f_neighbor < f_best:
x_best = x_neighbor.copy()
f_best = f_neighbor
# LINEAR COOLING (matches lecture formula: T_i = T_0 - β*i)
T = T_init - beta * iteration
# Update history
history['iteration'].append(iteration)
history['f_best'].append(f_best)
history['f_current'].append(f_current)
history['T'].append(T)
history['acceptance_rate'].append(n_accepted / (iteration + 1))
history['repair_rate'].append(n_repaired / (iteration + 1))
history['skip_rate'].append(n_skipped / (iteration + 1))
# Progress reporting
if iteration % 10 == 0:
print(f"Iter {iteration:4d}: f_best={f_best:7.2f}, f_current={f_current:7.2f}, "
f"T={T:6.2f}, accept={n_accepted/(iteration+1):5.1%}, "
f"repair={n_repaired/(iteration+1):5.1%}, skip={n_skipped/(iteration+1):5.1%}")
# Stop if temperature too low
if T <= T_final:
print(f"\n✅ Reached T_final={T_final} at iteration {iteration}")
break
# Final verification
is_feas_final, _ = feasibility_check(x_best, x0, v0, t0, params)
print("\n" + "="*80)
print("SA COMPLETE")
print("="*80)
print(f"Best fitness: {f_best:.2f}")
print(f"Final feasibility: {'✅ FEASIBLE' if is_feas_final else '❌ INFEASIBLE'}")
print(f"Acceptance rate: {n_accepted/iteration:.2%}")
print(f"Repair rate: {n_repaired/iteration:.2%}")
print(f"Skip rate: {n_skipped/iteration:.2%}")
print("="*80 + "\n")
if not is_feas_final:
print("⚠️ WARNING: Best solution is INFEASIBLE - increase max_iter or adjust repair")
return x_best, f_best, history
# ============================================================================
# VISUALIZATION
# ============================================================================
def plot_convergence(history):
"""Plot SA convergence history with repair tracking"""
fig, axes = plt.subplots(3, 2, figsize=(14, 12))
fig.suptitle('Simulated Annealing Convergence (Corrected)', fontsize=14, fontweight='bold')
iters = history['iteration']
# Plot 1: Objective value
ax = axes[0, 0]
ax.plot(iters, history['f_best'], 'g-', linewidth=2, label='Best')
ax.plot(iters, history['f_current'], 'b-', alpha=0.5, label='Current')
ax.set_xlabel('Iteration')
ax.set_ylabel('Fitness (Pure Objective)')
ax.set_title('Objective Function Value')
ax.legend()
ax.grid(True, alpha=0.3)
# Plot 2: Temperature
ax = axes[0, 1]
ax.plot(iters, history['T'], 'r-', linewidth=2)
ax.set_xlabel('Iteration')
ax.set_ylabel('Temperature')
ax.set_title('Cooling Schedule (LINEAR)')
ax.grid(True, alpha=0.3)
# Plot 3: Acceptance rate
ax = axes[1, 0]
ax.plot(iters, [r*100 for r in history['acceptance_rate']], 'orange', linewidth=2)
ax.set_xlabel('Iteration')
ax.set_ylabel('Acceptance Rate (%)')
ax.set_title('Solution Acceptance Rate')
ax.grid(True, alpha=0.3)
# Plot 4: Repair rate
ax = axes[1, 1]
ax.plot(iters, [r*100 for r in history['repair_rate']], 'purple', linewidth=2, label='Repair')
ax.plot(iters, [r*100 for r in history['skip_rate']], 'red', linewidth=2, label='Skip')
ax.set_xlabel('Iteration')
ax.set_ylabel('Rate (%)')
ax.set_title('Repair & Skip Rates')
ax.legend()
ax.grid(True, alpha=0.3)
# Plot 5: Temperature vs Best Fitness (trajectory)
ax = axes[2, 0]
scatter = ax.scatter(history['T'], history['f_best'],
c=iters, cmap='viridis', s=2, alpha=0.6)
ax.set_xlabel('Temperature')
ax.set_ylabel('Best Fitness')
ax.set_title('Fitness vs Temperature')
ax.set_xscale('log')
plt.colorbar(scatter, ax=ax, label='Iteration')
ax.grid(True, alpha=0.3)
# Plot 6: Summary statistics
ax = axes[2, 1]
ax.axis('off')
final_stats = f"""
FINAL STATISTICS
{'='*40}
Best Fitness: {history['f_best'][-1]:.2f}
Final Temperature: {history['T'][-1]:.4f}
Acceptance Rate: {history['acceptance_rate'][-1]:.2%}
Repair Rate: {history['repair_rate'][-1]:.2%}
Skip Rate: {history['skip_rate'][-1]:.2%}
Iterations: {len(iters)}
"""
ax.text(0.1, 0.5, final_stats, fontsize=11, family='monospace',
verticalalignment='center')
plt.tight_layout()
return fig
# ============================================================================
# RANDOM SEARCH BASELINE (for comparison)
# ============================================================================
def random_search_baseline(params, x0, v0, t0, n_samples=500, seed=42):
"""Random search baseline - now uses pure objective"""
from metaheuristic_intersection import objective_function, feasibility_check
np.random.seed(seed)
print("\n" + "="*80)
print("RANDOM SEARCH BASELINE")
print("="*80)
best_f = float('inf')
best_x = None
best_feasible = False
n_feasible = 0
for i in range(n_samples):
x = generate_random_solution(params)
# Check feasibility first
is_feas, _ = feasibility_check(x, x0, v0, t0, params)
if is_feas:
n_feasible += 1
# Only evaluate objective if feasible
f, _, _, _ = objective_function(x, x0, v0, t0, params)
# Update best
if not best_feasible or f < best_f:
best_x = x.copy()
best_f = f
best_feasible = True
if (i+1) % 100 == 0:
status = f"feasible_rate={n_feasible/(i+1):.2%}"
if best_feasible:
status += f", best_f={best_f:.2f}"
print(f"Sample {i+1}/{n_samples}: {status}")
print(f"\nRandom search complete:")
if best_feasible:
print(f" Best fitness: {best_f:.2f} ✅")
else:
print(f" No feasible solution found ❌")
print(f" Feasibility rate: {n_feasible/n_samples:.2%}")
print("="*80 + "\n")
return best_x, best_f, best_feasible
# ============================================================================
# ADVANCED VISUALIZATION FOR M3
# ============================================================================
def plot_live_sa_dashboard(history, params, x_current, x_best, x0, v0, t0,
info_current, info_best, iteration):
"""
Real-time dashboard showing SA progress
Updates during SA run to show algorithm "thinking"
Shows 6 subplots:
1. Convergence (f_best vs iteration)
2. Current solution trajectories
3. Best solution trajectories
4. Temperature schedule
5. Acceptance rate
6. Constraint violation breakdown
"""
from matplotlib.gridspec import GridSpec
# Create figure with custom layout
fig = plt.figure(figsize=(20, 12))
gs = GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)
# Main title with iteration count
fig.suptitle(f'Simulated Annealing - Iteration {iteration}',
fontsize=16, fontweight='bold')
# ========================================================================
# Plot 1: Convergence (top-left, large)
# ========================================================================
ax1 = fig.add_subplot(gs[0, :2])
iters = history['iteration']
ax1.plot(iters, history['f_best'], 'g-', linewidth=3, label='Best', marker='o', markersize=4)
ax1.plot(iters, history['f_current'], 'b-', alpha=0.5, linewidth=2, label='Current')
ax1.axhline(y=info_best['f_total'], color='green', linestyle='--', alpha=0.5, label='Best Raw Objective')
ax1.set_xlabel('Iteration', fontsize=11)
ax1.set_ylabel('Fitness (with penalties)', fontsize=11)
ax1.set_title('Convergence: Fitness Over Time', fontsize=12, fontweight='bold')
ax1.legend(loc='upper right', fontsize=10)
ax1.grid(True, alpha=0.3)
# Annotate current point
ax1.plot(iteration, info_current['fitness'], 'ro', markersize=10, zorder=10)
ax1.annotate(f"Current: {info_current['fitness']:.1f}",
xy=(iteration, info_current['fitness']),
xytext=(10, 10), textcoords='offset points',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.8),
fontsize=9)
# ========================================================================
# Plot 2: Current Solution Trajectories (middle-left)
# ========================================================================
ax2 = fig.add_subplot(gs[1, 0])
N, K, dt = params.N, params.K, params.dt
u_current = x_current[:N*K].reshape(N, K)
colors = ['blue', 'cyan', 'red', 'orange']
for i in range(N):
x_traj, v_traj = simulate_vehicle_trajectory(u_current[i], x0[i], v0[i], dt, K)
t_traj = t0[i] + np.arange(len(x_traj)) * dt
ax2.plot(t_traj, x_traj, color=colors[i], linewidth=2, label=f'V{i}', alpha=0.7)
ax2.axhspan(params.L - params.S, params.L, color='red', alpha=0.2)
ax2.axhline(y=params.L - params.S, color='red', linestyle='--', linewidth=1)
ax2.axhline(y=params.L, color='green', linestyle='--', linewidth=1)
ax2.set_xlabel('Time (s)', fontsize=10)
ax2.set_ylabel('Position (m)', fontsize=10)
ax2.set_title(f'Current Solution (f={info_current["f_total"]:.1f})', fontsize=11, fontweight='bold')
ax2.legend(fontsize=8, loc='lower right')
ax2.grid(True, alpha=0.3)
# ========================================================================
# Plot 3: Best Solution Trajectories (middle-center)
# ========================================================================
ax3 = fig.add_subplot(gs[1, 1])
u_best = x_best[:N*K].reshape(N, K)
for i in range(N):
x_traj, v_traj = simulate_vehicle_trajectory(u_best[i], x0[i], v0[i], dt, K)
t_traj = t0[i] + np.arange(len(x_traj)) * dt
ax3.plot(t_traj, x_traj, color=colors[i], linewidth=2, label=f'V{i}', alpha=0.7)
ax3.axhspan(params.L - params.S, params.L, color='green', alpha=0.2)
ax3.axhline(y=params.L - params.S, color='red', linestyle='--', linewidth=1)
ax3.axhline(y=params.L, color='green', linestyle='--', linewidth=1)
ax3.set_xlabel('Time (s)', fontsize=10)
ax3.set_ylabel('Position (m)', fontsize=10)
ax3.set_title(f'Best Solution (f={info_best["f_total"]:.1f})', fontsize=11, fontweight='bold')
ax3.legend(fontsize=8, loc='lower right')
ax3.grid(True, alpha=0.3)
# ========================================================================
# Plot 4: Temperature Schedule (top-right)
# ========================================================================
ax4 = fig.add_subplot(gs[0, 2])
ax4.semilogy(iters, history['T'], 'r-', linewidth=3)
ax4.plot(iteration, history['T'][-1], 'ko', markersize=10)
ax4.set_xlabel('Iteration', fontsize=10)
ax4.set_ylabel('Temperature (log scale)', fontsize=10)
ax4.set_title('Cooling Schedule', fontsize=11, fontweight='bold')
ax4.grid(True, alpha=0.3)
# ========================================================================
# Plot 5: Acceptance & Feasibility Rates (middle-right)
# ========================================================================
ax5 = fig.add_subplot(gs[1, 2])
ax5_twin = ax5.twinx()
line1 = ax5.plot(iters, [r*100 for r in history['acceptance_rate']],
'orange', linewidth=2, label='Acceptance')
line2 = ax5_twin.plot(iters, [r*100 for r in history['feasible_rate']],
'purple', linewidth=2, label='Feasibility')
ax5.set_xlabel('Iteration', fontsize=10)
ax5.set_ylabel('Acceptance Rate (%)', fontsize=10, color='orange')
ax5_twin.set_ylabel('Feasibility Rate (%)', fontsize=10, color='purple')
ax5.set_title('Algorithm Performance', fontsize=11, fontweight='bold')
# Combined legend
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax5.legend(lines, labels, fontsize=9, loc='upper right')
ax5.grid(True, alpha=0.3)
# ========================================================================
# Plot 6: Constraint Violation Breakdown (bottom, full width)
# ========================================================================
ax6 = fig.add_subplot(gs[2, :])
# Get violations for current solution
violations = info_current['violations']
constraint_names = [
'C3: Accel',
'C4: Velocity',
'C5: Reaching',
'C6: Rear-End',
'C6B: Lateral',
'C7: Priority'
]
constraint_keys = [
'constraint_3_acceleration_limits',
'constraint_4_velocity_limits',
'constraint_5_reaching_zones',
'constraint_6_rear_end_collision',
'constraint_6b_lateral_physical',
'constraint_7_lateral_collision'
]
violation_counts = []
colors_bar = []
for key in constraint_keys:
if key in violations:
count = len(violations[key]['violations'])
violation_counts.append(count)
colors_bar.append('red' if count > 0 else 'green')
else:
violation_counts.append(0)
colors_bar.append('green')
bars = ax6.barh(constraint_names, violation_counts, color=colors_bar, alpha=0.7, edgecolor='black')
# Annotate bars
for i, (bar, count) in enumerate(zip(bars, violation_counts)):
if count > 0:
ax6.text(count + 0.1, i, f'{count}', va='center', fontsize=10, fontweight='bold')
ax6.set_xlabel('Number of Violations', fontsize=11)
ax6.set_title('Current Solution: Constraint Satisfaction', fontsize=12, fontweight='bold')
ax6.grid(True, axis='x', alpha=0.3)
# Add feasibility status
feas_text = "✓ FEASIBLE" if info_current['is_feasible'] else "✗ INFEASIBLE"
feas_color = 'green' if info_current['is_feasible'] else 'red'
ax6.text(0.98, 0.95, feas_text, transform=ax6.transAxes,
fontsize=14, fontweight='bold', color=feas_color,
ha='right', va='top',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
plt.tight_layout()
return fig
def simulated_annealing_with_live_viz(params, x0, v0, t0,
T_init=1000.0,
T_final=1.0,
cooling=0.95,
max_iter=5000,
seed=42,
update_interval=100):
"""
SA with LIVE VISUALIZATION - shows algorithm working in real-time
This creates a figure that updates every `update_interval` iterations
showing the search process as it happens.
"""
np.random.seed(seed)
# Initialize
x_current = generate_random_solution(params)
f_current, is_feas_current, info_current = penalized_objective(
x_current, x0, v0, t0, params
)
x_best = x_current.copy()
f_best = f_current
best_feasible = is_feas_current
info_best = info_current
history = {
'iteration': [],
'f_best': [],
'f_current': [],
'T': [],
'acceptance_rate': [],
'feasible_rate': []
}
T = T_init
n_accepted = 0
n_feasible = 0
print("\n" + "="*80)
print("LIVE SIMULATED ANNEALING VISUALIZATION")
print("="*80)
print("Dashboard will update every 100 iterations...")
print("Close the figure window to continue to next update.")
print("="*80 + "\n")
# Main loop
for iteration in range(max_iter):
# Generate and evaluate neighbor
x_neighbor = generate_neighbor(x_current, params, T, T_init)
f_neighbor, is_feas_neighbor, info_neighbor = penalized_objective(
x_neighbor, x0, v0, t0, params
)
# Acceptance
delta_f = f_neighbor - f_current
if delta_f < 0:
accept = True
else:
accept = (np.random.random() < np.exp(-delta_f / T))
if accept:
x_current = x_neighbor
f_current = f_neighbor
is_feas_current = is_feas_neighbor
info_current = info_neighbor
n_accepted += 1
# Update best
if is_feas_neighbor and not best_feasible:
x_best = x_neighbor.copy()
f_best = f_neighbor
best_feasible = True
info_best = info_neighbor
elif is_feas_neighbor == best_feasible and f_neighbor < f_best:
x_best = x_neighbor.copy()
f_best = f_neighbor
info_best = info_neighbor
if is_feas_current:
n_feasible += 1
T *= cooling
# Update history
history['iteration'].append(iteration)
history['f_best'].append(f_best)
history['f_current'].append(f_current)
history['T'].append(T)
history['acceptance_rate'].append(n_accepted / (iteration + 1))
history['feasible_rate'].append(n_feasible / (iteration + 1))
# Live visualization update
if iteration % update_interval == 0 or iteration == max_iter - 1:
print(f"\n>>> Iteration {iteration}: Updating dashboard...")
fig = plot_live_sa_dashboard(
history, params, x_current, x_best, x0, v0, t0,
info_current, info_best, iteration
)
plt.show(block=False)
plt.pause(0.1)
# Close figure to prevent memory buildup
plt.close(fig)
if T < T_final:
break
print("\n" + "="*80)
print("SA COMPLETED")
print("="*80)
return x_best, f_best, history, info_best
# ============================================================================
# PARAMETRIC STUDY - Show effect of SA parameters
# ============================================================================
def parametric_study_cooling_rate(params, x0, v0, t0):
"""
Study effect of cooling rate on SA performance
Tests: alpha = [0.90, 0.93, 0.95, 0.97, 0.99]
Shows tradeoff: fast cooling (0.90) vs slow cooling (0.99)
"""
print("\n" + "="*80)
print("PARAMETRIC STUDY: Effect of Cooling Rate")
print("="*80)
cooling_rates = [0.90, 0.93, 0.95, 0.97, 0.99]
results = []
for alpha in cooling_rates:
print(f"\nTesting cooling rate: {alpha}")
x_best, f_best, history = simulated_annealing(
params, x0, v0, t0,
T_init=1000.0,
T_final=1.0,
cooling=alpha,
max_iter=5000,
seed=42
)
results.append({
'alpha': alpha,
'f_best': f_best,
'history': history,
'n_iterations': len(history['iteration'])
})
# Plot comparison
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
fig.suptitle('Parametric Study: Cooling Rate Effect', fontsize=14, fontweight='bold')
# Plot 1: Convergence curves
ax = axes[0]
for res in results:
ax.plot(res['history']['iteration'], res['history']['f_best'],
linewidth=2, label=f"α={res['alpha']}", marker='o', markersize=3)
ax.set_xlabel('Iteration', fontsize=11)
ax.set_ylabel('Best Fitness', fontsize=11)
ax.set_title('Convergence Speed vs Cooling Rate', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# Plot 2: Final fitness comparison
ax = axes[1]
alphas = [res['alpha'] for res in results]
finals = [res['f_best'] for res in results]
iters = [res['n_iterations'] for res in results]
ax.bar(range(len(alphas)), finals, color='steelblue', alpha=0.7, edgecolor='black')
ax.set_xticks(range(len(alphas)))
ax.set_xticklabels([f"{a:.2f}" for a in alphas])
ax.set_xlabel('Cooling Rate α', fontsize=11)
ax.set_ylabel('Final Best Fitness', fontsize=11)
ax.set_title('Final Solution Quality', fontsize=12)
ax.grid(True, axis='y', alpha=0.3)
# Annotate best
best_idx = np.argmin(finals)
ax.bar(best_idx, finals[best_idx], color='green', alpha=0.7, edgecolor='black')
ax.text(best_idx, finals[best_idx] + 5, '★ BEST', ha='center', fontsize=10, fontweight='bold')
plt.tight_layout()
plt.show()
print("\n" + "="*80)
print("Parametric Study Results:")
print("="*80)
for res in results:
print(f"α={res['alpha']:.2f}: f_best={res['f_best']:.2f}, iterations={res['n_iterations']}")
print("="*80)
return results
def parametric_study_temperature(params, x0, v0, t0):
"""
Study effect of initial temperature
Tests: T_init = [100, 500, 1000, 2000, 5000]
"""
print("\n" + "="*80)
print("PARAMETRIC STUDY: Effect of Initial Temperature")
print("="*80)
temperatures = [100, 500, 1000, 2000, 5000]
results = []
for T in temperatures:
print(f"\nTesting T_init: {T}")
x_best, f_best, history = simulated_annealing(
params, x0, v0, t0,
T_init=float(T),
T_final=1.0,
cooling=0.95,
max_iter=5000,
seed=42
)
results.append({
'T_init': T,
'f_best': f_best,
'history': history
})
# Plot
fig, ax = plt.subplots(figsize=(10, 6))
for res in results:
ax.plot(res['history']['iteration'], res['history']['f_best'],
linewidth=2, label=f"T={res['T_init']}", marker='o', markersize=3)
ax.set_xlabel('Iteration', fontsize=11)
ax.set_ylabel('Best Fitness', fontsize=11)
ax.set_title('Effect of Initial Temperature on Convergence', fontsize=13, fontweight='bold')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
return results
# ============================================================================
# STATISTICAL VALIDATION - Multiple runs
# ============================================================================
def statistical_validation(params, x0, v0, t0, n_runs=10):
"""
Run SA multiple times with different seeds
Report mean, std, best, worst
"""
print("\n" + "="*80)