Skip to content

Commit 644272d

Browse files
committed
save best prgoram
1 parent 41e365b commit 644272d

File tree

2 files changed

+112
-8
lines changed

2 files changed

+112
-8
lines changed

openevolve/controller.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class OpenEvolve:
3333
3434
Orchestrates the evolution process, coordinating between the prompt sampler,
3535
LLM ensemble, evaluator, and program database.
36+
37+
Features:
38+
- Tracks the absolute best program across evolution steps
39+
- Ensures the best solution is not lost during the MAP-Elites process
40+
- Always includes the best program in the selection process for inspiration
41+
- Maintains detailed logs and metadata about improvements
3642
"""
3743

3844
def __init__(
@@ -238,6 +244,11 @@ async def run(
238244
iteration_time = time.time() - iteration_start
239245
self._log_iteration(i, parent, child_program, iteration_time)
240246

247+
# Specifically check if this is the new best program
248+
if self.database.best_program_id == child_program.id:
249+
logger.info(f"🌟 New best solution found at iteration {i+1}: {child_program.id}")
250+
logger.info(f"Metrics: {', '.join(f'{name}={value:.4f}' for name, value in child_program.metrics.items())}")
251+
241252
# Save checkpoint
242253
if (i + 1) % self.config.checkpoint_interval == 0:
243254
self._save_checkpoint(i + 1)
@@ -255,17 +266,25 @@ async def run(
255266
logger.error(f"Error in iteration {i+1}: {str(e)}")
256267
continue
257268

258-
# Get the best program
259-
best_program = self.database.get_best_program()
269+
# Get the best program using our tracking mechanism
270+
best_program = None
271+
if self.database.best_program_id:
272+
best_program = self.database.get(self.database.best_program_id)
273+
logger.info(f"Using tracked best program: {self.database.best_program_id}")
274+
275+
# Fallback to calculating best program if tracked program not found
276+
if best_program is None:
277+
best_program = self.database.get_best_program()
278+
logger.info("Using calculated best program (tracked program not found)")
260279

261280
if best_program:
262281
logger.info(
263282
f"Evolution complete. Best program has metrics: "
264283
f"{', '.join(f'{name}={value:.4f}' for name, value in best_program.metrics.items())}"
265284
)
266285

267-
# Save the best program
268-
self._save_best_program(best_program)
286+
# Save the best program (using our tracked best program)
287+
self._save_best_program()
269288

270289
return best_program
271290
else:
@@ -322,13 +341,25 @@ def _save_checkpoint(self, iteration: int) -> None:
322341

323342
logger.info(f"Saved checkpoint at iteration {iteration} to {checkpoint_path}")
324343

325-
def _save_best_program(self, program: Program) -> None:
344+
def _save_best_program(self, program: Optional[Program] = None) -> None:
326345
"""
327346
Save the best program
328347
329348
Args:
330-
program: Best program
349+
program: Best program (if None, uses the tracked best program)
331350
"""
351+
# If no program is provided, use the tracked best program from the database
352+
if program is None:
353+
if self.database.best_program_id:
354+
program = self.database.get(self.database.best_program_id)
355+
else:
356+
# Fallback to calculating best program if no tracked best program
357+
program = self.database.get_best_program()
358+
359+
if not program:
360+
logger.warning("No best program found to save")
361+
return
362+
332363
best_dir = os.path.join(self.output_dir, "best")
333364
os.makedirs(best_dir, exist_ok=True)
334365

@@ -345,4 +376,16 @@ def _save_best_program(self, program: Program) -> None:
345376
import json
346377
json.dump(program.metrics, f, indent=2)
347378

379+
# Also save metadata about the best program
380+
meta_path = os.path.join(best_dir, "best_program_info.json")
381+
with open(meta_path, "w") as f:
382+
import json
383+
json.dump({
384+
"id": program.id,
385+
"generation": program.generation,
386+
"timestamp": program.timestamp,
387+
"parent_id": program.parent_id,
388+
"metrics": program.metrics
389+
}, f, indent=2)
390+
348391
logger.info(f"Saved best program to {code_path} with metrics to {metrics_path}")

openevolve/database.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class ProgramDatabase:
5858
5959
The database implements a combination of MAP-Elites algorithm and
6060
island-based population model to maintain diversity during evolution.
61+
It also tracks the absolute best program separately to ensure it's never lost.
6162
"""
6263

6364
def __init__(self, config: DatabaseConfig):
@@ -76,6 +77,9 @@ def __init__(self, config: DatabaseConfig):
7677
# Archive of elite programs
7778
self.archive: Set[str] = set()
7879

