Commit
·
e356b9f
1
Parent(s):
b13842d
all supported algos now in bencmarks/
Browse files- benchmarks/constraint_satisfaction/test_constraint_benchmark.py +1 -1
- benchmarks/optimizer_performance/benchmark_optimizers.py +167 -237
- benchmarks/optimizer_performance/benchmark_schedule_performance.py +3 -3
- benchmarks/service_quality/benchmark_service_quality.py +0 -214
- benchmarks/service_quality/test_service_quality_benchmark.py +210 -4
- greedyOptim/hybrid_optimizers.py +9 -2
benchmarks/constraint_satisfaction/test_constraint_benchmark.py
CHANGED
|
@@ -74,7 +74,7 @@ def main():
|
|
| 74 |
)
|
| 75 |
|
| 76 |
# Generate schedules using different methods
|
| 77 |
-
methods = ['ga', 'pso', 'sa']
|
| 78 |
schedules = []
|
| 79 |
|
| 80 |
print(f"Generating {len(methods)} schedules using different optimization methods...")
|
|
|
|
| 74 |
)
|
| 75 |
|
| 76 |
# Generate schedules using different methods
|
| 77 |
+
methods = ['ga', 'pso', 'sa', 'cmaes', 'nsga2', 'adaptive', 'ensemble']
|
| 78 |
schedules = []
|
| 79 |
|
| 80 |
print(f"Generating {len(methods)} schedules using different optimization methods...")
|
benchmarks/optimizer_performance/benchmark_optimizers.py
CHANGED
|
@@ -14,18 +14,71 @@ import os
|
|
| 14 |
# Add project root to path
|
| 15 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
| 16 |
|
| 17 |
-
from DataService.
|
| 18 |
from DataService.schedule_optimizer import MetroScheduleOptimizer
|
| 19 |
from greedyOptim.scheduler import TrainsetSchedulingOptimizer
|
| 20 |
-
from
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
class OptimizerBenchmark:
|
| 26 |
"""Benchmark different optimization algorithms"""
|
| 27 |
|
| 28 |
-
def __init__(self
|
| 29 |
self.results = {
|
| 30 |
"benchmark_info": {
|
| 31 |
"date": datetime.now().isoformat(),
|
|
@@ -34,78 +87,61 @@ class OptimizerBenchmark:
|
|
| 34 |
"test_configurations": [],
|
| 35 |
"results": []
|
| 36 |
}
|
| 37 |
-
self.data_generator = MetroDataGenerator(num_trains=num_trains, num_stations=num_stations)
|
| 38 |
|
| 39 |
-
def generate_test_data(self):
|
| 40 |
"""Generate consistent test data for all optimizers"""
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
)
|
| 44 |
-
|
| 45 |
-
trains = self.data_generator.generate_train_health_statuses()
|
| 46 |
-
# Limit to requested number
|
| 47 |
-
trains = trains[:num_trains]
|
| 48 |
-
|
| 49 |
-
return route, trains
|
| 50 |
|
| 51 |
def benchmark_optimizer(
|
| 52 |
self,
|
| 53 |
optimizer_name: str,
|
| 54 |
-
|
| 55 |
num_trains: int,
|
| 56 |
-
num_stations: int = 22,
|
| 57 |
num_runs: int = 3
|
| 58 |
) -> Dict[str, Any]:
|
| 59 |
"""Benchmark a single optimizer"""
|
| 60 |
print(f"\n{'='*70}")
|
| 61 |
print(f"Benchmarking: {optimizer_name}")
|
| 62 |
-
print(f"Fleet Size: {num_trains} trains
|
| 63 |
print(f"{'='*70}")
|
| 64 |
|
| 65 |
run_times = []
|
| 66 |
success_count = 0
|
| 67 |
-
schedules_generated = []
|
| 68 |
|
| 69 |
for run in range(num_runs):
|
| 70 |
-
print(f"
|
| 71 |
|
| 72 |
try:
|
| 73 |
# Generate fresh data for each run
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
# Create request
|
| 77 |
-
request = ScheduleRequest(
|
| 78 |
-
date=date.today(),
|
| 79 |
-
num_trains=num_trains,
|
| 80 |
-
route=route,
|
| 81 |
-
trains=trains
|
| 82 |
-
)
|
| 83 |
|
| 84 |
# Time the optimization
|
| 85 |
start_time = time.perf_counter()
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
|
| 90 |
end_time = time.perf_counter()
|
| 91 |
elapsed = end_time - start_time
|
| 92 |
|
| 93 |
run_times.append(elapsed)
|
| 94 |
success_count += 1
|
| 95 |
-
schedules_generated.append(schedule)
|
| 96 |
|
| 97 |
-
print(f"✓ Completed in {elapsed:.4f}s")
|
| 98 |
|
| 99 |
except Exception as e:
|
| 100 |
-
print(f"✗ Failed: {str(e)[:
|
| 101 |
-
|
|
|
|
| 102 |
|
| 103 |
# Calculate statistics
|
| 104 |
if run_times:
|
| 105 |
result = {
|
| 106 |
"optimizer": optimizer_name,
|
| 107 |
"fleet_size": num_trains,
|
| 108 |
-
"num_stations": num_stations,
|
| 109 |
"num_runs": num_runs,
|
| 110 |
"successful_runs": success_count,
|
| 111 |
"success_rate": f"{(success_count/num_runs)*100:.1f}%",
|
|
@@ -113,17 +149,13 @@ class OptimizerBenchmark:
|
|
| 113 |
"min_seconds": min(run_times),
|
| 114 |
"max_seconds": max(run_times),
|
| 115 |
"mean_seconds": statistics.mean(run_times),
|
| 116 |
-
"
|
| 117 |
-
|
| 118 |
-
"all_runs": run_times
|
| 119 |
-
},
|
| 120 |
-
"schedule_quality": self._analyze_schedules(schedules_generated) if schedules_generated else None
|
| 121 |
}
|
| 122 |
else:
|
| 123 |
result = {
|
| 124 |
"optimizer": optimizer_name,
|
| 125 |
"fleet_size": num_trains,
|
| 126 |
-
"num_stations": num_stations,
|
| 127 |
"num_runs": num_runs,
|
| 128 |
"successful_runs": 0,
|
| 129 |
"success_rate": "0%",
|
|
@@ -134,45 +166,12 @@ class OptimizerBenchmark:
|
|
| 134 |
print(f" Success Rate: {result['success_rate']}")
|
| 135 |
if run_times:
|
| 136 |
print(f" Average Time: {result['execution_times']['mean_seconds']:.4f}s")
|
| 137 |
-
print(f" Std Dev: {result['execution_times']['stdev_seconds']:.4f}s")
|
| 138 |
|
| 139 |
return result
|
| 140 |
-
|
| 141 |
-
def _analyze_schedules(self, schedules: List) -> Dict[str, Any]:
|
| 142 |
-
"""Analyze quality metrics of generated schedules"""
|
| 143 |
-
if not schedules:
|
| 144 |
-
return None
|
| 145 |
-
|
| 146 |
-
total_trips_list = []
|
| 147 |
-
trains_used_list = []
|
| 148 |
-
|
| 149 |
-
for schedule in schedules:
|
| 150 |
-
if hasattr(schedule, 'trips'):
|
| 151 |
-
total_trips_list.append(len(schedule.trips))
|
| 152 |
-
if hasattr(schedule, 'train_schedules'):
|
| 153 |
-
trains_used_list.append(len(schedule.train_schedules))
|
| 154 |
-
|
| 155 |
-
quality = {}
|
| 156 |
-
|
| 157 |
-
if total_trips_list:
|
| 158 |
-
quality["trips"] = {
|
| 159 |
-
"mean": statistics.mean(total_trips_list),
|
| 160 |
-
"min": min(total_trips_list),
|
| 161 |
-
"max": max(total_trips_list)
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
if trains_used_list:
|
| 165 |
-
quality["trains_utilized"] = {
|
| 166 |
-
"mean": statistics.mean(trains_used_list),
|
| 167 |
-
"min": min(trains_used_list),
|
| 168 |
-
"max": max(trains_used_list)
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
return quality if quality else None
|
| 172 |
-
|
| 173 |
def run_comprehensive_benchmark(
|
| 174 |
self,
|
| 175 |
-
fleet_sizes: List[int] = [
|
| 176 |
num_runs: int = 3
|
| 177 |
):
|
| 178 |
"""Run comprehensive benchmark across all optimizers and fleet sizes"""
|
|
@@ -185,19 +184,14 @@ class OptimizerBenchmark:
|
|
| 185 |
|
| 186 |
# Define optimizers to test
|
| 187 |
optimizers = [
|
| 188 |
-
("
|
| 189 |
-
("
|
| 190 |
-
("
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"fleet_sizes": fleet_sizes,
|
| 197 |
-
"num_stations": 22,
|
| 198 |
-
"runs_per_config": num_runs,
|
| 199 |
-
"optimizers": [name for name, _ in optimizers]
|
| 200 |
-
}
|
| 201 |
]
|
| 202 |
|
| 203 |
# Run benchmarks
|
|
@@ -206,10 +200,10 @@ class OptimizerBenchmark:
|
|
| 206 |
print(f"# FLEET SIZE: {fleet_size} TRAINS")
|
| 207 |
print(f"{'#'*70}")
|
| 208 |
|
| 209 |
-
for optimizer_name,
|
| 210 |
result = self.benchmark_optimizer(
|
| 211 |
optimizer_name,
|
| 212 |
-
|
| 213 |
fleet_size,
|
| 214 |
num_runs=num_runs
|
| 215 |
)
|
|
@@ -220,6 +214,13 @@ class OptimizerBenchmark:
|
|
| 220 |
|
| 221 |
# Generate comparison summary
|
| 222 |
self._generate_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
def _generate_summary(self):
|
| 225 |
"""Generate comparative summary of results"""
|
|
@@ -249,183 +250,112 @@ class OptimizerBenchmark:
|
|
| 249 |
avg_time = result["execution_times"]["mean_seconds"] if "execution_times" in result else "N/A"
|
| 250 |
success = result["success_rate"]
|
| 251 |
|
| 252 |
-
if avg_time
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
fleet_summary.append({
|
| 255 |
"optimizer": optimizer,
|
| 256 |
-
"
|
| 257 |
-
"success_rate": success
|
| 258 |
})
|
| 259 |
-
else:
|
| 260 |
-
print(f"{optimizer:<25} {'FAILED':<15} {success:<15}")
|
| 261 |
|
|
|
|
|
|
|
| 262 |
summary["by_fleet_size"][fleet_size] = fleet_summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
-
#
|
| 265 |
print("\n" + "="*70)
|
| 266 |
-
print("OVERALL PERFORMANCE
|
| 267 |
print("="*70)
|
| 268 |
-
|
| 269 |
-
optimizer_avg_times = {}
|
| 270 |
-
for result in self.results["results"]:
|
| 271 |
-
if "execution_times" in result:
|
| 272 |
-
optimizer = result["optimizer"]
|
| 273 |
-
if optimizer not in optimizer_avg_times:
|
| 274 |
-
optimizer_avg_times[optimizer] = []
|
| 275 |
-
optimizer_avg_times[optimizer].append(result["execution_times"]["mean_seconds"])
|
| 276 |
-
|
| 277 |
-
rankings = []
|
| 278 |
-
for optimizer, times in optimizer_avg_times.items():
|
| 279 |
-
avg = statistics.mean(times)
|
| 280 |
-
rankings.append((optimizer, avg))
|
| 281 |
-
|
| 282 |
-
rankings.sort(key=lambda x: x[1])
|
| 283 |
-
|
| 284 |
-
print(f"\n{'Rank':<8} {'Optimizer':<25} {'Avg Time (s)':<15}")
|
| 285 |
print("-" * 70)
|
| 286 |
-
for rank, (optimizer, avg_time) in enumerate(rankings, 1):
|
| 287 |
-
print(f"{rank:<8} {optimizer:<25} {avg_time:<15.4f}")
|
| 288 |
-
summary["overall_rankings"][optimizer] = {
|
| 289 |
-
"rank": rank,
|
| 290 |
-
"avg_time_seconds": avg_time
|
| 291 |
-
}
|
| 292 |
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
|
|
|
| 299 |
|
| 300 |
-
|
| 301 |
-
json.dump(self.results, f, indent=2, default=str)
|
| 302 |
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
print(f"{'='*70}")
|
| 306 |
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
"""Generate a formatted performance report"""
|
| 311 |
-
report_filename = f"performance_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
| 312 |
|
| 313 |
-
with open(
|
| 314 |
-
f.write("
|
| 315 |
-
f.write("
|
| 316 |
-
f.write("="*
|
| 317 |
-
f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
| 318 |
-
|
| 319 |
-
# Executive Summary
|
| 320 |
-
f.write("EXECUTIVE SUMMARY\n")
|
| 321 |
-
f.write("-"*80 + "\n")
|
| 322 |
-
if "summary" in self.results and "overall_rankings" in self.results["summary"]:
|
| 323 |
-
f.write("\nOverall Performance Ranking:\n")
|
| 324 |
-
for optimizer, data in self.results["summary"]["overall_rankings"].items():
|
| 325 |
-
f.write(f" {data['rank']}. {optimizer}: {data['avg_time_seconds']:.4f}s average\n")
|
| 326 |
-
f.write("\n")
|
| 327 |
-
|
| 328 |
-
# Detailed Results
|
| 329 |
-
f.write("\nDETAILED RESULTS BY FLEET SIZE\n")
|
| 330 |
-
f.write("-"*80 + "\n\n")
|
| 331 |
|
| 332 |
-
for
|
| 333 |
-
f.write(f"
|
| 334 |
-
f.write(
|
| 335 |
-
f.write(f"
|
|
|
|
| 336 |
|
| 337 |
-
if "
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
f.write(f" Trains Utilized (avg): {result['schedule_quality']['trains_utilized']['mean']:.1f}\n")
|
| 351 |
-
|
| 352 |
-
f.write("\n" + "-"*80 + "\n\n")
|
| 353 |
-
|
| 354 |
-
# Recommendations
|
| 355 |
-
f.write("\nRECOMMENDATIONS\n")
|
| 356 |
-
f.write("-"*80 + "\n")
|
| 357 |
-
f.write("Based on the benchmark results:\n\n")
|
| 358 |
-
|
| 359 |
-
if "summary" in self.results and "overall_rankings" in self.results["summary"]:
|
| 360 |
-
rankings = sorted(
|
| 361 |
-
self.results["summary"]["overall_rankings"].items(),
|
| 362 |
-
key=lambda x: x[1]["rank"]
|
| 363 |
-
)
|
| 364 |
-
if rankings:
|
| 365 |
-
fastest = rankings[0]
|
| 366 |
-
f.write(f"• {fastest[0]} showed the best overall performance\n")
|
| 367 |
-
f.write(f" with an average execution time of {fastest[1]['avg_time_seconds']:.4f}s\n\n")
|
| 368 |
|
| 369 |
-
f.write("
|
| 370 |
-
f.write("
|
| 371 |
-
f.write("
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
|
| 377 |
def main():
|
| 378 |
-
"""Main execution function"""
|
| 379 |
import argparse
|
| 380 |
-
|
| 381 |
parser = argparse.ArgumentParser(description="Benchmark metro schedule optimizers")
|
| 382 |
-
parser.add_argument(
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
)
|
| 389 |
-
parser.add_argument(
|
| 390 |
-
"--runs",
|
| 391 |
-
type=int,
|
| 392 |
-
default=3,
|
| 393 |
-
help="Number of runs per configuration (default: 3)"
|
| 394 |
-
)
|
| 395 |
-
parser.add_argument(
|
| 396 |
-
"--quick",
|
| 397 |
-
action="store_true",
|
| 398 |
-
help="Quick test with fewer configurations"
|
| 399 |
-
)
|
| 400 |
|
| 401 |
args = parser.parse_args()
|
| 402 |
|
| 403 |
if args.quick:
|
| 404 |
-
fleet_sizes = [10, 20, 30]
|
| 405 |
-
num_runs = 2
|
| 406 |
print("\n*** QUICK BENCHMARK MODE ***")
|
|
|
|
|
|
|
| 407 |
else:
|
| 408 |
fleet_sizes = args.fleet_sizes
|
| 409 |
-
|
| 410 |
|
| 411 |
-
# Run benchmark
|
| 412 |
benchmark = OptimizerBenchmark()
|
| 413 |
benchmark.run_comprehensive_benchmark(
|
| 414 |
fleet_sizes=fleet_sizes,
|
| 415 |
-
num_runs=
|
| 416 |
)
|
| 417 |
-
|
| 418 |
-
# Save results
|
| 419 |
-
json_file = benchmark.save_results()
|
| 420 |
-
report_file = benchmark.generate_performance_report()
|
| 421 |
-
|
| 422 |
-
print("\n" + "="*70)
|
| 423 |
-
print("BENCHMARK COMPLETE")
|
| 424 |
-
print("="*70)
|
| 425 |
-
print(f"JSON Results: {json_file}")
|
| 426 |
-
print(f"Text Report: {report_file}")
|
| 427 |
-
print("="*70 + "\n")
|
| 428 |
-
|
| 429 |
|
| 430 |
if __name__ == "__main__":
|
| 431 |
main()
|
|
|
|
| 14 |
# Add project root to path
|
| 15 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
| 16 |
|
| 17 |
+
from DataService.enhanced_generator import EnhancedMetroDataGenerator
|
| 18 |
from DataService.schedule_optimizer import MetroScheduleOptimizer
|
| 19 |
from greedyOptim.scheduler import TrainsetSchedulingOptimizer
|
| 20 |
+
from DataService.metro_models import Route, TrainHealthStatus
|
| 21 |
+
|
| 22 |
+
# --- Adapters for Uniform Interface ---
|
| 23 |
+
|
| 24 |
+
class OptimizerAdapter:
|
| 25 |
+
"""Base adapter for different optimizers"""
|
| 26 |
+
def optimize(self, data: Dict) -> Any:
|
| 27 |
+
raise NotImplementedError
|
| 28 |
+
|
| 29 |
+
class GeneticAdapter(OptimizerAdapter):
|
| 30 |
+
"""Adapter for Genetic Algorithm"""
|
| 31 |
+
def optimize(self, data: Dict) -> Any:
|
| 32 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 33 |
+
return optimizer.optimize(method='ga')
|
| 34 |
+
|
| 35 |
+
class PSOAdapter(OptimizerAdapter):
|
| 36 |
+
"""Adapter for Particle Swarm Optimization"""
|
| 37 |
+
def optimize(self, data: Dict) -> Any:
|
| 38 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 39 |
+
return optimizer.optimize(method='pso')
|
| 40 |
+
|
| 41 |
+
class SAAdapter(OptimizerAdapter):
|
| 42 |
+
"""Adapter for Simulated Annealing"""
|
| 43 |
+
def optimize(self, data: Dict) -> Any:
|
| 44 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 45 |
+
return optimizer.optimize(method='sa')
|
| 46 |
+
|
| 47 |
+
class CMAESAdapter(OptimizerAdapter):
|
| 48 |
+
"""Adapter for CMA-ES"""
|
| 49 |
+
def optimize(self, data: Dict) -> Any:
|
| 50 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 51 |
+
return optimizer.optimize(method='cmaes')
|
| 52 |
+
|
| 53 |
+
class NSGA2Adapter(OptimizerAdapter):
|
| 54 |
+
"""Adapter for NSGA-II"""
|
| 55 |
+
def optimize(self, data: Dict) -> Any:
|
| 56 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 57 |
+
return optimizer.optimize(method='nsga2')
|
| 58 |
+
|
| 59 |
+
class AdaptiveAdapter(OptimizerAdapter):
|
| 60 |
+
"""Adapter for Adaptive Algorithm"""
|
| 61 |
+
def optimize(self, data: Dict) -> Any:
|
| 62 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 63 |
+
return optimizer.optimize(method='adaptive')
|
| 64 |
+
|
| 65 |
+
class EnsembleAdapter(OptimizerAdapter):
|
| 66 |
+
"""Adapter for Ensemble Method"""
|
| 67 |
+
def optimize(self, data: Dict) -> Any:
|
| 68 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 69 |
+
return optimizer.optimize(method='ensemble')
|
| 70 |
+
|
| 71 |
+
class ORToolsAdapter(OptimizerAdapter):
|
| 72 |
+
"""Adapter for OR-Tools CP-SAT"""
|
| 73 |
+
def optimize(self, data: Dict) -> Any:
|
| 74 |
+
optimizer = TrainsetSchedulingOptimizer(data)
|
| 75 |
+
return optimizer.optimize(method='cp-sat')
|
| 76 |
|
| 77 |
|
| 78 |
class OptimizerBenchmark:
|
| 79 |
"""Benchmark different optimization algorithms"""
|
| 80 |
|
| 81 |
+
def __init__(self):
|
| 82 |
self.results = {
|
| 83 |
"benchmark_info": {
|
| 84 |
"date": datetime.now().isoformat(),
|
|
|
|
| 87 |
"test_configurations": [],
|
| 88 |
"results": []
|
| 89 |
}
|
|
|
|
| 90 |
|
| 91 |
+
def generate_test_data(self, num_trains: int) -> Dict:
|
| 92 |
"""Generate consistent test data for all optimizers"""
|
| 93 |
+
generator = EnhancedMetroDataGenerator(num_trainsets=num_trains)
|
| 94 |
+
# We need the full dataset as expected by TrainsetSchedulingEvaluator
|
| 95 |
+
full_data = generator.generate_complete_enhanced_dataset()
|
| 96 |
+
return full_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
def benchmark_optimizer(
|
| 99 |
self,
|
| 100 |
optimizer_name: str,
|
| 101 |
+
adapter_class,
|
| 102 |
num_trains: int,
|
|
|
|
| 103 |
num_runs: int = 3
|
| 104 |
) -> Dict[str, Any]:
|
| 105 |
"""Benchmark a single optimizer"""
|
| 106 |
print(f"\n{'='*70}")
|
| 107 |
print(f"Benchmarking: {optimizer_name}")
|
| 108 |
+
print(f"Fleet Size: {num_trains} trains")
|
| 109 |
print(f"{'='*70}")
|
| 110 |
|
| 111 |
run_times = []
|
| 112 |
success_count = 0
|
|
|
|
| 113 |
|
| 114 |
for run in range(num_runs):
|
| 115 |
+
print(f"Run {run + 1}/{num_runs}...", end=" ", flush=True)
|
| 116 |
|
| 117 |
try:
|
| 118 |
# Generate fresh data for each run
|
| 119 |
+
data = self.generate_test_data(num_trains)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
# Time the optimization
|
| 122 |
start_time = time.perf_counter()
|
| 123 |
|
| 124 |
+
adapter = adapter_class()
|
| 125 |
+
result = adapter.optimize(data)
|
| 126 |
|
| 127 |
end_time = time.perf_counter()
|
| 128 |
elapsed = end_time - start_time
|
| 129 |
|
| 130 |
run_times.append(elapsed)
|
| 131 |
success_count += 1
|
|
|
|
| 132 |
|
| 133 |
+
print(f"✓ Completed in {elapsed:.4f}s | Fitness: {result.fitness_score:.2f}")
|
| 134 |
|
| 135 |
except Exception as e:
|
| 136 |
+
print(f"✗ Failed: {str(e)[:100]}")
|
| 137 |
+
# import traceback
|
| 138 |
+
# traceback.print_exc()
|
| 139 |
|
| 140 |
# Calculate statistics
|
| 141 |
if run_times:
|
| 142 |
result = {
|
| 143 |
"optimizer": optimizer_name,
|
| 144 |
"fleet_size": num_trains,
|
|
|
|
| 145 |
"num_runs": num_runs,
|
| 146 |
"successful_runs": success_count,
|
| 147 |
"success_rate": f"{(success_count/num_runs)*100:.1f}%",
|
|
|
|
| 149 |
"min_seconds": min(run_times),
|
| 150 |
"max_seconds": max(run_times),
|
| 151 |
"mean_seconds": statistics.mean(run_times),
|
| 152 |
+
"stdev_seconds": statistics.stdev(run_times) if len(run_times) > 1 else 0
|
| 153 |
+
}
|
|
|
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
else:
|
| 156 |
result = {
|
| 157 |
"optimizer": optimizer_name,
|
| 158 |
"fleet_size": num_trains,
|
|
|
|
| 159 |
"num_runs": num_runs,
|
| 160 |
"successful_runs": 0,
|
| 161 |
"success_rate": "0%",
|
|
|
|
| 166 |
print(f" Success Rate: {result['success_rate']}")
|
| 167 |
if run_times:
|
| 168 |
print(f" Average Time: {result['execution_times']['mean_seconds']:.4f}s")
|
|
|
|
| 169 |
|
| 170 |
return result
|
| 171 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
def run_comprehensive_benchmark(
|
| 173 |
self,
|
| 174 |
+
fleet_sizes: List[int] = [10, 20, 30],
|
| 175 |
num_runs: int = 3
|
| 176 |
):
|
| 177 |
"""Run comprehensive benchmark across all optimizers and fleet sizes"""
|
|
|
|
| 184 |
|
| 185 |
# Define optimizers to test
|
| 186 |
optimizers = [
|
| 187 |
+
("Genetic Algorithm", GeneticAdapter),
|
| 188 |
+
("Particle Swarm", PSOAdapter),
|
| 189 |
+
("Simulated Annealing", SAAdapter),
|
| 190 |
+
("CMA-ES", CMAESAdapter),
|
| 191 |
+
("NSGA-II", NSGA2Adapter),
|
| 192 |
+
("Adaptive Algorithm", AdaptiveAdapter),
|
| 193 |
+
("Ensemble Method", EnsembleAdapter),
|
| 194 |
+
# ("OR-Tools CP-SAT", ORToolsAdapter), # Uncomment if OR-Tools is installed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
]
|
| 196 |
|
| 197 |
# Run benchmarks
|
|
|
|
| 200 |
print(f"# FLEET SIZE: {fleet_size} TRAINS")
|
| 201 |
print(f"{'#'*70}")
|
| 202 |
|
| 203 |
+
for optimizer_name, adapter_class in optimizers:
|
| 204 |
result = self.benchmark_optimizer(
|
| 205 |
optimizer_name,
|
| 206 |
+
adapter_class,
|
| 207 |
fleet_size,
|
| 208 |
num_runs=num_runs
|
| 209 |
)
|
|
|
|
| 214 |
|
| 215 |
# Generate comparison summary
|
| 216 |
self._generate_summary()
|
| 217 |
+
|
| 218 |
+
# Save results
|
| 219 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 220 |
+
filename = f"optimizer_benchmark_{timestamp}.json"
|
| 221 |
+
with open(filename, 'w') as f:
|
| 222 |
+
json.dump(self.results, f, indent=2)
|
| 223 |
+
print(f"\nResults saved to: {filename}")
|
| 224 |
|
| 225 |
def _generate_summary(self):
|
| 226 |
"""Generate comparative summary of results"""
|
|
|
|
| 250 |
avg_time = result["execution_times"]["mean_seconds"] if "execution_times" in result else "N/A"
|
| 251 |
success = result["success_rate"]
|
| 252 |
|
| 253 |
+
if isinstance(avg_time, float):
|
| 254 |
+
time_str = f"{avg_time:.4f}"
|
| 255 |
+
else:
|
| 256 |
+
time_str = str(avg_time)
|
| 257 |
+
|
| 258 |
+
print(f"{optimizer:<25} {time_str:<15} {success:<15}")
|
| 259 |
+
|
| 260 |
+
if isinstance(avg_time, float):
|
| 261 |
fleet_summary.append({
|
| 262 |
"optimizer": optimizer,
|
| 263 |
+
"time": avg_time
|
|
|
|
| 264 |
})
|
|
|
|
|
|
|
| 265 |
|
| 266 |
+
# Rank for this fleet size
|
| 267 |
+
fleet_summary.sort(key=lambda x: x["time"])
|
| 268 |
summary["by_fleet_size"][fleet_size] = fleet_summary
|
| 269 |
+
|
| 270 |
+
# Update overall stats
|
| 271 |
+
for item in fleet_summary:
|
| 272 |
+
opt = item["optimizer"]
|
| 273 |
+
if opt not in summary["overall_rankings"]:
|
| 274 |
+
summary["overall_rankings"][opt] = []
|
| 275 |
+
summary["overall_rankings"][opt].append(item["time"])
|
| 276 |
|
| 277 |
+
# Print overall rankings
|
| 278 |
print("\n" + "="*70)
|
| 279 |
+
print("OVERALL PERFORMANCE RANKINGS (by average time)")
|
| 280 |
print("="*70)
|
| 281 |
+
print(f"{'Rank':<8} {'Optimizer/Method':<30} {'Avg Time (s)':<15}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
print("-" * 70)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
+
overall_stats = []
|
| 285 |
+
for opt, times in summary["overall_rankings"].items():
|
| 286 |
+
if times:
|
| 287 |
+
overall_stats.append({
|
| 288 |
+
"optimizer": opt,
|
| 289 |
+
"avg_time": statistics.mean(times)
|
| 290 |
+
})
|
| 291 |
|
| 292 |
+
overall_stats.sort(key=lambda x: x["avg_time"])
|
|
|
|
| 293 |
|
| 294 |
+
for i, stat in enumerate(overall_stats):
|
| 295 |
+
print(f"{i+1:<8} {stat['optimizer']:<30} {stat['avg_time']:.4f}")
|
|
|
|
| 296 |
|
| 297 |
+
# Save report to text file
|
| 298 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 299 |
+
report_file = f"optimizer_performance_report_{timestamp}.txt"
|
|
|
|
|
|
|
| 300 |
|
| 301 |
+
with open(report_file, "w") as f:
|
| 302 |
+
f.write("OPTIMIZER PERFORMANCE BENCHMARK REPORT\n")
|
| 303 |
+
f.write(f"Date: {datetime.now().isoformat()}\n")
|
| 304 |
+
f.write("="*70 + "\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
+
for fleet_size in fleet_sizes:
|
| 307 |
+
f.write(f"Fleet Size: {fleet_size} trains\n")
|
| 308 |
+
f.write("-" * 70 + "\n")
|
| 309 |
+
f.write(f"{'Optimizer':<25} {'Avg Time (s)':<15} {'Success Rate':<15}\n")
|
| 310 |
+
f.write("-" * 70 + "\n")
|
| 311 |
|
| 312 |
+
fleet_results = [r for r in self.results["results"] if r["fleet_size"] == fleet_size]
|
| 313 |
+
for result in fleet_results:
|
| 314 |
+
optimizer = result["optimizer"]
|
| 315 |
+
avg_time = result["execution_times"]["mean_seconds"] if "execution_times" in result else "N/A"
|
| 316 |
+
success = result["success_rate"]
|
| 317 |
+
|
| 318 |
+
if isinstance(avg_time, float):
|
| 319 |
+
time_str = f"{avg_time:.4f}"
|
| 320 |
+
else:
|
| 321 |
+
time_str = str(avg_time)
|
| 322 |
+
|
| 323 |
+
f.write(f"{optimizer:<25} {time_str:<15} {success:<15}\n")
|
| 324 |
+
f.write("\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
+
f.write("="*70 + "\n")
|
| 327 |
+
f.write("OVERALL RANKINGS\n")
|
| 328 |
+
f.write("="*70 + "\n")
|
| 329 |
+
for i, stat in enumerate(overall_stats):
|
| 330 |
+
f.write(f"{i+1}. {stat['optimizer']}: {stat['avg_time']:.4f}s\n")
|
| 331 |
+
|
| 332 |
+
print(f"\nPerformance report saved to: {report_file}")
|
| 333 |
|
| 334 |
def main():
|
|
|
|
| 335 |
import argparse
|
|
|
|
| 336 |
parser = argparse.ArgumentParser(description="Benchmark metro schedule optimizers")
|
| 337 |
+
parser.add_argument("--fleet-sizes", type=int, nargs="+", default=[10, 20, 30],
|
| 338 |
+
help="Fleet sizes to test (default: 10 20 30)")
|
| 339 |
+
parser.add_argument("--runs", type=int, default=3,
|
| 340 |
+
help="Number of runs per configuration (default: 3)")
|
| 341 |
+
parser.add_argument("--quick", action="store_true",
|
| 342 |
+
help="Quick test with fewer configurations")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
args = parser.parse_args()
|
| 345 |
|
| 346 |
if args.quick:
|
|
|
|
|
|
|
| 347 |
print("\n*** QUICK BENCHMARK MODE ***")
|
| 348 |
+
fleet_sizes = [10, 20]
|
| 349 |
+
runs = 1
|
| 350 |
else:
|
| 351 |
fleet_sizes = args.fleet_sizes
|
| 352 |
+
runs = args.runs
|
| 353 |
|
|
|
|
| 354 |
benchmark = OptimizerBenchmark()
|
| 355 |
benchmark.run_comprehensive_benchmark(
|
| 356 |
fleet_sizes=fleet_sizes,
|
| 357 |
+
num_runs=runs
|
| 358 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
if __name__ == "__main__":
|
| 361 |
main()
|
benchmarks/optimizer_performance/benchmark_schedule_performance.py
CHANGED
|
@@ -152,7 +152,7 @@ class SchedulePerformanceBenchmark:
|
|
| 152 |
def benchmark_greedy_optimizers(
|
| 153 |
self,
|
| 154 |
num_trains: int,
|
| 155 |
-
methods: List[str] = ['ga', 'cmaes', 'pso'],
|
| 156 |
num_runs: int = 3
|
| 157 |
) -> Dict[str, List[Dict[str, Any]]]:
|
| 158 |
"""Benchmark greedyOptim methods"""
|
|
@@ -451,8 +451,8 @@ def main():
|
|
| 451 |
parser.add_argument(
|
| 452 |
"--methods",
|
| 453 |
nargs="+",
|
| 454 |
-
default=['ga', 'cmaes', 'pso'],
|
| 455 |
-
help="Greedy optimization methods to test (default: ga cmaes pso)"
|
| 456 |
)
|
| 457 |
parser.add_argument(
|
| 458 |
"--runs",
|
|
|
|
| 152 |
def benchmark_greedy_optimizers(
|
| 153 |
self,
|
| 154 |
num_trains: int,
|
| 155 |
+
methods: List[str] = ['ga', 'cmaes', 'pso', 'sa', 'nsga2', 'adaptive', 'ensemble'],
|
| 156 |
num_runs: int = 3
|
| 157 |
) -> Dict[str, List[Dict[str, Any]]]:
|
| 158 |
"""Benchmark greedyOptim methods"""
|
|
|
|
| 451 |
parser.add_argument(
|
| 452 |
"--methods",
|
| 453 |
nargs="+",
|
| 454 |
+
default=['ga', 'cmaes', 'pso', 'sa', 'nsga2', 'adaptive', 'ensemble'],
|
| 455 |
+
help="Greedy optimization methods to test (default: ga cmaes pso sa nsga2 adaptive ensemble)"
|
| 456 |
)
|
| 457 |
parser.add_argument(
|
| 458 |
"--runs",
|
benchmarks/service_quality/benchmark_service_quality.py
CHANGED
|
@@ -1,214 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Service Quality Benchmark Script
|
| 3 |
-
Runs comprehensive benchmarks for headway consistency, wait times, and coverage.
|
| 4 |
-
"""
|
| 5 |
-
import time
|
| 6 |
-
import json
|
| 7 |
-
from typing import Dict, List
|
| 8 |
-
from datetime import datetime
|
| 9 |
-
|
| 10 |
-
from .service_analyzer import ServiceQualityAnalyzer, ServiceQualityMetrics
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def run_service_quality_benchmark(
|
| 14 |
-
schedules: List[Dict],
|
| 15 |
-
output_file: str = "service_quality_benchmark_results.json",
|
| 16 |
-
verbose: bool = True
|
| 17 |
-
) -> Dict:
|
| 18 |
-
"""Run service quality benchmark on multiple schedules.
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
schedules: List of schedule dictionaries to analyze
|
| 22 |
-
output_file: Path to save results JSON
|
| 23 |
-
verbose: Whether to print detailed output
|
| 24 |
-
|
| 25 |
-
Returns:
|
| 26 |
-
Dictionary with benchmark results
|
| 27 |
-
"""
|
| 28 |
-
if verbose:
|
| 29 |
-
print("=" * 80)
|
| 30 |
-
print("SERVICE QUALITY BENCHMARK")
|
| 31 |
-
print("=" * 80)
|
| 32 |
-
print(f"Analyzing {len(schedules)} schedules...")
|
| 33 |
-
print()
|
| 34 |
-
|
| 35 |
-
analyzer = ServiceQualityAnalyzer()
|
| 36 |
-
start_time = time.time()
|
| 37 |
-
|
| 38 |
-
results = []
|
| 39 |
-
|
| 40 |
-
# Analyze each schedule
|
| 41 |
-
for i, schedule in enumerate(schedules, 1):
|
| 42 |
-
if verbose:
|
| 43 |
-
print(f"Schedule {i}/{len(schedules)}:")
|
| 44 |
-
|
| 45 |
-
schedule_start = time.time()
|
| 46 |
-
metrics = analyzer.analyze_schedule(schedule)
|
| 47 |
-
analysis_time = (time.time() - schedule_start) * 1000 # ms
|
| 48 |
-
|
| 49 |
-
result = {
|
| 50 |
-
'schedule_id': i,
|
| 51 |
-
'analysis_time_ms': round(analysis_time, 2),
|
| 52 |
-
'metrics': {
|
| 53 |
-
'headway_consistency': {
|
| 54 |
-
'peak_mean_minutes': round(metrics.peak_headway_mean, 2),
|
| 55 |
-
'peak_std_minutes': round(metrics.peak_headway_std, 2),
|
| 56 |
-
'peak_cv': round(metrics.peak_headway_coefficient_variation, 3),
|
| 57 |
-
'offpeak_mean_minutes': round(metrics.offpeak_headway_mean, 2),
|
| 58 |
-
'offpeak_std_minutes': round(metrics.offpeak_headway_std, 2),
|
| 59 |
-
'offpeak_cv': round(metrics.offpeak_headway_coefficient_variation, 3),
|
| 60 |
-
'score': round(metrics.headway_consistency_score, 2)
|
| 61 |
-
},
|
| 62 |
-
'wait_times': {
|
| 63 |
-
'avg_wait_peak_minutes': round(metrics.avg_wait_time_peak, 2),
|
| 64 |
-
'max_wait_peak_minutes': round(metrics.max_wait_time_peak, 2),
|
| 65 |
-
'avg_wait_offpeak_minutes': round(metrics.avg_wait_time_offpeak, 2),
|
| 66 |
-
'max_wait_offpeak_minutes': round(metrics.max_wait_time_offpeak, 2),
|
| 67 |
-
'reduction_vs_baseline_percent': round(metrics.wait_time_reduction_vs_baseline, 2),
|
| 68 |
-
'score': round(metrics.wait_time_score, 2)
|
| 69 |
-
},
|
| 70 |
-
'service_coverage': {
|
| 71 |
-
'operational_hours': round(metrics.operational_hours, 2),
|
| 72 |
-
'peak_hours_covered': round(metrics.peak_hours_covered, 2),
|
| 73 |
-
'offpeak_hours_covered': round(metrics.offpeak_hours_covered, 2),
|
| 74 |
-
'coverage_percent': round(metrics.service_coverage_percent, 2),
|
| 75 |
-
'peak_coverage_percent': round(metrics.peak_coverage_percent, 2),
|
| 76 |
-
'service_gaps': metrics.gaps_in_service,
|
| 77 |
-
'score': round(metrics.coverage_score, 2)
|
| 78 |
-
},
|
| 79 |
-
'overall_quality_score': round(metrics.overall_quality_score, 2)
|
| 80 |
-
}
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
results.append(result)
|
| 84 |
-
|
| 85 |
-
if verbose:
|
| 86 |
-
print(f" Headway Consistency Score: {result['metrics']['headway_consistency']['score']:.2f}/100")
|
| 87 |
-
print(f" Peak: {metrics.peak_headway_mean:.1f}min ± {metrics.peak_headway_std:.1f}min (CV: {metrics.peak_headway_coefficient_variation:.3f})")
|
| 88 |
-
print(f" Off-Peak: {metrics.offpeak_headway_mean:.1f}min ± {metrics.offpeak_headway_std:.1f}min (CV: {metrics.offpeak_headway_coefficient_variation:.3f})")
|
| 89 |
-
print(f" Wait Time Score: {result['metrics']['wait_times']['score']:.2f}/100")
|
| 90 |
-
print(f" Peak avg wait: {metrics.avg_wait_time_peak:.1f}min (max: {metrics.max_wait_time_peak:.1f}min)")
|
| 91 |
-
print(f" Off-peak avg wait: {metrics.avg_wait_time_offpeak:.1f}min (max: {metrics.max_wait_time_offpeak:.1f}min)")
|
| 92 |
-
print(f" Improvement vs baseline: {metrics.wait_time_reduction_vs_baseline:.1f}%")
|
| 93 |
-
print(f" Coverage Score: {result['metrics']['service_coverage']['score']:.2f}/100")
|
| 94 |
-
print(f" Peak coverage: {metrics.peak_coverage_percent:.1f}%")
|
| 95 |
-
print(f" Overall coverage: {metrics.service_coverage_percent:.1f}%")
|
| 96 |
-
print(f" Service gaps: {metrics.gaps_in_service}")
|
| 97 |
-
print(f" OVERALL QUALITY: {metrics.overall_quality_score:.2f}/100")
|
| 98 |
-
print(f" Analysis time: {analysis_time:.2f}ms")
|
| 99 |
-
print()
|
| 100 |
-
|
| 101 |
-
total_time = time.time() - start_time
|
| 102 |
-
|
| 103 |
-
# Calculate aggregate statistics
|
| 104 |
-
aggregate = _calculate_aggregate_stats(results)
|
| 105 |
-
|
| 106 |
-
# Prepare final output
|
| 107 |
-
benchmark_results = {
|
| 108 |
-
'benchmark_info': {
|
| 109 |
-
'timestamp': datetime.now().isoformat(),
|
| 110 |
-
'total_schedules': len(schedules),
|
| 111 |
-
'total_time_seconds': round(total_time, 3),
|
| 112 |
-
'avg_analysis_time_ms': round(aggregate['avg_analysis_time_ms'], 2)
|
| 113 |
-
},
|
| 114 |
-
'aggregate_metrics': aggregate,
|
| 115 |
-
'individual_results': results
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
# Print summary
|
| 119 |
-
if verbose:
|
| 120 |
-
print("=" * 80)
|
| 121 |
-
print("AGGREGATE RESULTS")
|
| 122 |
-
print("=" * 80)
|
| 123 |
-
print(f"Schedules analyzed: {len(schedules)}")
|
| 124 |
-
print(f"Total benchmark time: {total_time:.2f}s")
|
| 125 |
-
print()
|
| 126 |
-
print("Average Scores:")
|
| 127 |
-
print(f" Headway Consistency: {aggregate['avg_headway_score']:.2f}/100")
|
| 128 |
-
print(f" Wait Time Quality: {aggregate['avg_wait_score']:.2f}/100")
|
| 129 |
-
print(f" Service Coverage: {aggregate['avg_coverage_score']:.2f}/100")
|
| 130 |
-
print(f" Overall Quality: {aggregate['avg_overall_score']:.2f}/100")
|
| 131 |
-
print()
|
| 132 |
-
print("Best Performers:")
|
| 133 |
-
print(f" Best headway consistency: Schedule {aggregate['best_headway_schedule']} ({aggregate['best_headway_score']:.2f})")
|
| 134 |
-
print(f" Best wait times: Schedule {aggregate['best_wait_schedule']} ({aggregate['best_wait_score']:.2f})")
|
| 135 |
-
print(f" Best coverage: Schedule {aggregate['best_coverage_schedule']} ({aggregate['best_coverage_score']:.2f})")
|
| 136 |
-
print(f" Best overall: Schedule {aggregate['best_overall_schedule']} ({aggregate['best_overall_score']:.2f})")
|
| 137 |
-
print()
|
| 138 |
-
print("Service Quality Metrics:")
|
| 139 |
-
print(f" Avg peak headway: {aggregate['avg_peak_headway']:.2f} ± {aggregate['avg_peak_headway_std']:.2f} minutes")
|
| 140 |
-
print(f" Avg off-peak headway: {aggregate['avg_offpeak_headway']:.2f} ± {aggregate['avg_offpeak_headway_std']:.2f} minutes")
|
| 141 |
-
print(f" Avg peak wait time: {aggregate['avg_peak_wait']:.2f} minutes")
|
| 142 |
-
print(f" Avg wait time reduction: {aggregate['avg_wait_reduction']:.1f}%")
|
| 143 |
-
print(f" Avg service coverage: {aggregate['avg_coverage_percent']:.1f}%")
|
| 144 |
-
print(f" Avg service gaps: {aggregate['avg_service_gaps']:.1f}")
|
| 145 |
-
print()
|
| 146 |
-
|
| 147 |
-
# Save to file
|
| 148 |
-
with open(output_file, 'w') as f:
|
| 149 |
-
json.dump(benchmark_results, f, indent=2)
|
| 150 |
-
|
| 151 |
-
if verbose:
|
| 152 |
-
print(f"Results saved to: {output_file}")
|
| 153 |
-
print("=" * 80)
|
| 154 |
-
|
| 155 |
-
return benchmark_results
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
def _calculate_aggregate_stats(results: List[Dict]) -> Dict:
|
| 159 |
-
"""Calculate aggregate statistics across all results."""
|
| 160 |
-
if not results:
|
| 161 |
-
return {}
|
| 162 |
-
|
| 163 |
-
# Extract all metrics
|
| 164 |
-
headway_scores = [r['metrics']['headway_consistency']['score'] for r in results]
|
| 165 |
-
wait_scores = [r['metrics']['wait_times']['score'] for r in results]
|
| 166 |
-
coverage_scores = [r['metrics']['service_coverage']['score'] for r in results]
|
| 167 |
-
overall_scores = [r['metrics']['overall_quality_score'] for r in results]
|
| 168 |
-
|
| 169 |
-
peak_headways = [r['metrics']['headway_consistency']['peak_mean_minutes'] for r in results]
|
| 170 |
-
peak_headway_stds = [r['metrics']['headway_consistency']['peak_std_minutes'] for r in results]
|
| 171 |
-
offpeak_headways = [r['metrics']['headway_consistency']['offpeak_mean_minutes'] for r in results]
|
| 172 |
-
offpeak_headway_stds = [r['metrics']['headway_consistency']['offpeak_std_minutes'] for r in results]
|
| 173 |
-
peak_waits = [r['metrics']['wait_times']['avg_wait_peak_minutes'] for r in results]
|
| 174 |
-
wait_reductions = [r['metrics']['wait_times']['reduction_vs_baseline_percent'] for r in results]
|
| 175 |
-
coverage_percents = [r['metrics']['service_coverage']['coverage_percent'] for r in results]
|
| 176 |
-
service_gaps = [r['metrics']['service_coverage']['service_gaps'] for r in results]
|
| 177 |
-
analysis_times = [r['analysis_time_ms'] for r in results]
|
| 178 |
-
|
| 179 |
-
# Find best performers
|
| 180 |
-
best_headway_idx = headway_scores.index(max(headway_scores))
|
| 181 |
-
best_wait_idx = wait_scores.index(max(wait_scores))
|
| 182 |
-
best_coverage_idx = coverage_scores.index(max(coverage_scores))
|
| 183 |
-
best_overall_idx = overall_scores.index(max(overall_scores))
|
| 184 |
-
|
| 185 |
-
return {
|
| 186 |
-
'avg_headway_score': round(sum(headway_scores) / len(headway_scores), 2),
|
| 187 |
-
'avg_wait_score': round(sum(wait_scores) / len(wait_scores), 2),
|
| 188 |
-
'avg_coverage_score': round(sum(coverage_scores) / len(coverage_scores), 2),
|
| 189 |
-
'avg_overall_score': round(sum(overall_scores) / len(overall_scores), 2),
|
| 190 |
-
|
| 191 |
-
'best_headway_schedule': best_headway_idx + 1,
|
| 192 |
-
'best_headway_score': round(max(headway_scores), 2),
|
| 193 |
-
'best_wait_schedule': best_wait_idx + 1,
|
| 194 |
-
'best_wait_score': round(max(wait_scores), 2),
|
| 195 |
-
'best_coverage_schedule': best_coverage_idx + 1,
|
| 196 |
-
'best_coverage_score': round(max(coverage_scores), 2),
|
| 197 |
-
'best_overall_schedule': best_overall_idx + 1,
|
| 198 |
-
'best_overall_score': round(max(overall_scores), 2),
|
| 199 |
-
|
| 200 |
-
'avg_peak_headway': round(sum(peak_headways) / len(peak_headways), 2),
|
| 201 |
-
'avg_peak_headway_std': round(sum(peak_headway_stds) / len(peak_headway_stds), 2),
|
| 202 |
-
'avg_offpeak_headway': round(sum(offpeak_headways) / len(offpeak_headways), 2),
|
| 203 |
-
'avg_offpeak_headway_std': round(sum(offpeak_headway_stds) / len(offpeak_headway_stds), 2),
|
| 204 |
-
'avg_peak_wait': round(sum(peak_waits) / len(peak_waits), 2),
|
| 205 |
-
'avg_wait_reduction': round(sum(wait_reductions) / len(wait_reductions), 2),
|
| 206 |
-
'avg_coverage_percent': round(sum(coverage_percents) / len(coverage_percents), 2),
|
| 207 |
-
'avg_service_gaps': round(sum(service_gaps) / len(service_gaps), 2),
|
| 208 |
-
'avg_analysis_time_ms': round(sum(analysis_times) / len(analysis_times), 2)
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
if __name__ == "__main__":
|
| 213 |
-
print("Service Quality Benchmark Module")
|
| 214 |
-
print("Import and use run_service_quality_benchmark() to analyze schedules")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
benchmarks/service_quality/test_service_quality_benchmark.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
-
|
| 4 |
"""
|
|
|
|
| 5 |
import sys
|
| 6 |
import os
|
| 7 |
|
|
@@ -12,7 +13,212 @@ from DataService.enhanced_generator import EnhancedMetroDataGenerator
|
|
| 12 |
from greedyOptim.scheduler import TrainsetSchedulingOptimizer
|
| 13 |
from greedyOptim.models import OptimizationConfig
|
| 14 |
from greedyOptim.service_blocks import create_service_blocks_for_schedule
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
def create_schedule_with_service_blocks(result, method_name):
|
|
@@ -76,7 +282,7 @@ def main():
|
|
| 76 |
)
|
| 77 |
|
| 78 |
# Generate schedules using different methods
|
| 79 |
-
methods = ['ga', 'pso', 'sa']
|
| 80 |
schedules = []
|
| 81 |
|
| 82 |
print(f"Generating {len(methods)} schedules using different optimization methods...")
|
|
|
|
| 1 |
"""
|
| 2 |
+
Service Quality Benchmark Script
|
| 3 |
+
Runs comprehensive benchmarks for headway consistency, wait times, and coverage.
|
| 4 |
"""
|
| 5 |
+
|
| 6 |
import sys
|
| 7 |
import os
|
| 8 |
|
|
|
|
| 13 |
from greedyOptim.scheduler import TrainsetSchedulingOptimizer
|
| 14 |
from greedyOptim.models import OptimizationConfig
|
| 15 |
from greedyOptim.service_blocks import create_service_blocks_for_schedule
|
| 16 |
+
|
| 17 |
+
import time
|
| 18 |
+
import json
|
| 19 |
+
from typing import Dict, List
|
| 20 |
+
from datetime import datetime
|
| 21 |
+
|
| 22 |
+
from .service_analyzer import ServiceQualityAnalyzer, ServiceQualityMetrics
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def run_service_quality_benchmark(
|
| 26 |
+
schedules: List[Dict],
|
| 27 |
+
output_file: str = "service_quality_benchmark_results.json",
|
| 28 |
+
verbose: bool = True
|
| 29 |
+
) -> Dict:
|
| 30 |
+
"""Run service quality benchmark on multiple schedules.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
schedules: List of schedule dictionaries to analyze
|
| 34 |
+
output_file: Path to save results JSON
|
| 35 |
+
verbose: Whether to print detailed output
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
Dictionary with benchmark results
|
| 39 |
+
"""
|
| 40 |
+
if verbose:
|
| 41 |
+
print("=" * 80)
|
| 42 |
+
print("SERVICE QUALITY BENCHMARK")
|
| 43 |
+
print("=" * 80)
|
| 44 |
+
print(f"Analyzing {len(schedules)} schedules...")
|
| 45 |
+
print()
|
| 46 |
+
|
| 47 |
+
analyzer = ServiceQualityAnalyzer()
|
| 48 |
+
start_time = time.time()
|
| 49 |
+
|
| 50 |
+
results = []
|
| 51 |
+
|
| 52 |
+
# Analyze each schedule
|
| 53 |
+
for i, schedule in enumerate(schedules, 1):
|
| 54 |
+
if verbose:
|
| 55 |
+
print(f"Schedule {i}/{len(schedules)}:")
|
| 56 |
+
|
| 57 |
+
schedule_start = time.time()
|
| 58 |
+
metrics = analyzer.analyze_schedule(schedule)
|
| 59 |
+
analysis_time = (time.time() - schedule_start) * 1000 # ms
|
| 60 |
+
|
| 61 |
+
result = {
|
| 62 |
+
'schedule_id': i,
|
| 63 |
+
'analysis_time_ms': round(analysis_time, 2),
|
| 64 |
+
'metrics': {
|
| 65 |
+
'headway_consistency': {
|
| 66 |
+
'peak_mean_minutes': round(metrics.peak_headway_mean, 2),
|
| 67 |
+
'peak_std_minutes': round(metrics.peak_headway_std, 2),
|
| 68 |
+
'peak_cv': round(metrics.peak_headway_coefficient_variation, 3),
|
| 69 |
+
'offpeak_mean_minutes': round(metrics.offpeak_headway_mean, 2),
|
| 70 |
+
'offpeak_std_minutes': round(metrics.offpeak_headway_std, 2),
|
| 71 |
+
'offpeak_cv': round(metrics.offpeak_headway_coefficient_variation, 3),
|
| 72 |
+
'score': round(metrics.headway_consistency_score, 2)
|
| 73 |
+
},
|
| 74 |
+
'wait_times': {
|
| 75 |
+
'avg_wait_peak_minutes': round(metrics.avg_wait_time_peak, 2),
|
| 76 |
+
'max_wait_peak_minutes': round(metrics.max_wait_time_peak, 2),
|
| 77 |
+
'avg_wait_offpeak_minutes': round(metrics.avg_wait_time_offpeak, 2),
|
| 78 |
+
'max_wait_offpeak_minutes': round(metrics.max_wait_time_offpeak, 2),
|
| 79 |
+
'reduction_vs_baseline_percent': round(metrics.wait_time_reduction_vs_baseline, 2),
|
| 80 |
+
'score': round(metrics.wait_time_score, 2)
|
| 81 |
+
},
|
| 82 |
+
'service_coverage': {
|
| 83 |
+
'operational_hours': round(metrics.operational_hours, 2),
|
| 84 |
+
'peak_hours_covered': round(metrics.peak_hours_covered, 2),
|
| 85 |
+
'offpeak_hours_covered': round(metrics.offpeak_hours_covered, 2),
|
| 86 |
+
'coverage_percent': round(metrics.service_coverage_percent, 2),
|
| 87 |
+
'peak_coverage_percent': round(metrics.peak_coverage_percent, 2),
|
| 88 |
+
'service_gaps': metrics.gaps_in_service,
|
| 89 |
+
'score': round(metrics.coverage_score, 2)
|
| 90 |
+
},
|
| 91 |
+
'overall_quality_score': round(metrics.overall_quality_score, 2)
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
results.append(result)
|
| 96 |
+
|
| 97 |
+
if verbose:
|
| 98 |
+
print(f" Headway Consistency Score: {result['metrics']['headway_consistency']['score']:.2f}/100")
|
| 99 |
+
print(f" Peak: {metrics.peak_headway_mean:.1f}min ± {metrics.peak_headway_std:.1f}min (CV: {metrics.peak_headway_coefficient_variation:.3f})")
|
| 100 |
+
print(f" Off-Peak: {metrics.offpeak_headway_mean:.1f}min ± {metrics.offpeak_headway_std:.1f}min (CV: {metrics.offpeak_headway_coefficient_variation:.3f})")
|
| 101 |
+
print(f" Wait Time Score: {result['metrics']['wait_times']['score']:.2f}/100")
|
| 102 |
+
print(f" Peak avg wait: {metrics.avg_wait_time_peak:.1f}min (max: {metrics.max_wait_time_peak:.1f}min)")
|
| 103 |
+
print(f" Off-peak avg wait: {metrics.avg_wait_time_offpeak:.1f}min (max: {metrics.max_wait_time_offpeak:.1f}min)")
|
| 104 |
+
print(f" Improvement vs baseline: {metrics.wait_time_reduction_vs_baseline:.1f}%")
|
| 105 |
+
print(f" Coverage Score: {result['metrics']['service_coverage']['score']:.2f}/100")
|
| 106 |
+
print(f" Peak coverage: {metrics.peak_coverage_percent:.1f}%")
|
| 107 |
+
print(f" Overall coverage: {metrics.service_coverage_percent:.1f}%")
|
| 108 |
+
print(f" Service gaps: {metrics.gaps_in_service}")
|
| 109 |
+
print(f" OVERALL QUALITY: {metrics.overall_quality_score:.2f}/100")
|
| 110 |
+
print(f" Analysis time: {analysis_time:.2f}ms")
|
| 111 |
+
print()
|
| 112 |
+
|
| 113 |
+
total_time = time.time() - start_time
|
| 114 |
+
|
| 115 |
+
# Calculate aggregate statistics
|
| 116 |
+
aggregate = _calculate_aggregate_stats(results)
|
| 117 |
+
|
| 118 |
+
# Prepare final output
|
| 119 |
+
benchmark_results = {
|
| 120 |
+
'benchmark_info': {
|
| 121 |
+
'timestamp': datetime.now().isoformat(),
|
| 122 |
+
'total_schedules': len(schedules),
|
| 123 |
+
'total_time_seconds': round(total_time, 3),
|
| 124 |
+
'avg_analysis_time_ms': round(aggregate['avg_analysis_time_ms'], 2)
|
| 125 |
+
},
|
| 126 |
+
'aggregate_metrics': aggregate,
|
| 127 |
+
'individual_results': results
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
# Print summary
|
| 131 |
+
if verbose:
|
| 132 |
+
print("=" * 80)
|
| 133 |
+
print("AGGREGATE RESULTS")
|
| 134 |
+
print("=" * 80)
|
| 135 |
+
print(f"Schedules analyzed: {len(schedules)}")
|
| 136 |
+
print(f"Total benchmark time: {total_time:.2f}s")
|
| 137 |
+
print()
|
| 138 |
+
print("Average Scores:")
|
| 139 |
+
print(f" Headway Consistency: {aggregate['avg_headway_score']:.2f}/100")
|
| 140 |
+
print(f" Wait Time Quality: {aggregate['avg_wait_score']:.2f}/100")
|
| 141 |
+
print(f" Service Coverage: {aggregate['avg_coverage_score']:.2f}/100")
|
| 142 |
+
print(f" Overall Quality: {aggregate['avg_overall_score']:.2f}/100")
|
| 143 |
+
print()
|
| 144 |
+
print("Best Performers:")
|
| 145 |
+
print(f" Best headway consistency: Schedule {aggregate['best_headway_schedule']} ({aggregate['best_headway_score']:.2f})")
|
| 146 |
+
print(f" Best wait times: Schedule {aggregate['best_wait_schedule']} ({aggregate['best_wait_score']:.2f})")
|
| 147 |
+
print(f" Best coverage: Schedule {aggregate['best_coverage_schedule']} ({aggregate['best_coverage_score']:.2f})")
|
| 148 |
+
print(f" Best overall: Schedule {aggregate['best_overall_schedule']} ({aggregate['best_overall_score']:.2f})")
|
| 149 |
+
print()
|
| 150 |
+
print("Service Quality Metrics:")
|
| 151 |
+
print(f" Avg peak headway: {aggregate['avg_peak_headway']:.2f} ± {aggregate['avg_peak_headway_std']:.2f} minutes")
|
| 152 |
+
print(f" Avg off-peak headway: {aggregate['avg_offpeak_headway']:.2f} ± {aggregate['avg_offpeak_headway_std']:.2f} minutes")
|
| 153 |
+
print(f" Avg peak wait time: {aggregate['avg_peak_wait']:.2f} minutes")
|
| 154 |
+
print(f" Avg wait time reduction: {aggregate['avg_wait_reduction']:.1f}%")
|
| 155 |
+
print(f" Avg service coverage: {aggregate['avg_coverage_percent']:.1f}%")
|
| 156 |
+
print(f" Avg service gaps: {aggregate['avg_service_gaps']:.1f}")
|
| 157 |
+
print()
|
| 158 |
+
|
| 159 |
+
# Save to file
|
| 160 |
+
with open(output_file, 'w') as f:
|
| 161 |
+
json.dump(benchmark_results, f, indent=2)
|
| 162 |
+
|
| 163 |
+
if verbose:
|
| 164 |
+
print(f"Results saved to: {output_file}")
|
| 165 |
+
print("=" * 80)
|
| 166 |
+
|
| 167 |
+
return benchmark_results
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _calculate_aggregate_stats(results: List[Dict]) -> Dict:
|
| 171 |
+
"""Calculate aggregate statistics across all results."""
|
| 172 |
+
if not results:
|
| 173 |
+
return {}
|
| 174 |
+
|
| 175 |
+
# Extract all metrics
|
| 176 |
+
headway_scores = [r['metrics']['headway_consistency']['score'] for r in results]
|
| 177 |
+
wait_scores = [r['metrics']['wait_times']['score'] for r in results]
|
| 178 |
+
coverage_scores = [r['metrics']['service_coverage']['score'] for r in results]
|
| 179 |
+
overall_scores = [r['metrics']['overall_quality_score'] for r in results]
|
| 180 |
+
|
| 181 |
+
peak_headways = [r['metrics']['headway_consistency']['peak_mean_minutes'] for r in results]
|
| 182 |
+
peak_headway_stds = [r['metrics']['headway_consistency']['peak_std_minutes'] for r in results]
|
| 183 |
+
offpeak_headways = [r['metrics']['headway_consistency']['offpeak_mean_minutes'] for r in results]
|
| 184 |
+
offpeak_headway_stds = [r['metrics']['headway_consistency']['offpeak_std_minutes'] for r in results]
|
| 185 |
+
peak_waits = [r['metrics']['wait_times']['avg_wait_peak_minutes'] for r in results]
|
| 186 |
+
wait_reductions = [r['metrics']['wait_times']['reduction_vs_baseline_percent'] for r in results]
|
| 187 |
+
coverage_percents = [r['metrics']['service_coverage']['coverage_percent'] for r in results]
|
| 188 |
+
service_gaps = [r['metrics']['service_coverage']['service_gaps'] for r in results]
|
| 189 |
+
analysis_times = [r['analysis_time_ms'] for r in results]
|
| 190 |
+
|
| 191 |
+
# Find best performers
|
| 192 |
+
best_headway_idx = headway_scores.index(max(headway_scores))
|
| 193 |
+
best_wait_idx = wait_scores.index(max(wait_scores))
|
| 194 |
+
best_coverage_idx = coverage_scores.index(max(coverage_scores))
|
| 195 |
+
best_overall_idx = overall_scores.index(max(overall_scores))
|
| 196 |
+
|
| 197 |
+
return {
|
| 198 |
+
'avg_headway_score': round(sum(headway_scores) / len(headway_scores), 2),
|
| 199 |
+
'avg_wait_score': round(sum(wait_scores) / len(wait_scores), 2),
|
| 200 |
+
'avg_coverage_score': round(sum(coverage_scores) / len(coverage_scores), 2),
|
| 201 |
+
'avg_overall_score': round(sum(overall_scores) / len(overall_scores), 2),
|
| 202 |
+
|
| 203 |
+
'best_headway_schedule': best_headway_idx + 1,
|
| 204 |
+
'best_headway_score': round(max(headway_scores), 2),
|
| 205 |
+
'best_wait_schedule': best_wait_idx + 1,
|
| 206 |
+
'best_wait_score': round(max(wait_scores), 2),
|
| 207 |
+
'best_coverage_schedule': best_coverage_idx + 1,
|
| 208 |
+
'best_coverage_score': round(max(coverage_scores), 2),
|
| 209 |
+
'best_overall_schedule': best_overall_idx + 1,
|
| 210 |
+
'best_overall_score': round(max(overall_scores), 2),
|
| 211 |
+
|
| 212 |
+
'avg_peak_headway': round(sum(peak_headways) / len(peak_headways), 2),
|
| 213 |
+
'avg_peak_headway_std': round(sum(peak_headway_stds) / len(peak_headway_stds), 2),
|
| 214 |
+
'avg_offpeak_headway': round(sum(offpeak_headways) / len(offpeak_headways), 2),
|
| 215 |
+
'avg_offpeak_headway_std': round(sum(offpeak_headway_stds) / len(offpeak_headway_stds), 2),
|
| 216 |
+
'avg_peak_wait': round(sum(peak_waits) / len(peak_waits), 2),
|
| 217 |
+
'avg_wait_reduction': round(sum(wait_reductions) / len(wait_reductions), 2),
|
| 218 |
+
'avg_coverage_percent': round(sum(coverage_percents) / len(coverage_percents), 2),
|
| 219 |
+
'avg_service_gaps': round(sum(service_gaps) / len(service_gaps), 2),
|
| 220 |
+
'avg_analysis_time_ms': round(sum(analysis_times) / len(analysis_times), 2)
|
| 221 |
+
}
|
| 222 |
|
| 223 |
|
| 224 |
def create_schedule_with_service_blocks(result, method_name):
|
|
|
|
| 282 |
)
|
| 283 |
|
| 284 |
# Generate schedules using different methods
|
| 285 |
+
methods = ['ga', 'pso', 'sa', 'cmaes', 'nsga2', 'adaptive', 'ensemble']
|
| 286 |
schedules = []
|
| 287 |
|
| 288 |
print(f"Generating {len(methods)} schedules using different optimization methods...")
|
greedyOptim/hybrid_optimizers.py
CHANGED
|
@@ -237,9 +237,16 @@ class AdaptiveOptimizer:
|
|
| 237 |
improvements[name] = 0
|
| 238 |
|
| 239 |
# Update probabilities (softmax-like)
|
| 240 |
-
|
|
|
|
| 241 |
for name in self.optimizers.keys():
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
def select_optimizer(self) -> str:
|
| 245 |
"""Select optimizer based on adaptive probabilities."""
|
|
|
|
| 237 |
improvements[name] = 0
|
| 238 |
|
| 239 |
# Update probabilities (softmax-like)
|
| 240 |
+
# Calculate raw weights
|
| 241 |
+
weights = {}
|
| 242 |
for name in self.optimizers.keys():
|
| 243 |
+
weights[name] = improvements[name] + 0.1
|
| 244 |
+
|
| 245 |
+
total_weight = sum(weights.values())
|
| 246 |
+
|
| 247 |
+
# Normalize
|
| 248 |
+
for name in self.optimizers.keys():
|
| 249 |
+
self.selection_probabilities[name] = weights[name] / total_weight
|
| 250 |
|
| 251 |
def select_optimizer(self) -> str:
|
| 252 |
"""Select optimizer based on adaptive probabilities."""
|