openevolve 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openevolve-0.2.0 → openevolve-0.2.2}/PKG-INFO +1 -1
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/_version.py +1 -1
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/database.py +29 -3
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/process_parallel.py +2 -1
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/sampler.py +13 -12
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/PKG-INFO +1 -1
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/SOURCES.txt +2 -0
- openevolve-0.2.2/tests/test_island_parent_consistency.py +174 -0
- openevolve-0.2.2/tests/test_prompt_sampler_comprehensive.py +297 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/LICENSE +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/README.md +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/__init__.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/cli.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/config.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/controller.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/evaluation_result.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/evaluator.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/iteration.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/__init__.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/base.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/ensemble.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/openai.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/__init__.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/templates.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/__init__.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/async_utils.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/code_utils.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/format_utils.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/metrics_utils.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/dependency_links.txt +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/entry_points.txt +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/requires.txt +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/top_level.txt +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/pyproject.toml +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/setup.cfg +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/setup.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_artifacts.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_artifacts_integration.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_cascade_validation.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_checkpoint_resume.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_code_utils.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_database.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_database_cleanup.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_evaluator_timeout.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_feature_stats_persistence.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_grid_stability.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_isolation.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_migration.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_tracking.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_iteration_counting.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_llm_ensemble.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_map_elites_features.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_process_parallel.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_prompt_sampler.py +0 -0
- {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_valid_configs.py +0 -0
|
@@ -188,7 +188,7 @@ class ProgramDatabase:
|
|
|
188
188
|
Args:
|
|
189
189
|
program: Program to add
|
|
190
190
|
iteration: Current iteration (defaults to last_iteration)
|
|
191
|
-
target_island: Specific island to add to (
|
|
191
|
+
target_island: Specific island to add to (auto-detects parent's island if None)
|
|
192
192
|
|
|
193
193
|
Returns:
|
|
194
194
|
Program ID
|
|
@@ -263,8 +263,34 @@ class ProgramDatabase:
|
|
|
263
263
|
|
|
264
264
|
self.feature_map[feature_key] = program.id
|
|
265
265
|
|
|
266
|
-
#
|
|
267
|
-
|
|
266
|
+
# Determine target island
|
|
267
|
+
# If target_island is not specified and program has a parent, inherit parent's island
|
|
268
|
+
if target_island is None and program.parent_id:
|
|
269
|
+
parent = self.programs.get(program.parent_id)
|
|
270
|
+
if parent and "island" in parent.metadata:
|
|
271
|
+
# Child inherits parent's island to maintain island isolation
|
|
272
|
+
island_idx = parent.metadata["island"]
|
|
273
|
+
logger.debug(
|
|
274
|
+
f"Program {program.id} inheriting island {island_idx} from parent {program.parent_id}"
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
# Parent not found or has no island, use current_island
|
|
278
|
+
island_idx = self.current_island
|
|
279
|
+
if parent:
|
|
280
|
+
logger.warning(
|
|
281
|
+
f"Parent {program.parent_id} has no island metadata, using current_island {island_idx}"
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
logger.warning(
|
|
285
|
+
f"Parent {program.parent_id} not found, using current_island {island_idx}"
|
|
286
|
+
)
|
|
287
|
+
elif target_island is not None:
|
|
288
|
+
# Explicit target island specified (e.g., for migrants)
|
|
289
|
+
island_idx = target_island
|
|
290
|
+
else:
|
|
291
|
+
# No parent and no target specified, use current island
|
|
292
|
+
island_idx = self.current_island
|
|
293
|
+
|
|
268
294
|
island_idx = island_idx % len(self.islands) # Ensure valid island
|
|
269
295
|
self.islands[island_idx].add(program.id)
|
|
270
296
|
|
|
@@ -441,7 +441,8 @@ class ProcessParallelController:
|
|
|
441
441
|
# Reconstruct program from dict
|
|
442
442
|
child_program = Program(**result.child_program_dict)
|
|
443
443
|
|
|
444
|
-
# Add to database
|
|
444
|
+
# Add to database (will auto-inherit parent's island)
|
|
445
|
+
# No need to specify target_island - database will handle parent island inheritance
|
|
445
446
|
self.database.add(child_program, iteration=completed_iteration)
|
|
446
447
|
|
|
447
448
|
# Store artifacts
|
|
@@ -116,7 +116,7 @@ class PromptSampler:
|
|
|
116
116
|
|
|
117
117
|
# Format evolution history
|
|
118
118
|
evolution_history = self._format_evolution_history(
|
|
119
|
-
previous_programs, top_programs, inspirations, language
|
|
119
|
+
previous_programs, top_programs, inspirations, language, feature_dimensions
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
# Format artifacts section if enabled and available
|
|
@@ -243,6 +243,7 @@ class PromptSampler:
|
|
|
243
243
|
top_programs: List[Dict[str, Any]],
|
|
244
244
|
inspirations: List[Dict[str, Any]],
|
|
245
245
|
language: str,
|
|
246
|
+
feature_dimensions: Optional[List[str]] = None,
|
|
246
247
|
) -> str:
|
|
247
248
|
"""Format the evolution history for the prompt"""
|
|
248
249
|
# Get templates
|
|
@@ -321,8 +322,8 @@ class PromptSampler:
|
|
|
321
322
|
# Use the full program code
|
|
322
323
|
program_code = program.get("code", "")
|
|
323
324
|
|
|
324
|
-
# Calculate
|
|
325
|
-
score =
|
|
325
|
+
# Calculate fitness score (prefers combined_score, excludes feature dimensions)
|
|
326
|
+
score = get_fitness_score(program.get("metrics", {}), feature_dimensions or [])
|
|
326
327
|
|
|
327
328
|
# Extract key features (this could be more sophisticated)
|
|
328
329
|
key_features = program.get("key_features", [])
|
|
@@ -371,8 +372,8 @@ class PromptSampler:
|
|
|
371
372
|
# Use the full program code
|
|
372
373
|
program_code = program.get("code", "")
|
|
373
374
|
|
|
374
|
-
# Calculate
|
|
375
|
-
score =
|
|
375
|
+
# Calculate fitness score (prefers combined_score, excludes feature dimensions)
|
|
376
|
+
score = get_fitness_score(program.get("metrics", {}), feature_dimensions or [])
|
|
376
377
|
|
|
377
378
|
# Extract key features
|
|
378
379
|
key_features = program.get("key_features", [])
|
|
@@ -401,7 +402,7 @@ class PromptSampler:
|
|
|
401
402
|
combined_programs_str = top_programs_str + diverse_programs_str
|
|
402
403
|
|
|
403
404
|
# Format inspirations section
|
|
404
|
-
inspirations_section_str = self._format_inspirations_section(inspirations, language)
|
|
405
|
+
inspirations_section_str = self._format_inspirations_section(inspirations, language, feature_dimensions)
|
|
405
406
|
|
|
406
407
|
# Combine into full history
|
|
407
408
|
return history_template.format(
|
|
@@ -411,7 +412,7 @@ class PromptSampler:
|
|
|
411
412
|
)
|
|
412
413
|
|
|
413
414
|
def _format_inspirations_section(
|
|
414
|
-
self, inspirations: List[Dict[str, Any]], language: str
|
|
415
|
+
self, inspirations: List[Dict[str, Any]], language: str, feature_dimensions: Optional[List[str]] = None
|
|
415
416
|
) -> str:
|
|
416
417
|
"""
|
|
417
418
|
Format the inspirations section for the prompt
|
|
@@ -436,11 +437,11 @@ class PromptSampler:
|
|
|
436
437
|
# Use the full program code
|
|
437
438
|
program_code = program.get("code", "")
|
|
438
439
|
|
|
439
|
-
# Calculate
|
|
440
|
-
score =
|
|
440
|
+
# Calculate fitness score (prefers combined_score, excludes feature dimensions)
|
|
441
|
+
score = get_fitness_score(program.get("metrics", {}), feature_dimensions or [])
|
|
441
442
|
|
|
442
443
|
# Determine program type based on metadata and score
|
|
443
|
-
program_type = self._determine_program_type(program)
|
|
444
|
+
program_type = self._determine_program_type(program, feature_dimensions or [])
|
|
444
445
|
|
|
445
446
|
# Extract unique features (emphasizing diversity rather than just performance)
|
|
446
447
|
unique_features = self._extract_unique_features(program)
|
|
@@ -461,7 +462,7 @@ class PromptSampler:
|
|
|
461
462
|
inspiration_programs=inspiration_programs_str.strip()
|
|
462
463
|
)
|
|
463
464
|
|
|
464
|
-
def _determine_program_type(self, program: Dict[str, Any]) -> str:
|
|
465
|
+
def _determine_program_type(self, program: Dict[str, Any], feature_dimensions: Optional[List[str]] = None) -> str:
|
|
465
466
|
"""
|
|
466
467
|
Determine the type/category of an inspiration program
|
|
467
468
|
|
|
@@ -472,7 +473,7 @@ class PromptSampler:
|
|
|
472
473
|
String describing the program type
|
|
473
474
|
"""
|
|
474
475
|
metadata = program.get("metadata", {})
|
|
475
|
-
score =
|
|
476
|
+
score = get_fitness_score(program.get("metrics", {}), feature_dimensions or [])
|
|
476
477
|
|
|
477
478
|
# Check metadata for explicit type markers
|
|
478
479
|
if metadata.get("diverse", False):
|
|
@@ -42,10 +42,12 @@ tests/test_feature_stats_persistence.py
|
|
|
42
42
|
tests/test_grid_stability.py
|
|
43
43
|
tests/test_island_isolation.py
|
|
44
44
|
tests/test_island_migration.py
|
|
45
|
+
tests/test_island_parent_consistency.py
|
|
45
46
|
tests/test_island_tracking.py
|
|
46
47
|
tests/test_iteration_counting.py
|
|
47
48
|
tests/test_llm_ensemble.py
|
|
48
49
|
tests/test_map_elites_features.py
|
|
49
50
|
tests/test_process_parallel.py
|
|
50
51
|
tests/test_prompt_sampler.py
|
|
52
|
+
tests/test_prompt_sampler_comprehensive.py
|
|
51
53
|
tests/test_valid_configs.py
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test for island parent-child consistency - Programs' parents should be in the corresponding islands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from openevolve.config import Config
|
|
7
|
+
from openevolve.database import ProgramDatabase, Program
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestIslandParentConsistency(unittest.TestCase):
|
|
11
|
+
"""Test that parent-child relationships respect island boundaries"""
|
|
12
|
+
|
|
13
|
+
def test_parent_child_island_consistency(self):
|
|
14
|
+
"""Test that children are added to the same island as their parents"""
|
|
15
|
+
config = Config()
|
|
16
|
+
config.database.num_islands = 3
|
|
17
|
+
database = ProgramDatabase(config.database)
|
|
18
|
+
|
|
19
|
+
# Create initial program on island 0
|
|
20
|
+
initial_program = Program(
|
|
21
|
+
id="initial",
|
|
22
|
+
code="def initial(): pass",
|
|
23
|
+
metrics={"score": 0.5},
|
|
24
|
+
iteration_found=0
|
|
25
|
+
)
|
|
26
|
+
database.add(initial_program) # Should go to island 0 (current_island)
|
|
27
|
+
|
|
28
|
+
# Verify initial program is on island 0
|
|
29
|
+
self.assertIn("initial", database.islands[0])
|
|
30
|
+
self.assertEqual(initial_program.metadata.get("island"), 0)
|
|
31
|
+
|
|
32
|
+
# Now switch to island 1
|
|
33
|
+
database.next_island()
|
|
34
|
+
self.assertEqual(database.current_island, 1)
|
|
35
|
+
|
|
36
|
+
# Create a child of the initial program
|
|
37
|
+
child_program = Program(
|
|
38
|
+
id="child1",
|
|
39
|
+
code="def child1(): pass",
|
|
40
|
+
parent_id="initial", # Parent is on island 0
|
|
41
|
+
metrics={"score": 0.6},
|
|
42
|
+
iteration_found=1
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Add child without specifying target_island
|
|
46
|
+
# This is what happens in process_parallel.py line 445
|
|
47
|
+
database.add(child_program)
|
|
48
|
+
|
|
49
|
+
# With the fix: child should go to parent's island (0), not current_island (1)
|
|
50
|
+
parent_island = database.programs["initial"].metadata.get("island", 0)
|
|
51
|
+
child_island = database.programs["child1"].metadata.get("island")
|
|
52
|
+
|
|
53
|
+
# Check if parent is in child's island (this is what the user's assertion checks)
|
|
54
|
+
if child_program.parent_id:
|
|
55
|
+
# This is the exact check from the issue report - should now pass
|
|
56
|
+
self.assertIn(
|
|
57
|
+
child_program.parent_id,
|
|
58
|
+
database.islands[child_island],
|
|
59
|
+
"Parent should be in child's island"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Verify child is on same island as parent
|
|
63
|
+
self.assertEqual(
|
|
64
|
+
parent_island,
|
|
65
|
+
child_island,
|
|
66
|
+
f"Child should be on same island as parent. Parent: island {parent_island}, Child: island {child_island}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_multiple_generations_island_drift(self):
|
|
70
|
+
"""Test that island drift happens across multiple generations"""
|
|
71
|
+
config = Config()
|
|
72
|
+
config.database.num_islands = 4
|
|
73
|
+
database = ProgramDatabase(config.database)
|
|
74
|
+
|
|
75
|
+
# Create a lineage
|
|
76
|
+
programs = []
|
|
77
|
+
for i in range(10):
|
|
78
|
+
if i == 0:
|
|
79
|
+
# Initial program
|
|
80
|
+
prog = Program(
|
|
81
|
+
id=f"prog_{i}",
|
|
82
|
+
code=f"def prog_{i}(): pass",
|
|
83
|
+
metrics={"score": 0.1 * i},
|
|
84
|
+
iteration_found=i
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
# Child of previous
|
|
88
|
+
prog = Program(
|
|
89
|
+
id=f"prog_{i}",
|
|
90
|
+
code=f"def prog_{i}(): pass",
|
|
91
|
+
parent_id=f"prog_{i-1}",
|
|
92
|
+
metrics={"score": 0.1 * i},
|
|
93
|
+
iteration_found=i
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
database.add(prog)
|
|
97
|
+
programs.append(prog)
|
|
98
|
+
|
|
99
|
+
# Switch islands periodically (simulating what happens in evolution)
|
|
100
|
+
if i % 3 == 0:
|
|
101
|
+
database.next_island()
|
|
102
|
+
|
|
103
|
+
# Check island consistency
|
|
104
|
+
inconsistent_pairs = []
|
|
105
|
+
for prog in programs:
|
|
106
|
+
if prog.parent_id:
|
|
107
|
+
parent = database.programs.get(prog.parent_id)
|
|
108
|
+
if parent:
|
|
109
|
+
parent_island = parent.metadata.get("island")
|
|
110
|
+
child_island = prog.metadata.get("island")
|
|
111
|
+
|
|
112
|
+
# Check if parent is in child's island
|
|
113
|
+
if prog.parent_id not in database.islands[child_island]:
|
|
114
|
+
inconsistent_pairs.append((prog.parent_id, prog.id))
|
|
115
|
+
|
|
116
|
+
# With the fix, we should find NO inconsistent parent-child island assignments
|
|
117
|
+
self.assertEqual(
|
|
118
|
+
len(inconsistent_pairs),
|
|
119
|
+
0,
|
|
120
|
+
f"Found {len(inconsistent_pairs)} inconsistent parent-child pairs: {inconsistent_pairs}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Verify all parent-child pairs are on the same island
|
|
124
|
+
for prog in programs:
|
|
125
|
+
if prog.parent_id:
|
|
126
|
+
parent = database.programs.get(prog.parent_id)
|
|
127
|
+
if parent:
|
|
128
|
+
parent_island = parent.metadata.get("island")
|
|
129
|
+
child_island = prog.metadata.get("island")
|
|
130
|
+
self.assertEqual(
|
|
131
|
+
parent_island,
|
|
132
|
+
child_island,
|
|
133
|
+
f"Parent {prog.parent_id} (island {parent_island}) and "
|
|
134
|
+
f"child {prog.id} (island {child_island}) should be on same island"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_explicit_migration_override(self):
|
|
139
|
+
"""Test that explicit target_island overrides parent island inheritance"""
|
|
140
|
+
config = Config()
|
|
141
|
+
config.database.num_islands = 3
|
|
142
|
+
database = ProgramDatabase(config.database)
|
|
143
|
+
|
|
144
|
+
# Create parent on island 0
|
|
145
|
+
parent = Program(
|
|
146
|
+
id="parent",
|
|
147
|
+
code="def parent(): pass",
|
|
148
|
+
metrics={"score": 0.5},
|
|
149
|
+
iteration_found=0
|
|
150
|
+
)
|
|
151
|
+
database.add(parent) # Goes to island 0
|
|
152
|
+
self.assertIn("parent", database.islands[0])
|
|
153
|
+
|
|
154
|
+
# Create child but explicitly send to island 2 (migration)
|
|
155
|
+
migrant_child = Program(
|
|
156
|
+
id="migrant",
|
|
157
|
+
code="def migrant(): pass",
|
|
158
|
+
parent_id="parent",
|
|
159
|
+
metrics={"score": 0.7},
|
|
160
|
+
iteration_found=1
|
|
161
|
+
)
|
|
162
|
+
database.add(migrant_child, target_island=2) # Explicit migration
|
|
163
|
+
|
|
164
|
+
# Verify migrant went to island 2, not parent's island 0
|
|
165
|
+
self.assertIn("migrant", database.islands[2])
|
|
166
|
+
self.assertNotIn("migrant", database.islands[0])
|
|
167
|
+
self.assertEqual(migrant_child.metadata.get("island"), 2)
|
|
168
|
+
|
|
169
|
+
# Parent should still be on island 0
|
|
170
|
+
self.assertEqual(database.programs["parent"].metadata.get("island"), 0)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
unittest.main()
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for PromptSampler including inspirations and feature_dimensions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
from openevolve.config import Config
|
|
8
|
+
from openevolve.prompt.sampler import PromptSampler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestPromptSamplerComprehensive(unittest.TestCase):
|
|
12
|
+
"""Comprehensive tests for prompt sampler edge cases"""
|
|
13
|
+
|
|
14
|
+
def setUp(self):
|
|
15
|
+
"""Set up test prompt sampler"""
|
|
16
|
+
config = Config()
|
|
17
|
+
# Add feature dimensions to config for testing
|
|
18
|
+
config.database.feature_dimensions = ["complexity", "memory_usage"]
|
|
19
|
+
self.prompt_sampler = PromptSampler(config.prompt)
|
|
20
|
+
self.feature_dimensions = config.database.feature_dimensions
|
|
21
|
+
|
|
22
|
+
def test_build_prompt_with_inspirations(self):
|
|
23
|
+
"""Test building a prompt with inspiration programs"""
|
|
24
|
+
current_program = "def optimized(): pass"
|
|
25
|
+
parent_program = "def original(): pass"
|
|
26
|
+
program_metrics = {
|
|
27
|
+
"combined_score": 0.85,
|
|
28
|
+
"accuracy": 0.9,
|
|
29
|
+
"speed": 0.8,
|
|
30
|
+
"complexity": 5,
|
|
31
|
+
"memory_usage": 100
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Create inspirations with diverse characteristics
|
|
35
|
+
inspirations = [
|
|
36
|
+
{
|
|
37
|
+
"id": "insp1",
|
|
38
|
+
"code": "def fast_implementation(): pass",
|
|
39
|
+
"metrics": {
|
|
40
|
+
"combined_score": 0.75,
|
|
41
|
+
"accuracy": 0.7,
|
|
42
|
+
"speed": 0.95,
|
|
43
|
+
"complexity": 3,
|
|
44
|
+
"memory_usage": 50
|
|
45
|
+
},
|
|
46
|
+
"metadata": {"diverse": True}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "insp2",
|
|
50
|
+
"code": "def memory_efficient(): pass",
|
|
51
|
+
"metrics": {
|
|
52
|
+
"combined_score": 0.65,
|
|
53
|
+
"accuracy": 0.8,
|
|
54
|
+
"speed": 0.5,
|
|
55
|
+
"complexity": 7,
|
|
56
|
+
"memory_usage": 20
|
|
57
|
+
},
|
|
58
|
+
"metadata": {"migrant": True}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# Build prompt with inspirations and feature_dimensions
|
|
63
|
+
prompt = self.prompt_sampler.build_prompt(
|
|
64
|
+
current_program=current_program,
|
|
65
|
+
parent_program=parent_program,
|
|
66
|
+
program_metrics=program_metrics,
|
|
67
|
+
inspirations=inspirations,
|
|
68
|
+
feature_dimensions=self.feature_dimensions
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Verify prompt was built successfully
|
|
72
|
+
self.assertIn("system", prompt)
|
|
73
|
+
self.assertIn("user", prompt)
|
|
74
|
+
|
|
75
|
+
# Check that inspirations are included
|
|
76
|
+
self.assertIn("fast_implementation", prompt["user"])
|
|
77
|
+
self.assertIn("memory_efficient", prompt["user"])
|
|
78
|
+
|
|
79
|
+
# Verify fitness scores are calculated correctly (excluding feature dimensions)
|
|
80
|
+
# The inspirations should show their fitness scores, not including complexity/memory_usage
|
|
81
|
+
self.assertIn("0.75", prompt["user"]) # insp1's combined_score
|
|
82
|
+
self.assertIn("0.65", prompt["user"]) # insp2's combined_score
|
|
83
|
+
|
|
84
|
+
def test_format_inspirations_section_with_feature_dimensions(self):
|
|
85
|
+
"""Test _format_inspirations_section directly with feature_dimensions"""
|
|
86
|
+
inspirations = [
|
|
87
|
+
{
|
|
88
|
+
"id": "test1",
|
|
89
|
+
"code": "def test_func(): return 42",
|
|
90
|
+
"metrics": {
|
|
91
|
+
"combined_score": 0.9,
|
|
92
|
+
"accuracy": 0.95,
|
|
93
|
+
"complexity": 10, # Feature dimension
|
|
94
|
+
"memory_usage": 200 # Feature dimension
|
|
95
|
+
},
|
|
96
|
+
"metadata": {"diverse": True}
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Call the method directly
|
|
101
|
+
result = self.prompt_sampler._format_inspirations_section(
|
|
102
|
+
inspirations,
|
|
103
|
+
"python",
|
|
104
|
+
feature_dimensions=["complexity", "memory_usage"]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Should not raise NameError
|
|
108
|
+
self.assertIsInstance(result, str)
|
|
109
|
+
self.assertIn("test_func", result)
|
|
110
|
+
self.assertIn("0.9000", result) # The fitness score
|
|
111
|
+
|
|
112
|
+
def test_format_inspirations_section_without_feature_dimensions(self):
|
|
113
|
+
"""Test _format_inspirations_section works without feature_dimensions"""
|
|
114
|
+
inspirations = [
|
|
115
|
+
{
|
|
116
|
+
"id": "test2",
|
|
117
|
+
"code": "def another_func(): pass",
|
|
118
|
+
"metrics": {"score": 0.7, "time": 1.2},
|
|
119
|
+
"metadata": {}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
# Call without feature_dimensions (should use default of None)
|
|
124
|
+
result = self.prompt_sampler._format_inspirations_section(
|
|
125
|
+
inspirations,
|
|
126
|
+
"python"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.assertIsInstance(result, str)
|
|
130
|
+
self.assertIn("another_func", result)
|
|
131
|
+
|
|
132
|
+
def test_determine_program_type_with_feature_dimensions(self):
|
|
133
|
+
"""Test _determine_program_type with feature_dimensions parameter"""
|
|
134
|
+
program = {
|
|
135
|
+
"metrics": {
|
|
136
|
+
"combined_score": 0.85,
|
|
137
|
+
"complexity": 5,
|
|
138
|
+
"memory_usage": 100
|
|
139
|
+
},
|
|
140
|
+
"metadata": {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Test with feature_dimensions
|
|
144
|
+
program_type = self.prompt_sampler._determine_program_type(
|
|
145
|
+
program,
|
|
146
|
+
feature_dimensions=["complexity", "memory_usage"]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
self.assertEqual(program_type, "High-Performer") # Based on combined_score of 0.85
|
|
150
|
+
|
|
151
|
+
def test_extract_unique_features_calls_determine_program_type(self):
|
|
152
|
+
"""Test that _extract_unique_features correctly handles program_type determination"""
|
|
153
|
+
program = {
|
|
154
|
+
"code": "", # Empty code to trigger default features
|
|
155
|
+
"metrics": {"score": 0.5},
|
|
156
|
+
"metadata": {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# This should not raise NameError when calling _determine_program_type
|
|
160
|
+
features = self.prompt_sampler._extract_unique_features(program)
|
|
161
|
+
|
|
162
|
+
self.assertIsInstance(features, str)
|
|
163
|
+
self.assertIn("approach to the problem", features)
|
|
164
|
+
|
|
165
|
+
def test_build_prompt_with_all_optional_parameters(self):
|
|
166
|
+
"""Test build_prompt with all optional parameters including inspirations"""
|
|
167
|
+
current_program = "def main(): pass"
|
|
168
|
+
|
|
169
|
+
# Comprehensive test data
|
|
170
|
+
previous_programs = [
|
|
171
|
+
{"id": "prev1", "code": "def v1(): pass", "metrics": {"score": 0.3}}
|
|
172
|
+
]
|
|
173
|
+
top_programs = [
|
|
174
|
+
{"id": "top1", "code": "def best(): pass", "metrics": {"combined_score": 0.95}}
|
|
175
|
+
]
|
|
176
|
+
inspirations = [
|
|
177
|
+
{"id": "insp1", "code": "def creative(): pass", "metrics": {"score": 0.6}}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
prompt = self.prompt_sampler.build_prompt(
|
|
181
|
+
current_program=current_program,
|
|
182
|
+
parent_program="def parent(): pass",
|
|
183
|
+
program_metrics={"combined_score": 0.7, "feature1": 10},
|
|
184
|
+
previous_programs=previous_programs,
|
|
185
|
+
top_programs=top_programs,
|
|
186
|
+
inspirations=inspirations,
|
|
187
|
+
language="python",
|
|
188
|
+
evolution_round=5,
|
|
189
|
+
diff_based_evolution=True,
|
|
190
|
+
feature_dimensions=["feature1"],
|
|
191
|
+
program_artifacts={"output": "test output"}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self.assertIn("system", prompt)
|
|
195
|
+
self.assertIn("user", prompt)
|
|
196
|
+
# Verify all components are included
|
|
197
|
+
self.assertIn("main", prompt["user"])
|
|
198
|
+
self.assertIn("best", prompt["user"])
|
|
199
|
+
self.assertIn("creative", prompt["user"])
|
|
200
|
+
|
|
201
|
+
def test_fitness_calculation_consistency(self):
|
|
202
|
+
"""Test that fitness calculation is consistent across all methods"""
|
|
203
|
+
metrics = {
|
|
204
|
+
"combined_score": 0.8,
|
|
205
|
+
"accuracy": 0.9,
|
|
206
|
+
"speed": 0.7,
|
|
207
|
+
"complexity": 5, # Feature dimension
|
|
208
|
+
"memory_usage": 100 # Feature dimension
|
|
209
|
+
}
|
|
210
|
+
feature_dimensions = ["complexity", "memory_usage"]
|
|
211
|
+
|
|
212
|
+
# Build a prompt with these metrics
|
|
213
|
+
prompt = self.prompt_sampler.build_prompt(
|
|
214
|
+
current_program="def test(): pass",
|
|
215
|
+
program_metrics=metrics,
|
|
216
|
+
inspirations=[
|
|
217
|
+
{"id": "i1", "code": "pass", "metrics": metrics}
|
|
218
|
+
],
|
|
219
|
+
feature_dimensions=feature_dimensions
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# The fitness score should be 0.8 (combined_score), not an average including features
|
|
223
|
+
self.assertIn("0.8000", prompt["user"]) # Fitness score in prompt
|
|
224
|
+
|
|
225
|
+
def test_empty_inspirations_list(self):
|
|
226
|
+
"""Test that empty inspirations list doesn't break anything"""
|
|
227
|
+
prompt = self.prompt_sampler.build_prompt(
|
|
228
|
+
current_program="def empty(): pass",
|
|
229
|
+
inspirations=[], # Empty list
|
|
230
|
+
feature_dimensions=["test_feature"]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
self.assertIn("system", prompt)
|
|
234
|
+
self.assertIn("user", prompt)
|
|
235
|
+
# Should complete without errors
|
|
236
|
+
|
|
237
|
+
def test_inspirations_with_missing_metrics(self):
|
|
238
|
+
"""Test handling of inspirations with missing or invalid metrics"""
|
|
239
|
+
inspirations = [
|
|
240
|
+
{
|
|
241
|
+
"id": "bad1",
|
|
242
|
+
"code": "def bad(): pass",
|
|
243
|
+
"metrics": {}, # Empty metrics
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "bad2",
|
|
247
|
+
"code": "def worse(): pass",
|
|
248
|
+
# No metrics key at all
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# Should handle gracefully without errors
|
|
253
|
+
result = self.prompt_sampler._format_inspirations_section(
|
|
254
|
+
inspirations,
|
|
255
|
+
"python",
|
|
256
|
+
feature_dimensions=["test"]
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
self.assertIsInstance(result, str)
|
|
260
|
+
|
|
261
|
+
def test_feature_dimensions_none_vs_empty_list(self):
|
|
262
|
+
"""Test that None and empty list for feature_dimensions are handled correctly"""
|
|
263
|
+
program = {"metrics": {"score": 0.5}}
|
|
264
|
+
|
|
265
|
+
# Test with None
|
|
266
|
+
type_none = self.prompt_sampler._determine_program_type(program, None)
|
|
267
|
+
|
|
268
|
+
# Test with empty list
|
|
269
|
+
type_empty = self.prompt_sampler._determine_program_type(program, [])
|
|
270
|
+
|
|
271
|
+
# Both should work and give same result
|
|
272
|
+
self.assertEqual(type_none, type_empty)
|
|
273
|
+
|
|
274
|
+
def test_feature_coordinates_formatting_in_prompt(self):
|
|
275
|
+
"""Test that feature coordinates are formatted correctly in the prompt"""
|
|
276
|
+
metrics = {
|
|
277
|
+
"combined_score": 0.75,
|
|
278
|
+
"complexity": 8,
|
|
279
|
+
"memory_usage": 150,
|
|
280
|
+
"cpu_usage": 0.3
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
prompt = self.prompt_sampler.build_prompt(
|
|
284
|
+
current_program="def test(): pass",
|
|
285
|
+
program_metrics=metrics,
|
|
286
|
+
feature_dimensions=["complexity", "memory_usage", "cpu_usage"]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Check that feature coordinates are included
|
|
290
|
+
user_msg = prompt["user"]
|
|
291
|
+
self.assertIn("complexity", user_msg)
|
|
292
|
+
self.assertIn("memory_usage", user_msg)
|
|
293
|
+
self.assertIn("cpu_usage", user_msg)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|