80+
# Track the absolute best program separately
81+
self.best_program_id: Optional[str] = None
82+
7983
# Load database from disk if path is provided
8084
if config.db_path and os.path.exists(config.db_path):
8185
self.load(config.db_path)
@@ -112,6 +116,9 @@ def add(self, program: Program) -> str:
112116
# Update archive
113117
self._update_archive(program)
114118

119+
# Update the absolute best program tracking
120+
self._update_best_program(program)
121+
115122
# Save to disk if configured
116123
if self.config.db_path:
117124
self._save_program(program)
@@ -160,6 +167,10 @@ def get_best_program(self, metric: Optional[str] = None) -> Optional[Program]:
160167
if not self.programs:
161168
return None
162169

170+
# If no specific metric and we have a tracked best program, return it
171+
if metric is None and self.best_program_id and self.best_program_id in self.programs:
172+
return self.programs[self.best_program_id]
173+
163174
if metric:
164175
# Sort by specific metric
165176
sorted_programs = sorted(
@@ -174,6 +185,13 @@ def get_best_program(self, metric: Optional[str] = None) -> Optional[Program]:
174185
key=lambda p: sum(p.metrics.values()) / max(1, len(p.metrics)),
175186
reverse=True
176187
)
188+
189+
# Update the best program tracking if we found a better program
190+
if sorted_programs and (self.best_program_id is None or
191+
sorted_programs[0].id != self.best_program_id):
192+
old_id = self.best_program_id
193+
self.best_program_id = sorted_programs[0].id
194+
logger.info(f"Updated best program tracking: {self.best_program_id} ")
177195

178196
return sorted_programs[0] if sorted_programs else None
179197

@@ -236,6 +254,7 @@ def save(self, path: Optional[str] = None) -> None:
236254
"feature_map": self.feature_map,
237255
"islands": [list(island) for island in self.islands],
238256
"archive": list(self.archive),
257+
"best_program_id": self.best_program_id,
239258
}
240259

241260
with open(os.path.join(save_path, "metadata.json"), "w") as f:
@@ -263,6 +282,7 @@ def load(self, path: str) -> None:
263282
self.feature_map = metadata.get("feature_map", {})
264283
self.islands = [set(island) for island in metadata.get("islands", [])]
265284
self.archive = set(metadata.get("archive", []))
285+
self.best_program_id = metadata.get("best_program_id")
266286

267287
# Load programs
268288
programs_dir = os.path.join(path, "programs")
@@ -425,6 +445,39 @@ def _update_archive(self, program: Program) -> None:
425445
if self._is_better(program, worst_program):
426446
self.archive.remove(worst_program.id)
427447
self.archive.add(program.id)
448+
449+
def _update_best_program(self, program: Program) -> None:
450+
"""
451+
Update the absolute best program tracking
452+
453+
Args:
454+
program: Program to consider as the new best
455+
"""
456+
# If we don't have a best program yet, this becomes the best
457+
if self.best_program_id is None:
458+
self.best_program_id = program.id
459+
logger.debug(f"Set initial best program to {program.id}")
460+
return
461+
462+
# Compare with current best program
463+
current_best = self.programs[self.best_program_id]
464+
465+
# Update if the new program is better
466+
if self._is_better(program, current_best):
467+
old_id = self.best_program_id
468+
self.best_program_id = program.id
469+
logger.info(f"New best program {program.id} replaces {old_id}")
470+
471+
# Log improvement in metrics
472+
if program.metrics and current_best.metrics:
473+
improvements = []
474+
for metric, value in program.metrics.items():
475+
if metric in current_best.metrics:
476+
diff = value - current_best.metrics[metric]
477+
improvements.append(f"{metric}: {diff:+.4f}")
478+
479+
if improvements:
480+
logger.info(f"Metric improvements: {', '.join(improvements)}")
428481

429482
def _sample_parent(self) -> Program:
430483
"""
@@ -466,13 +519,21 @@ def _sample_inspirations(
466519
"""
467520
inspirations = []
468521

522+
# Always include the absolute best program if available and different from parent
523+
if self.best_program_id is not None and self.best_program_id != parent.id:
524+
best_program = self.programs[self.best_program_id]
525+
inspirations.append(best_program)
526+
logger.debug(f"Including best program {self.best_program_id} in inspirations")
527+
469528
# Add top programs as inspirations
470529
top_n = max(1, int(n * self.config.elite_selection_ratio))
471530
top_programs = self.get_top_programs(n=top_n)
472-
inspirations.extend(top_programs)
531+
for program in top_programs:
532+
if program.id not in [p.id for p in inspirations] and program.id != parent.id:
533+
inspirations.append(program)
473534

474535
# Add diverse programs
475-
if len(self.programs) > n:
536+
if len(self.programs) > n and len(inspirations) < n:
476537
# Sample from different feature cells
477538
feature_coords = self._calculate_feature_coords(parent)
478539

0 commit comments

Comments
 (0)