Skip to content

Commit 5dd0434

Browse files
committed
Fix migration to use MAP-Elites deduplication logic
Refactors the migration process to use the add() method for adding migrants, ensuring MAP-Elites deduplication, feature map updates, and proper island tracking. Updates logging to reflect the new process. Adds a test to verify that migrants are deduplicated correctly in the MAP-Elites feature map during migration.
1 parent 92c7808 commit 5dd0434

File tree

2 files changed

+89
-16
lines changed

2 files changed

+89
-16
lines changed

openevolve/database.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,23 +1670,15 @@ def migrate_programs(self) -> None:
16701670
metadata={**migrant.metadata, "island": target_island, "migrant": True},
16711671
)
16721672

1673-
# Add to target island
1674-
self.islands[target_island].add(migrant_copy.id)
1675-
self.programs[migrant_copy.id] = migrant_copy
1676-
1677-
# Update island-specific best program if migrant is better
1678-
self._update_island_best_program(migrant_copy, target_island)
1679-
1680-
# Log migration with MAP-Elites coordinates
1681-
feature_coords = self._calculate_feature_coords(migrant_copy)
1682-
coords_dict = {
1683-
self.config.feature_dimensions[j]: feature_coords[j]
1684-
for j in range(len(feature_coords))
1685-
}
1673+
# Use add() method to properly handle MAP-Elites deduplication,
1674+
# feature map updates, and island tracking
1675+
self.add(migrant_copy, target_island=target_island)
1676+
1677+
# Log migration
16861678
logger.info(
1687-
"Program migrated to island %d at MAP-Elites coords: %s",
1679+
"Program %s migrated to island %d",
1680+
migrant_copy.id[:8],
16881681
target_island,
1689-
coords_dict,
16901682
)
16911683

16921684
# Update last migration generation

tests/test_migration_no_duplicates.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,90 @@ def test_migration_with_feature_map_conflicts_resolved_cleanly(self):
250250

251251
# No _migrant suffixes should exist
252252
migrant_programs = [pid for pid in all_program_ids if '_migrant' in pid]
253-
self.assertEqual(len(migrant_programs), 0,
253+
self.assertEqual(len(migrant_programs), 0,
254254
f"Found programs with _migrant suffix: {migrant_programs}")
255255

256+
def test_migration_uses_map_elites_deduplication(self):
257+
"""Test that migrants go through MAP-Elites deduplication (same cell = keep better)"""
258+
# Create two programs that will map to the EXACT SAME feature coordinates
259+
# Use custom complexity/diversity metrics to control the coordinates explicitly
260+
prog_low = Program(
261+
id="low_score",
262+
code="def low_func(): return 1",
263+
language="python",
264+
metrics={
265+
"complexity": 50.0, # Custom complexity (overrides built-in)
266+
"diversity": 30.0, # Custom diversity (overrides built-in)
267+
"score": 0.3,
268+
"combined_score": 0.3
269+
},
270+
metadata={"island": 0, "generation": 3},
271+
)
272+
273+
prog_high = Program(
274+
id="high_score",
275+
code="def high_func(): return 2",
276+
language="python",
277+
metrics={
278+
"complexity": 50.0, # Same as prog_low
279+
"diversity": 30.0, # Same as prog_low
280+
"score": 0.9, # Better score
281+
"combined_score": 0.9
282+
},
283+
metadata={"island": 0, "generation": 3},
284+
)
285+
286+
# Add both to island 0
287+
# MAP-Elites should keep only the better one (high_score) in the feature map
288+
self.db.add(prog_low)
289+
290+
# Get the feature coords for prog_low
291+
coords_low = self.db._calculate_feature_coords(prog_low)
292+
293+
# Add high score - should replace low score in same cell
294+
self.db.add(prog_high)
295+
296+
# Get the feature coords for prog_high (should be identical due to same custom metrics)
297+
coords_high = self.db._calculate_feature_coords(prog_high)
298+
299+
# Verify they map to the same cell
300+
self.assertEqual(coords_low, coords_high, "Programs with same custom metrics should map to same cell")
301+
302+
# Verify MAP-Elites deduplication worked on island 0
303+
# Check the feature map (not self.islands which contains all programs)
304+
island_0_feature_map = self.db.island_feature_maps[0]
305+
feature_key = self.db._feature_coords_to_key(coords_high)
306+
307+
# This cell should have exactly one program
308+
self.assertIn(feature_key, island_0_feature_map, "Cell should be occupied")
309+
cell_program_id = island_0_feature_map[feature_key]
310+
self.assertEqual(cell_program_id, "high_score", "Better program should be kept in MAP-Elites cell")
311+
312+
# Set generation to trigger migration
313+
self.db.island_generations[0] = 3
314+
315+
# Force migration - high_score will migrate to island 1
316+
self.db.migrate_programs()
317+
318+
# CRITICAL TEST: Check that migrant was added to island 1 feature map
319+
# (Current implementation bypasses add() so this will FAIL)
320+
island_1_feature_map = self.db.island_feature_maps[1]
321+
322+
# The migrant should be in the feature map at the same coordinates
323+
migrant_in_feature_map = feature_key in island_1_feature_map
324+
325+
self.assertTrue(migrant_in_feature_map,
326+
"Migrant should be added to target island's feature map (currently bypasses add())")
327+
328+
# If migrant is in feature map, verify it's the high-score version
329+
if migrant_in_feature_map:
330+
migrant_id = island_1_feature_map[feature_key]
331+
migrant_program = self.db.programs[migrant_id]
332+
# The migrant is a copy, so code should match high_score's code
333+
self.assertEqual(migrant_program.code, "def high_func(): return 2", "Migrant should have high_score's code")
334+
self.assertEqual(migrant_program.metrics["combined_score"], 0.9,
335+
"Migrant should preserve high score")
336+
256337

257338
if __name__ == '__main__':
258339
unittest.main()

0 commit comments

Comments
 (0)