diff --git a/openevolve/database.py b/openevolve/database.py index 0f487c0ec..8f18fc762 100644 --- a/openevolve/database.py +++ b/openevolve/database.py @@ -30,7 +30,7 @@ ProgramState, ) from openevolve.utils.code_utils import calculate_edit_distance -from openevolve.utils.metrics_utils import safe_numeric_average, get_fitness_score +from openevolve.utils.metrics_utils import get_fitness_score, safe_numeric_average logger = logging.getLogger(__name__) @@ -186,6 +186,9 @@ def __init__( # Track the last iteration number (for resuming) self.last_iteration: int = 0 + # Initialize before loading so restored MAP-Elites scales are retained. + self.feature_stats: Dict[str, Dict[str, Union[float, float, List[float]]]] = {} + # Load database from disk if path is provided if config.db_path and os.path.exists(config.db_path): self.load(config.db_path) @@ -211,7 +214,6 @@ def __init__( self.diversity_reference_size: int = getattr(config, "diversity_reference_size", 20) # Feature scaling infrastructure - self.feature_stats: Dict[str, Dict[str, Union[float, float, List[float]]]] = {} self.feature_scaling_method: str = "minmax" # Options: minmax, zscore, percentile # Per-dimension bins support @@ -1136,6 +1138,7 @@ def _llm_judge_novelty(self, program: Program, similar_program: Program) -> bool Use LLM to judge if a program is novel compared to a similar existing program """ import asyncio + from openevolve.novelty_judge import NOVELTY_SYSTEM_MSG, NOVELTY_USER_MSG user_msg = NOVELTY_USER_MSG.format( diff --git a/tests/test_constructor_feature_stats.py b/tests/test_constructor_feature_stats.py new file mode 100644 index 000000000..5d5d0e33d --- /dev/null +++ b/tests/test_constructor_feature_stats.py @@ -0,0 +1,45 @@ +"""Loading through db_path must retain the checkpoint's MAP-Elites scale.""" + +import tempfile +import unittest + +from openevolve.config import DatabaseConfig +from openevolve.database import Program, ProgramDatabase + + +class TestConstructorFeatureStats(unittest.TestCase): + def setUp(self): + self.tempdir = tempfile.TemporaryDirectory() + self.addCleanup(self.tempdir.cleanup) + self.config = DatabaseConfig( + in_memory=True, + num_islands=1, + feature_dimensions=["custom"], + feature_bins=10, + archive_size=1, + ) + self.db = ProgramDatabase(self.config) + self.db._update_feature_stats("custom", 0.0) + self.db._update_feature_stats("custom", 100.0) + self.program = Program( + id="candidate", code="pass", metrics={"combined_score": 0.5, "custom": 25.0} + ) + self.db.add(self.program) + self.db.save(self.tempdir.name) + self.config.db_path = self.tempdir.name + + def test_constructor_preserves_loaded_feature_ranges(self): + restored = ProgramDatabase(self.config) + self.assertEqual(restored.feature_stats, self.db.feature_stats) + + def test_constructor_preserves_feature_placement_without_second_load(self): + expected = self.db._calculate_feature_coords(self.program) + restored = ProgramDatabase(self.config) + self.assertEqual(expected, [2]) + self.assertEqual( + restored._calculate_feature_coords(restored.programs[self.program.id]), expected + ) + + +if __name__ == "__main__": + unittest.main()