Skip to content
Open
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
21 changes: 15 additions & 6 deletions openevolve/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2078,12 +2078,18 @@ def migrate_programs(self) -> None:
self._validate_migration_results()
return

# Choose every island's migrants from the population before this round. Choosing them
# while migrating lets an island's top slots go to copies it has just received (which
# _migrate_one skips), and lets those copies displace its own programs from their cells
# before they get the chance to migrate.
planned_moves = []
for i, island in enumerate(self.islands):
if len(island) == 0:
continue

# Select top programs from this island for migration
island_programs = [self.programs[pid] for pid in island if pid in self.programs]
# Migrant copies are never migrated again, so they must not take migration slots
island_programs = [
self.programs[pid]
for pid in island
if pid in self.programs and not self.programs[pid].metadata.get("migrant", False)
]
if not island_programs:
continue

Expand All @@ -2102,7 +2108,10 @@ def migrate_programs(self) -> None:

for migrant in migrants:
for target_island in target_islands:
self._migrate_one(migrant.id, target_island)
planned_moves.append((migrant.id, target_island))

for program_id, target_island in planned_moves:
self._migrate_one(program_id, target_island)

# Update last migration generation
self.last_migration_generation = max(self.island_generations)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_island_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ def test_migration_ring_topology(self):
# This is a known limitation of the current implementation that processes islands
# sequentially while modifying them, causing interference between migration rounds.

def test_every_island_migrates_its_own_best(self):
"""Migrants are chosen before any are moved, so an island's own best still migrates
even when a better migrant has just arrived from the previous island in the ring."""
# Different code lengths put the programs in different MAP-Elites cells
for pid, score, island, pad in [("a", 0.9, 0, 0), ("b", 0.5, 1, 400), ("c", 0.3, 2, 800)]:
program = self._create_test_program(pid, score, island)
program.code += "#" * (pad + 1)
self.db.add(program, target_island=island)

self.db.migrate_programs()

for island in self.db.islands:
scores = sorted(self.db.programs[pid].metrics["score"] for pid in island)
self.assertEqual(scores, [0.3, 0.5, 0.9])

def test_migration_rate_respected(self):
"""Test that migration rate is properly applied"""
# Add multiple programs to island 0
Expand Down