55import asyncio
66import logging
77import os
8+ import shutil
89import re
910import time
1011import uuid
1112from pathlib import Path
1213from typing import Any , Dict , List , Optional , Tuple , Union
1314import traceback
15+ import concurrent .futures
1416
1517from openevolve .config import Config , load_config
1618from openevolve .database import Program , ProgramDatabase
1719from openevolve .evaluator import Evaluator
1820from openevolve .llm .ensemble import LLMEnsemble
1921from openevolve .prompt .sampler import PromptSampler
22+ from openevolve .iteration import run_iteration_sync , Result
2023from openevolve .utils .code_utils import (
21- apply_diff ,
2224 extract_code_language ,
23- extract_diffs ,
24- format_diff_summary ,
25- parse_evolve_blocks ,
26- parse_full_rewrite ,
2725)
2826from openevolve .utils .format_utils import (
2927 format_metrics_safe ,
@@ -129,7 +127,8 @@ def __init__(
129127 # Load initial program
130128 self .initial_program_path = initial_program_path
131129 self .initial_program_code = self ._load_initial_program ()
132- self .language = extract_code_language (self .initial_program_code )
130+ if not self .config .language :
131+ self .config .language = extract_code_language (self .initial_program_code )
133132
134133 # Extract file extension from initial program
135134 self .file_extension = os .path .splitext (initial_program_path )[1 ]
@@ -162,8 +161,9 @@ def __init__(
162161 self .evaluator_prompt_sampler ,
163162 database = self .database ,
164163 )
164+ self .evaluation_file = evaluation_file
165165
166- logger .info (f"Initialized OpenEvolve with { initial_program_path } " f"and { evaluation_file } " )
166+ logger .info (f"Initialized OpenEvolve with { initial_program_path } " )
167167
168168 def _setup_logging (self ) -> None :
169169 """Set up logging"""
@@ -236,7 +236,7 @@ async def run(
236236 initial_program = Program (
237237 id = initial_program_id ,
238238 code = self .initial_program_code ,
239- language = self .language ,
239+ language = self .config . language ,
240240 metrics = initial_metrics ,
241241 iteration_found = start_iteration ,
242242 )
@@ -263,171 +263,112 @@ async def run(
263263 logger .info (f"Using island-based evolution with { self .config .database .num_islands } islands" )
264264 self .database .log_island_status ()
265265
266- for i in range (start_iteration , total_iterations ):
267- iteration_start = time .time ()
268-
269- # Manage island evolution - switch islands periodically
270- if i > start_iteration and current_island_counter >= programs_per_island :
271- self .database .next_island ()
272- current_island_counter = 0
273- logger .debug (f"Switched to island { self .database .current_island } " )
274-
275- current_island_counter += 1
276-
277- # Sample parent and inspirations from current island
278- parent , inspirations = self .database .sample ()
279-
280- # Get artifacts for the parent program if available
281- parent_artifacts = self .database .get_artifacts (parent .id )
282-
283- # Get actual top programs for prompt context (separate from inspirations)
284- # This ensures the LLM sees only high-performing programs as examples
285- actual_top_programs = self .database .get_top_programs (5 )
286-
287- # Build prompt
288- prompt = self .prompt_sampler .build_prompt (
289- current_program = parent .code ,
290- parent_program = parent .code , # We don't have the parent's code, use the same
291- program_metrics = parent .metrics ,
292- previous_programs = [p .to_dict () for p in self .database .get_top_programs (3 )],
293- top_programs = [p .to_dict () for p in actual_top_programs ], # Use actual top programs
294- inspirations = [p .to_dict () for p in inspirations ], # Pass inspirations separately
295- language = self .language ,
296- evolution_round = i ,
297- diff_based_evolution = self .config .diff_based_evolution ,
298- program_artifacts = parent_artifacts if parent_artifacts else None ,
299- )
300-
301- # Generate code modification
302- try :
303- llm_response = await self .llm_ensemble .generate_with_context (
304- system_message = prompt ["system" ],
305- messages = [{"role" : "user" , "content" : prompt ["user" ]}],
266+ # create temp file to save database snapshots to for process workers to load from
267+ temp_db_path = "tmp/" + str (uuid .uuid4 ())
268+ self .database .save (temp_db_path , start_iteration )
269+
270+ with concurrent .futures .ProcessPoolExecutor (
271+ max_workers = self .config .evaluator .parallel_evaluations
272+ ) as executor :
273+ futures = []
274+ for i in range (start_iteration , total_iterations ):
275+ futures .append (
276+ executor .submit (
277+ run_iteration_sync , i , self .config , self .evaluation_file , temp_db_path
278+ )
306279 )
307280
308- # Parse the response
309- if self .config .diff_based_evolution :
310- diff_blocks = extract_diffs (llm_response )
311-
312- if not diff_blocks :
313- logger .warning (f"Iteration { i + 1 } : No valid diffs found in response" )
281+ iteration = start_iteration + 1
282+ for future in concurrent .futures .as_completed (futures ):
283+ logger .info (f"Completed iteration { iteration } " )
284+ try :
285+ result : Result = future .result ()
286+ # if result is nonType
287+ if not isinstance (result , Result ):
288+ logger .warning (f"No valid diffs or program length exceeded limit" )
314289 continue
290+ # Manage island evolution - switch islands periodically
291+ if (
292+ iteration - 1 > start_iteration
293+ and current_island_counter >= programs_per_island
294+ ):
295+ self .database .next_island ()
296+ current_island_counter = 0
297+ logger .debug (f"Switched to island { self .database .current_island } " )
298+
299+ current_island_counter += 1
300+
301+ # Add to database (will be added to current island)
302+ self .database .add (result .child_program , iteration = iteration )
303+
304+ # Log prompts
305+ self .database .log_prompt (
306+ template_key = (
307+ "full_rewrite_user" if not self .config .diff_based_evolution else "diff_user"
308+ ),
309+ program_id = result .child_program .id ,
310+ prompt = result .prompt ,
311+ responses = [result .llm_response ],
312+ )
315313
316- # Apply the diffs
317- child_code = apply_diff (parent .code , llm_response )
318- changes_summary = format_diff_summary (diff_blocks )
319- else :
320- # Parse full rewrite
321- new_code = parse_full_rewrite (llm_response , self .language )
322-
323- if not new_code :
324- logger .warning (f"Iteration { i + 1 } : No valid code found in response" )
325- continue
326-
327- child_code = new_code
328- changes_summary = "Full rewrite"
329-
330- # Check code length
331- if len (child_code ) > self .config .max_code_length :
332- logger .warning (
333- f"Iteration { i + 1 } : Generated code exceeds maximum length "
334- f"({ len (child_code )} > { self .config .max_code_length } )"
314+ # Store artifacts if they exist (after program is added to database)
315+ if result .artifacts :
316+ self .database .store_artifacts (result .child_program .id , result .artifacts )
317+
318+ # Log prompts
319+ self .database .log_prompt (
320+ template_key = (
321+ "full_rewrite_user" if not self .config .diff_based_evolution else "diff_user"
322+ ),
323+ program_id = result .child_program .id ,
324+ prompt = result .prompt ,
325+ responses = [result .llm_response ],
335326 )
336- continue
337327
338- # Evaluate the child program
339- child_id = str (uuid .uuid4 ())
340- child_metrics = await self .evaluator .evaluate_program (child_code , child_id )
341-
342- # Handle artifacts if they exist
343- artifacts = self .evaluator .get_pending_artifacts (child_id )
344-
345- # Create a child program
346- child_program = Program (
347- id = child_id ,
348- code = child_code ,
349- language = self .language ,
350- parent_id = parent .id ,
351- generation = parent .generation + 1 ,
352- metrics = child_metrics ,
353- metadata = {
354- "changes" : changes_summary ,
355- "parent_metrics" : parent .metrics ,
356- },
357- )
328+ # Increment generation for current island
329+ self .database .increment_island_generation ()
358330
359- # Add to database (will be added to current island)
360- self .database .add (child_program , iteration = i + 1 )
361-
362- # Log prompts
363- self .database .log_prompt (
364- template_key = (
365- "full_rewrite_user" if not self .config .diff_based_evolution else "diff_user"
366- ),
367- program_id = child_id ,
368- prompt = prompt ,
369- responses = [llm_response ],
370- )
331+ # Check if migration should occur
332+ if self .database .should_migrate ():
333+ logger .info (f"Performing migration at iteration { iteration } " )
334+ self .database .migrate_programs ()
335+ self .database .log_island_status ()
371336
372- # Store artifacts if they exist
373- if artifacts :
374- self .database .store_artifacts (child_id , artifacts )
375-
376- # Log prompts
377- self .database .log_prompt (
378- template_key = (
379- "full_rewrite_user" if not self .config .diff_based_evolution else "diff_user"
380- ),
381- program_id = child_id ,
382- prompt = prompt ,
383- responses = [llm_response ],
384- )
337+ # Log progress
338+ self ._log_iteration (
339+ iteration , result .parent , result .child_program , result .iteration_time
340+ )
385341
386- # Increment generation for current island
387- self .database .increment_island_generation ()
388-
389- # Check if migration should occur
390- if self .database .should_migrate ():
391- logger .info (f"Performing migration at iteration { i + 1 } " )
392- self .database .migrate_programs ()
393- self .database .log_island_status ()
394-
395- # Log progress
396- iteration_time = time .time () - iteration_start
397- self ._log_iteration (i , parent , child_program , iteration_time )
398-
399- # Specifically check if this is the new best program
400- if self .database .best_program_id == child_program .id :
401- logger .info (f"🌟 New best solution found at iteration { i + 1 } : { child_program .id } " )
402- logger .info (f"Metrics: { format_metrics_safe (child_program .metrics )} " )
403-
404- # Save checkpoint
405- if (i + 1 ) % self .config .checkpoint_interval == 0 :
406- self ._save_checkpoint (i + 1 )
407- # Also log island status at checkpoints
408- logger .info (f"Island status at checkpoint { i + 1 } :" )
409- self .database .log_island_status ()
410-
411- # Check if target score reached
412- if target_score is not None :
413- # Only consider numeric metrics for target score calculation
414- numeric_metrics = [
415- v
416- for v in child_metrics .values ()
417- if isinstance (v , (int , float )) and not isinstance (v , bool )
418- ]
419- if numeric_metrics :
420- avg_score = sum (numeric_metrics ) / len (numeric_metrics )
342+ # Specifically check if this is the new best program
343+ if self .database .best_program_id == result .child_program .id :
344+ logger .info (
345+ f"🌟 New best solution found at iteration { iteration } : { result .child_program .id } "
346+ )
347+ logger .info (f"Metrics: { format_metrics_safe (result .child_program .metrics )} " )
348+
349+ # Save checkpoint
350+ if (iteration ) % self .config .checkpoint_interval == 0 :
351+ self ._save_checkpoint (iteration )
352+ # Also log island status at checkpoints
353+ logger .info (f"Island status at checkpoint { iteration } :" )
354+ self .database .log_island_status ()
355+
356+ # Check if target score reached
357+ if target_score is not None :
358+ avg_score = sum (result ["child_metrics" ].values ()) / max (
359+ 1 , len (result .child_metrics )
360+ )
421361 if avg_score >= target_score :
422362 logger .info (
423- f"Target score { target_score } reached after { i + 1 } iterations"
363+ f"Target score { target_score } reached after { iteration } iterations"
424364 )
425365 break
366+ self .database .save (temp_db_path , iteration )
426367
427- except Exception as e :
428- logger .exception (f"Error in iteration { i + 1 } : { str (e )} " )
429- continue
430-
368+ except Exception as e :
369+ logger .error (f"Error in iteration { i + 1 } : { str (e )} " )
370+ continue
371+ shutil . rmtree ( temp_db_path )
431372 # Get the best program using our tracking mechanism
432373 best_program = None
433374 if self .database .best_program_id :
@@ -607,4 +548,4 @@ def _save_best_program(self, program: Optional[Program] = None) -> None:
607548 indent = 2 ,
608549 )
609550
610- logger .info (f"Saved best program to { code_path } with program info to { info_path } " )
551+ logger .info (f"Saved best program to { code_path } with program info to { info_path } " )
0 commit comments