Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ database:
# MAP-Elites quality-diversity
population_size: 500
num_islands: 5 # Parallel evolution
# island_initial_programs: [null, "seeds/alt.py"] # Optional per-island starting programs
migration_interval: 20
feature_dimensions: ["complexity", "diversity", "performance"]

Expand Down
9 changes: 9 additions & 0 deletions configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ database:
num_islands: 5 # Number of separate populations
migration_interval: 50 # Migrate every N generations
migration_rate: 0.1 # Fraction of top programs to migrate
# island_initial_programs: [null, "seeds/greedy.py"] # Optional per-island starting programs
```

### Parameter Guidelines

- **num_islands**: 3-10 for most problems (more = more diversity)
- **migration_interval**: 25-100 generations (higher = more independence)
- **migration_rate**: 0.05-0.2 (5%-20%, higher = faster knowledge sharing)
- **island_initial_programs**: Start islands from different programs, e.g. distinct
algorithmic approaches. Entry *i* is the program island *i* starts from; `null` or a
missing entry keeps the default (the island starts empty and takes its first parent
from the other islands). Entry 0 must be `null`, because island 0 always starts from
the initial program given on the command line, and the list may not be longer than
`num_islands`. Relative paths resolve against the config file's directory. Each seed is
evaluated once at the start of a fresh run; resuming from a checkpoint does not add
them again.

### When to Use What

Expand Down
4 changes: 4 additions & 0 deletions configs/default_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ database:
population_size: 1000 # Maximum number of programs to keep in memory
archive_size: 100 # Size of elite archive
num_islands: 5 # Number of islands for island model (separate populations)
# Optional starting program per island index; null/missing starts the island as usual.
# Entry 0 must be null: island 0 always starts from the initial program given on the
# command line. Relative paths resolve against this config file's directory.
# island_initial_programs: [null, "seeds/greedy.py", null, "seeds/random_search.py"]

# Island-based evolution parameters
# Islands provide diversity by maintaining separate populations that evolve independently.
Expand Down
4 changes: 4 additions & 0 deletions configs/island_config_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ database:

# Island-based evolution settings
num_islands: 5 # Number of separate populations
# Optional starting program per island; null/missing starts the island as usual.
# Entry 0 must be null (island 0 starts from the command-line initial program).
# Relative paths resolve against this config file's directory.
# island_initial_programs: [null, "seeds/greedy.py", null, "seeds/random_search.py"]
migration_interval: 50 # Migrate every 50 generations
migration_rate: 0.1 # Migrate 10% of top programs

Expand Down
28 changes: 28 additions & 0 deletions openevolve/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ class DatabaseConfig:
population_size: int = 1000
archive_size: int = 100
num_islands: int = 5
# Entry i is the program island i starts from; null or missing keeps the default.
# Island 0 always starts from the initial program passed on the command line.
island_initial_programs: List[Optional[str]] = field(default_factory=list)

# Selection parameters
elite_selection_ratio: float = 0.1
Expand Down Expand Up @@ -384,6 +387,19 @@ class DatabaseConfig:
embedding_api_base: Optional[str] = None
similarity_threshold: float = 0.99

def validate_island_initial_programs(self) -> None:
"""Check island_initial_programs against num_islands and the island 0 rule"""
if len(self.island_initial_programs) > self.num_islands:
raise ValueError(
"database.island_initial_programs has more entries than database.num_islands"
)

if self.island_initial_programs and self.island_initial_programs[0] is not None:
raise ValueError(
"database.island_initial_programs[0] must be null; island 0 starts from the "
"initial program passed on the command line"
)


@dataclass
class EvaluatorConfig:
Expand Down Expand Up @@ -486,6 +502,16 @@ def from_yaml(cls, path: Union[str, Path]) -> "Config":
if not template_path.is_absolute():
config.prompt.template_dir = str((config_path.parent / template_path).resolve())

# Resolve island_initial_programs relative to config file location
config.database.island_initial_programs = [
(
str((config_path.parent / seed_path).resolve())
if seed_path is not None and not Path(seed_path).is_absolute()
else seed_path
)
for seed_path in config.database.island_initial_programs
]

return config

@classmethod
Expand Down Expand Up @@ -531,6 +557,8 @@ def from_dict(cls, config_dict: Dict[str, Any]) -> "Config":
"prompt.island_system_messages has more entries than database.num_islands"
)

config.database.validate_island_initial_programs()

if config.prompt.programs_as_changes_description and not config.diff_based_evolution:
raise ValueError(
"prompt.programs_as_changes_description=true requires diff_based_evolution=true "
Expand Down
78 changes: 57 additions & 21 deletions openevolve/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ def __init__(
if not self.file_extension.startswith("."):
self.file_extension = f".{self.file_extension}"

# Load per-island initial programs (island 0 always starts from the initial program).
# Validate again: a Config built in code never passes through Config.from_dict
self.config.database.validate_island_initial_programs()
self.island_initial_program_codes: Dict[int, str] = {}
for island_idx, seed_path in enumerate(self.config.database.island_initial_programs):
if seed_path is None:
continue
if not os.path.isfile(seed_path):
raise FileNotFoundError(
f"database.island_initial_programs[{island_idx}] '{seed_path}' does not exist"
)
if Path(seed_path).suffix != Path(initial_program_path).suffix:
logger.warning(
f"Initial program for island {island_idx} '{seed_path}' has a different "
f"extension than '{initial_program_path}'"
)
with open(seed_path, "r") as f:
self.island_initial_program_codes[island_idx] = f.read()

# Set the file_suffix in config (can be overridden in YAML)
if not hasattr(self.config, "file_suffix") or self.config.file_suffix == ".py":
self.config.file_suffix = self.file_extension
Expand Down Expand Up @@ -250,6 +269,38 @@ def _load_initial_program(self) -> str:
with open(self.initial_program_path, "r") as f:
return f.read()

async def _add_initial_program(
self, code: str, iteration: int, target_island: Optional[int] = None
) -> Program:
"""Evaluate a starting program, add it to the database, and store its artifacts"""
program_id = str(uuid.uuid4())

# Evaluate the initial program
metrics = await self.evaluator.evaluate_program(code, program_id)

program = Program(
id=program_id,
code=code,
changes_description=self.config.prompt.initial_changes_description,
language=self.config.language,
metrics=metrics,
iteration_found=iteration,
)

if self.database.add(program, target_island=target_island) is None:
logger.warning(
f"Population strategy rejected the initial program for island {target_island}"
)
return program

# Check for and store artifacts from initial program
artifacts = self.evaluator.get_pending_artifacts(program_id)
if artifacts:
self.database.store_artifacts(program_id, artifacts)
logger.info(f"Stored artifacts for initial program")

return program

async def run(
self,
iterations: Optional[int] = None,
Expand Down Expand Up @@ -291,29 +342,14 @@ async def run(

if should_add_initial:
logger.info("Adding initial program to database")
initial_program_id = str(uuid.uuid4())

# Evaluate the initial program
initial_metrics = await self.evaluator.evaluate_program(
self.initial_program_code, initial_program_id
initial_program = await self._add_initial_program(
self.initial_program_code, start_iteration
)
initial_metrics = initial_program.metrics

initial_program = Program(
id=initial_program_id,
code=self.initial_program_code,
changes_description=self.config.prompt.initial_changes_description,
language=self.config.language,
metrics=initial_metrics,
iteration_found=start_iteration,
)

self.database.add(initial_program)

# Check for and store artifacts from initial program
initial_artifacts = self.evaluator.get_pending_artifacts(initial_program_id)
if initial_artifacts:
self.database.store_artifacts(initial_program_id, initial_artifacts)
logger.info(f"Stored artifacts for initial program")
for island_idx, seed_code in self.island_initial_program_codes.items():
logger.info(f"Adding initial program for island {island_idx} to database")
await self._add_initial_program(seed_code, start_iteration, island_idx)

# Check if combined_score is present in the metrics
if "combined_score" not in initial_metrics:
Expand Down
Loading
Loading