@@ -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