Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion openevolve/process_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,10 @@ async def run_evolution(
# Check for improvement
if self.config.early_stopping_patience > 0:
improvement = current_score - best_score
if improvement >= self.config.convergence_threshold:
if (
current_score > best_score
and improvement >= self.config.convergence_threshold
):
best_score = current_score
iterations_without_improvement = 0
logger.debug(
Expand Down
36 changes: 36 additions & 0 deletions tests/test_early_stopping_failures.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,42 @@ def test_genuine_plateau_still_stops(self):

self.assertTrue(controller.early_stopping_triggered)

def test_zero_threshold_counts_equal_scores_as_a_plateau(self):
self.config.convergence_threshold = 0.0
controller, _ = self._run_evolution(lambda i: {"combined_score": 0.0}, max_iterations=8)

self.assertTrue(controller.early_stopping_triggered)
self.assertEqual(self.database.last_iteration, 4)

def test_zero_threshold_still_accepts_small_negative_score_improvements(self):
self.config.convergence_threshold = 0.0
controller, submit = self._run_evolution(
lambda i: {"combined_score": -0.5 + i * 1e-9}, max_iterations=8
)

self.assertFalse(controller.early_stopping_triggered)
self.assertEqual(submit.call_count, 8)

def test_positive_threshold_still_accepts_an_exact_threshold_improvement(self):
self.config.early_stopping_patience = 2
self.config.convergence_threshold = 0.25
controller, submit = self._run_evolution(
lambda i: {"combined_score": 0.25 * ((i - 1) // 2)}, max_iterations=8
)

self.assertFalse(controller.early_stopping_triggered)
self.assertEqual(submit.call_count, 8)

def test_event_based_stopping_still_accepts_a_zero_target(self):
self.config.early_stopping_patience = -1
self.config.convergence_threshold = 0.0
controller, _ = self._run_evolution(
lambda i: {"combined_score": 0.0 if i == 3 else -0.5}, max_iterations=8
)

self.assertTrue(controller.early_stopping_triggered)
self.assertEqual(self.database.last_iteration, 3)

def test_negative_best_not_poisoned_by_failures(self):
"""After a real (negative) best score, failures scoring 0.0 must not look
like improvements that reset the patience counter"""
Expand Down
Loading