From f46233d4999ba3da28bb2444d51d5dabee7d6a82 Mon Sep 17 00:00:00 2001 From: MashAliK <42744726+MashAliK@users.noreply.github.com> Date: Mon, 9 Jun 2025 01:29:14 -0400 Subject: [PATCH 1/4] init --- openevolve/config.py | 2 + openevolve/controller.py | 207 +++++++++++++-------------------- openevolve/database.py | 86 ++++++++------ openevolve/iteration.py | 144 +++++++++++++++++++++++ openevolve/llm/ensemble.py | 4 +- openevolve/utils/__init__.py | 2 - openevolve/utils/code_utils.py | 36 ------ pyproject.toml | 2 + 8 files changed, 285 insertions(+), 198 deletions(-) create mode 100644 openevolve/iteration.py diff --git a/openevolve/config.py b/openevolve/config.py index 1a1a19338..b78710fd8 100644 --- a/openevolve/config.py +++ b/openevolve/config.py @@ -140,6 +140,7 @@ class DatabaseConfig: # Evolutionary parameters population_size: int = 1000 + allowed_population_overflow: int = 50 archive_size: int = 100 num_islands: int = 5 @@ -196,6 +197,7 @@ class Config: log_level: str = "INFO" log_dir: Optional[str] = None random_seed: Optional[int] = None + language: str = None # Component configurations llm: LLMConfig = field(default_factory=LLMConfig) diff --git a/openevolve/controller.py b/openevolve/controller.py index 466b6d779..89e9f4a19 100644 --- a/openevolve/controller.py +++ b/openevolve/controller.py @@ -10,19 +10,16 @@ import uuid from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union +import concurrent.futures from openevolve.config import Config, load_config from openevolve.database import Program, ProgramDatabase from openevolve.evaluator import Evaluator from openevolve.llm.ensemble import LLMEnsemble from openevolve.prompt.sampler import PromptSampler +from openevolve.iteration import run_iteration_sync, Result from openevolve.utils.code_utils import ( - apply_diff, extract_code_language, - extract_diffs, - format_diff_summary, - parse_evolve_blocks, - parse_full_rewrite, ) from openevolve.utils.format_utils import ( format_metrics_safe, @@ -83,7 +80,8 @@ def __init__( # Load initial program self.initial_program_path = initial_program_path self.initial_program_code = self._load_initial_program() - self.language = extract_code_language(self.initial_program_code) + if not self.config.language: + self.config.language = extract_code_language(self.initial_program_code) # Extract file extension from initial program self.file_extension = os.path.splitext(initial_program_path)[1] @@ -115,8 +113,9 @@ def __init__( self.llm_evaluator_ensemble, self.evaluator_prompt_sampler, ) + self.evaluation_file = evaluation_file - logger.info(f"Initialized OpenEvolve with {initial_program_path} " f"and {evaluation_file}") + logger.info(f"Initialized OpenEvolve with {initial_program_path}") def _setup_logging(self) -> None: """Set up logging""" @@ -189,7 +188,7 @@ async def run( initial_program = Program( id=initial_program_id, code=self.initial_program_code, - language=self.language, + language=self.config.language, metrics=initial_metrics, iteration_found=start_iteration, ) @@ -216,127 +215,85 @@ async def run( logger.info(f"Using island-based evolution with {self.config.database.num_islands} islands") self.database.log_island_status() - for i in range(start_iteration, total_iterations): - iteration_start = time.time() - - # Manage island evolution - switch islands periodically - if i > start_iteration and current_island_counter >= programs_per_island: - self.database.next_island() - current_island_counter = 0 - logger.debug(f"Switched to island {self.database.current_island}") - - current_island_counter += 1 - - # Sample parent and inspirations from current island - parent, inspirations = self.database.sample() - - # Build prompt - prompt = self.prompt_sampler.build_prompt( - current_program=parent.code, - parent_program=parent.code, # We don't have the parent's code, use the same - program_metrics=parent.metrics, - previous_programs=[p.to_dict() for p in self.database.get_top_programs(3)], - top_programs=[p.to_dict() for p in inspirations], - language=self.language, - evolution_round=i, - allow_full_rewrite=self.config.allow_full_rewrites, - ) - - # Generate code modification - try: - llm_response = await self.llm_ensemble.generate_with_context( - system_message=prompt["system"], - messages=[{"role": "user", "content": prompt["user"]}], - ) - - # Parse the response - if self.config.diff_based_evolution: - diff_blocks = extract_diffs(llm_response) - - if not diff_blocks: - logger.warning(f"Iteration {i+1}: No valid diffs found in response") - continue - - # Apply the diffs - child_code = apply_diff(parent.code, llm_response) - changes_summary = format_diff_summary(diff_blocks) - else: - # Parse full rewrite - new_code = parse_full_rewrite(llm_response, self.language) - - if not new_code: - logger.warning(f"Iteration {i+1}: No valid code found in response") - continue - - child_code = new_code - changes_summary = "Full rewrite" - - # Check code length - if len(child_code) > self.config.max_code_length: - logger.warning( - f"Iteration {i+1}: Generated code exceeds maximum length " - f"({len(child_code)} > {self.config.max_code_length})" + # create temp file to save database snapshots to for process workers to load from + temp_db_path = "temp/" + str(uuid.uuid4()) + self.database.save(temp_db_path, start_iteration) + + with concurrent.futures.ProcessPoolExecutor( + max_workers=self.config.evaluator.parallel_evaluations + ) as executor: + futures = [] + for i in range(start_iteration, total_iterations): + futures.append( + executor.submit( + run_iteration_sync, i, self.config, self.evaluation_file, temp_db_path ) - continue - - # Evaluate the child program - child_id = str(uuid.uuid4()) - child_metrics = await self.evaluator.evaluate_program(child_code, child_id) - - # Create a child program - child_program = Program( - id=child_id, - code=child_code, - language=self.language, - parent_id=parent.id, - generation=parent.generation + 1, - metrics=child_metrics, - metadata={ - "changes": changes_summary, - "parent_metrics": parent.metrics, - }, ) - # Add to database (will be added to current island) - self.database.add(child_program, iteration=i + 1) - - # Increment generation for current island - self.database.increment_island_generation() - - # Check if migration should occur - if self.database.should_migrate(): - logger.info(f"Performing migration at iteration {i+1}") - self.database.migrate_programs() - self.database.log_island_status() - - # Log progress - iteration_time = time.time() - iteration_start - self._log_iteration(i, parent, child_program, iteration_time) - - # Specifically check if this is the new best program - if self.database.best_program_id == child_program.id: - logger.info( - f"🌟 New best solution found at iteration {i+1}: {child_program.id}" + iteration = start_iteration + 1 + for future in concurrent.futures.as_completed(futures): + logger.info(f"Completed iteration {iteration}") + try: + result: Result = future.result() + # Manage island evolution - switch islands periodically + if ( + iteration - 1 > start_iteration + and current_island_counter >= programs_per_island + ): + self.database.next_island() + current_island_counter = 0 + logger.debug(f"Switched to island {self.database.current_island}") + + current_island_counter += 1 + + # Add to database (will be added to current island) + self.database.add(result.child_program, iteration=iteration) + + # Increment generation for current island + self.database.increment_island_generation() + + # Check if migration should occur + if self.database.should_migrate(): + logger.info(f"Performing migration at iteration {iteration}") + self.database.migrate_programs() + self.database.log_island_status() + + # Log progress + self._log_iteration( + iteration, result.parent, result.child_program, result.iteration_time ) - logger.info(f"Metrics: {format_metrics_safe(child_program.metrics)}") - - # Save checkpoint - if (i + 1) % self.config.checkpoint_interval == 0: - self._save_checkpoint(i + 1) - # Also log island status at checkpoints - logger.info(f"Island status at checkpoint {i+1}:") - self.database.log_island_status() - - # Check if target score reached - if target_score is not None: - avg_score = sum(child_metrics.values()) / max(1, len(child_metrics)) - if avg_score >= target_score: - logger.info(f"Target score {target_score} reached after {i+1} iterations") - break - - except Exception as e: - logger.error(f"Error in iteration {i+1}: {str(e)}") - continue + + # Specifically check if this is the new best program + if self.database.best_program_id == result.child_program.id: + logger.info( + f"🌟 New best solution found at iteration {iteration}: {result.child_program.id}" + ) + logger.info(f"Metrics: {format_metrics_safe(result.child_program.metrics)}") + + # Save checkpoint + if (iteration) % self.config.checkpoint_interval == 0: + self._save_checkpoint(iteration) + # Also log island status at checkpoints + logger.info(f"Island status at checkpoint {iteration}:") + self.database.log_island_status() + + # Check if target score reached + if target_score is not None: + avg_score = sum(result["child_metrics"].values()) / max( + 1, len(result.child_metrics) + ) + if avg_score >= target_score: + logger.info( + f"Target score {target_score} reached after {iteration} iterations" + ) + break + + self.database.save(temp_db_path, iteration) + iteration += 1 + except Exception: + logger.exception(f"Error in iteration {iteration}:") + continue + os.rmdir(temp_db_path) # Get the best program using our tracking mechanism best_program = None diff --git a/openevolve/database.py b/openevolve/database.py index 983138d94..89b22b52b 100644 --- a/openevolve/database.py +++ b/openevolve/database.py @@ -5,16 +5,18 @@ import json import logging import os +import shutil import random import time from dataclasses import asdict, dataclass, field from pathlib import Path +from Levenshtein import distance, ratio +from filelock import FileLock, Timeout from typing import Any, Dict, List, Optional, Set, Tuple, Union import numpy as np from openevolve.config import DatabaseConfig -from openevolve.utils.code_utils import calculate_edit_distance from openevolve.utils.metrics_utils import safe_numeric_average logger = logging.getLogger(__name__) @@ -131,9 +133,6 @@ def add( self.programs[program.id] = program - # Enforce population size limit - self._enforce_population_limit() - # Calculate feature coordinates for MAP-Elites feature_coords = self._calculate_feature_coords(program) @@ -163,6 +162,10 @@ def add( self._save_program(program) logger.debug(f"Added program {program.id} to island {island_idx}") + + # Enforce population size limit + self._enforce_population_limit() + return program.id def get(self, program_id: str) -> Optional[Program]: @@ -304,12 +307,19 @@ def save(self, path: Optional[str] = None, iteration: int = 0) -> None: logger.warning("No database path specified, skipping save") return - # Create directory if it doesn't exist - os.makedirs(save_path, exist_ok=True) + lock_path = save_path + ".lock" + try: + with FileLock(lock_path, timeout=10): + # Create directory and remove old path if it exists + if os.path.exists(save_path): + shutil.rmtree(save_path) + os.makedirs(save_path) - # Save each program - for program in self.programs.values(): - self._save_program(program, save_path) + # Save each program + for program in self.programs.values(): + self._save_program(program, save_path) + except Timeout: + logger.exception("Could not acquire the lock within 10 seconds") # Save metadata metadata = { @@ -361,19 +371,24 @@ def load(self, path: str) -> None: logger.info(f"Loaded database metadata with last_iteration={self.last_iteration}") # Load programs + lock_path = path + ".lock" programs_dir = os.path.join(path, "programs") - if os.path.exists(programs_dir): - for program_file in os.listdir(programs_dir): - if program_file.endswith(".json"): - program_path = os.path.join(programs_dir, program_file) - try: - with open(program_path, "r") as f: - program_data = json.load(f) - - program = Program.from_dict(program_data) - self.programs[program.id] = program - except Exception as e: - logger.warning(f"Error loading program {program_file}: {str(e)}") + try: + with FileLock(lock_path, timeout=10): + if os.path.exists(programs_dir): + for program_file in os.listdir(programs_dir): + if program_file.endswith(".json"): + program_path = os.path.join(programs_dir, program_file) + try: + with open(program_path, "r") as f: + program_data = json.load(f) + + program = Program.from_dict(program_data) + self.programs[program.id] = program + except Exception as e: + logger.warning(f"Error loading program {program_file}: {str(e)}") + except Timeout: + logger.exception("Could not acquire the lock within 10 seconds") logger.info(f"Loaded database with {len(self.programs)} programs from {path}") @@ -414,7 +429,7 @@ def _calculate_feature_coords(self, program: Program) -> List[int]: if dim == "complexity": # Use code length as complexity measure complexity = len(program.code) - bin_idx = min(int(complexity / 1000 * self.feature_bins), self.feature_bins - 1) + bin_idx = min(int(complexity / 1000), self.feature_bins - 1) coords.append(bin_idx) elif dim == "diversity": # Use average edit distance to other programs @@ -424,13 +439,10 @@ def _calculate_feature_coords(self, program: Program) -> List[int]: sample_programs = random.sample( list(self.programs.values()), min(5, len(self.programs)) ) - avg_distance = sum( - calculate_edit_distance(program.code, other.code) - for other in sample_programs + avg_distance_ratio = sum( + 1 - ratio(program.code, other.code) for other in sample_programs ) / len(sample_programs) - bin_idx = min( - int(avg_distance / 1000 * self.feature_bins), self.feature_bins - 1 - ) + bin_idx = min(int(avg_distance_ratio * 20), self.feature_bins - 1) coords.append(bin_idx) elif dim == "score": # Use average of metrics @@ -448,7 +460,10 @@ def _calculate_feature_coords(self, program: Program) -> List[int]: else: # Default to middle bin if feature not found coords.append(self.feature_bins // 2) - + logging.info( + "MAP-Elites coords: %s", + str({self.config.feature_dimensions[i]: coords[i] for i in range(len(coords))}), + ) return coords def _feature_coords_to_key(self, coords: List[int]) -> str: @@ -699,7 +714,10 @@ def _enforce_population_limit(self) -> None: """ Enforce the population size limit by removing worst programs if needed """ - if len(self.programs) <= self.config.population_size: + if ( + len(self.programs) + <= self.config.population_size + self.config.allowed_population_overflow + ): return # Calculate how many programs to remove @@ -884,7 +902,7 @@ def _calculate_island_diversity(self, programs: List[Program]) -> float: if len(programs) < 2: return 0.0 - total_distance = 0 + total_diversity_ratio = 0 comparisons = 0 # Sample up to 10 programs for efficiency @@ -895,10 +913,12 @@ def _calculate_island_diversity(self, programs: List[Program]) -> float: for i, prog1 in enumerate(sample_programs): for prog2 in sample_programs[i + 1 :]: - total_distance += calculate_edit_distance(prog1.code, prog2.code) + total_diversity_ratio += 1 - ratio( + prog1.code, prog2.code + ) # ratio measures similarity comparisons += 1 - return total_distance / max(1, comparisons) + return total_diversity_ratio / max(1, comparisons) def log_island_status(self) -> None: """Log current status of all islands""" diff --git a/openevolve/iteration.py b/openevolve/iteration.py new file mode 100644 index 000000000..b447055bd --- /dev/null +++ b/openevolve/iteration.py @@ -0,0 +1,144 @@ +import asyncio +import os +import uuid +import logging +import time +from dataclasses import dataclass + +from openevolve.database import Program, ProgramDatabase +from openevolve.config import Config +from openevolve.evaluator import Evaluator +from openevolve.llm.ensemble import LLMEnsemble +from openevolve.prompt.sampler import PromptSampler +from openevolve.utils.code_utils import ( + apply_diff, + extract_diffs, + format_diff_summary, + parse_full_rewrite, +) + + +@dataclass +class Result: + """Resulting program and metrics from an iteration of OpenEvolve""" + + child_program: str = None + parent: str = None + child_metrics: str = None + iteration_time: float = None + + +def run_iteration_sync(iteration: int, config: Config, evaluation_file: str, database_path: str): + # setup logger showing PID for current process + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) + logging.basicConfig( + level=getattr(logging, config.log_level), + format="%(asctime)s - %(levelname)s - PID %(process)d - %(message)s", + ) + logger = logging.getLogger(__name__) + logger.info("Starting iteration in PID %s", os.getpid()) + + llm_ensemble = LLMEnsemble(config.llm.models) + llm_evaluator_ensemble = LLMEnsemble(config.llm.evaluator_models) + + prompt_sampler = PromptSampler(config.prompt) + evaluator_prompt_sampler = PromptSampler(config.prompt) + evaluator_prompt_sampler.set_templates("evaluator_system_message") + + # Pass random seed to database if specified + if config.random_seed is not None: + config.database.random_seed = config.random_seed + + # load most recent database snapshot + config.database.db_path = database_path + database = ProgramDatabase(config.database) + + evaluator = Evaluator( + config.evaluator, + evaluation_file, + llm_evaluator_ensemble, + evaluator_prompt_sampler, + ) + + # Sample parent and inspirations from current island + parent, inspirations = database.sample() + + # Build prompt + prompt = prompt_sampler.build_prompt( + current_program=parent.code, + parent_program=parent.code, # We don't have the parent's code, use the same + program_metrics=parent.metrics, + previous_programs=[p.to_dict() for p in database.get_top_programs(3)], + top_programs=[p.to_dict() for p in inspirations], + language=config.language, + evolution_round=iteration, + allow_full_rewrite=config.allow_full_rewrites, + ) + + async def _run(): + result = Result(parent=parent) + iteration_start = time.time() + + # Generate code modification + try: + llm_response = await llm_ensemble.generate_with_context( + system_message=prompt["system"], + messages=[{"role": "user", "content": prompt["user"]}], + ) + + # Parse the response + if config.diff_based_evolution: + diff_blocks = extract_diffs(llm_response) + + if not diff_blocks: + logger.warning(f"Iteration {iteration+1}: No valid diffs found in response") + return + + # Apply the diffs + child_code = apply_diff(parent.code, llm_response) + changes_summary = format_diff_summary(diff_blocks) + else: + # Parse full rewrite + new_code = parse_full_rewrite(llm_response, config.language) + + if not new_code: + logger.warning(f"Iteration {iteration+1}: No valid code found in response") + return + + child_code = new_code + changes_summary = "Full rewrite" + + # Check code length + if len(child_code) > config.max_code_length: + logger.warning( + f"Iteration {iteration+1}: Generated code exceeds maximum length " + f"({len(child_code)} > {config.max_code_length})" + ) + return + + # Evaluate the child program + child_id = str(uuid.uuid4()) + result.child_metrics = await evaluator.evaluate_program(child_code, child_id) + + # Create a child program + result.child_program = Program( + id=child_id, + code=child_code, + language=config.language, + parent_id=parent.id, + generation=parent.generation + 1, + metrics=result.child_metrics, + metadata={ + "changes": changes_summary, + "parent_metrics": parent.metrics, + }, + ) + + except Exception as e: + logger.exception("Error in PID %s:", os.getpid()) + + result.iteration_time = time.time() - iteration_start + return result + + return asyncio.run(_run()) diff --git a/openevolve/llm/ensemble.py b/openevolve/llm/ensemble.py index b286ff68e..37347458d 100644 --- a/openevolve/llm/ensemble.py +++ b/openevolve/llm/ensemble.py @@ -11,7 +11,7 @@ from openevolve.llm.openai import OpenAILLM from openevolve.config import LLMModelConfig -logger = logging.getLogger(__name__) +# logger = logging.getLogger(__name__) class LLMEnsemble: @@ -28,7 +28,7 @@ def __init__(self, models_cfg: List[LLMModelConfig]): total = sum(self.weights) self.weights = [w / total for w in self.weights] - logger.info( + logging.info( f"Initialized LLM ensemble with models: " + ", ".join( f"{model.name} (weight: {weight:.2f})" diff --git a/openevolve/utils/__init__.py b/openevolve/utils/__init__.py index 89a4a1b62..42b4058d7 100644 --- a/openevolve/utils/__init__.py +++ b/openevolve/utils/__init__.py @@ -10,7 +10,6 @@ ) from openevolve.utils.code_utils import ( apply_diff, - calculate_edit_distance, extract_code_language, extract_diffs, format_diff_summary, @@ -32,7 +31,6 @@ "retry_async", "run_in_executor", "apply_diff", - "calculate_edit_distance", "extract_code_language", "extract_diffs", "format_diff_summary", diff --git a/openevolve/utils/code_utils.py b/openevolve/utils/code_utils.py index 60fb63001..301bca4ca 100644 --- a/openevolve/utils/code_utils.py +++ b/openevolve/utils/code_utils.py @@ -144,42 +144,6 @@ def format_diff_summary(diff_blocks: List[Tuple[str, str]]) -> str: return "\n".join(summary) -def calculate_edit_distance(code1: str, code2: str) -> int: - """ - Calculate the Levenshtein edit distance between two code snippets - - Args: - code1: First code snippet - code2: Second code snippet - - Returns: - Edit distance (number of operations needed to transform code1 into code2) - """ - if code1 == code2: - return 0 - - # Simple implementation of Levenshtein distance - m, n = len(code1), len(code2) - dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] - - for i in range(m + 1): - dp[i][0] = i - - for j in range(n + 1): - dp[0][j] = j - - for i in range(1, m + 1): - for j in range(1, n + 1): - cost = 0 if code1[i - 1] == code2[j - 1] else 1 - dp[i][j] = min( - dp[i - 1][j] + 1, # deletion - dp[i][j - 1] + 1, # insertion - dp[i - 1][j - 1] + cost, # substitution - ) - - return dp[m][n] - - def extract_code_language(code: str) -> str: """ Try to determine the language of a code snippet diff --git a/pyproject.toml b/pyproject.toml index f02ff90e5..f7daee63f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,8 @@ dependencies = [ "pyyaml>=6.0", "numpy>=1.22.0", "tqdm>=4.64.0", + "levenshtein>=0.27.1", + "filelock>=3.18.0", ] [project.optional-dependencies] From 095a5cb940ad051aece5bcd9b82e6b87a85f14b0 Mon Sep 17 00:00:00 2001 From: MashAliK <42744726+MashAliK@users.noreply.github.com> Date: Mon, 9 Jun 2025 01:32:41 -0400 Subject: [PATCH 2/4] revert ensemble --- openevolve/llm/ensemble.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openevolve/llm/ensemble.py b/openevolve/llm/ensemble.py index 37347458d..b286ff68e 100644 --- a/openevolve/llm/ensemble.py +++ b/openevolve/llm/ensemble.py @@ -11,7 +11,7 @@ from openevolve.llm.openai import OpenAILLM from openevolve.config import LLMModelConfig -# logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class LLMEnsemble: @@ -28,7 +28,7 @@ def __init__(self, models_cfg: List[LLMModelConfig]): total = sum(self.weights) self.weights = [w / total for w in self.weights] - logging.info( + logger.info( f"Initialized LLM ensemble with models: " + ", ".join( f"{model.name} (weight: {weight:.2f})" From 3b3e9c0347992eb88036a0c79fb0d9688d6c81e5 Mon Sep 17 00:00:00 2001 From: MashAliK <42744726+MashAliK@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:05:30 -0400 Subject: [PATCH 3/4] fix tmp removal --- openevolve/controller.py | 5 +++-- openevolve/database.py | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/openevolve/controller.py b/openevolve/controller.py index 89e9f4a19..e2485cb5e 100644 --- a/openevolve/controller.py +++ b/openevolve/controller.py @@ -5,6 +5,7 @@ import asyncio import logging import os +import shutil import re import time import uuid @@ -216,7 +217,7 @@ async def run( self.database.log_island_status() # create temp file to save database snapshots to for process workers to load from - temp_db_path = "temp/" + str(uuid.uuid4()) + temp_db_path = "tmp/" + str(uuid.uuid4()) self.database.save(temp_db_path, start_iteration) with concurrent.futures.ProcessPoolExecutor( @@ -293,7 +294,7 @@ async def run( except Exception: logger.exception(f"Error in iteration {iteration}:") continue - os.rmdir(temp_db_path) + shutil.rmtree(temp_db_path) # Get the best program using our tracking mechanism best_program = None diff --git a/openevolve/database.py b/openevolve/database.py index 89b22b52b..c555d127b 100644 --- a/openevolve/database.py +++ b/openevolve/database.py @@ -10,7 +10,7 @@ import time from dataclasses import asdict, dataclass, field from pathlib import Path -from Levenshtein import distance, ratio +from Levenshtein import ratio from filelock import FileLock, Timeout from typing import Any, Dict, List, Optional, Set, Tuple, Union @@ -307,7 +307,8 @@ def save(self, path: Optional[str] = None, iteration: int = 0) -> None: logger.warning("No database path specified, skipping save") return - lock_path = save_path + ".lock" + lock_name = os.path.basename(save_path) + '.lock' + lock_path = os.path.join('tmp/locks', lock_name) try: with FileLock(lock_path, timeout=10): # Create directory and remove old path if it exists @@ -371,7 +372,8 @@ def load(self, path: str) -> None: logger.info(f"Loaded database metadata with last_iteration={self.last_iteration}") # Load programs - lock_path = path + ".lock" + lock_name = os.path.basename(path) + '.lock' + lock_path = os.path.join('tmp/locks', lock_name) programs_dir = os.path.join(path, "programs") try: with FileLock(lock_path, timeout=10): From 474b778dd1e1f2ddd4a88c04ae1d2c5708c604ab Mon Sep 17 00:00:00 2001 From: MashAliK <42744726+MashAliK@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:12:53 -0400 Subject: [PATCH 4/4] lint --- openevolve/database.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openevolve/database.py b/openevolve/database.py index c555d127b..2f985a4a5 100644 --- a/openevolve/database.py +++ b/openevolve/database.py @@ -307,8 +307,8 @@ def save(self, path: Optional[str] = None, iteration: int = 0) -> None: logger.warning("No database path specified, skipping save") return - lock_name = os.path.basename(save_path) + '.lock' - lock_path = os.path.join('tmp/locks', lock_name) + lock_name = os.path.basename(save_path) + ".lock" + lock_path = os.path.join("tmp/locks", lock_name) try: with FileLock(lock_path, timeout=10): # Create directory and remove old path if it exists @@ -372,8 +372,8 @@ def load(self, path: str) -> None: logger.info(f"Loaded database metadata with last_iteration={self.last_iteration}") # Load programs - lock_name = os.path.basename(path) + '.lock' - lock_path = os.path.join('tmp/locks', lock_name) + lock_name = os.path.basename(path) + ".lock" + lock_path = os.path.join("tmp/locks", lock_name) programs_dir = os.path.join(path, "programs") try: with FileLock(lock_path, timeout=10): @@ -452,7 +452,7 @@ def _calculate_feature_coords(self, program: Program) -> List[int]: bin_idx = 0 else: avg_score = safe_numeric_average(program.metrics) - bin_idx = min(int(avg_score * self.feature_bins), self.feature_bins - 1) + bin_idx = max(0, min(int(avg_score * self.feature_bins), self.feature_bins - 1)) coords.append(bin_idx) elif dim in program.metrics: # Use specific metric