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
2 changes: 1 addition & 1 deletion openevolve/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Version information for openevolve package."""

__version__ = "0.2.26"
__version__ = "0.2.27"
199 changes: 129 additions & 70 deletions openevolve/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ async def _run_evolution_async(
# Process evaluator
evaluator_path = _prepare_evaluator(evaluator, temp_dir, temp_files)

# Auto-disable cascade evaluation if the evaluator doesn't define stage functions
if config_obj.evaluator.cascade_evaluation:
with open(evaluator_path, "r") as f:
eval_content = f.read()
if "evaluate_stage1" not in eval_content:
config_obj.evaluator.cascade_evaluation = False

# Create and run controller
controller = OpenEvolve(
initial_program_path=program_path,
Expand Down Expand Up @@ -239,13 +246,40 @@ def _prepare_evaluator(

# If it's a callable, create a wrapper module
if callable(evaluator):
# Create a unique global name for this evaluator
evaluator_id = f"_openevolve_evaluator_{uuid.uuid4().hex[:8]}"
# Try to get the source code of the callable so it can be serialized
# into a standalone file that works in subprocesses
try:
func_source = inspect.getsource(evaluator)
# Dedent in case the function was defined inside another scope
import textwrap

func_source = textwrap.dedent(func_source)
func_name = evaluator.__name__

# Build a self-contained evaluator module with the function source
# and an evaluate() entry point that calls it
evaluator_code = f"""
# Auto-generated evaluator from user-provided callable
import importlib.util
import sys
import os
import copy
import json
import time

{func_source}

# Store in globals so the wrapper can find it
globals()[evaluator_id] = evaluator
def evaluate(program_path):
'''Wrapper that calls the user-provided evaluator function'''
return {func_name}(program_path)
"""
except (OSError, TypeError):
# If we can't get source (e.g. built-in, lambda, or closure),
# fall back to the globals-based approach
evaluator_id = f"_openevolve_evaluator_{uuid.uuid4().hex[:8]}"
globals()[evaluator_id] = evaluator

evaluator_code = f"""
evaluator_code = f"""
# Wrapper for user-provided evaluator function
import {__name__} as api_module

Expand Down Expand Up @@ -335,57 +369,67 @@ def initial_sort(arr):
lines.insert(func_end + 1, " " * (indent + 4) + "# EVOLVE-BLOCK-END")
func_source = "\n".join(lines)

# Create evaluator that tests the function
def evaluator(program_path):
import importlib.util
import sys
# Create a self-contained evaluator as a code string so it works in subprocesses.
# Closure-based evaluators fail with process-based parallelism because subprocess
# workers cannot access the parent process's memory.
evaluator_code = f"""
import importlib.util
import copy

# Load the evolved program
spec = importlib.util.spec_from_file_location("evolved", program_path)
if spec is None or spec.loader is None:
return {"score": 0.0, "error": "Failed to load program"}
FUNC_NAME = {func_name!r}
TEST_CASES = {test_cases!r}

module = importlib.util.module_from_spec(spec)
def evaluate(program_path):
'''Auto-generated evaluator for evolve_function'''
# Load the evolved program
spec = importlib.util.spec_from_file_location("evolved", program_path)
if spec is None or spec.loader is None:
return {{"combined_score": 0.0, "score": 0.0, "error": "Failed to load program"}}

try:
spec.loader.exec_module(module)
except Exception as e:
return {"score": 0.0, "error": f"Failed to execute program: {str(e)}"}
module = importlib.util.module_from_spec(spec)

if not hasattr(module, func_name):
return {"score": 0.0, "error": f"Function '{func_name}' not found"}
try:
spec.loader.exec_module(module)
except Exception as e:
return {{"combined_score": 0.0, "score": 0.0, "error": f"Failed to execute program: {{str(e)}}"}}

evolved_func = getattr(module, func_name)
correct = 0
total = len(test_cases)
errors = []
if not hasattr(module, FUNC_NAME):
return {{"combined_score": 0.0, "score": 0.0, "error": f"Function '{{FUNC_NAME}}' not found"}}

for input_val, expected in test_cases:
try:
# Handle case where input is a list/mutable - make a copy
if isinstance(input_val, list):
test_input = input_val.copy()
else:
test_input = input_val

