Skip to content

Commit 3ce52ae

Browse files
committed
fixes
1 parent 1bda7fc commit 3ce52ae

File tree

4 files changed

+206
-5
lines changed

4 files changed

+206
-5
lines changed

openevolve/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version information for openevolve package."""
22

3-
__version__ = "0.2.1"
3+
__version__ = "0.2.2"

openevolve/database.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def add(
188188
Args:
189189
program: Program to add
190190
iteration: Current iteration (defaults to last_iteration)
191-
target_island: Specific island to add to (uses current_island if None)
191+
target_island: Specific island to add to (auto-detects parent's island if None)
192192
193193
Returns:
194194
Program ID
@@ -263,8 +263,34 @@ def add(
263263

264264
self.feature_map[feature_key] = program.id
265265

266-
# Add to specific island (not random!)
267-
island_idx = target_island if target_island is not None else self.current_island
266+
# Determine target island
267+
# If target_island is not specified and program has a parent, inherit parent's island
268+
if target_island is None and program.parent_id:
269+
parent = self.programs.get(program.parent_id)
270+
if parent and "island" in parent.metadata:
271+
# Child inherits parent's island to maintain island isolation
272+
island_idx = parent.metadata["island"]
273+
logger.debug(
274+
f"Program {program.id} inheriting island {island_idx} from parent {program.parent_id}"
275+
)
276+
else:
277+
# Parent not found or has no island, use current_island
278+
island_idx = self.current_island
279+
if parent:
280+
logger.warning(
281+
f"Parent {program.parent_id} has no island metadata, using current_island {island_idx}"
282+
)
283+
else:
284+
logger.warning(
285+
f"Parent {program.parent_id} not found, using current_island {island_idx}"
286+
)
287+
elif target_island is not None:
288+
# Explicit target island specified (e.g., for migrants)
289+
island_idx = target_island
290+
else:
291+
# No parent and no target specified, use current island
292+
island_idx = self.current_island
293+
268294
island_idx = island_idx % len(self.islands) # Ensure valid island
269295
self.islands[island_idx].add(program.id)
270296

openevolve/process_parallel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ async def run_evolution(
441441
# Reconstruct program from dict
442442
child_program = Program(**result.child_program_dict)
443443

444-
# Add to database
444+
# Add to database (will auto-inherit parent's island)
445+
# No need to specify target_island - database will handle parent island inheritance
445446
self.database.add(child_program, iteration=completed_iteration)
446447

447448
# Store artifacts
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""
2+
Test for island parent-child consistency - Programs' parents should be in the corresponding islands
3+
"""
4+
5+
import unittest
6+
from openevolve.config import Config
7+
from openevolve.database import ProgramDatabase, Program
8+
9+
10+
class TestIslandParentConsistency(unittest.TestCase):
11+
"""Test that parent-child relationships respect island boundaries"""
12+
13+
def test_parent_child_island_consistency(self):
14+
"""Test that children are added to the same island as their parents"""
15+
config = Config()
16+
config.database.num_islands = 3
17+
database = ProgramDatabase(config.database)
18+
19+
# Create initial program on island 0
20+
initial_program = Program(
21+
id="initial",
22+
code="def initial(): pass",
23+
metrics={"score": 0.5},
24+
iteration_found=0
25+
)
26+
database.add(initial_program) # Should go to island 0 (current_island)
27+
28+
# Verify initial program is on island 0
29+
self.assertIn("initial", database.islands[0])
30+
self.assertEqual(initial_program.metadata.get("island"), 0)
31+
32+
# Now switch to island 1
33+
database.next_island()
34+
self.assertEqual(database.current_island, 1)
35+
36+
# Create a child of the initial program
37+
child_program = Program(
38+
id="child1",
39+
code="def child1(): pass",
40+
parent_id="initial", # Parent is on island 0
41+
metrics={"score": 0.6},
42+
iteration_found=1
43+
)
44+
45+
# Add child without specifying target_island
46+
# This is what happens in process_parallel.py line 445
47+
database.add(child_program)
48+
49+
# With the fix: child should go to parent's island (0), not current_island (1)
50+
parent_island = database.programs["initial"].metadata.get("island", 0)
51+
child_island = database.programs["child1"].metadata.get("island")
52+
53+
# Check if parent is in child's island (this is what the user's assertion checks)
54+
if child_program.parent_id:
55+
# This is the exact check from the issue report - should now pass
56+
self.assertIn(
57+
child_program.parent_id,
58+
database.islands[child_island],
59+
"Parent should be in child's island"
60+
)
61+
62+
# Verify child is on same island as parent
63+
self.assertEqual(
64+
parent_island,
65+
child_island,
66+
f"Child should be on same island as parent. Parent: island {parent_island}, Child: island {child_island}"
67+
)
68+
69+
def test_multiple_generations_island_drift(self):
70+
"""Test that island drift happens across multiple generations"""
71+
config = Config()
72+
config.database.num_islands = 4
73+
database = ProgramDatabase(config.database)
74+
75+
# Create a lineage
76+
programs = []
77+
for i in range(10):
78+
if i == 0:
79+
# Initial program
80+
prog = Program(
81+
id=f"prog_{i}",
82+
code=f"def prog_{i}(): pass",
83+
metrics={"score": 0.1 * i},
84+
iteration_found=i
85+
)
86+
else:
87+
# Child of previous
88+
prog = Program(
89+
id=f"prog_{i}",
90+
code=f"def prog_{i}(): pass",
91+
parent_id=f"prog_{i-1}",
92+
metrics={"score": 0.1 * i},
93+
iteration_found=i
94+
)
95+
96+
database.add(prog)
97+
programs.append(prog)
98+
99+
# Switch islands periodically (simulating what happens in evolution)
100+
if i % 3 == 0:
101+
database.next_island()
102+
103+
# Check island consistency
104+
inconsistent_pairs = []
105+
for prog in programs:
106+
if prog.parent_id:
107+
parent = database.programs.get(prog.parent_id)
108+
if parent:
109+
parent_island = parent.metadata.get("island")
110+
child_island = prog.metadata.get("island")
111+
112+
# Check if parent is in child's island
113+
if prog.parent_id not in database.islands[child_island]:
114+
inconsistent_pairs.append((prog.parent_id, prog.id))
115+
116+
# With the fix, we should find NO inconsistent parent-child island assignments
117+
self.assertEqual(
118+
len(inconsistent_pairs),
119+
0,
120+
f"Found {len(inconsistent_pairs)} inconsistent parent-child pairs: {inconsistent_pairs}"
121+
)
122+
123+
# Verify all parent-child pairs are on the same island
124+
for prog in programs:
125+
if prog.parent_id:
126+
parent = database.programs.get(prog.parent_id)
127+
if parent:
128+
parent_island = parent.metadata.get("island")
129+
child_island = prog.metadata.get("island")
130+
self.assertEqual(
131+
parent_island,
132+
child_island,
133+
f"Parent {prog.parent_id} (island {parent_island}) and "
134+
f"child {prog.id} (island {child_island}) should be on same island"
135+
)
136+
137+
138+
def test_explicit_migration_override(self):
139+
"""Test that explicit target_island overrides parent island inheritance"""
140+
config = Config()
141+
config.database.num_islands = 3
142+
database = ProgramDatabase(config.database)
143+
144+
# Create parent on island 0
145+
parent = Program(
146+
id="parent",
147+
code="def parent(): pass",
148+
metrics={"score": 0.5},
149+
iteration_found=0
150+
)
151+
database.add(parent) # Goes to island 0
152+
self.assertIn("parent", database.islands[0])
153+
154+
# Create child but explicitly send to island 2 (migration)
155+
migrant_child = Program(
156+
id="migrant",
157+
code="def migrant(): pass",
158+
parent_id="parent",
159+
metrics={"score": 0.7},
160+
iteration_found=1
161+
)
162+
database.add(migrant_child, target_island=2) # Explicit migration
163+
164+
# Verify migrant went to island 2, not parent's island 0
165+
self.assertIn("migrant", database.islands[2])
166+
self.assertNotIn("migrant", database.islands[0])
167+
self.assertEqual(migrant_child.metadata.get("island"), 2)
168+
169+
# Parent should still be on island 0
170+
self.assertEqual(database.programs["parent"].metadata.get("island"), 0)
171+
172+
173+
if __name__ == "__main__":
174+
unittest.main()

0 commit comments

Comments
 (0)