Skip to content

Commit 1c9c833

Browse files
committed
Introduce a "threshold-elites" variant alongside map-elites
1 parent 65cbbe8 commit 1c9c833

File tree

2 files changed

+133
-2
lines changed

2 files changed

+133
-2
lines changed

openevolve/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ class DatabaseConfig:
351351
novelty_llm: Optional["LLMInterface"] = None
352352
embedding_model: Optional[str] = None
353353
similarity_threshold: float = 0.99
354-
354+
variant: str = "map-elites" # Parameters to support a new Quality-Diversity variant
355+
elite_threshold: int = 3 # Distance threshold used by the threshold-elites variant
355356

356357
@dataclass
357358
class EvaluatorConfig:

openevolve/database.py

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import shutil
1111
import time
1212
import uuid
13+
import math
14+
import copy
1315
from dataclasses import asdict, dataclass, field, fields
1416

1517
# FileLock removed - no longer needed with threaded parallel processing
@@ -209,7 +211,29 @@ def __init__(self, config: DatabaseConfig):
209211
self.similarity_threshold = config.similarity_threshold
210212

211213
def add(
212-
self, program: Program, iteration: int = None, target_island: Optional[int] = None
214+
self, program: Program, iteration: int = None, target_island: Optional[int] = None,
215+
) -> str:
216+
"""
217+
Add a program to the database
218+
219+
Args:
220+
program: Program to add
221+
iteration: Current iteration (defaults to last_iteration)
222+
target_island: Specific island to add to (auto-detects parent's island if None)
223+
224+
Returns:
225+
Program ID
226+
"""
227+
variant = getattr(self.config, "variant", "map-elites")
228+
if variant == "map-elites":
229+
self.add_map_elites_program(program, iteration, target_island)
230+
elif variant == "threshold-elites":
231+
self.add_threshold_elites_program(program, iteration, target_island)
232+
else:
233+
raise NotImplementedError()
234+
235+
def add_map_elites_program(
236+
self, program: Program, iteration: int = None, target_island: Optional[int] = None,
213237
) -> str:
214238
"""
215239
Add a program to the database
@@ -367,6 +391,110 @@ def add(
367391

368392
return program.id
369393

394+
def add_threshold_elites_program(
395+
self, program: Program, iteration: int = None, target_island: Optional[int] = None,
396+
) -> str:
397+
# Store the program
398+
# If iteration is provided, update the program's iteration_found
399+
if iteration is not None:
400+
program.iteration_found = iteration
401+
# Update last_iteration if needed
402+
self.last_iteration = max(self.last_iteration, iteration)
403+
404+
self.programs[program.id] = program
405+
406+
# Calculate feature coordinates for Threshold-Elites
407+
embd = self._calculate_feature_coords(program)
408+
score = get_fitness_score(program.metrics, self.config.feature_dimensions)
409+
program.embedding = embd
410+
program.metrics["elite_score"] = score
411+
412+
# Determine target island
413+
# If target_island is not specified and program has a parent, inherit parent's island
414+
if target_island is None and program.parent_id:
415+
parent = self.programs.get(program.parent_id)
416+
if parent and "island" in parent.metadata:
417+
# Child inherits parent's island to maintain island isolation
418+
island_idx = parent.metadata["island"]
419+
logger.debug(
420+
f"Program {program.id} inheriting island {island_idx} from parent {program.parent_id}"
421+
)
422+
else:
423+
# Parent not found or has no island, use current_island
424+
island_idx = self.current_island
425+
if parent:
426+
logger.warning(
427+
f"Parent {program.parent_id} has no island metadata, using current_island {island_idx}"
428+
)
429+
else:
430+
logger.warning(
431+
f"Parent {program.parent_id} not found, using current_island {island_idx}"
432+
)
433+
elif target_island is not None:
434+
# Explicit target island specified (e.g., for migrants)
435+
island_idx = target_island
436+
else:
437+
# No parent and no target specified, use current island
438+
island_idx = self.current_island
439+
440+
island_idx = island_idx % len(self.islands) # Ensure valid island
441+
442+
# Novelty check before adding
443+
if not self._is_novel(program.id, island_idx):
444+
logger.debug(
445+
f"Program {program.id} failed in novelty check and won't be added in the island {island_idx}"
446+
)
447+
return program.id # Do not add non-novel program
448+
449+
450+
elite_threshold = getattr(self.config, "elite_threshold", 3)
451+
for pid in self.islands[island_idx]:
452+
other = self.programs[pid]
453+
454+
if other.embedding is None:
455+
logger.warning(
456+
f"Warning: Program {other.id} has no embedding, skipping similarity check"
457+
)
458+
continue
459+
460+
#similarity = self._cosine_similarity(embd, other.embedding)
461+
distance = math.sqrt(sum((a - b) ** 2 for a, b in zip(embd, other.embedding)))
462+
463+
if distance < elite_threshold:
464+
# self._is_better(program, other)
465+
if score > other.metrics["elite_score"]:
466+
other.metrics["elite_score"] = float("-inf")
467+
else:
468+
program.metrics["elite_score"] = float("-inf")
469+
470+
471+
# Add to island
472+
self.islands[island_idx].add(program.id)
473+
474+
# Track which island this program belongs to
475+
program.metadata["island"] = island_idx
476+
477+
# Update archive
478+
self._update_archive(program)
479+
480+
# Enforce population size limit BEFORE updating best program tracking
481+
# This ensures newly added programs aren't immediately removed
482+
self._enforce_population_limit(exclude_program_id=program.id)
483+
484+
# Update the absolute best program tracking (after population enforcement)
485+
self._update_best_program(program)
486+
487+
# Update island-specific best program tracking
488+
self._update_island_best_program(program, island_idx)
489+
490+
# Save to disk if configured
491+
if self.config.db_path:
492+
self._save_program(program)
493+
494+
logger.debug(f"Added program {program.id} to island {island_idx}")
495+
496+
return program.id
497+
370498
def get(self, program_id: str) -> Optional[Program]:
371499
"""
372500
Get a program by ID
@@ -1313,6 +1441,7 @@ def _sample_exploration_parent(self) -> Program:
13131441
metadata={"island": self.current_island},
13141442
artifacts_json=best_program.artifacts_json,
13151443
artifact_dir=best_program.artifact_dir,
1444+
embedding=copy.deepcopy(best_program.embedding)
13161445
)
13171446
self.programs[copy_program.id] = copy_program
13181447
self.islands[self.current_island].add(copy_program.id)
@@ -1359,6 +1488,7 @@ def _sample_exploration_parent(self) -> Program:
13591488
metadata={"island": self.current_island},
13601489
artifacts_json=best_program.artifacts_json,
13611490
artifact_dir=best_program.artifact_dir,
1491+
embedding=copy.deepcopy(best_program.embedding)
13621492
)
13631493
self.programs[copy_program.id] = copy_program
13641494
self.islands[self.current_island].add(copy_program.id)

0 commit comments

Comments
 (0)