Skip to content

Commit d3a6bbe

Browse files
committed
Update database.py
1 parent 025f00b commit d3a6bbe

File tree

1 file changed

+78
-66
lines changed

1 file changed

+78
-66
lines changed

openevolve/database.py

Lines changed: 78 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ def __init__(self, config: DatabaseConfig):
108108
# In-memory program storage
109109
self.programs: Dict[str, Program] = {}
110110

111-
# Feature grid for MAP-Elites
112-
self.feature_map: Dict[str, str] = {}
111+
# Per-island feature grids for MAP-Elites
112+
self.island_feature_maps: List[Dict[str, str]] = [
113+
{} for _ in range(config.num_islands)
114+
]
113115

114116
# Handle both int and dict types for feature_bins
115117
if isinstance(config.feature_bins, int):
@@ -208,18 +210,49 @@ def add(
208210
# Calculate feature coordinates for MAP-Elites
209211
feature_coords = self._calculate_feature_coords(program)
210212

211-
# Add to feature map (replacing existing if better)
213+
# Determine target island
214+
# If target_island is not specified and program has a parent, inherit parent's island
215+
if target_island is None and program.parent_id:
216+
parent = self.programs.get(program.parent_id)
217+
if parent and "island" in parent.metadata:
218+
# Child inherits parent's island to maintain island isolation
219+
island_idx = parent.metadata["island"]
220+
logger.debug(
221+
f"Program {program.id} inheriting island {island_idx} from parent {program.parent_id}"
222+
)
223+
else:
224+
# Parent not found or has no island, use current_island
225+
island_idx = self.current_island
226+
if parent:
227+
logger.warning(
228+
f"Parent {program.parent_id} has no island metadata, using current_island {island_idx}"
229+
)
230+
else:
231+
logger.warning(
232+
f"Parent {program.parent_id} not found, using current_island {island_idx}"
233+
)
234+
elif target_island is not None:
235+
# Explicit target island specified (e.g., for migrants)
236+
island_idx = target_island
237+
else:
238+
# No parent and no target specified, use current island
239+
island_idx = self.current_island
240+
241+
island_idx = island_idx % len(self.islands) # Ensure valid island
242+
243+
# Add to island-specific feature map (replacing existing if better)
212244
feature_key = self._feature_coords_to_key(feature_coords)
213-
should_replace = feature_key not in self.feature_map
245+
island_feature_map = self.island_feature_maps[island_idx]
246+
should_replace = feature_key not in island_feature_map
214247

215248
if not should_replace:
216249
# Check if the existing program still exists before comparing
217-
existing_program_id = self.feature_map[feature_key]
250+
existing_program_id = island_feature_map[feature_key]
218251
if existing_program_id not in self.programs:
219252
# Stale reference, replace it
220253
should_replace = True
221254
logger.debug(
222-
f"Replacing stale program reference {existing_program_id} in feature map"
255+
f"Replacing stale program reference {existing_program_id} in island {island_idx} feature map"
223256
)
224257
else:
225258
# Program exists, compare fitness
@@ -232,30 +265,32 @@ def add(
232265
for i in range(len(feature_coords))
233266
}
234267

235-
if feature_key not in self.feature_map:
236-
# New cell occupation
237-
logger.info("New MAP-Elites cell occupied: %s", coords_dict)
238-
# Check coverage milestone
268+
if feature_key not in island_feature_map:
269+
# New cell occupation in this island
270+
logger.info("New MAP-Elites cell occupied in island %d: %s", island_idx, coords_dict)
271+
# Check coverage milestone for this island
239272
total_possible_cells = self.feature_bins ** len(self.config.feature_dimensions)
240-
coverage = (len(self.feature_map) + 1) / total_possible_cells
241-
if coverage in [0.1, 0.25, 0.5, 0.75, 0.9]:
273+
island_coverage = (len(island_feature_map) + 1) / total_possible_cells
274+
if island_coverage in [0.1, 0.25, 0.5, 0.75, 0.9]:
242275
logger.info(
243-
"MAP-Elites coverage reached %.1f%% (%d/%d cells)",
244-
coverage * 100,
245-
len(self.feature_map) + 1,
276+
"Island %d MAP-Elites coverage reached %.1f%% (%d/%d cells)",
277+
island_idx,
278+
island_coverage * 100,
279+
len(island_feature_map) + 1,
246280
total_possible_cells,
247281
)
248282
else:
249-
# Cell replacement - existing program being replaced
250-
existing_program_id = self.feature_map[feature_key]
283+
# Cell replacement - existing program being replaced in this island
284+
existing_program_id = island_feature_map[feature_key]
251285
if existing_program_id in self.programs:
252286
existing_program = self.programs[existing_program_id]
253287
new_fitness = get_fitness_score(program.metrics, self.config.feature_dimensions)
254288
existing_fitness = get_fitness_score(
255289
existing_program.metrics, self.config.feature_dimensions
256290
)
257291
logger.info(
258-
"MAP-Elites cell improved: %s (fitness: %.3f -> %.3f)",
292+
"Island %d MAP-Elites cell improved: %s (fitness: %.3f -> %.3f)",
293+
island_idx,
259294
coords_dict,
260295
existing_fitness,
261296
new_fitness,
@@ -266,37 +301,9 @@ def add(
266301
self.archive.discard(existing_program_id)
267302
self.archive.add(program.id)
268303

269-
self.feature_map[feature_key] = program.id
304+
island_feature_map[feature_key] = program.id
270305

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

302309
# Track which island this program belongs to
@@ -506,7 +513,7 @@ def save(self, path: Optional[str] = None, iteration: int = 0) -> None:
506513

507514
# Save metadata
508515
metadata = {
509-
"feature_map": self.feature_map,
516+
"island_feature_maps": self.island_feature_maps,
510517
"islands": [list(island) for island in self.islands],
511518
"archive": list(self.archive),
512519
"best_program_id": self.best_program_id,
@@ -541,7 +548,7 @@ def load(self, path: str) -> None:
541548
with open(metadata_path, "r") as f:
542549
metadata = json.load(f)
543550

544-
self.feature_map = metadata.get("feature_map", {})
551+
self.island_feature_maps = metadata.get("island_feature_maps", [{} for _ in range(self.config.num_islands)])
545552
saved_islands = metadata.get("islands", [])
546553
self.archive = set(metadata.get("archive", []))
547554
self.best_program_id = metadata.get("best_program_id")
@@ -625,13 +632,16 @@ def _reconstruct_islands(self, saved_islands: List[List[str]]) -> None:
625632
original_archive_size = len(self.archive)
626633
self.archive = {pid for pid in self.archive if pid in self.programs}
627634

628-
# Clean up feature_map - remove missing programs
635+
# Clean up island_feature_maps - remove missing programs
629636
feature_keys_to_remove = []
630-
for key, program_id in self.feature_map.items():
631-
if program_id not in self.programs:
632-
feature_keys_to_remove.append(key)
633-
for key in feature_keys_to_remove:
634-
del self.feature_map[key]
637+
for island_idx, island_map in enumerate(self.island_feature_maps):
638+
island_keys_to_remove = []
639+
for key, program_id in island_map.items():
640+
if program_id not in self.programs:
641+
island_keys_to_remove.append(key)
642+
feature_keys_to_remove.append((island_idx, key))
643+
for key in island_keys_to_remove:
644+
del island_map[key]
635645

636646
# Clean up island best programs - remove stale references
637647
self._cleanup_stale_island_bests()
@@ -657,7 +667,7 @@ def _reconstruct_islands(self, saved_islands: List[List[str]]) -> None:
657667
)
658668

659669
if feature_keys_to_remove:
660-
logger.info(f"Removed {len(feature_keys_to_remove)} missing programs from feature map")
670+
logger.info(f"Removed {len(feature_keys_to_remove)} missing programs from island feature maps")
661671

662672
logger.info(f"Reconstructed islands: restored {restored_programs} programs to islands")
663673

@@ -1345,13 +1355,14 @@ def _enforce_population_limit(self, exclude_program_id: Optional[str] = None) ->
13451355
if program_id in self.programs:
13461356
del self.programs[program_id]
13471357

1348-
# Remove from feature map
1349-
keys_to_remove = []
1350-
for key, pid in self.feature_map.items():
1351-
if pid == program_id:
1352-
keys_to_remove.append(key)
1353-
for key in keys_to_remove:
1354-
del self.feature_map[key]
1358+
# Remove from island feature maps
1359+
for island_idx, island_map in enumerate(self.island_feature_maps):
1360+
keys_to_remove = []
1361+
for key, pid in island_map.items():
1362+
if pid == program_id:
1363+
keys_to_remove.append(key)
1364+
for key in keys_to_remove:
1365+
del island_map[key]
13551366

13561367
# Remove from islands
13571368
for island in self.islands:
@@ -1445,9 +1456,10 @@ def migrate_programs(self) -> None:
14451456
continue
14461457

14471458
for target_island in target_islands:
1448-
# Create a copy for migration (to avoid removing from source)
1459+
# Create a copy for migration with simple new UUID
1460+
import uuid
14491461
migrant_copy = Program(
1450-
id=f"{migrant.id}_migrant_{target_island}",
1462+
id=str(uuid.uuid4()),
14511463
code=migrant.code,
14521464
language=migrant.language,
14531465
parent_id=migrant.id,

0 commit comments

Comments
 (0)