result = evolved_func(test_input)
if result == expected:
correct += 1
else:
errors.append(f"Input {input_val}: expected {expected}, got {result}")
except Exception as e:
errors.append(f"Input {input_val}: {str(e)}")

return {
"score": correct / total,
"test_pass_rate": correct / total,
"tests_passed": correct,
"total_tests": total,
"errors": errors[:3], # Limit error details
}
evolved_func = getattr(module, FUNC_NAME)
correct = 0
total = len(TEST_CASES)
errors = []

for input_val, expected in TEST_CASES:
try:
# Handle case where input is a list/mutable - make a copy
if isinstance(input_val, list):
test_input = input_val.copy()
else:
test_input = input_val

result = evolved_func(test_input)
if result == expected:
correct += 1
else:
errors.append(f"Input {{input_val}}: expected {{expected}}, got {{result}}")
except Exception as e:
errors.append(f"Input {{input_val}}: {{str(e)}}")

score = correct / total if total > 0 else 0.0
return {{
"combined_score": score,
"score": score,
"test_pass_rate": score,
"tests_passed": correct,
"total_tests": total,
"errors": errors[:3],
}}
"""

return run_evolution(
initial_program=func_source, evaluator=evaluator, iterations=iterations, **kwargs
initial_program=func_source, evaluator=evaluator_code, iterations=iterations, **kwargs
)


Expand Down Expand Up @@ -447,36 +491,51 @@ def benchmark_sort(instance):
lines.append(" " * (indent + 4) + "# EVOLVE-BLOCK-END")
class_source = "\n".join(lines)

# Create evaluator
def evaluator(program_path):
import importlib.util
# Create a self-contained evaluator as a code string so it works in subprocesses.
import textwrap

# Load the evolved program
spec = importlib.util.spec_from_file_location("evolved", program_path)
if spec is None or spec.loader is None:
return {"score": 0.0, "error": "Failed to load program"}
class_name = algorithm_class.__name__
benchmark_source = textwrap.dedent(inspect.getsource(benchmark))

module = importlib.util.module_from_spec(spec)
evaluator_code = f"""
import importlib.util

try:
spec.loader.exec_module(module)
except Exception as e:
return {"score": 0.0, "error": f"Failed to execute program: {str(e)}"}
CLASS_NAME = {class_name!r}

if not hasattr(module, algorithm_class.__name__):
return {"score": 0.0, "error": f"Class '{algorithm_class.__name__}' not found"}
{benchmark_source}

AlgorithmClass = getattr(module, algorithm_class.__name__)
def evaluate(program_path):
'''Auto-generated evaluator for evolve_algorithm'''
spec = importlib.util.spec_from_file_location("evolved", program_path)
if spec is None or spec.loader is None:
return {{"combined_score": 0.0, "score": 0.0, "error": "Failed to load program"}}

try:
instance = AlgorithmClass()
metrics = benchmark(instance)
return metrics if isinstance(metrics, dict) else {"score": metrics}
except Exception as e:
return {"score": 0.0, "error": str(e)}
module = importlib.util.module_from_spec(spec)

try:
spec.loader.exec_module(module)
except Exception as e:
return {{"combined_score": 0.0, "score": 0.0, "error": f"Failed to execute program: {{str(e)}}"}}

if not hasattr(module, CLASS_NAME):
return {{"combined_score": 0.0, "score": 0.0, "error": f"Class '{{CLASS_NAME}}' not found"}}

AlgorithmClass = getattr(module, CLASS_NAME)

try:
instance = AlgorithmClass()
metrics = {benchmark.__name__}(instance)
if not isinstance(metrics, dict):
metrics = {{"score": metrics}}
if "combined_score" not in metrics:
metrics["combined_score"] = metrics.get("score", 0.0)
return metrics
except Exception as e:
return {{"combined_score": 0.0, "score": 0.0, "error": str(e)}}
"""

return run_evolution(
initial_program=class_source, evaluator=evaluator, iterations=iterations, **kwargs
initial_program=class_source, evaluator=evaluator_code, iterations=iterations, **kwargs
)


Expand Down
Loading
Loading