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.
Files changed (55) hide show
  1. {openevolve-0.2.0 → openevolve-0.2.2}/PKG-INFO +1 -1
  2. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/_version.py +1 -1
  3. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/database.py +29 -3
  4. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/process_parallel.py +2 -1
  5. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/sampler.py +13 -12
  6. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/PKG-INFO +1 -1
  7. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/SOURCES.txt +2 -0
  8. openevolve-0.2.2/tests/test_island_parent_consistency.py +174 -0
  9. openevolve-0.2.2/tests/test_prompt_sampler_comprehensive.py +297 -0
  10. {openevolve-0.2.0 → openevolve-0.2.2}/LICENSE +0 -0
  11. {openevolve-0.2.0 → openevolve-0.2.2}/README.md +0 -0
  12. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/__init__.py +0 -0
  13. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/cli.py +0 -0
  14. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/config.py +0 -0
  15. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/controller.py +0 -0
  16. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/evaluation_result.py +0 -0
  17. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/evaluator.py +0 -0
  18. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/iteration.py +0 -0
  19. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/__init__.py +0 -0
  20. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/base.py +0 -0
  21. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/ensemble.py +0 -0
  22. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/llm/openai.py +0 -0
  23. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/__init__.py +0 -0
  24. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/prompt/templates.py +0 -0
  25. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/__init__.py +0 -0
  26. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/async_utils.py +0 -0
  27. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/code_utils.py +0 -0
  28. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/format_utils.py +0 -0
  29. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve/utils/metrics_utils.py +0 -0
  30. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/dependency_links.txt +0 -0
  31. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/entry_points.txt +0 -0
  32. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/requires.txt +0 -0
  33. {openevolve-0.2.0 → openevolve-0.2.2}/openevolve.egg-info/top_level.txt +0 -0
  34. {openevolve-0.2.0 → openevolve-0.2.2}/pyproject.toml +0 -0
  35. {openevolve-0.2.0 → openevolve-0.2.2}/setup.cfg +0 -0
  36. {openevolve-0.2.0 → openevolve-0.2.2}/setup.py +0 -0
  37. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_artifacts.py +0 -0
  38. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_artifacts_integration.py +0 -0
  39. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_cascade_validation.py +0 -0
  40. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_checkpoint_resume.py +0 -0
  41. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_code_utils.py +0 -0
  42. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_database.py +0 -0
  43. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_database_cleanup.py +0 -0
  44. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_evaluator_timeout.py +0 -0
  45. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_feature_stats_persistence.py +0 -0
  46. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_grid_stability.py +0 -0
  47. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_isolation.py +0 -0
  48. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_migration.py +0 -0
  49. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_island_tracking.py +0 -0
  50. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_iteration_counting.py +0 -0
  51. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_llm_ensemble.py +0 -0
  52. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_map_elites_features.py +0 -0
  53. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_process_parallel.py +0 -0
  54. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_prompt_sampler.py +0 -0
  55. {openevolve-0.2.0 → openevolve-0.2.2}/tests/test_valid_configs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openevolve
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Open-source implementation of AlphaEvolve
5
5
  Author: codelion
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """Version information for openevolve package."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
@@ -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 (uses current_island if None)
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
- # Add to specific island (not random!)
267
- island_idx = target_island if target_island is not None else self.current_island
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 a composite score using safe numeric average
325
- score = safe_numeric_average(program.get("metrics", {}))
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 a composite score using safe numeric average
375
- score = safe_numeric_average(program.get("metrics", {}))
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 a composite score using safe numeric average
440
- score = safe_numeric_average(program.get("metrics", {}))
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 = safe_numeric_average(program.get("metrics", {}))
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openevolve
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Open-source implementation of AlphaEvolve
5
5
  Author: codelion
6
6
  License: Apache-2.0
@@ -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