@@ -425,6 +425,17 @@ async def run_evolution(
425425 # Island management
426426 programs_per_island = max (1 , max_iterations // (self .config .database .num_islands * 10 ))
427427 current_island_counter = 0
428+
429+ # Early stopping tracking
430+ early_stopping_enabled = self .config .early_stopping_patience is not None
431+ if early_stopping_enabled :
432+ best_score = float ('-inf' )
433+ iterations_without_improvement = 0
434+ logger .info (f"Early stopping enabled: patience={ self .config .early_stopping_patience } , "
435+ f"threshold={ self .config .convergence_threshold } , "
436+ f"metric={ self .config .early_stopping_metric } " )
437+ else :
438+ logger .info ("Early stopping disabled" )
428439
429440 # Process results as they complete
430441 while (
@@ -563,6 +574,41 @@ async def run_evolution(
563574 )
564575 break
565576
577+ # Check early stopping
578+ if early_stopping_enabled and child_program .metrics :
579+ # Get the metric to track for early stopping
580+ current_score = None
581+ if self .config .early_stopping_metric in child_program .metrics :
582+ current_score = child_program .metrics [self .config .early_stopping_metric ]
583+ else :
584+ # Fall back to average of numeric metrics if specified metric doesn't exist
585+ numeric_metrics = [
586+ v for v in child_program .metrics .values ()
587+ if isinstance (v , (int , float )) and not isinstance (v , bool )
588+ ]
589+ if numeric_metrics :
590+ current_score = sum (numeric_metrics ) / len (numeric_metrics )
591+
592+ if current_score is not None and isinstance (current_score , (int , float )):
593+ # Check for improvement
594+ improvement = current_score - best_score
595+ if improvement >= self .config .convergence_threshold :
596+ best_score = current_score
597+ iterations_without_improvement = 0
598+ logger .debug (f"New best score: { best_score :.4f} (improvement: { improvement :+.4f} )" )
599+ else :
600+ iterations_without_improvement += 1
601+ logger .debug (f"No improvement: { iterations_without_improvement } /{ self .config .early_stopping_patience } " )
602+
603+ # Check if we should stop
604+ if iterations_without_improvement >= self .config .early_stopping_patience :
605+ logger .info (
606+ f"Early stopping triggered at iteration { completed_iteration } : "
607+ f"No improvement for { iterations_without_improvement } iterations "
608+ f"(best score: { best_score :.4f} )"
609+ )
610+ break
611+
566612 except Exception as e :
567613 logger .error (f"Error processing result from iteration { completed_iteration } : { e } " )
568614
0 commit comments