JustinTX commited on
Commit
1b2fd52
·
verified ·
1 Parent(s): c14cbfc

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0 +0 -0
  2. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0.0 +0 -0
  3. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/EVAL_AGENTS.md +635 -0
  4. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics.py +197 -0
  5. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics_old.py +445 -0
  6. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/service_state.json +608 -0
  7. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_0/main.py +94 -0
  8. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/edit.diff +121 -0
  9. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/main.py +108 -0
  10. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/original.py +94 -0
  11. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/search_replace.txt +60 -0
  12. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/edit.diff +172 -0
  13. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/original.py +138 -0
  14. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/search_replace.txt +75 -0
  15. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/edit.diff +191 -0
  16. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/main.py +179 -0
  17. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/original.py +170 -0
  18. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/search_replace.txt +120 -0
  19. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/edit.diff +202 -0
  20. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/main.py +174 -0
  21. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/original.py +170 -0
  22. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/search_replace.txt +93 -0
  23. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/edit.diff +248 -0
  24. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/main.py +233 -0
  25. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/original.py +180 -0
  26. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/search_replace.txt +326 -0
  27. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/edit.diff +336 -0
  28. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/main.py +264 -0
  29. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/original.py +168 -0
  30. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/rewrite.txt +255 -0
  31. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/edit.diff +207 -0
  32. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/main.py +188 -0
  33. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/original.py +173 -0
  34. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/search_replace.txt +195 -0
  35. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/edit.diff +343 -0
  36. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/main.py +206 -0
  37. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/original.py +205 -0
  38. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/rewrite.txt +197 -0
  39. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/edit.diff +307 -0
  40. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/main.py +188 -0
  41. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/original.py +195 -0
  42. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/rewrite.txt +179 -0
  43. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/edit.diff +447 -0
  44. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/main.py +310 -0
  45. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/original.py +173 -0
  46. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/search_replace.txt +469 -0
  47. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/edit.diff +458 -0
  48. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/main.py +297 -0
  49. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/original.py +194 -0
  50. examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/rewrite.txt +288 -0
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0 ADDED
File without changes
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0.0 ADDED
File without changes
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/EVAL_AGENTS.md ADDED
@@ -0,0 +1,635 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Generation 31 Evaluation
2
+
3
+ **Auxiliary Metrics Implemented:**
4
+
5
+ **Group 1: Radii Distribution**
6
+ * **`avg_radius`**: Mean radius of all circles. Useful for tracking the typical size of circles being packed.
7
+ * **`std_dev_radius`**: Standard deviation of radii. Indicates the dispersion of circle sizes; higher values mean more diverse sizes.
8
+ * **`std_dev_radius_normalized`**: Standard deviation of radii divided by the average radius. A normalized measure of radii diversity. Higher values suggest a broader range of circle sizes, potentially indicating more complex or optimized packing strategies. Useful for identifying if the solution is stuck using similarly sized circles.
9
+
10
+ **Group 2: Boundary Utilization**
11
+ * **`min_dist_to_boundary_avg`**: Average minimum distance from each circle's edge to the closest unit square boundary. Lower values suggest that circles are placed closer to the boundaries, indicating more effective utilization of the packing space's perimeter. This can hint at tighter packing and better overall space exploitation.
12
+
13
+ **Group 3: Packing Efficiency**
14
+ * **`packing_efficiency_area_ratio`**: Ratio of the total area covered by all circles (sum of πr²) to the area of the unit square (which is 1.0). This metric directly quantifies the density of the packing in terms of area covered. Higher values mean a greater portion of the unit square is occupied by circles, representing better efficiency.
15
+
16
+
17
+ ## Generation 9 Evaluation
18
+
19
+ **Auxiliary Metrics Implemented:**
20
+
21
+ **Group 1: Radii Statistics**
22
+ * **`std_dev_radius`**: Standard deviation of the radii of all 26 circles. A higher value indicates greater diversity in circle sizes, while a lower value suggests more uniformly sized circles. This can reveal different packing strategies.
23
+ * **`median_radius`**: The median radius among all circles. Provides a robust measure of the typical circle size, less sensitive to outliers than the mean.
24
+ * **`min_radius`**: The smallest radius found among all circles. Useful for identifying if solutions are introducing very tiny circles, potentially indicating difficulty in packing or a strategy to fill small gaps.
25
+ * **`max_radius`**: The largest radius found among all circles. Indicates the maximum size of circles the algorithm is able to place.
26
+
27
+ **Group 2: Geometric Packing Characteristics**
28
+ * **`avg_min_dist_to_boundary`**: The average of the minimum distances from the edge of each circle to the closest boundary of the unit square. A value close to zero (but positive) suggests that circles are generally packed efficiently against the container edges.
29
+ * **`min_overall_dist_to_boundary`**: The absolute minimum distance from any circle's edge to any boundary of the unit square. A value closer to zero indicates excellent utilization of the container's perimeter.
30
+ * **`min_inter_circle_gap_or_overlap`**: The smallest gap or largest overlap between any two circles. A negative value indicates an overlap, which is a violation of the primary constraint. A positive value indicates a minimum gap between circles. This helps to identify how tightly packed the circles are without violating the non-overlap rule.
31
+ * **`avg_inter_circle_gap_or_overlap`**: The average gap or overlap between all unique pairs of circles. This metric provides a general sense of how "loose" or "tight" the overall packing is.
32
+ * **`num_degenerate_circles`**: The count of circles with a radius smaller than a predefined threshold (currently 1e-5). A high number of degenerate circles might indicate that the algorithm is struggling to find valid placements for some circles and is effectively "removing" them by making their radii minuscule.
33
+
34
+ **Observations and Recommendations for Generation 9:**
35
+
36
+ * **Primary Score**: The primary score for Generation 9 is 1.8471 (sum of radii). This value should be compared against previous generations to understand progress.
37
+ * **Radii Distribution**: By tracking `std_dev_radius`, `median_radius`, `min_radius`, and `max_radius`, we can observe if the evolution is converging on a specific distribution of circle sizes (e.g., uniform, or a mix of large and small to fill space).
38
+ * **Boundary Utilization**: `avg_min_dist_to_boundary` and `min_overall_dist_to_boundary` will show if the solutions are effectively using the boundaries of the unit square, which is crucial for maximizing total radius.
39
+ * **Packing Tightness**: `min_inter_circle_gap_or_overlap` and `avg_inter_circle_gap_or_overlap` will provide critical feedback on how tightly circles are packed. If `min_inter_circle_gap_or_overlap` is negative, it indicates an overlap, which should be strongly penalized or eliminated. If it's consistently positive and large, it suggests wasted space.
40
+ * **Degeneracy**: `num_degenerate_circles` will alert us if the algorithm is generating effectively zero-sized circles, which is usually not a desired outcome for maximizing radii.
41
+
42
+ ## Generation 44 Evaluation
43
+
44
+ **Auxiliary Metrics Implemented:**
45
+
46
+ **Group 1: Packing Efficiency**
47
+ * **`total_circle_area_ratio`**: Sum of all circle areas (sum of pi * r^2) divided by the unit square area (which is 1.0). This metric directly quantifies the density of the packing in terms of area covered. A higher value indicates better space utilization, complementing the primary metric (sum of radii) by considering the actual area occupied.
48
+
49
+ **Group 2: Radii Statistics**
50
+ * **`std_dev_radius`**: Standard deviation of the radii of the 26 circles. This helps understand the diversity of circle sizes. A lower standard deviation suggests more uniformly sized circles, while a higher one indicates a mix of large and small circles, which might be a strategy to fill space efficiently.
51
+ * **`min_radius`**: The smallest radius found among all 26 circles. Tracking this helps identify if the solution is using very small circles to fill tiny gaps, which can be an indicator of a fine-tuned packing strategy or, conversely, a struggle to place larger circles.
52
+ * **`max_radius`**: The largest radius found among all 26 circles. Indicates the maximum size of circles the algorithm is able to place, providing insight into how large individual components of the solution are.
53
+ * **`avg_radius`**: The average radius across all 26 circles. Provides a general measure of the typical circle size being used in the packing.
54
+
55
+ **Observations and Recommendations for Generation 44:**
56
+
57
+ * **Primary Score**: The primary score for Generation 44 is 2.6001 (sum of radii). This value should be compared against previous generations to understand progress.
58
+ * **Packing Density**: The `total_circle_area_ratio` provides an alternative view on packing efficiency. A high value, especially in conjunction with a good primary score, suggests efficient use of the square's area.
59
+ * **Radii Distribution**: By tracking `std_dev_radius`, `min_radius`, `max_radius`, and `avg_radius`, we can infer the strategy employed by the evolutionary algorithm. For instance, a high `std_dev_radius` coupled with a good score might mean the algorithm found an optimal mix of large and small circles. A consistently low `min_radius` might highlight the difficulty in placing the last few circles.
60
+ * **Evolution Stage**: At generation 44, the evolution might be in an 'optimization' or 'convergence' stage. These metrics can help detect if the algorithm is exploring new configurations (diverse radii, higher area ratio) or refining existing ones. If the primary score plateaus, these auxiliary metrics can reveal if the algorithm is stuck in a local optimum (e.g., repeating similar radii distributions) or if there's still diversity to exploit.
61
+
62
+ ## Generation 52 Evaluation
63
+
64
+ **Auxiliary Metrics Implemented:**
65
+
66
+ **Group 1: Radii Distribution**
67
+ * **`avg_radius`**: Mean radius of all circles. Useful for tracking the typical size of circles in the packing. Higher values often correlate with a higher primary score.
68
+ * **`std_dev_radius`**: Standard deviation of radii. Measures the diversity in circle sizes. A higher value indicates a greater mix of large and small circles.
69
+ * **`median_radius`**: The median radius of all circles. Provides a robust measure of central tendency for circle sizes, less affected by outliers than the mean.
70
+ * **`min_radius`**: The smallest radius among all circles. Helps identify if the solution is generating very tiny circles.
71
+ * **`max_radius`**: The largest radius among all circles. Helps identify if the solution is generating very large circles.
72
+ * **`std_dev_radius_normalized`**: Standard deviation of radii divided by the average radius. This provides a normalized measure of radii diversity, useful for comparing solutions with different average circle sizes. Higher values suggest more diverse circle sizes relative to their mean.
73
+
74
+ **Group 2: Boundary Proximity**
75
+ * **`min_dist_to_boundary_avg`**: Average of the minimum distances from each circle's edge to the closest unit square boundary. Lower values indicate that circles are, on average, packed tighter against the edges. This is an important aspect of efficient packing.
76
+ * **`overall_min_dist_to_boundary`**: The absolute minimum distance from any circle's edge to any of the four unit square boundaries (0, 1 on x-axis; 0, 1 on y-axis). A value close to zero (but non-negative) indicates that at least one circle is very tightly placed against a boundary, which is often crucial for optimal packing. Significant negative values would indicate circles outside the boundary (which the primary evaluator should catch as invalid).
77
+
78
+ **Group 3: Packing Efficiency**
79
+ * **`packing_efficiency_area_ratio`**: Ratio of the total area covered by all circles (sum of πr²) to the area of the unit square (1.0). This metric directly reflects the density of the packing. Higher values indicate better utilization of space and generally correlate strongly with the primary score.
80
+ * **`total_packed_area`**: The sum of the areas of all circles. For a unit square, this is numerically equal to `packing_efficiency_area_ratio`.
81
+
82
+ **Group 4: Spatial Distribution**
83
+ * **`local_packing_density_std_dev`**: Standard deviation of packing densities across a 5x5 grid overlaying the unit square. This measures the uniformity of circle distribution. A lower value indicates a more even spread of circles throughout the square, suggesting a balanced packing strategy rather than clustering in one area.
84
+
85
+ **Group 5: Central Tendency of Placement**
86
+ * **`avg_dist_from_square_center`**: Average Euclidean distance of each circle's center from the center of the unit square (0.5, 0.5). Lower values suggest that circles are generally placed closer to the center of the square.
87
+ * **`std_dev_dist_from_square_center`**: Standard deviation of the distances of circle centers from the square's center. A lower standard deviation suggests that the circle centers are more uniformly distributed around their average distance from the center, indicating a more consistent placement pattern (e.g., all circles are equally close/far from the center).
88
+
89
+
90
+ ## Generation 62 Evaluation
91
+
92
+ **Auxiliary Metrics Implemented:**
93
+
94
+ **Group 1: Area and Radii Statistics**
95
+ * **`total_packed_area_ratio`**: The sum of the areas of all circles (pi * r^2) divided by the area of the unit square (1). This provides a direct measure of the total area covered by the circles. Higher values indicate more efficient packing in terms of physical space occupied.
96
+ * **`avg_radius`**: The average radius of all packed circles. Useful for understanding the typical size of circles in the solution.
97
+ * **`radii_std_dev`**: The standard deviation of the radii of all circles. A higher standard deviation indicates greater diversity in circle sizes, while a lower value suggests more uniform sizes.
98
+ * **`radii_coefficient_of_variation`**: The ratio of the standard deviation to the mean of the radii (std_dev / mean). This dimensionless measure provides insight into the relative variability of circle sizes, independent of the absolute scale of the radii.
99
+ * **`max_radius`**: The maximum radius among all circles. Useful for identifying the largest circle in the packing.
100
+ * **`min_radius`**: The minimum radius among all circles. Useful for identifying the smallest circle in the packing.
101
+ * **`sum_of_radii_from_aux`**: The sum of all radii, calculated independently in auxiliary metrics. This is included for comparison and to ensure consistency with the primary metric's core value.
102
+
103
+ **Group 2: Spatial Distribution**
104
+ * **`avg_min_boundary_distance`**: The average of the minimum distances from each circle's edge to the closest boundary of the unit square (0 or 1 on x/y axis). Smaller positive values indicate that circles are packed more closely to the container boundaries, suggesting better utilization of the available space at the edges.
105
+ * **`min_overall_boundary_distance`**: The absolute minimum distance from any circle's edge to any boundary of the unit square. This can highlight if any single circle is extremely close to an edge.
106
+ * **`center_of_mass_x`**: The average x-coordinate of all circle centers. Indicates the horizontal center of the packing arrangement. Values close to 0.5 suggest a balanced horizontal distribution.
107
+ * **`center_of_mass_y`**: The average y-coordinate of all circle centers. Indicates the vertical center of the packing arrangement. Values close to 0.5 suggest a balanced vertical distribution.
108
+ * **`center_x_variance`**: The variance of the x-coordinates of the circle centers. Higher variance suggests a wider horizontal spread of circles.
109
+ * **`center_y_variance`**: The variance of the y-coordinates of the circle centers. Higher variance suggests a wider vertical spread of circles.
110
+
111
+ **Observations and Recommendations for Generation 62:**
112
+ * **Stage**: The evolution is currently in the OPTIMIZATION/CONVERGENCE stage (Generation 62).
113
+ * **Primary Score**: The primary score is 2.6256. This is the sum of radii, which is the direct optimization target.
114
+ * **Purpose of Auxiliary Metrics**: These metrics are designed to provide a more nuanced understanding of the packing geometry beyond just the sum of radii. They can help identify specific characteristics of successful solutions or diagnose why solutions might be getting stuck in local optima.
115
+ * `total_packed_area_ratio`: Directly measures area efficiency, which can sometimes diverge from `sum(r)` optimization. If `sum(r)` increases but `sum(r^2)` plateaus, it might indicate solutions are favoring many small circles over fewer large ones.
116
+ * `radii_std_dev` and `radii_coefficient_of_variation`: Can inform about the diversity of circle sizes. If these values become consistently very low, it might suggest a lack of exploration in terms of size distribution. If too high, it might indicate solutions where a few very large circles dominate, potentially leading to hard-to-fill gaps.
117
+
118
+ ## Generation 181 Evaluation
119
+
120
+ **Auxiliary Metrics Implemented:**
121
+
122
+ **Group 1: Space Utilization**
123
+ * **`area_coverage_ratio`**: The ratio of the total area covered by all circles to the area of the unit square (1.0). A value between 0 and 1. Higher values indicate more efficient packing in terms of area, complementing the primary metric of sum of radii. This can highlight if the solution is optimizing for sum of radii using many small circles or fewer large ones that fill space well.
124
+
125
+ **Group 2: Radii Distribution**
126
+ * **`avg_radius`**: The average radius of all 26 circles. This helps understand the typical size of circles in the solution.
127
+ * **`std_dev_radius`**: The standard deviation of the radii of all 26 circles. A higher standard deviation suggests a more diverse set of circle sizes (some large, some small), while a lower value indicates more uniform sizes. This can reveal evolutionary strategies.
128
+
129
+ ## Generation 193 Evaluation
130
+
131
+ **Auxiliary Metrics Implemented:**
132
+
133
+ **Group 1: Violation Severity Metrics**
134
+ * **`violation_overlap_magnitude_sum`**: Sum of the magnitudes of overlap for all pairs of overlapping circles. A value of 0 indicates no overlaps. Lower values are better. This metric quantifies *how much* overlap exists in the current solution, providing a more granular insight than just a boolean "overlap detected".
135
+ * **`violation_out_of_bounds_magnitude_sum`**: Sum of the distances by which circles extend beyond the [0,1]x[0,1] unit square boundaries. A value of 0 indicates all circles are within bounds. Lower values are better. This quantifies *how much* circles are out of bounds, indicating the severity of boundary violations.
136
+ * **`violation_radii_negative_count`**: The number of circles that have a non-positive (zero or negative) radius. Such circles are invalid. A value of 0 is ideal. This highlights a fundamental issue in circle generation.
137
+
138
+ **Group 2: Valid Circle Statistics**
139
+ * **`valid_circles_count`**: The number of circles that are *individually* within the unit square, have positive radii, and do not overlap with any other such individually valid circles. This acts as a "partial success" count, indicating how many circles are placed perfectly. Higher values are better.
140
+ * **`valid_circles_total_area`**: The sum of the areas of the `valid_circles` (those counted in `valid_circles_count`). This reflects the total area covered by properly placed, non-overlapping circles. Higher values indicate more effective partial packing.
141
+ * **`valid_circles_density`**: The `valid_circles_total_area` divided by the area of the unit square (1.0). Represents the packing density achieved by only the valid circles. Higher values are better.
142
+ * **`valid_circles_avg_radius`**: The average radius of the `valid_circles`. Indicates the typical size of successfully placed circles. Useful for understanding if the solution focuses on many small valid circles or fewer large ones.
143
+ * **`valid_circles_std_radius`**: The standard deviation of the radii of the `valid_circles`. Lower values suggest a more uniform size among valid circles, higher values suggest more diversity in radii among valid circles.
144
+
145
+ **Observations and Recommendations for Generation 193 (Score: 0.0000):**
146
+
147
+ The primary score of 0.0000 strongly suggests that the current solution has significant constraint violations (overlaps, out-of-bounds, or invalid radii). The newly implemented auxiliary metrics are designed to diagnose the *nature and severity* of these violations.
148
+
149
+ **Recommendations:**
150
+ 1. **Prioritize Constraint Satisfaction**: Focus the evolution on reducing `violation_overlap_magnitude_sum` and `violation_out_of_bounds_magnitude_sum`. These are direct indicators of how far the solution is from a valid packing.
151
+ 2. **Monitor `valid_circles_count`**: Even if the primary score is 0, an increasing `valid_circles_count` would indicate incremental progress towards finding at least a subset of circles that are properly placed. This can provide positive reinforcement for intermediate steps.
152
+ 3. **Address Negative Radii**: If `violation_radii_negative_count` is non-zero, the circle generation mechanism needs immediate attention, as negative radii are fundamentally invalid.
153
+
154
+ These auxiliary metrics should help the evolution process by providing more specific feedback than just a 0 score, enabling it to pinpoint the main issues and guide it towards more valid solutions.
155
+
156
+
157
+ **Group 3: Boundary Interaction**
158
+ * **`avg_min_dist_center_to_boundary`**: The average of the minimum distances from each circle\'s center to any of the four unit square boundaries. Smaller values suggest that circles are placed closer to the edges on average, indicating better utilization of the boundary regions.
159
+ * **`num_boundary_touching_circles`**: The count of circles whose circumference is within a small epsilon (1e-4) distance of any of the unit square boundaries. This metric directly quantifies how many circles are "hugging" the edges, a common characteristic of efficient packing solutions.
160
+
161
+ **Observations and Recommendations for Generation 181:**
162
+
163
+ * **Primary Metric Analysis:** The primary metric is the `sum of radii`. At generation 181, the evolution is likely in a convergence or plateau phase. The auxiliary metrics are designed to provide more granular insights beyond just the sum.
164
+ * **`area_coverage_ratio`**: This metric will show if the evolution is effectively translating increased sum of radii into increased packed area. A high sum of radii with a disproportionately lower area coverage might hint at issues with circle placement or a preference for many small circles rather than fewer large ones that fill space well.
165
+ * **`std_dev_radius`**: Monitoring `std_dev_radius` can reveal if the evolution is exploring solutions with diverse circle sizes (e.g., one large circle and many tiny ones) or more uniform sizes. A sudden change in this metric might indicate a new packing strategy being explored or a local optimum being escaped.
166
+ * **`avg_min_dist_center_to_boundary` and `num_boundary_touching_circles`**: Optimal circle packing often involves placing circles against boundaries. These metrics will help understand how effectively the current solution utilizes the edges of the unit square. If these values are not optimized (e.g., high average distance, low number of touching circles), it might indicate that the solution is not fully exploiting the available space, suggesting a potential area for further optimization.
167
+
168
+
169
+
170
+ ## Generation 172 Evaluation
171
+
172
+ **Auxiliary Metrics Implemented:**
173
+
174
+ **Group 1: Packing Efficiency**
175
+ * **`packing_density`**: The proportion of the unit square's area covered by circles. Calculated as the sum of all circle areas divided by the unit square area (which is 1). Higher values indicate better overall space utilization.
176
+ * **`total_circle_area`**: The raw sum of the areas of all circles. Directly represents the total space occupied by circles.
177
+
178
+ **Group 2: Radii Statistics**
179
+ * **`radii_mean`**: The average radius across all 26 circles. Provides insight into the typical size of circles being packed.
180
+ * **`radii_std_dev`**: The standard deviation of the radii. Measures the diversity in circle sizes. A low value means circles are of similar size, while a high value indicates a wider range of sizes.
181
+ * **`radii_median`**: The median radius, offering a robust measure of central tendency for circle sizes, less affected by outliers than the mean.
182
+ * **`radii_min`**: The minimum radius among all circles. Useful for identifying solutions that include very small circles.
183
+ * **`radii_max`**: The largest radius among all circles. Useful for identifying solutions that include very large circles.
184
+
185
+ **Group 3: Spatial Distribution and Boundary Interaction**
186
+ * **`avg_distance_to_square_center`**: The average Euclidean distance of circle centers from the center of the unit square (0.5, 0.5). Smaller values suggest circles are more centrally located, while larger values might indicate packing towards the edges.
187
+ * **`num_boundary_contacts`**: The count of circles that are making contact with one or more boundaries of the unit square. A higher count suggests effective utilization of the boundary conditions.
188
+ * **`avg_min_distance_to_boundary`**: The average of the minimum distances from each circle's edge to its closest boundary. Smaller values mean circles are generally closer to the boundaries.
189
+ * **`min_overall_distance_to_boundary`**: The absolute minimum distance from any circle's edge to any boundary. A value near zero indicates at least one circle is very close to or touching a boundary.
190
+
191
+ **Group 4: Proximity to Overlap**
192
+ * **`min_dist_to_overlap`**: The minimum positive distance between the edges of any two non-overlapping circles. A smaller positive value indicates circles are very close to overlapping. If overlaps occur, this metric is 0.0.
193
+ * **`max_overlap_magnitude`**: The maximum magnitude of overlap found between any two circles. If there is no overlap, this value is 0.0. A positive value indicates invalid packing and the extent of the worst overlap.
194
+
195
+ **Group 5: Spatial Spread**
196
+ * **`convex_hull_area`**: The area of the convex hull formed by the centers of all packed circles. Measures how widely dispersed the centers are.
197
+ * **`convex_hull_area_ratio`**: The ratio of the `convex_hull_area` to the area of the unit square (1.0). A higher ratio (closer to 1.0) indicates that the circle centers are spread out more broadly across the unit square, suggesting better overall utilization of the available space, not just by the circles themselves but by their distribution.
198
+
199
+ **Analysis for Generation 172 (and general recommendations):**
200
+
201
+ Given the current primary score of 2.6304 and the introduction of the new metric focusing on spatial spread, we are likely in the Optimization or Convergence stage. The primary metric (sum of radii) indicates global performance, but how those radii are distributed spatially can be critical for further optimization or to break local optima.
202
+
203
+ **Recommendations:**
204
+ * **Monitor `convex_hull_area_ratio`**: Observe its trend. If it's increasing, it suggests solutions are becoming more spread out. If it's plateauing at a low value while the primary score also stagnates, it might indicate that the circles are clustered, and the evolution needs encouragement to explore more dispersed arrangements. A high ratio (closer to 1.0) generally implies better overall space utilization.
205
+ * **Correlation with Primary Score**: Analyze how `convex_hull_area_ratio` correlates with the `combined_score`. Ideally, a higher primary score should correlate with a higher convex hull area ratio, as it suggests the solution is not just packing circles densely but also distributing them effectively across the entire unit square.
206
+ * **Identify Local Optima**: If the primary score plateaus, but `convex_hull_area_ratio` remains low (e.g., significantly less than 0.8-0.9), it could mean the evolution is finding ways to pack efficiently in a localized region but failing to explore the full extent of the search space. This would be a signal to introduce more exploration-focused operators or diversity-inducing strategies.
207
+ * **Combine with Density**: Compare `convex_hull_area_ratio` with `packing_density` (from Group 1). A solution with high `packing_density` but a relatively low `convex_hull_area_ratio` suggests a dense but localized packing. Conversely, a high `convex_hull_area_ratio` with low `packing_density` might indicate a spread-out but sparse packing. The ideal would be high values for both.
208
+
209
+
210
+ ## Generation 151 Evaluation
211
+
212
+ **Auxiliary Metrics Implemented:**
213
+
214
+ **Group 1: Packing Efficiency**
215
+ * **`packing_density`**: The ratio of the total area covered by all circles to the area of the unit square (0-1 range). Higher values indicate a more efficient use of the available space, which is critical for good circle packing.
216
+ * **`total_circle_area`**: The sum of the areas of all individual circles. This is a direct component of `packing_density` and helps understand the absolute area being covered.
217
+
218
+ **Group 2: Spatial Distribution**
219
+ * **`avg_distance_to_square_center`**: The average Euclidean distance of each circle's center from the center of the unit square (0.5, 0.5). Lower values suggest a more centralized and compact packing, which can be an indicator of a well-balanced solution.
220
+
221
+ **Group 3: Boundary Interaction**
222
+ * **`num_boundary_contacts`**: The count of circles whose edges are within a small epsilon distance (1e-4) of any of the four boundaries of the unit square. A higher number indicates that more circles are interacting with the container's edges, which is often a characteristic of tightly packed arrangements and can help prevent circles from floating freely in the interior.
223
+
224
+
225
+ ## Generation 132 Evaluation
226
+
227
+ **Auxiliary Metrics Implemented:**
228
+
229
+ **Group 1: Geometric Properties**
230
+ * **`total_circle_area`**: The sum of the areas of all 26 circles (pi * r^2). This metric indicates the overall spatial efficiency of the packing in terms of area. A higher value suggests that the solution is finding larger circles in general, which usually means a better packing. This complements the primary metric (sum of radii) by providing the actual area utilization.
231
+ * **`packing_density`**: The total area covered by circles as a proportion of the unit square's area (which is 1). This is a direct measure of how much of the square is filled. For a unit square, this value is identical to `total_circle_area`.
232
+ * **`min_overall_dist_to_boundary`**: The smallest distance from the edge of any circle to any of the four boundaries of the unit square. A value close to zero (but positive) indicates that circles are effectively utilizing the space near the boundaries, which is crucial for optimal packing. A larger value might suggest inefficient use of the boundary regions.
233
+
234
+ **Group 2: Radii Statistics**
235
+ * **`avg_radius`**: The average radius of all 26 circles. This provides a general sense of the size of circles being packed.
236
+ * **`std_dev_radius`**: The standard deviation of the radii of all 26 circles. A lower standard deviation might suggest solutions are converging to similar-sized circles, potentially indicating a local optimum, while a higher standard deviation could mean the solution is exploring more diverse circle sizes for better packing.
237
+ * **`min_radius`**: The smallest radius among the 26 circles. Useful for tracking if very small circles are appearing in the solutions.
238
+ * **`max_radius`**: The largest radius among the 26 circles. Useful for tracking the maximum size of circles achieved in the packing.
239
+ * **`skewness_radii`**: Measures the asymmetry of the radii distribution. Positive skew indicates more small radii, negative skew more large radii. Can reveal if solutions favor many small circles or few large ones.
240
+ * **`kurtosis_radii`**: Measures the \"tailedness\" of the radii distribution. Higher kurtosis indicates more extreme radii (very small or very large) compared to a normal distribution, suggesting a specialized packing strategy.
241
+
242
+ **Group 3: Centroid Spatial Distribution**
243
+ * **`std_dev_centroid_x`**: Standard deviation of the x-coordinates of the circle centers. Indicates how spread out the circles are horizontally. Lower values mean circles are more vertically aligned or clustered.
244
+ * **`std_dev_centroid_y`**: Standard deviation of the y-coordinates of the circle centers. Indicates how spread out the circles are vertically. Lower values mean circles are more horizontally aligned or clustered.
245
+ * **`avg_distance_from_center_of_mass`**: Average Euclidean distance of each circle's centroid from the overall center of mass of all 26 circles. Lower values indicate a more compact internal arrangement of the packing, centered around its own mass.
246
+ * **`num_circles_in_corners`**: Count of circles whose centroid is within a corner region (e.g., x or y < 0.1 or > 0.9). This quantifies how many circles are actively placed in the four corners, which are often key areas for efficient packing.
247
+
248
+ * `avg_min_boundary_distance`: Helps to understand how effectively the boundaries of the unit square are utilized. Solutions that push circles close to the edges often achieve higher packing densities. A consistently high value might indicate suboptimal boundary utilization.
249
+ * `center_of_mass_x/y` and `center_x/y_variance`: Provide insights into the overall distribution and spread of the circles. If centers are heavily skewed or overly clustered, it might suggest a packing strategy that could be improved by a more even distribution.
250
+
251
+ **Next Steps**:
252
+ * Monitor these auxiliary metrics over future generations.
253
+ * Look for correlations between changes in these metrics and changes in the primary score.
254
+ * If the primary score plateaus, analyze the auxiliary metrics for clues on how to encourage new types of solutions (e.g., more diverse radii, better boundary utilization).
255
+
256
+ ## Generation 72 Evaluation
257
+
258
+
259
+ **Auxiliary Metrics Implemented:**
260
+
261
+ ## Generation 96 Evaluation
262
+
263
+ **Evolution Stage:** Optimization / Convergence. The system has reached generation 96 with a primary score of 2.6248, suggesting it is well past initial exploration and is likely refining solutions. The focus is on improving the primary score (sum of radii) while maintaining validity.
264
+
265
+ **Auxiliary Metrics Implemented and Their Usefulness:**
266
+
267
+ **Group 1: Basic Validity and Setup Checks**
268
+ * **`actual_num_circles_attempted`**: The number of circles the program attempted to place. For this problem, it should ideally be 26. Useful for identifying fundamental issues in early generations if the number of circles is incorrect.
269
+ * **`expected_num_circles`**: The target number of circles (fixed at 26). Provides a reference for `actual_num_circles_attempted`.
270
+ * **`is_num_circles_count_correct`**: A boolean flag indicating if the number of circles placed matches the expected count. A quick check for basic program correctness.
271
+ * **`num_radii_positive`**: Count of circles with positive radii.
272
+ * **`num_radii_zero`**: Count of circles with zero radii.
273
+ * **`num_radii_negative`**: Count of circles with negative radii. Essential for debugging; negative radii result in invalid solutions.
274
+ * **`has_negative_radii`**: Boolean indicating if any circle has a negative radius. Crucial for understanding why a solution might be invalid or have a zero primary score.
275
+ * **`has_zero_radii`**: Boolean indicating if any circle has a zero radius. While not invalid, zero-radius circles don't contribute to the primary score and might indicate a sub-optimal solution.
276
+
277
+ *Usefulness for Gen 96:* These metrics confirm the basic correctness of the solution (26 circles, all positive radii) which is a prerequisite for achieving a positive primary score. If any of these indicate an issue, it would explain a low primary score despite many generations.
278
+
279
+ **Group 2: Radii Distribution Statistics**
280
+ * **`avg_radius`**: The average radius of all circles. Tracks the general size of circles favored by the evolutionary process. An increasing average radius might correlate with higher primary scores.
281
+ * **`std_dev_radius`**: The standard deviation of the radii. Higher values indicate a more diverse set of circle sizes, which can be beneficial for fitting into irregular spaces and achieving tighter packing.
282
+ * **`std_dev_radius_normalized`**: The standard deviation of radii divided by the average radius. Provides a relative measure of radii diversity, useful for comparing solutions where absolute radii values might differ significantly.
283
+ * **`min_radius`**: The smallest radius among all circles. A very small `min_radius` could suggest using tiny circles to fill small gaps.
284
+ * **`max_radius`**: The largest radius among all circles. Large `max_radius` often indicates an attempt to cover as much area as possible with a few dominant circles.
285
+ * **`median_radius`**: The median radius of all circles. Offers another perspective on the central tendency of circle sizes, less sensitive to outliers than the mean.
286
+
287
+ *Usefulness for Gen 96:* These metrics reveal the *strategy* of the current generation regarding circle sizing. For a challenging problem like circle packing, using a diverse set of radii (high `std_dev_radius`) is often key to optimal solutions. Tracking trends in these metrics can show if the evolution is exploring different size distributions or converging on a specific one.
288
+
289
+ **Group 3: Boundary Interaction Metrics**
290
+ * **`min_dist_to_boundary_avg`**: The average of the minimum distances from each circle's edge to the closest of the four unit square boundaries. Lower values indicate that circles are generally placed closer to the edges, suggesting better utilization of the square's perimeter.
291
+ * **`overall_min_dist_to_boundary`**: The absolute minimum distance from any circle's edge to any boundary. This metric helps confirm that circles are not violating the boundary constraints, and if it's very close to zero, it means at least one circle is tightly packed against an edge.
292
+
293
+ *Usefulness for Gen 96:* Maximizing packed area usually involves utilizing the boundaries effectively. These metrics tell us how well the solution is "pushing" circles against the walls. Ideal solutions will likely have these values very close to zero without crossing into negative (overlap).
294
+
295
+ **Group 4: Packing Efficiency (Area-based)**
296
+ * **`packing_efficiency_area_ratio`**: The total area covered by all circles divided by the area of the unit square (which is 1.0). This is a direct measure of space utilization based on area, complementing the primary score (sum of radii).
297
+ * **`total_packed_area`**: The sum of the areas of all circles. Directly represents the absolute area covered.
298
+
299
+ *Usefulness for Gen 96:* While the primary score maximizes `sum(r)`, these metrics maximize `sum(pi*r^2)`. Observing both allows us to understand if the evolutionary pressure for `sum(r)` is also leading to efficient area packing, or if there's a trade-off. A solution might have a good `sum(r)` but still have inefficient area usage, or vice-versa.
300
+
301
+
302
+ **Group 6: Central Tendency of Placement**
303
+ * **`avg_dist_from_square_center`**: The average Euclidean distance of each circle's center from the center of the unit square (0.5, 0.5). Indicates if circles are generally clustered towards the center or more spread out.
304
+ * **`std_dev_dist_from_square_center`**: The standard deviation of these distances. A lower std dev suggests a more uniform distribution of circle centers around the average distance from the center.
305
+
306
+ *Usefulness for Gen 96:* These metrics can highlight strategies like placing larger circles towards the center or spreading circles evenly. Tracking these can reveal if the evolution is exploring centralized vs. decentralized packing patterns.
307
+
308
+ **Group 7: Inter-Circle Contact Analysis**
309
+ * **`avg_contact_count`**: The average number of other circles each circle is touching (within a small tolerance). Higher values generally indicate tighter packing.
310
+ * **`max_contact_count`**: The maximum number of contacts for any single circle. Can indicate "key" circles that are central to the packing structure.
311
+
312
+ *Usefulness for Gen 96:* Optimal circle packings often feature many circles in contact with their neighbors. These metrics directly quantify the "tightness" of the packing, which is a strong indicator of efficiency.
313
+
314
+ **Group 8: Inter-Circle Gap Analysis**
315
+ * **`min_gap_between_non_touching_circles`**: The smallest positive gap observed between any two circles that are *not* considered to be touching. A smaller positive value suggests less "wasted" space between non-touching circles.
316
+ * **`min_pairwise_separation_avg`**: The average of `(distance between centers - sum of radii)` for all unique pairs of circles. For valid solutions, this should be non-negative. Lower values indicate tighter average packing.
317
+ * **`min_pairwise_separation_min`**: The minimum of `(distance between centers - sum of radii)` for all unique pairs. This value should be non-negative (ideally close to zero) for valid, tightly packed solutions. If it's negative, it indicates an overlap, which would invalidate the solution (but the primary evaluator already checks for this).
318
+
319
+ *Usefulness for Gen 96:* These metrics are crucial for identifying remaining inefficiencies or "holes" in the packing. Even if circles don't overlap, large gaps suggest room for improvement. A `min_gap_between_non_touching_circles` that is consistently decreasing could indicate steady progress in optimizing packing.
320
+
321
+ **Overall Recommendations for Gen 96:**
322
+ With a primary score of 2.6248, the solution is valid and likely in an optimization phase. I would primarily focus on:
323
+ - **`packing_efficiency_area_ratio`**: Is the area utilization also high, or is the `sum(r)` metric leading to strategies that don't maximize area?
324
+ - **`std_dev_radius_normalized`**: Is the solution using diverse radii to fill space efficiently, or sticking to more uniform sizes? High diversity is often key.
325
+ - **`avg_contact_count` and `min_gap_between_non_touching_circles`**: Are circles making many contacts and minimizing empty space between them? These are direct indicators of packing tightness.
326
+ - **`min_dist_to_boundary_avg`**: How well are the boundaries being utilized? Lower values are generally better, indicating full use of the available space.
327
+
328
+ Tracking the trends of these metrics over future generations will help identify if the evolution is still making meaningful progress, if it's converging to a local optimum, or if it's exploring new strategies.
329
+
330
+
331
+ **Group 1: Area Utilization**
332
+ * **`total_area_covered`**: Measures the sum of the areas of all 26 circles (pi * r^2), representing the total space occupied by the circles within the unit square. A higher value indicates better overall space utilization and packing density. This complements the primary `sum_of_radii` by considering the actual 2D space.
333
+
334
+ **Group 2: Geometric Tightness**
335
+ * **`avg_min_boundary_distance`**: The average of the minimum distances from each circle's edge to the closest boundary of the unit square. A lower value for this metric suggests that circles are generally pushed closer to the edges of the packing region, indicating a 'tighter' or more compact arrangement against the container walls.
336
+
337
+ **Group 3: Radius Distribution**
338
+ * **`avg_radius`**: The average radius of the 26 circles. Useful for understanding the typical size of circles being packed.
339
+ * **`std_dev_radius`**: The standard deviation of the radii of the 26 circles. A higher standard deviation indicates greater diversity in circle sizes (e.g., a mix of very large and very small circles), while a lower standard deviation suggests more uniform circle sizes. This helps to characterize the packing strategy.
340
+ * **`min_radius`**: The smallest radius among the 26 circles.
341
+ * **`max_radius`**: The largest radius among the 26 circles.
342
+
343
+
344
+ ## Generation 73 Evaluation
345
+
346
+ **Auxiliary Metrics Implemented:**
347
+
348
+ **Group 1: Inter-Circle Relationships**
349
+ * **`avg_contact_count`**: The average number of other circles that each circle is touching (within a small tolerance). A higher average contact count suggests a more tightly interlocked and potentially stable packing configuration, which can be indicative of a more efficient use of space.
350
+ * **`max_contact_count`**: The maximum number of contacts found for any single circle in the packing. This highlights circles that are highly constrained or central to the packing structure. High values might indicate a "keystone" circle.
351
+ * **`min_gap_between_non_touching_circles`**: The smallest positive distance between the edges of any two circles that are *not* considered to be touching. A smaller value indicates that even non-contacting circles are close to each other, suggesting a generally tighter packing and less "wasted" empty space, potentially revealing opportunities for further optimization if this value is consistently high.
352
+
353
+ **Observations and Recommendations for Generation 73:**
354
+
355
+ * **Evolution Stage**: We are in Generation 73, which likely corresponds to the **Optimization** or **Convergence** stage. The primary goal is to maximize the sum of radii while maintaining a valid packing.
356
+ * **Insights from New Metrics**:
357
+ * **`avg_contact_count` and `max_contact_count`**: These metrics will help understand the structural integrity and efficiency of the packing. Optimal circle packings often feature many tangencies. If the primary score is stagnating, an increase in these contact metrics might indicate a breakthrough in finding more efficient geometric arrangements. Conversely, if these counts are low, it might suggest a "loose" packing that has room for improvement through rearrangement.
358
+ * **`min_gap_between_non_touching_circles`**: This metric provides a fine-grained view of the packing's tightness. A persistently large minimum gap suggests that there's still room to "compress" the packing, potentially by slightly adjusting circle positions or radii. If this value consistently approaches zero (but remains positive), it indicates a highly optimized, tight packing where circles are almost touching everywhere. This metric is crucial for identifying if the algorithm is leaving small, unexploited pockets of space.
359
+ * **Recommendations**:
360
+ * Monitor the trend of `avg_contact_count` and `min_gap_between_non_touching_circles`. Ideally, `avg_contact_count` should increase, and `min_gap_between_non_touching_circles` should decrease towards zero as the primary score improves.
361
+ * If the primary score plateaus, analyze if these new metrics are also stagnant. If `min_gap_between_non_touching_circles` is high, it suggests the algorithm might benefit from exploration strategies that focus on minor positional adjustments or subtle radius changes to close these gaps. If `avg_contact_count` is low, it might indicate a need for exploration that focuses on finding configurations where more circles are tangent.
362
+
363
+ ## Generation 83 Evaluation
364
+
365
+ **Auxiliary Metrics Implemented:**
366
+
367
+ **Group 1: Packing Efficiency**
368
+ * **`total_area_covered`**: This metric calculates the sum of the areas of all circles (`pi * r^2`). The unit square has an area of 1. A higher `total_area_covered` indicates a more efficient use of space within the unit square. This metric complements the primary `sum_of_radii` by providing a direct measure of the physical space occupied by the circles, which can be particularly useful when comparing solutions with different radii distributions.
369
+
370
+ **Group 2: Radii Statistics**
371
+ * **`avg_radius`**: This is the average radius of all `n=26` circles. It indicates whether the solution favors generally larger or smaller circles.
372
+ * **`std_dev_radius`**: This is the standard deviation of the radii of all `n=26` circles. It quantifies the diversity of circle sizes. A high standard deviation suggests a mix of large and small circles, which might be beneficial for packing irregular spaces efficiently. A low standard deviation indicates a more uniform size distribution among the circles. These metrics can help the evolution process understand if specific radius distributions are emerging or are more effective.
373
+
374
+ **Group 3: Inter-Circle Gaps**
375
+ * **`min_gap_between_circles`**: This metric calculates the smallest positive distance between the edges of any two distinct circles. The primary evaluator only checks for *overlap* (i.e., `gap < 0`). This auxiliary metric provides a more nuanced view of how "tight" the packing is without violating the non-overlap constraint. A smaller positive `min_gap` suggests a denser packing configuration, pushing circles closer together towards a theoretical optimum. This can help guide the optimizer to find solutions where circles are just touching or nearly touching.
376
+
377
+ ## Generation 107 Evaluation
378
+
379
+ **Auxiliary Metrics Implemented:**
380
+
381
+ **Group 1: Packing Efficiency**
382
+ * **`total_area_covered`**: The sum of the areas of all circles (πr²). This metric directly measures the total space utilized by the circles within the unit square. A higher value indicates better packing density and efficiency in utilizing the available area.
383
+
384
+ **Group 2: Radii Statistics**
385
+ * **`avg_radius`**: The average radius of all 26 circles. This provides insight into the typical size of circles being packed. A higher average radius suggests solutions are trying to fit larger circles.
386
+ * **`std_dev_radius`**: The standard deviation of the radii of all 26 circles. A higher standard deviation indicates greater diversity in circle sizes (some very large, some very small), while a lower standard deviation suggests more uniform circle sizes. This helps understand the distribution strategy.
387
+ * **`min_radius`**: The smallest radius among all circles. Can indicate if the solution relies on extremely small "filler" circles.
388
+ * **`max_radius`**: The largest radius among all circles. Indicates the size of the largest circle successfully packed.
389
+
390
+ **Group 3: Boundary Proximity**
391
+ * **`min_boundary_distance`**: The minimum distance from the edge of any circle to any of the four boundaries of the unit square. A value closer to zero (but non-negative) indicates that at least one circle is tightly packed against a boundary. Higher values might suggest "looser" packing or room for circles to expand/shift closer to the edges, potentially increasing the primary score.
392
+
393
+ ## Generation 108 Evaluation
394
+
395
+ **Auxiliary Metrics Implemented:**
396
+
397
+ **Group 1: Boundary Proximity and Violations**
398
+ * **`avg_min_boundary_dist`**: Average minimum distance from each circle's edge to the closest unit square boundary (0 or 1). Negative values indicate that a circle extends beyond the boundary. Lower positive values suggest tighter packing against the edges, while more negative values indicate a more severe boundary violation. This metric is a continuous measure of boundary adherence.
399
+ * **`max_boundary_excursion`**: The maximum magnitude (absolute distance) by which any single circle extends outside the [0,1] unit square. A value of 0 means all circles are within bounds. A positive value directly quantifies the extent of the most significant boundary violation, providing a critical insight into *how* invalid a solution is in terms of boundary constraints.
400
+
401
+ **Group 2: Overlap Quantification**
402
+ * **`max_overlap_magnitude`**: The maximum value of `(radii_i + radii_j - distance_ij)` for any pair of circles `i` and `j` where this value is positive. This metric quantifies the largest overlap detected between any two circles. A value of 0 indicates no overlaps. A higher positive value denotes a more severe overlap, which is crucial for understanding the "cost" of invalid solutions and guiding repairs.
403
+ * **`num_overlaps`**: The total count of unique pairs of circles that exhibit an overlap (i.e., `radii_i + radii_j > distance_ij`). This metric provides a frequency count of overlap occurrences, indicating whether a solution has many minor overlaps or just a few significant ones.
404
+
405
+ **Observations and Recommendations for Generation 108:**
406
+
407
+ * **Evolution Stage**: At generation 108, the evolution is likely in an **Optimization** or **Convergence** stage. Solutions should be generally valid or very close to valid.
408
+ * **Insights from New Metrics**:
409
+ * **`max_overlap_magnitude` and `num_overlaps`**: These are critical for detecting and diagnosing near-valid or invalid solutions. If the primary score drops to zero, these metrics will explain *why* by quantifying the extent and frequency of overlaps. During optimization, if `max_overlap_magnitude` is consistently small and positive, it indicates the algorithm is struggling with the last bit of overlap removal, suggesting that a small perturbation or local search might be needed.
410
+ * **`avg_min_boundary_dist` and `max_boundary_excursion`**: Similar to overlaps, these metrics provide continuous feedback on boundary violations. If solutions are scoring low due to being slightly out of bounds, `max_boundary_excursion` will highlight the severity. `avg_min_boundary_dist` can show if circles are generally far from the boundaries (potential for larger radii) or tightly packed against them (good utilization). If `max_boundary_excursion` is non-zero, it means the solution is technically invalid, and the magnitude helps understand the problem.
411
+ * **Recommendations**:
412
+ * Monitor `max_overlap_magnitude` and `max_boundary_excursion`. Ideally, these should both be close to zero or exactly zero in later stages of evolution. Any significant positive value indicates an invalid solution that needs to be addressed.
413
+ * If valid solutions (`max_overlap_magnitude` and `max_boundary_excursion` are zero) are found, `avg_min_boundary_dist` should be monitored to ensure circles are effectively using the boundary space (i.e., this value should be small but positive).
414
+ * These metrics help differentiate between solutions that are "a little bit wrong" and "very wrong," providing valuable gradient information where the binary validation from the primary evaluator does not. This is crucial for escaping local optima where solutions might be stuck with small, persistent violations.
415
+
416
+
417
+ ## Generation 118 Evaluation
418
+
419
+ **Auxiliary Metrics Implemented:**
420
+
421
+ **Group 1: Circle Count and Basic Validation**
422
+ * **`actual_num_circles_attempted`**: The number of circles found in the solution. This should ideally match the `expected_num_circles` (26).
423
+ * **`expected_num_circles`**: The hardcoded expected number of circles, which is 26 for this problem.
424
+ * **`is_num_circles_count_correct`**: A boolean flag indicating if `actual_num_circles_attempted` matches `expected_num_circles`. Useful for early validation checks; False indicates a fundamental structural issue.
425
+ * **`num_radii_positive`**: Count of circles with positive radii. For a valid, non-degenerate solution, this should ideally be equal to `expected_num_circles`.
426
+ * **`num_radii_zero`**: Count of circles with zero radii. A non-zero count might indicate degenerate circles that don't contribute to packing.
427
+ * **`num_radii_negative`**: Count of circles with negative radii. This metric should always be 0 for valid solutions as negative radii are physically impossible in this context.
428
+ * **`has_negative_radii`**: Boolean flag (True/False) indicating if any circle has a negative radius. If True, the solution is invalid.
429
+ * **`has_zero_radii`**: Boolean flag (True/False) indicating if any circle has a zero radius. While not strictly invalid, it suggests a circle is not contributing to packing.
430
+
431
+ **Group 2: Radii Distribution Metrics**
432
+ * **`avg_radius`**: The average radius across all circles. Provides a sense of the typical size of circles being packed.
433
+ * **`std_dev_radius`**: The standard deviation of radii. Higher values indicate a more diverse set of circle sizes, while lower values suggest a more uniform sizing strategy.
434
+ * **`median_radius`**: The median radius, offering a robust measure of central tendency for radii, less sensitive to extreme outliers than the mean.
435
+ * **`min_radius`**: The smallest radius among all circles. Helps identify if very small circles are being used.
436
+ * **`max_radius`**: The largest radius among all circles. Helps identify if very large circles are being used.
437
+ * **`std_dev_radius_normalized`**: Standard deviation of radii divided by the average radius. This metric normalizes for the overall scale of radii, providing a relative measure of diversity in circle sizes (higher value = more relative diversity).
438
+
439
+ **Group 3: Boundary Metrics**
440
+ * **`avg_min_boundary_dist`**: Average minimum signed distance from each circle's edge to the closest unit square boundary (0 or 1). Negative values mean the circle is outside the boundary. Lower positive values suggest tighter packing against the edges.
441
+ * **`overall_min_boundary_dist`**: The absolute minimum signed distance any circle's edge is from any boundary. This is the smallest gap or largest excursion.
442
+ * **`max_boundary_excursion`**: The maximum magnitude by which any circle extends outside the [0,1] unit square. Should be 0 for valid solutions; a non-zero value indicates a violation.
443
+
444
+ **Group 4: Packing Efficiency Metrics**
445
+ * **`packing_efficiency_area_ratio`**: Ratio of the total area covered by circles (sum of πr²) to the area of the unit square (which is 1.0). This metric directly quantifies how much of the available space is filled by the circles. Higher values indicate better utilization of space.
446
+ * **`total_packed_area`**: The absolute sum of the areas of all circles. This is directly proportional to `packing_efficiency_area_ratio` since the unit square's area is 1.
447
+
448
+ **Group 5: Spatial Distribution Metrics**
449
+ * **`local_packing_density_std_dev`**: Standard deviation of packing densities calculated across a 5x5 grid dividing the unit square. Lower values indicate a more uniform distribution of circles throughout the packing space, suggesting an even spread. Higher values suggest clustering or significant empty regions.
450
+
451
+ **Group 6: Central Tendency Metrics**
452
+ * **`avg_dist_from_square_center`**: Average Euclidean distance of each circle's center from the exact center of the unit square (0.5, 0.5). Lower values might suggest a more centralized packing configuration.
453
+ * **`std_dev_dist_from_square_center`**: Standard deviation of these distances. A lower std dev indicates that circles are distributed more uniformly around their average distance from the center, implying less variance in their radial positioning.
454
+
455
+ **Group 7: Contact Metrics**
456
+ * **`avg_contact_count`**: Average number of times a circle touches another circle (within a small tolerance). Higher values indicate a denser, more interconnected packing where circles are efficiently utilizing shared boundaries.
457
+ * **`max_contact_count`**: The maximum number of contacts any single circle has with other circles. This can highlight "keystone" circles that are central to the packing structure or identify areas of very high local density.
458
+
459
+ **Group 8: Gap and Overlap Metrics**
460
+ * **`min_gap_between_non_touching_circles`**: The smallest positive gap found between any two circles that are not considered touching. Smaller values suggest tighter overall packing, even for pairs not in direct contact. A value of 0.0 could indicate all circles are either touching or overlapping.
461
+ * **`min_pairwise_separation_avg`**: The average of (distance between centers - sum of radii) for all distinct pairs of circles. For valid solutions, this should be non-negative. This metric provides an average measure of the "tightness" of the packing across all pairs.
462
+ * **`min_pairwise_separation_min`**: The minimum value of (distance between centers - sum of radii) for all distinct pairs of circles. This represents the smallest separation or largest overlap. Ideally, it should be close to 0 (or slightly positive) for optimal valid packing.
463
+ * **`max_overlap_magnitude`**: The maximum negative value of (distance between centers - sum of radii) for any overlapping pair. A positive value indicates the largest magnitude of overlap. Should be 0 for valid solutions.
464
+ * **`num_overlaps`**: Count of pairs of circles that are overlapping. Should be 0 for valid solutions.
465
+
466
+ **Group 9: Unique Radii Metrics**
467
+ * **`num_unique_radii`**: Count of distinct radius values (considering a small floating-point tolerance). A higher count suggests a more diverse set of circle sizes, potentially indicating exploration of various packing strategies. A low count might point towards solutions favoring uniform or a few specific sizes.
468
+
469
+ **Group 10: Edge Contact Metrics (Specific)**
470
+ * **`num_circles_touching_boundary`**: Count of circles whose edge is within a small tolerance of any of the four unit square boundaries. A higher count suggests good utilization of the packing boundary, which is often crucial for maximizing total radius.
471
+
472
+ **Observations and Recommendations for Generation 118:**
473
+
474
+ * **Evolution Stage**: At Generation 118, the evolution is firmly in the **Optimization** or **Convergence** stage. Solutions should be generally valid and continually refining to achieve higher primary scores.
475
+
476
+ * **Primary Score (2.6304)**: This is the current sum of radii. When comparing against previous generations, observe the trend. Is it increasing steadily, stagnating, or showing oscillations? The auxiliary metrics will help diagnose the reasons behind these trends.
477
+
478
+ * **Key Diagnostic Metrics**:
479
+ * **Validation Check (`is_num_circles_count_correct`, `has_negative_radii`, `has_zero_radii`, `max_boundary_excursion`, `max_overlap_magnitude`, `num_overlaps`)**: These are foundational. For a valid solution, `is_num_circles_count_correct` should be True, `has_negative_radii` should be False, `has_zero_radii` should ideally be False (unless a strategy to use degenerate circles is effective), and `max_boundary_excursion`, `max_overlap_magnitude`, and `num_overlaps` should all be 0. If any of these indicate a violation, the primary score might be misleadingly low, and the evolution needs to prioritize fixing these structural issues.
480
+
481
+ * **Packing Density (`packing_efficiency_area_ratio`, `total_packed_area`)**: Track these to see if the solutions are not just summing radii, but also efficiently covering the area. A high primary score *should* correlate with a high `packing_efficiency_area_ratio`. Discrepancies might suggest unusual radii distributions that sum to high values but don't effectively fill space (e.g., many tiny circles).
482
+
483
+ * **Tightness of Packing (`avg_contact_count`, `min_pairwise_separation_min`, `min_gap_between_non_touching_circles`)**: These metrics are critical for assessing how tightly circles are packed both with each other and in the empty spaces. Ideally, `avg_contact_count` and `num_circles_touching_boundary` should be high, and `min_pairwise_separation_min` (for valid solutions) and `min_gap_between_non_touching_circles` should be close to 0, indicating a dense arrangement.
484
+
485
+ * **Spatial Uniformity and Centralization (`local_packing_density_std_dev`, `avg_dist_from_square_center`, `std_dev_dist_from_square_center`)**: Low `local_packing_density_std_dev` suggests an even spread, which is often desirable for maximizing total area. `avg_dist_from_square_center` can tell if solutions are becoming too centralized or are effectively using the corners/edges. Deviations here might point to local optima where circles are clustered instead of dispersed for maximal packing.
486
+
487
+ * **Radii Diversity (`std_dev_radius_normalized`, `num_unique_radii`)**: In the optimization phase, a balance is often sought. Too uniform sizes (`std_dev_radius_normalized` low) might miss opportunities to fill complex gaps. Too diverse (`std_dev_radius_normalized` high) might make global optimization harder. `num_unique_radii` helps understand the variety of sizes being explored.
488
+
489
+ * **Recommendations**:
490
+ * **Monitor for Stagnation**: If the primary score plateaus, analyze the trends of `packing_efficiency_area_ratio`, `avg_contact_count`, and `std_dev_radius_normalized`. Stagnation in these values might indicate a local optimum.
491
+ * **Focus on Gaps**: If `min_gap_between_non_touching_circles` is consistently positive and not approaching zero, it means there's unused space. The evolution might need mechanisms to encourage circles to expand or shift to fill these gaps more efficiently.
492
+ * **Encourage Boundary Use**: A low `num_circles_touching_boundary` for a high-scoring solution might suggest room for improvement by pushing circles closer to the edges.
493
+ * **Diversity in Exploration**: If `num_unique_radii` is consistently low, consider if the evolution is exploring enough diverse circle sizes. Introducing more varied radii could unlock new packing configurations.
494
+
495
+
496
+ ## Generation 119 Evaluation
497
+
498
+ **Auxiliary Metrics Implemented:**
499
+
500
+ **Group: Spatial Arrangement and Balance**
501
+ * **`bounding_box_area_ratio`**: This metric calculates the ratio of the area of the minimum bounding box that fully encloses all circles (considering their radii) to the area of the unit square (1.0). A lower value indicates a more compact and less spread-out arrangement of the circles within the overall solution space. This is particularly useful in later stages of evolution to identify solutions that are not only efficient in packing but also spatially coherent.
502
+ * **`center_of_mass_distance_to_square_center`**: This metric measures the Euclidean distance between the average position (center of mass) of all circle centers and the exact center of the unit square (0.5, 0.5). A smaller distance suggests a more centrally balanced packing, which can be an indicator of robust and aesthetically pleasing solutions, especially when the primary metric alone might favor solutions heavily skewed to one side.
503
+
504
+ **Re-iterating existing key metrics for context (from previous generations):**
505
+ * **`avg_radius`**: The average radius of all packed circles. Useful for understanding if solutions tend towards uniformly sized circles or a mix.
506
+ * **`std_dev_radius`**: The standard deviation of the radii of all packed circles. A higher standard deviation suggests a more diverse set of circle sizes, which can be crucial for achieving optimal packing in some configurations.
507
+ * **`avg_min_boundary_dist`**: Average minimum distance from each circle's edge to the closest unit square boundary. Negative values indicate circles are outside the boundary; positive values indicate distance from the boundary. Lower positive values suggest tighter packing against the edges.
508
+ * **`num_circles_touching_boundary`**: Count of circles whose edge is within a small tolerance of any boundary. Indicates how many circles are actively utilizing the edges of the packing area.
509
+
510
+ **Observations and Recommendations for Generation 119:**
511
+
512
+ * **Evolution Stage**: We are in the Optimization/Convergence stage (Generation 119). The primary score is 2.6282. At this stage, subtle characteristics beyond just the sum of radii become important for refining solutions and potentially escaping local optima.
513
+ * **Value of new metrics**:
514
+ * `bounding_box_area_ratio` can help detect solutions where circles are packed well but are unnecessarily spread out within the unit square, leaving large empty areas. A good solution should have a low ratio, indicating a tight overall cluster.
515
+ * `center_of_mass_distance_to_square_center` can help identify solutions that are visually balanced. While not directly tied to the primary metric of sum of radii, it could correlate with more stable or generalizable packing strategies.
516
+ * **Recommendation**: Encourage solutions that minimize `bounding_box_area_ratio` and `center_of_mass_distance_to_square_center` while maintaining a high primary score. This might lead to more aesthetically pleasing and physically stable packings. Monitor trends in `std_dev_radius` to see if the optimizer is exploring diverse radius distributions or converging on uniform sizes.
517
+
518
+
519
+ ## Generation 129 Evaluation
520
+
521
+ **Auxiliary Metrics Implemented:**
522
+
523
+ **Group 1: Basic Packing Validation & Counts**
524
+ * **`actual_num_circles_attempted`**: The number of circles found in the solution. Should ideally be 26.
525
+ * **`expected_num_circles`**: The target number of circles (26 for this problem).
526
+ * **`is_num_circles_count_correct`**: Boolean indicating if `actual_num_circles_attempted` matches `expected_num_circles`. Useful for early stage debugging.
527
+ * **`num_radii_positive`**: Count of circles with positive radii. Should ideally be equal to `actual_num_circles_attempted`.
528
+ * **`num_radii_zero`**: Count of circles with zero radius. Indicates \'missing\' circles or degenerate solutions.
529
+ * **`num_radii_negative`**: Count of circles with negative radius. Indicates an invalid solution state.
530
+ * **`has_negative_radii`**: Boolean flag for existence of any negative radii.
531
+ * **`has_zero_radii`**: Boolean flag for existence of any zero radii.
532
+
533
+ **Group 2: Radii Distribution Statistics**
534
+ * **`avg_radius`**: Average radius of all circles. Tracks general size of circles.
535
+ * **`std_dev_radius`**: Standard deviation of radii. Higher values indicate more diverse circle sizes.
536
+ * **`std_dev_radius_normalized`**: Standard deviation of radii divided by the average radius. Provides a relative measure of diversity, useful for comparing solutions with different overall scales.
537
+ * **`median_radius`**: Median radius. Less sensitive to outliers than average.
538
+ * **`min_radius`**: Smallest radius found. Can indicate presence of very small \'filler\' circles.
539
+ * **`max_radius`**: Largest radius found.
540
+ * **`num_unique_radii`**: Count of distinct radius values (within a small tolerance). A higher count can indicate greater diversity in solution strategy.
541
+
542
+ **Group 3: Packing Efficiency & Geometry**
543
+ * **`total_circle_area`**: Sum of the areas of all circles ($\pi \\sum r_i^2$). Higher values are generally better but might not capture overlap if validation fails.
544
+ * **`packing_density_relative_to_unit_square`**: Ratio of `total_circle_area` to the unit square area (1.0). Provides a theoretical density, without accounting for overlaps.
545
+ * **`avg_min_edge_dist_to_nn`**: Average of the minimum edge distances to the nearest neighbor for each circle. A value close to 0 (but non-negative) indicates tight packing without overlap. Positive values suggest gaps, negative values suggest overlap.
546
+ * **`bounding_box_area_ratio`**: Ratio of the area of the minimum bounding box encompassing all circles (including their full extent) to the unit square area (1.0). Lower values indicate a more compact arrangement of the circle *extents*, not necessarily their internal arrangement.
547
+
548
+ **Group 4: Boundary Interaction**
549
+ * **`boundary_contacts_strict_count`**: Number of circles whose edge is strictly (within `1e-6` tolerance) touching any of the unit square boundaries. Indicates effective use of boundaries.
550
+ * **`boundary_contacts_loose_count`**: Number of circles whose edge is loosely (within `0.01` tolerance) touching any of the unit square boundaries. A broader measure of boundary utilization.
551
+ * **`avg_min_dist_to_wall`**: Average of the minimum distance from each circle\'s edge to its nearest unit square wall. Lower values indicate circles are generally closer to the boundaries.
552
+ * **`num_circles_touching_boundary`**: Similar to `boundary_contacts_strict_count`, counts circles with edges very close to boundaries.
553
+
554
+ **Group 5: Spatial Distribution & Balance**
555
+ * **`avg_dist_from_square_center`**: Average Euclidean distance of each circle\'s center from the center of the unit square (0.5, 0.5). Lower values imply circles are more concentrated towards the center.
556
+ * **`std_dev_dist_from_square_center`**: Standard deviation of distances of circle centers from the unit square center. Indicates how spread out the circles are around the center.
557
+ * **`center_of_mass_distance_to_square_center`**: Euclidean distance between the packing\'s center of mass (average of all circle centers) and the unit square\'s center (0.5, 0.5). Lower values indicate a more centrally balanced packing overall.
558
+
559
+ **Group 6: Inter-Circle Relationships (Gaps & Overlaps)**
560
+ * **`num_contacting_pairs`**: Count of pairs of circles that are touching (distance between centers equals sum of radii within tolerance).
561
+ * **`avg_contact_count`**: Average number of contacts per circle. High values can indicate dense packing.
562
+ * **`max_contact_count`**: Maximum number of contacts for any single circle. Useful for identifying highly constrained circles.
563
+ * **`min_gap_between_non_touching_circles`**: The smallest positive gap found between any two circles that are *not* considered touching. A smaller value indicates tighter packing in general, even for non-touching pairs. Zero if all circles are touching or overlapping.
564
+ * **`min_pairwise_separation_avg`**: Average of `(distance between centers - sum of radii)` for all pairs. Should be >= 0 for valid solutions. Averages the \'tightness\' or \'looseness\' of packing.
565
+ * **`min_pairwise_separation_min`**: Minimum of `(distance between centers - sum of radii)` for all pairs. The smallest gap or difference from touching. Closer to 0 indicates tighter packing. Negative values imply overlap.
566
+ * **`max_overlap_magnitude`**: The maximum magnitude of overlap found. A positive value indicates the largest overlap, 0 if no overlaps. This is a critical metric for validity.
567
+ * **`num_overlaps`**: Count of pairs of circles that overlap. Crucial for validity assessment.
568
+
569
+ **Observations and Recommendations for Generation 129:**
570
+
571
+ * **Evolution Stage**: At Generation 129, the evolution is likely in the **Optimization** or **Convergence** stage. Solutions should be generally valid and continually refining to achieve higher primary scores.
572
+ * **Primary Score (2.6306)**: This is the current sum of radii. We need to monitor if this score is increasing, stagnating, or oscillating. The auxiliary metrics will help diagnose the reasons behind these trends.
573
+ * **Key Diagnostic Metrics**:
574
+ * **Validation Check (`is_num_circles_count_correct`, `has_negative_radii`, `has_zero_radii`, `max_overlap_magnitude`, `num_overlaps`)**: For a valid solution, `is_num_circles_count_correct` should be True, `has_negative_radii` should be False, `has_zero_radii` should ideally be False (unless a strategy to use degenerate circles is effective), and `max_overlap_magnitude`, `num_overlaps` should all be 0. If any of these indicate a violation, the primary score might be misleadingly low, and the evolution needs to prioritize fixing these structural issues.
575
+ * **Packing Density (`total_circle_area`, `packing_density_relative_to_unit_square`)**: Track these to see if the solutions are not just summing radii, but also efficiently covering the area. A high primary score *should* correlate with a high `packing_density_relative_to_unit_square`. Discrepancies might suggest unusual radii distributions that sum to high values but don\'t effectively fill space (e.g., many tiny circles).
576
+ * **Tightness of Packing (`avg_contact_count`, `min_pairwise_separation_min`, `min_gap_between_non_touching_circles`)**: These metrics are critical for assessing how tightly circles are packed. Ideally, `avg_contact_count` should be high, and `min_pairwise_separation_min` (for valid solutions) and `min_gap_between_non_touching_circles` should be close to 0, indicating a dense arrangement.
577
+ * **Spatial Uniformity and Balance (`avg_dist_from_square_center`, `center_of_mass_distance_to_square_center`)**: `avg_dist_from_square_center` can tell if solutions are becoming too centralized or are effectively using the corners/edges. `center_of_mass_distance_to_square_center` helps to identify centrally balanced packings.
578
+ * **Radii Diversity (`std_dev_radius_normalized`, `num_unique_radii`)**: In the optimization phase, a balance is often sought. Too uniform sizes (`std_dev_radius_normalized` low) might miss opportunities to fill complex gaps. Too diverse (`std_dev_radius_normalized` high) might make global optimization harder. `num_unique_radii` helps understand the variety of sizes being explored.
579
+ * **Recommendations**:
580
+ * **Monitor for Stagnation**: If the primary score plateaus, analyze the trends of `total_circle_area`, `avg_contact_count`, and `std_dev_radius_normalized`. Stagnation in these values might indicate a local optimum.
581
+ * **Focus on Gaps**: If `min_gap_between_non_touching_circles` is consistently positive and not approaching zero, it means there\'s unused space. The evolution might need mechanisms to encourage circles to expand or shift to fill these gaps more efficiently.
582
+ * **Encourage Boundary Use**: A low `boundary_contacts_strict_count` or `num_circles_touching_boundary` for a high-scoring solution might suggest room for improvement by pushing circles closer to the edges.
583
+ * **Diversity in Exploration**: If `num_unique_radii` is consistently low, consider if the evolution is exploring enough diverse circle sizes. Introducing more varied radii could unlock new packing configurations.
584
+
585
+
586
+ ## Generation 181 Evaluation
587
+
588
+ **Auxiliary Metrics Implemented:**
589
+
590
+ **Group 1: Space Utilization**
591
+ * **`area_coverage_ratio`**: The ratio of the total area covered by all circles to the area of the unit square (1.0). A value between 0 and 1. Higher values indicate more efficient packing in terms of area, complementing the primary metric of sum of radii. This can highlight if the solution is optimizing for sum of radii using many small circles or fewer large ones that fill space well.
592
+
593
+ **Group 2: Radii Distribution**
594
+ * **`avg_radius`**: The average radius of all 26 circles. This helps understand the typical size of circles in the solution.
595
+ * **`std_dev_radius`**: The standard deviation of the radii of all 26 circles. A higher standard deviation suggests a more diverse set of circle sizes (some large, some small), while a lower value indicates more uniform sizes. This can reveal evolutionary strategies.
596
+
597
+ **Group 3: Boundary Interaction**
598
+ * **`avg_min_dist_center_to_boundary`**: The average of the minimum distances from each circle\'s center to any of the four unit square boundaries. Smaller values suggest that circles are placed closer to the edges on average, indicating better utilization of the boundary regions.
599
+ * **`num_boundary_touching_circles`**: The count of circles whose circumference is within a small epsilon (1e-4) distance of any of the unit square boundaries. This metric directly quantifies how many circles are "hugging" the edges, a common characteristic of efficient packing solutions.
600
+
601
+ **Observations and Recommendations for Generation 181:**
602
+
603
+ * **Primary Metric Analysis:** The primary metric is the `sum of radii`. At generation 181, the evolution is likely in a convergence or plateau phase. The auxiliary metrics are designed to provide more granular insights beyond just the sum.
604
+ * **`area_coverage_ratio`**: This metric will show if the evolution is effectively translating increased sum of radii into increased packed area. A high sum of radii with a disproportionately lower area coverage might hint at issues with circle placement or a preference for many small circles rather than fewer large ones that fill space well.
605
+ * **`std_dev_radius`**: Monitoring `std_dev_radius` can reveal if the evolution is exploring solutions with diverse circle sizes (e.g., one large circle and many tiny ones) or more uniform sizes. A sudden change in this metric might indicate a new packing strategy being explored or a local optimum being escaped.
606
+ * **`avg_min_dist_center_to_boundary` and `num_boundary_touching_circles`**: Optimal circle packing often involves placing circles against boundaries. These metrics will help understand how effectively the current solution utilizes the edges of the unit square. If these values are not optimized (e.g., high average distance, low number of touching circles), it might indicate that the solution is not fully exploiting the available space, suggesting a potential area for further optimization.
607
+
608
+
609
+ ## Generation 191 Evaluation
610
+
611
+ **Auxiliary Metrics Implemented:**
612
+
613
+ **Group 1: Radii Statistics**
614
+ * **`radii_std_dev`**: Standard deviation of the radii of the 26 circles. Measures the diversity of circle sizes. A higher value indicates more varied circle sizes (useful for filling gaps), while a lower value suggests more uniform sizes (potentially for a more regular packing).
615
+ * **`radii_coefficient_of_variation`**: Coefficient of variation (standard deviation / mean) of the radii. This normalizes the standard deviation, allowing for comparison of relative variability across different scales of radii, which can be useful if the overall radii scale changes significantly.
616
+ * **`radii_min`**: Minimum radius among the 26 circles.
617
+ * **`radii_max`**: Maximum radius among the 26 circles.
618
+ * **`radii_median`**: Median radius among the 26 circles.
619
+ * **`num_unique_radii`**: The count of unique radius values present in the solution. This can indicate if the solution uses a few distinct circle sizes (e.g., from a predefined set) or a continuous distribution.
620
+
621
+ **Group 2: Boundary Interaction**
622
+ * **`avg_boundary_distance`**: Average minimum distance from each circle's edge to the closest boundary of the unit square. Smaller values indicate better utilization of the boundaries, which is often characteristic of optimal packings.
623
+ * **`min_boundary_distance`**: The smallest minimum distance found across all circles, indicating how close at least one circle's edge is to a boundary. A value close to zero suggests a circle is touching a boundary.
624
+ * **`max_boundary_distance`**: The largest minimum distance found across all circles, indicating the circle whose edge is furthest from any boundary. A large value here might suggest inefficient use of space.
625
+
626
+ **Evaluation Stage & Rationale:**
627
+
628
+ Given that we are at Generation 191 (out of 200), the evolution process is likely in the **CONVERGENCE** or **PLATEAU** stage. At this point, the primary metric (sum of radii) might be saturating.
629
+
630
+ These auxiliary metrics are designed to provide insights beyond just the total sum of radii:
631
+ * **Radii Statistics** help understand the strategy employed by the optimizer regarding circle sizes. If the standard deviation is very low, it might suggest the optimizer is stuck in a local optimum that favors uniform circles, whereas a higher diversity could indicate a more adaptive packing strategy.
632
+ * **Boundary Interaction** metrics reveal how efficiently the solution uses the edges of the unit square. Optimal packings often have many circles touching the boundaries. If `avg_boundary_distance` is high, it could indicate that the packing is too "loose" or centralized, leaving space near the edges unused.
633
+
634
+ These metrics can help identify subtle differences between solutions that have similar primary scores, potentially guiding further exploration or refinement in the late stages of evolution.
635
+
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import numpy as np
4
+ from typing import Dict, Any, Tuple, List
5
+
6
+
7
+ def evaluate_aux(results_dir: str) -> Dict[str, Any]:
8
+ """
9
+ Main entry point for all auxiliary metrics.
10
+
11
+ This function will be automatically called by the evaluation service.
12
+ """
13
+ metrics = {}
14
+ try:
15
+ # Load data once
16
+ data = _load_generation_data(results_dir)
17
+
18
+ if data is None:
19
+ return {"error": "Failed to load generation data from extra.npz"}
20
+
21
+ centers = data["centers"]
22
+ radii = data["radii"]
23
+ n_expected = 26 # Hardcoded from evaluate_ori.py
24
+
25
+ # Check if centers and radii are valid before proceeding
26
+ # If shapes are entirely off, provide placeholder data to avoid further errors
27
+ if centers.shape[0] != n_expected or radii.shape[0] != n_expected:
28
+ metrics["data_shape_error"] = f"Expected N={n_expected}, got centers.shape[0]={centers.shape[0]} and radii.shape[0]={radii.shape[0]}"
29
+ # Provide dummy data for downstream calculations if shapes are incorrect
30
+ # This ensures metrics functions don't crash but return 0/default values
31
+ centers = np.zeros((n_expected, 2))
32
+ radii = np.zeros(n_expected)
33
+
34
+ # Group 1: Violation Severity
35
+ try:
36
+ metrics.update(_calculate_violation_severity(centers, radii))
37
+ except Exception as e:
38
+ metrics["violation_severity_error"] = str(e)
39
+
40
+ # Group 2: Valid Circle Statistics
41
+ try:
42
+ metrics.update(_calculate_valid_circle_stats(centers, radii))
43
+ except Exception as e:
44
+ metrics["valid_circle_stats_error"] = str(e)
45
+
46
+ except Exception as e:
47
+ metrics["evaluate_aux_overall_error"] = str(e)
48
+
49
+ return metrics
50
+
51
+
52
+ def _load_generation_data(results_dir: str):
53
+ """Load common data files (e.g., extra.npz)."""
54
+ extra_file = os.path.join(results_dir, "extra.npz")
55
+ if os.path.exists(extra_file):
56
+ try:
57
+ return np.load(extra_file)
58
+ except Exception as e:
59
+ print(f"Error loading extra.npz: {e}")
60
+ return None
61
+ else:
62
+ print(f"extra.npz not found at {extra_file}")
63
+ return None
64
+
65
+
66
+ def _calculate_violation_severity(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
67
+ """
68
+ Calculate the total magnitude of overlaps and out-of-bounds violations.
69
+ """
70
+ n_expected = 26 # Hardcoded from evaluate_ori.py
71
+ # If the input data is malformed (e.g., from an earlier error handling step),
72
+ # return default violation values. This prevents crashes if `evaluate_aux` passed
73
+ # placeholder data due to initial shape mismatch.
74
+ if centers.shape[0] != n_expected or radii.shape[0] != n_expected:
75
+ # A placeholder for radii should still have n_expected elements
76
+ return {
77
+ "violation_overlap_magnitude_sum": 0.0,
78
+ "violation_out_of_bounds_magnitude_sum": 0.0,
79
+ "violation_radii_negative_count": sum(1 for r in radii if r < 0) if radii.shape[0] == n_expected else n_expected,
80
+ }
81
+
82
+ overlap_magnitude_sum = 0.0
83
+ out_of_bounds_magnitude_sum = 0.0
84
+ atol = 1e-6 # From evaluate_ori.py
85
+
86
+ # Out-of-bounds violations
87
+ for i in range(n_expected):
88
+ x, y = centers[i]
89
+ r = radii[i]
90
+
91
+ # Only consider positive radii for out-of-bounds magnitude calculation
92
+ if r <= 0: continue
93
+
94
+ if x - r < 0:
95
+ out_of_bounds_magnitude_sum += abs(x - r)
96
+ if x + r > 1:
97
+ out_of_bounds_magnitude_sum += abs(x + r - 1)
98
+ if y - r < 0:
99
+ out_of_bounds_magnitude_sum += abs(y - r)
100
+ if y + r > 1:
101
+ out_of_bounds_magnitude_sum += abs(y + r - 1)
102
+
103
+ # Overlap violations
104
+ for i in range(n_expected):
105
+ for j in range(i + 1, n_expected):
106
+ # Only consider positive radii for overlap calculation
107
+ if radii[i] <= 0 or radii[j] <= 0: continue
108
+
109
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
110
+ required_min_dist = radii[i] + radii[j]
111
+ if dist < required_min_dist - atol: # Overlap detected
112
+ overlap_magnitude_sum += (required_min_dist - dist)
113
+
114
+ return {
115
+ "violation_overlap_magnitude_sum": float(overlap_magnitude_sum),
116
+ "violation_out_of_bounds_magnitude_sum": float(out_of_bounds_magnitude_sum),
117
+ "violation_radii_negative_count": sum(1 for r in radii if r < 0),
118
+ }
119
+
120
+
121
+ def _calculate_valid_circle_stats(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
122
+ """
123
+ Calculate statistics for circles that are considered "validly placed"
124
+ (i.e., within bounds and not overlapping other valid circles).
125
+ """
126
+ n_expected = 26 # Hardcoded from evaluate_ori.py
127
+ # If the input data is malformed, return default values.
128
+ if centers.shape[0] != n_expected or radii.shape[0] != n_expected:
129
+ return {
130
+ "valid_circles_count": 0,
131
+ "valid_circles_total_area": 0.0,
132
+ "valid_circles_density": 0.0,
133
+ "valid_circles_avg_radius": 0.0,
134
+ "valid_circles_std_radius": 0.0,
135
+ }
136
+
137
+ atol = 1e-6
138
+
139
+ # Step 1: Identify circles that are individually within bounds and have positive radii
140
+ individually_valid_indices = []
141
+ for i in range(n_expected):
142
+ x, y = centers[i]
143
+ r = radii[i]
144
+
145
+ if r <= 0: # Radii must be positive to be considered valid here
146
+ continue
147
+
148
+ is_outside = (\
149
+ x - r < -atol or x + r > 1 + atol or y - r < -atol or y + r > 1 + atol\
150
+ )
151
+ if not is_outside:
152
+ individually_valid_indices.append(i)
153
+
154
+ # Step 2: Check for overlaps ONLY among the individually valid circles
155
+ # A circle is 'valid' if it is individually valid AND does not overlap with *any* other individually valid circle.
156
+
157
+ is_non_overlapping_in_group = {idx: True for idx in individually_valid_indices}
158
+
159
+ for i_idx_pos in range(len(individually_valid_indices)):
160
+ i = individually_valid_indices[i_idx_pos]
161
+ for j_idx_pos in range(i_idx_pos + 1, len(individually_valid_indices)):\
162
+ j = individually_valid_indices[j_idx_pos]
163
+
164
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
165
+ required_min_dist = radii[i] + radii[j]
166
+
167
+ if dist < required_min_dist - atol: # Overlap detected between two individually valid circles
168
+ is_non_overlapping_in_group[i] = False
169
+ is_non_overlapping_in_group[j] = False
170
+
171
+ final_valid_circles_radii = []
172
+ for idx in individually_valid_indices:
173
+ if is_non_overlapping_in_group[idx]:
174
+ final_valid_circles_radii.append(radii[idx])
175
+
176
+ valid_circles_count = len(final_valid_circles_radii)
177
+
178
+ if valid_circles_count == 0:
179
+ return {
180
+ "valid_circles_count": 0,
181
+ "valid_circles_total_area": 0.0,
182
+ "valid_circles_density": 0.0,
183
+ "valid_circles_avg_radius": 0.0,
184
+ "valid_circles_std_radius": 0.0,
185
+ }
186
+
187
+ final_valid_circles_radii_np = np.array(final_valid_circles_radii)
188
+ total_area = np.sum(np.pi * final_valid_circles_radii_np ** 2)
189
+ unit_square_area = 1.0 # 1x1 unit square
190
+
191
+ return {
192
+ "valid_circles_count": valid_circles_count,
193
+ "valid_circles_total_area": float(total_area),
194
+ "valid_circles_density": float(total_area / unit_square_area),
195
+ "valid_circles_avg_radius": float(np.mean(final_valid_circles_radii_np)),
196
+ "valid_circles_std_radius": float(np.std(final_valid_circles_radii_np)),
197
+ }
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics_old.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import json
4
+ from typing import Dict, Any, Tuple
5
+ import math
6
+
7
+ def evaluate_aux(results_dir: str) -> Dict[str, Any]:
8
+ """
9
+ Main entry point for all auxiliary metrics.
10
+
11
+ This function will be automatically called by the evaluation service.
12
+ """
13
+ metrics = {}
14
+
15
+ try:
16
+ # Load data from extra.npz, which contains centers, radii, reported_sum
17
+ extra_npz_path = os.path.join(results_dir, "extra.npz")
18
+ if not os.path.exists(extra_npz_path):
19
+ metrics["error_extra_npz_missing"] = f"extra.npz not found at {extra_npz_path}"
20
+ metrics["failed_at"] = "extra_npz_load"
21
+ return metrics
22
+
23
+ data = np.load(extra_npz_path)
24
+
25
+ # Ensure we have the expected keys
26
+ if "centers" not in data or "radii" not in data or "reported_sum" not in data:
27
+ metrics["error_extra_npz_corrupt"] = "extra.npz is missing 'centers', 'radii', or 'reported_sum'"
28
+ metrics["failed_at"] = "extra_npz_corrupt"
29
+ data.close() # Close the numpy array file
30
+ return metrics
31
+
32
+ centers = data["centers"]
33
+ radii = data["radii"]
34
+ data.close() # Close the numpy array file to prevent resource warnings
35
+
36
+ # Load metrics.json to get the primary score and validation info
37
+ metrics_json_path = os.path.join(results_dir, "metrics.json")
38
+ if os.path.exists(metrics_json_path):
39
+ with open(metrics_json_path, 'r') as f:
40
+ primary_metrics = json.load(f)
41
+ metrics["primary_combined_score"] = primary_metrics.get("combined_score", 0.0)
42
+
43
+ validation_message = primary_metrics.get("public", {}).get("validation_message")
44
+ if not validation_message:
45
+ validation_message = primary_metrics.get("error", "No specific validation message.")
46
+ metrics["primary_validation_message"] = validation_message
47
+
48
+ metrics["primary_num_circles_reported"] = primary_metrics.get("public", {}).get("num_circles", 0)
49
+ else:
50
+ metrics["primary_combined_score"] = 0.0
51
+ metrics["primary_validation_message"] = "metrics.json not found."
52
+ metrics["primary_num_circles_reported"] = 0
53
+
54
+
55
+ # Group 1: Circle count and basic validation properties
56
+ metrics.update(_calculate_circle_counts_and_basic_validations(centers, radii))
57
+
58
+ # Group X: New auxiliary metrics
59
+ try:
60
+ metrics.update(_calculate_unique_radii_metrics(radii))
61
+ except Exception as e:
62
+ metrics["unique_radii_error"] = str(e)
63
+
64
+ try:
65
+ metrics.update(_calculate_edge_contact_metrics(centers, radii))
66
+ except Exception as e:
67
+ metrics["edge_contact_error"] = str(e)
68
+
69
+ # Call the existing helper functions, passing centers and radii
70
+ # Wrap each in a try-except to ensure robustness
71
+ try:
72
+ metrics.update(_calculate_radii_distribution_metrics_updated(radii))
73
+ except Exception as e:
74
+ metrics["radii_distribution_error"] = str(e)
75
+
76
+ try:
77
+ metrics.update(_calculate_boundary_metrics(centers, radii))
78
+ except Exception as e:
79
+ metrics["boundary_metrics_error"] = str(e)
80
+
81
+ try:
82
+ metrics.update(_calculate_packing_efficiency_metrics(radii))
83
+ except Exception as e:
84
+ metrics["packing_efficiency_error"] = str(e)
85
+
86
+ try:
87
+ metrics.update(_calculate_spatial_distribution_metrics(centers, radii))
88
+ except Exception as e:
89
+ metrics["spatial_distribution_error"] = str(e)
90
+
91
+ try:
92
+ metrics.update(_calculate_central_tendency_metrics(centers))
93
+ except Exception as e:
94
+ metrics["central_tendency_error"] = str(e)
95
+
96
+ try:
97
+ metrics.update(_calculate_contact_metrics(centers, radii))
98
+ except Exception as e:
99
+ metrics["contact_metrics_error"] = str(e)
100
+
101
+ try:
102
+ metrics.update(_calculate_gap_metrics(centers, radii))
103
+ except Exception as e:
104
+ metrics["gap_metrics_error"] = str(e)
105
+
106
+ except Exception as e:
107
+ metrics["error_evaluate_aux"] = str(e)
108
+ metrics["failed_at"] = "top_level_evaluate_aux"
109
+
110
+ return metrics
111
+
112
+ def _calculate_circle_counts_and_basic_validations(centers: np.ndarray, radii: np.ndarray) -> Dict[str, Any]:
113
+ """
114
+ Calculate basic statistics about the number of circles and simple validation checks.
115
+ This provides initial diagnostics if the primary score is zero.
116
+ """
117
+ result = {
118
+ "actual_num_circles_attempted": int(centers.shape[0]),
119
+ "expected_num_circles": 26, # Hardcoded from evaluate_ori.py
120
+ }
121
+ # Add a flag for correct number of circles, useful for initial debugging
122
+ result["is_num_circles_count_correct"] = (centers.shape[0] == result["expected_num_circles"])
123
+
124
+ if radii.size > 0:
125
+ result["num_radii_positive"] = int(np.sum(radii > 0))
126
+ result["num_radii_zero"] = int(np.sum(radii == 0))
127
+ result["num_radii_negative"] = int(np.sum(radii < 0))
128
+ result["has_negative_radii"] = bool(np.any(radii < 0))
129
+ result["has_zero_radii"] = bool(np.any(radii == 0))
130
+ else:
131
+ result["num_radii_positive"] = 0
132
+ result["num_radii_zero"] = 0
133
+ result["num_radii_negative"] = 0
134
+ result["has_negative_radii"] = True # If no radii, it's effectively "invalid"
135
+ result["has_zero_radii"] = True # If no radii, it's effectively "invalid"
136
+
137
+ return result
138
+
139
+ def _calculate_radii_distribution_metrics_updated(radii: np.ndarray) -> Dict[str, float]:
140
+ """
141
+ Calculate metrics related to the distribution of circle radii.
142
+ - avg_radius: Mean radius of all circles.
143
+ - std_dev_radius: Standard deviation of radii.
144
+ - std_dev_radius_normalized: Standard deviation of radii divided by the average radius.
145
+ Higher values indicate more diverse circle sizes.
146
+ - min_radius, max_radius, median_radius: Min, max, and median of radii.
147
+ """
148
+ metrics = {}
149
+ if len(radii) == 0:
150
+ return {
151
+ "avg_radius": 0.0,
152
+ "std_dev_radius": 0.0,
153
+ "std_dev_radius_normalized": 0.0,
154
+ "median_radius": 0.0,
155
+ "min_radius": 0.0,
156
+ "max_radius": 0.0
157
+ }
158
+
159
+ avg_radius = np.mean(radii)
160
+ std_dev_radius = np.std(radii)
161
+ median_radius = np.median(radii)
162
+ min_radius = np.min(radii)
163
+ max_radius = np.max(radii)
164
+
165
+ metrics["avg_radius"] = float(avg_radius)
166
+ metrics["std_dev_radius"] = float(std_dev_radius)
167
+ metrics["median_radius"] = float(median_radius)
168
+ metrics["min_radius"] = float(min_radius)
169
+ metrics["max_radius"] = float(max_radius)
170
+
171
+ if abs(avg_radius) > 1e-9: # Avoid division by zero for very small or zero average radii
172
+ metrics["std_dev_radius_normalized"] = float(std_dev_radius / avg_radius)
173
+ else:
174
+ metrics["std_dev_radius_normalized"] = 0.0 # All radii are ~zero, so std dev is also zero or undefined in a meaningful way
175
+
176
+ return metrics
177
+
178
+ def _calculate_boundary_metrics(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
179
+ """
180
+ Calculate metrics related to circles' proximity to boundaries.
181
+ - min_dist_to_boundary_avg: Average minimum distance from each circle's edge to the closest unit square boundary.
182
+ Lower values suggest tighter packing against the edges.
183
+ - overall_min_dist_to_boundary: The absolute minimum distance any circle edge is from any boundary.
184
+ """
185
+ metrics = {}
186
+
187
+ if len(centers) == 0:
188
+ return {"min_dist_to_boundary_avg": 0.0, "overall_min_dist_to_boundary": 0.0}
189
+
190
+ min_distances = []
191
+ for i in range(len(centers)):
192
+ x, y = centers[i]
193
+ r = radii[i]
194
+
195
+ # Distances from circle edge to the four boundaries of the unit square [0,1]x[0,1]
196
+ # A positive value means the circle is inside the boundary.
197
+ dist_left = x - r
198
+ dist_right = 1 - (x + r)
199
+ dist_bottom = y - r
200
+ dist_top = 1 - (y + r)
201
+
202
+ # We want the distance from the circle's edge to the closest unit square boundary.
203
+ # This is the minimum of these four values for each circle.
204
+ min_dist_circle_to_boundary = min(dist_left, dist_right, dist_bottom, dist_top)
205
+ min_distances.append(min_dist_circle_to_boundary)
206
+
207
+ metrics["min_dist_to_boundary_avg"] = float(np.mean(min_distances))
208
+ if min_distances:
209
+ metrics["overall_min_dist_to_boundary"] = float(np.min(min_distances))
210
+ else:
211
+ metrics["overall_min_dist_to_boundary"] = 0.0 # Should not happen if circles exist
212
+
213
+ return metrics
214
+
215
+ def _calculate_packing_efficiency_metrics(radii: np.ndarray) -> Dict[str, float]:
216
+ """
217
+ Calculate metrics related to the overall packing efficiency.
218
+ - packing_efficiency_area_ratio: Ratio of the total area covered by circles to the area of the unit square (1.0).
219
+ Higher values indicate better utilization of space.
220
+ - total_packed_area: Sum of the areas of all circles.
221
+ """
222
+ metrics = {}
223
+
224
+ if len(radii) == 0:
225
+ return {"packing_efficiency_area_ratio": 0.0, "total_packed_area": 0.0}
226
+
227
+ # Only consider positive radii for area calculation
228
+ positive_radii = radii[radii > 0]
229
+ if len(positive_radii) == 0:
230
+ return {"packing_efficiency_area_ratio": 0.0, "total_packed_area": 0.0}
231
+
232
+ total_circle_area = np.sum(np.pi * positive_radii**2)
233
+ unit_square_area = 1.0 # Unit square has area 1x1 = 1
234
+
235
+ metrics["packing_efficiency_area_ratio"] = float(total_circle_area / unit_square_area)
236
+ metrics["total_packed_area"] = float(total_circle_area)
237
+
238
+ return metrics
239
+
240
+
241
+ def _calculate_spatial_distribution_metrics(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
242
+ """
243
+ Calculate metrics related to the spatial distribution uniformity of circles.
244
+ - local_packing_density_std_dev: Standard deviation of packing densities across a grid.
245
+ Lower values indicate a more uniform distribution of circles.
246
+ """
247
+ metrics = {}
248
+ if len(centers) == 0:
249
+ return {"local_packing_density_std_dev": 0.0}
250
+
251
+ grid_size = 5 # e.g., 5x5 grid
252
+ cell_size = 1.0 / grid_size
253
+ local_densities = np.zeros(grid_size * grid_size)
254
+
255
+ for i in range(len(centers)):
256
+ x, y = centers[i]
257
+ r = radii[i]
258
+
259
+ # Find the cell index for the center of the circle
260
+ col_idx = int(x / cell_size)
261
+ row_idx = int(y / cell_size)
262
+
263
+ # Clamp indices to grid boundaries (important for circles near edges)
264
+ col_idx = max(0, min(grid_size - 1, col_idx))
265
+ row_idx = max(0, min(grid_size - 1, row_idx))
266
+
267
+ cell_flat_idx = row_idx * grid_size + col_idx
268
+
269
+ # Add circle's area to the cell's density (area covered)
270
+ # Only add area for positive radii
271
+ if r > 0:
272
+ local_densities[cell_flat_idx] += np.pi * r**2
273
+
274
+ # Calculate standard deviation of local densities
275
+ metrics["local_packing_density_std_dev"] = float(np.std(local_densities))
276
+
277
+ return metrics
278
+
279
+
280
+ def _calculate_central_tendency_metrics(centers: np.ndarray) -> Dict[str, float]:
281
+ """
282
+ Calculate metrics related to the central tendency of circle placements.
283
+ - avg_dist_from_square_center: Average Euclidean distance of each circle's center from the center of the unit square (0.5, 0.5).
284
+ Lower values might suggest more centralized packing.
285
+ - std_dev_dist_from_square_center: Standard deviation of these distances.
286
+ A lower std dev indicates more uniform distribution around the average distance.
287
+ """
288
+ spatial_metrics = {}
289
+ if centers.shape[0] == 0:
290
+ return {
291
+ "avg_dist_from_square_center": 0.0,
292
+ "std_dev_dist_from_square_center": 0.0
293
+ }
294
+
295
+ # Center of the unit square is (0.5, 0.5)
296
+ center_of_square = np.array([0.5, 0.5])
297
+ distances_from_center = np.linalg.norm(centers - center_of_square, axis=1)
298
+
299
+ spatial_metrics["avg_dist_from_square_center"] = float(np.mean(distances_from_center))
300
+ spatial_metrics["std_dev_dist_from_square_center"] = float(np.std(distances_from_center))
301
+
302
+ return spatial_metrics
303
+
304
+ def _calculate_contact_metrics(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
305
+ """
306
+ Calculate metrics related to the number of circles touching each other.
307
+ - avg_contact_count: Average number of contacts per circle.
308
+ - max_contact_count: Maximum number of contacts for any single circle.
309
+ """
310
+ metrics = {}
311
+
312
+ num_circles = len(centers)
313
+ if num_circles < 2:
314
+ return {
315
+ "avg_contact_count": 0.0,
316
+ "max_contact_count": 0.0
317
+ }
318
+
319
+ contact_counts = np.zeros(num_circles)
320
+ touch_tolerance = 1e-4 # How close they need to be to be considered 'touching'
321
+
322
+ for i in range(num_circles):
323
+ for j in range(i + 1, num_circles):
324
+ dist_centers = np.linalg.norm(centers[i] - centers[j])
325
+ sum_radii = radii[i] + radii[j]
326
+
327
+ if abs(dist_centers - sum_radii) < touch_tolerance:
328
+ contact_counts[i] += 1
329
+ contact_counts[j] += 1
330
+
331
+ metrics["avg_contact_count"] = float(np.mean(contact_counts))
332
+ metrics["max_contact_count"] = float(np.max(contact_counts))
333
+
334
+ return metrics
335
+
336
+ def _calculate_gap_metrics(centers: np.ndarray, radii: np.ndarray) -> Dict[str, float]:
337
+ """
338
+ Calculate metrics related to the gaps between non-touching circles and overall pairwise separation.
339
+ - min_gap_between_non_touching_circles: The smallest positive gap found between any two circles that are not considered touching.
340
+ Smaller values indicate tighter packing in general, even for non-touching pairs.
341
+ - min_pairwise_separation_avg: Average of (distance between centers - sum of radii) for all pairs.
342
+ Should be >= 0 for valid solutions. Averages the "tightness".
343
+ - min_pairwise_separation_min: Minimum of (distance between centers - sum of radii) for all pairs.
344
+ The smallest gap or difference from touching. Closer to 0 indicates tighter packing.
345
+ """
346
+ metrics = {}
347
+
348
+ num_circles = len(centers)
349
+ if num_circles < 2:
350
+ return {
351
+ "min_gap_between_non_touching_circles": 0.0,
352
+ "min_pairwise_separation_avg": 0.0,
353
+ "min_pairwise_separation_min": 0.0
354
+ }
355
+
356
+ min_gap_non_touching = float('inf')
357
+ pairwise_separations = []
358
+ touch_tolerance = 1e-4 # Same tolerance as for 'touching' in contact metrics
359
+
360
+ for i in range(num_circles):
361
+ for j in range(i + 1, num_circles):
362
+ dist_centers = np.linalg.norm(centers[i] - centers[j])
363
+ sum_radii = radii[i] + radii[j]
364
+
365
+ separation = dist_centers - sum_radii
366
+ pairwise_separations.append(separation)
367
+
368
+ # Only consider positive gaps for non-touching circles
369
+ if separation > touch_tolerance:
370
+ if separation < min_gap_non_touching:
371
+ min_gap_non_touching = separation
372
+
373
+
374
+ if min_gap_non_touching == float('inf'): # No non-touching circles or only touching ones
375
+ metrics["min_gap_between_non_touching_circles"] = 0.0
376
+ else:
377
+ metrics["min_gap_between_non_touching_circles"] = float(min_gap_non_touching)
378
+
379
+ # Calculate overall pairwise separation metrics
380
+ if pairwise_separations:
381
+ # Filter for non-negative separations (to exclude potential small overlaps if atol is used in primary evaluator)
382
+ valid_pairwise_separations = [s for s in pairwise_separations if s >= -1e-7]
383
+ if valid_pairwise_separations:
384
+ metrics["min_pairwise_separation_avg"] = float(np.mean(valid_pairwise_separations))
385
+ metrics["min_pairwise_separation_min"] = float(np.min(valid_pairwise_separations))
386
+ else: # All separations are negative (overlaps)
387
+ metrics["min_pairwise_separation_avg"] = -1.0 # Indicating pervasive overlap
388
+ metrics["min_pairwise_separation_min"] = -1.0
389
+ else:
390
+ metrics["min_pairwise_separation_avg"] = 0.0
391
+ metrics["min_pairwise_separation_min"] = 0.0
392
+
393
+ return metrics
394
+
395
+ def _calculate_unique_radii_metrics(radii: np.ndarray) -> Dict[str, Any]:
396
+ """
397
+ Calculate metrics related to the uniqueness of radii values.
398
+ - num_unique_radii: Count of distinct radius values.
399
+ """
400
+ metrics = {}
401
+ if len(radii) == 0:
402
+ return {"num_unique_radii": 0}
403
+
404
+ # Use a small tolerance for floating point comparison
405
+ # Group radii that are very close to each other
406
+ unique_radii_approx = []
407
+ radii_sorted = np.sort(radii)
408
+ if len(radii_sorted) > 0:
409
+ unique_radii_approx.append(radii_sorted[0])
410
+ for r in radii_sorted[1:]:
411
+ if not any(np.isclose(r, ur, atol=1e-6) for ur in unique_radii_approx):
412
+ unique_radii_approx.append(r)
413
+
414
+ metrics["num_unique_radii"] = len(unique_radii_approx)
415
+
416
+ return metrics
417
+
418
+ def _calculate_edge_contact_metrics(centers: np.ndarray, radii: np.ndarray) -> Dict[str, Any]:
419
+ """
420
+ Calculate metrics related to circles touching the boundaries of the unit square.
421
+ - num_circles_touching_boundary: Count of circles whose edge is within a tolerance of any boundary.
422
+ """
423
+ metrics = {}
424
+ if len(centers) == 0:
425
+ return {"num_circles_touching_boundary": 0}
426
+
427
+ touch_tolerance = 1e-6 # How close to the boundary to be considered 'touching'
428
+ num_touching = 0
429
+
430
+ for i in range(len(centers)):
431
+ x, y = centers[i]
432
+ r = radii[i]
433
+
434
+ # Check if any part of the circle is very close to a boundary
435
+ touching_left = np.isclose(x - r, 0.0, atol=touch_tolerance)
436
+ touching_right = np.isclose(x + r, 1.0, atol=touch_tolerance)
437
+ touching_bottom = np.isclose(y - r, 0.0, atol=touch_tolerance)
438
+ touching_top = np.isclose(y + r, 1.0, atol=touch_tolerance)
439
+
440
+ if touching_left or touching_right or touching_bottom or touching_top:
441
+ num_touching += 1
442
+
443
+ metrics["num_circles_touching_boundary"] = num_touching
444
+
445
+ return metrics
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/service_state.json ADDED
@@ -0,0 +1,608 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generation_history": [
3
+ {
4
+ "generation": 100,
5
+ "primary_score": 2.627802785716175,
6
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/results",
7
+ "timestamp": 1770368264.4355226
8
+ },
9
+ {
10
+ "generation": 101,
11
+ "primary_score": 2.630059586364479,
12
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/results",
13
+ "timestamp": 1770368420.8388004
14
+ },
15
+ {
16
+ "generation": 102,
17
+ "primary_score": 2.6275651712984684,
18
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/results",
19
+ "timestamp": 1770368475.2202969
20
+ },
21
+ {
22
+ "generation": 103,
23
+ "primary_score": 2.623328072002969,
24
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/results",
25
+ "timestamp": 1770368532.681484
26
+ },
27
+ {
28
+ "generation": 104,
29
+ "primary_score": 2.623275635820109,
30
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/results",
31
+ "timestamp": 1770368562.7988071
32
+ },
33
+ {
34
+ "generation": 105,
35
+ "primary_score": 2.627644194591019,
36
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/results",
37
+ "timestamp": 1770368644.3605745
38
+ },
39
+ {
40
+ "generation": 106,
41
+ "primary_score": 0.0,
42
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/results",
43
+ "timestamp": 1770368691.5511065
44
+ },
45
+ {
46
+ "generation": 107,
47
+ "primary_score": 2.624779102568135,
48
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/results",
49
+ "timestamp": 1770368784.4540813
50
+ },
51
+ {
52
+ "generation": 108,
53
+ "primary_score": 2.6275651712984622,
54
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/results",
55
+ "timestamp": 1770368846.7950912
56
+ },
57
+ {
58
+ "generation": 109,
59
+ "primary_score": 2.6253961352430393,
60
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_109/results",
61
+ "timestamp": 1770368895.4643133
62
+ },
63
+ {
64
+ "generation": 110,
65
+ "primary_score": 0.0,
66
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_110/results",
67
+ "timestamp": 1770369012.6630769
68
+ },
69
+ {
70
+ "generation": 111,
71
+ "primary_score": 2.6304385384917235,
72
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_111/results",
73
+ "timestamp": 1770369105.2047956
74
+ },
75
+ {
76
+ "generation": 112,
77
+ "primary_score": 2.6276441945910727,
78
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_112/results",
79
+ "timestamp": 1770369200.876738
80
+ },
81
+ {
82
+ "generation": 113,
83
+ "primary_score": 2.627644194591002,
84
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_113/results",
85
+ "timestamp": 1770369285.9413383
86
+ },
87
+ {
88
+ "generation": 114,
89
+ "primary_score": 2.62795530298166,
90
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_114/results",
91
+ "timestamp": 1770369361.6175752
92
+ },
93
+ {
94
+ "generation": 115,
95
+ "primary_score": 2.6279553029818175,
96
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_115/results",
97
+ "timestamp": 1770369412.4848516
98
+ },
99
+ {
100
+ "generation": 116,
101
+ "primary_score": 2.627644194591079,
102
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_116/results",
103
+ "timestamp": 1770369586.0493877
104
+ },
105
+ {
106
+ "generation": 117,
107
+ "primary_score": 2.6275651712985044,
108
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_117/results",
109
+ "timestamp": 1770369677.695366
110
+ },
111
+ {
112
+ "generation": 118,
113
+ "primary_score": 2.630438538491813,
114
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_118/results",
115
+ "timestamp": 1770369748.085098
116
+ },
117
+ {
118
+ "generation": 119,
119
+ "primary_score": 2.628190433300157,
120
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_119/results",
121
+ "timestamp": 1770369909.7375238
122
+ },
123
+ {
124
+ "generation": 120,
125
+ "primary_score": 2.6303685368421084,
126
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_120/results",
127
+ "timestamp": 1770370020.2653842
128
+ },
129
+ {
130
+ "generation": 121,
131
+ "primary_score": 2.6304385384917572,
132
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_121/results",
133
+ "timestamp": 1770370092.1136258
134
+ },
135
+ {
136
+ "generation": 122,
137
+ "primary_score": 2.626930181657929,
138
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_122/results",
139
+ "timestamp": 1770370215.946814
140
+ },
141
+ {
142
+ "generation": 123,
143
+ "primary_score": 2.630438538491878,
144
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_123/results",
145
+ "timestamp": 1770370275.8721023
146
+ },
147
+ {
148
+ "generation": 124,
149
+ "primary_score": 2.6304385384916955,
150
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_124/results",
151
+ "timestamp": 1770370334.4639869
152
+ },
153
+ {
154
+ "generation": 125,
155
+ "primary_score": 2.6276441945909474,
156
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_125/results",
157
+ "timestamp": 1770370413.8604546
158
+ },
159
+ {
160
+ "generation": 127,
161
+ "primary_score": 2.63043853849197,
162
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_127/results",
163
+ "timestamp": 1770370658.6300313
164
+ },
165
+ {
166
+ "generation": 126,
167
+ "primary_score": 2.630086122839868,
168
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_126/results",
169
+ "timestamp": 1770370665.5616004
170
+ },
171
+ {
172
+ "generation": 128,
173
+ "primary_score": 2.6276441945910145,
174
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_128/results",
175
+ "timestamp": 1770370704.6136477
176
+ },
177
+ {
178
+ "generation": 129,
179
+ "primary_score": 2.6305882995585836,
180
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_129/results",
181
+ "timestamp": 1770370740.4867632
182
+ },
183
+ {
184
+ "generation": 130,
185
+ "primary_score": 2.6304385384919327,
186
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_130/results",
187
+ "timestamp": 1770370831.5151834
188
+ },
189
+ {
190
+ "generation": 131,
191
+ "primary_score": 2.630438538491973,
192
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_131/results",
193
+ "timestamp": 1770370967.0375316
194
+ },
195
+ {
196
+ "generation": 132,
197
+ "primary_score": 2.6276441945909843,
198
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_132/results",
199
+ "timestamp": 1770371001.8830607
200
+ },
201
+ {
202
+ "generation": 133,
203
+ "primary_score": 2.630438541160854,
204
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_133/results",
205
+ "timestamp": 1770371321.7284298
206
+ },
207
+ {
208
+ "generation": 134,
209
+ "primary_score": 2.610171496508795,
210
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_134/results",
211
+ "timestamp": 1770371447.814107
212
+ },
213
+ {
214
+ "generation": 135,
215
+ "primary_score": 2.546718418606294,
216
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_135/results",
217
+ "timestamp": 1770371500.621022
218
+ },
219
+ {
220
+ "generation": 136,
221
+ "primary_score": 2.6307880455050396,
222
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_136/results",
223
+ "timestamp": 1770371601.3681126
224
+ },
225
+ {
226
+ "generation": 137,
227
+ "primary_score": 2.630438538493498,
228
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_137/results",
229
+ "timestamp": 1770371793.2958894
230
+ },
231
+ {
232
+ "generation": 138,
233
+ "primary_score": 2.627565171298509,
234
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_138/results",
235
+ "timestamp": 1770371845.3592117
236
+ },
237
+ {
238
+ "generation": 139,
239
+ "primary_score": 2.627644194591326,
240
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_139/results",
241
+ "timestamp": 1770371954.1052127
242
+ },
243
+ {
244
+ "generation": 140,
245
+ "primary_score": 2.627565171298538,
246
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_140/results",
247
+ "timestamp": 1770371986.07799
248
+ },
249
+ {
250
+ "generation": 141,
251
+ "primary_score": 2.630438538491825,
252
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_141/results",
253
+ "timestamp": 1770372166.244058
254
+ },
255
+ {
256
+ "generation": 142,
257
+ "primary_score": 2.630438538491822,
258
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_142/results",
259
+ "timestamp": 1770372318.9203444
260
+ },
261
+ {
262
+ "generation": 143,
263
+ "primary_score": 2.630956770495767,
264
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_143/results",
265
+ "timestamp": 1770372629.53698
266
+ },
267
+ {
268
+ "generation": 147,
269
+ "primary_score": 0.0,
270
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_147/results",
271
+ "timestamp": 1770372737.9324698
272
+ },
273
+ {
274
+ "generation": 146,
275
+ "primary_score": 2.6304385384916906,
276
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_146/results",
277
+ "timestamp": 1770372784.632734
278
+ },
279
+ {
280
+ "generation": 145,
281
+ "primary_score": 2.6304385384918474,
282
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_145/results",
283
+ "timestamp": 1770372836.7061484
284
+ },
285
+ {
286
+ "generation": 144,
287
+ "primary_score": 2.618699273378392,
288
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_144/results",
289
+ "timestamp": 1770372854.5265253
290
+ },
291
+ {
292
+ "generation": 149,
293
+ "primary_score": 0.0,
294
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_149/results",
295
+ "timestamp": 1770372873.9997818
296
+ },
297
+ {
298
+ "generation": 150,
299
+ "primary_score": 2.630438538491793,
300
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_150/results",
301
+ "timestamp": 1770373064.8933883
302
+ },
303
+ {
304
+ "generation": 151,
305
+ "primary_score": 2.630438538492031,
306
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_151/results",
307
+ "timestamp": 1770373142.0564628
308
+ },
309
+ {
310
+ "generation": 152,
311
+ "primary_score": 2.6304385384933773,
312
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_152/results",
313
+ "timestamp": 1770373279.2682846
314
+ },
315
+ {
316
+ "generation": 153,
317
+ "primary_score": 2.6309722676543466,
318
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_153/results",
319
+ "timestamp": 1770373398.3440638
320
+ },
321
+ {
322
+ "generation": 154,
323
+ "primary_score": 2.630438538491778,
324
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_154/results",
325
+ "timestamp": 1770373554.9796312
326
+ },
327
+ {
328
+ "generation": 155,
329
+ "primary_score": 2.6309722676529743,
330
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_155/results",
331
+ "timestamp": 1770373619.8349507
332
+ },
333
+ {
334
+ "generation": 156,
335
+ "primary_score": 2.6309722676541765,
336
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_156/results",
337
+ "timestamp": 1770373648.742567
338
+ },
339
+ {
340
+ "generation": 157,
341
+ "primary_score": 2.6307297079879097,
342
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_157/results",
343
+ "timestamp": 1770373769.6128163
344
+ },
345
+ {
346
+ "generation": 148,
347
+ "primary_score": 2.6247583060090878,
348
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_148/results",
349
+ "timestamp": 1770373887.1183414
350
+ },
351
+ {
352
+ "generation": 158,
353
+ "primary_score": 2.63043853849208,
354
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_158/results",
355
+ "timestamp": 1770373919.4995773
356
+ },
357
+ {
358
+ "generation": 159,
359
+ "primary_score": 2.630956770495728,
360
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_159/results",
361
+ "timestamp": 1770373930.5367246
362
+ },
363
+ {
364
+ "generation": 160,
365
+ "primary_score": 2.630438538492033,
366
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_160/results",
367
+ "timestamp": 1770374019.0576909
368
+ },
369
+ {
370
+ "generation": 162,
371
+ "primary_score": 2.6304385384928324,
372
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_162/results",
373
+ "timestamp": 1770374194.9657853
374
+ },
375
+ {
376
+ "generation": 161,
377
+ "primary_score": 2.627644194592924,
378
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_161/results",
379
+ "timestamp": 1770374242.4285102
380
+ },
381
+ {
382
+ "generation": 163,
383
+ "primary_score": 2.6304385384919717,
384
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_163/results",
385
+ "timestamp": 1770374568.6636136
386
+ },
387
+ {
388
+ "generation": 164,
389
+ "primary_score": 2.6304385384918794,
390
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_164/results",
391
+ "timestamp": 1770374859.4440196
392
+ },
393
+ {
394
+ "generation": 165,
395
+ "primary_score": 0.0,
396
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_165/results",
397
+ "timestamp": 1770374899.6456108
398
+ },
399
+ {
400
+ "generation": 166,
401
+ "primary_score": 2.6304385384917084,
402
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_166/results",
403
+ "timestamp": 1770375031.8308184
404
+ },
405
+ {
406
+ "generation": 169,
407
+ "primary_score": 0.0,
408
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_169/results",
409
+ "timestamp": 1770375201.07436
410
+ },
411
+ {
412
+ "generation": 168,
413
+ "primary_score": 2.630438538491773,
414
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_168/results",
415
+ "timestamp": 1770375234.6721294
416
+ },
417
+ {
418
+ "generation": 171,
419
+ "primary_score": 2.6304385384916213,
420
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_171/results",
421
+ "timestamp": 1770375893.9940681
422
+ },
423
+ {
424
+ "generation": 172,
425
+ "primary_score": 2.630438538491881,
426
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_172/results",
427
+ "timestamp": 1770375935.559544
428
+ },
429
+ {
430
+ "generation": 173,
431
+ "primary_score": 2.6304385384919726,
432
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_173/results",
433
+ "timestamp": 1770376646.7313297
434
+ },
435
+ {
436
+ "generation": 174,
437
+ "primary_score": 2.6313498236524246,
438
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_174/results",
439
+ "timestamp": 1770377136.3693814
440
+ },
441
+ {
442
+ "generation": 167,
443
+ "primary_score": 2.630972267663472,
444
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_167/results",
445
+ "timestamp": 1770377152.4078481
446
+ },
447
+ {
448
+ "generation": 175,
449
+ "primary_score": 2.6304385384919224,
450
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_175/results",
451
+ "timestamp": 1770377182.1636982
452
+ },
453
+ {
454
+ "generation": 178,
455
+ "primary_score": 0.0,
456
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_178/results",
457
+ "timestamp": 1770377375.7531352
458
+ },
459
+ {
460
+ "generation": 176,
461
+ "primary_score": 2.6304385384944418,
462
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_176/results",
463
+ "timestamp": 1770377632.595092
464
+ },
465
+ {
466
+ "generation": 177,
467
+ "primary_score": 2.6304385384919007,
468
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_177/results",
469
+ "timestamp": 1770377686.0090642
470
+ },
471
+ {
472
+ "generation": 179,
473
+ "primary_score": 2.6304385384918825,
474
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_179/results",
475
+ "timestamp": 1770377794.781375
476
+ },
477
+ {
478
+ "generation": 181,
479
+ "primary_score": 0.0,
480
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_181/results",
481
+ "timestamp": 1770377851.4961154
482
+ },
483
+ {
484
+ "generation": 180,
485
+ "primary_score": 2.6307131975278724,
486
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_180/results",
487
+ "timestamp": 1770378129.779276
488
+ },
489
+ {
490
+ "generation": 184,
491
+ "primary_score": 0.0,
492
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_184/results",
493
+ "timestamp": 1770378180.9694002
494
+ },
495
+ {
496
+ "generation": 182,
497
+ "primary_score": 2.6312861332806503,
498
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_182/results",
499
+ "timestamp": 1770378244.6418588
500
+ },
501
+ {
502
+ "generation": 183,
503
+ "primary_score": 2.631349823652531,
504
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_183/results",
505
+ "timestamp": 1770378456.571181
506
+ },
507
+ {
508
+ "generation": 185,
509
+ "primary_score": 2.630972267653296,
510
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_185/results",
511
+ "timestamp": 1770378687.1381004
512
+ },
513
+ {
514
+ "generation": 186,
515
+ "primary_score": 2.6304385384917306,
516
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_186/results",
517
+ "timestamp": 1770378762.222908
518
+ },
519
+ {
520
+ "generation": 187,
521
+ "primary_score": 2.630956770495924,
522
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_187/results",
523
+ "timestamp": 1770379183.46851
524
+ },
525
+ {
526
+ "generation": 189,
527
+ "primary_score": 2.6304385384934723,
528
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_189/results",
529
+ "timestamp": 1770379393.5039442
530
+ },
531
+ {
532
+ "generation": 188,
533
+ "primary_score": 0.0,
534
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_188/results",
535
+ "timestamp": 1770379655.7670033
536
+ },
537
+ {
538
+ "generation": 190,
539
+ "primary_score": 2.6309722676531266,
540
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_190/results",
541
+ "timestamp": 1770379767.8907144
542
+ },
543
+ {
544
+ "generation": 191,
545
+ "primary_score": 2.630727093852131,
546
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_191/results",
547
+ "timestamp": 1770379977.1524208
548
+ },
549
+ {
550
+ "generation": 193,
551
+ "primary_score": 0.0,
552
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_193/results",
553
+ "timestamp": 1770380088.646061
554
+ },
555
+ {
556
+ "generation": 194,
557
+ "primary_score": 2.630438538491753,
558
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_194/results",
559
+ "timestamp": 1770380753.8565228
560
+ },
561
+ {
562
+ "generation": 195,
563
+ "primary_score": 2.6359830849177337,
564
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_195/results",
565
+ "timestamp": 1770380987.2627246
566
+ },
567
+ {
568
+ "generation": 196,
569
+ "primary_score": 0.0,
570
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_196/results",
571
+ "timestamp": 1770381066.0764062
572
+ },
573
+ {
574
+ "generation": 198,
575
+ "primary_score": 2.6308312675237584,
576
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_198/results",
577
+ "timestamp": 1770381677.519458
578
+ },
579
+ {
580
+ "generation": 170,
581
+ "primary_score": 1.7282363218086794,
582
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_170/results",
583
+ "timestamp": 1770381723.5313928
584
+ },
585
+ {
586
+ "generation": 197,
587
+ "primary_score": 2.630438538492146,
588
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_197/results",
589
+ "timestamp": 1770381736.0612416
590
+ },
591
+ {
592
+ "generation": 192,
593
+ "primary_score": 2.6342924025205767,
594
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_192/results",
595
+ "timestamp": 1770381746.0184104
596
+ },
597
+ {
598
+ "generation": 199,
599
+ "primary_score": 2.630831267524132,
600
+ "results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_199/results",
601
+ "timestamp": 1770381860.7550879
602
+ }
603
+ ],
604
+ "last_agent_trigger_gen": 193,
605
+ "total_notifications": 199,
606
+ "total_agent_runs": 35,
607
+ "last_update": 1770396676.1481898
608
+ }
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_0/main.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ """Constructor-based circle packing for n=26 circles"""
3
+
4
+ import numpy as np
5
+
6
+
7
+ def construct_packing():
8
+ """
9
+ Construct a specific arrangement of 26 circles in a unit square
10
+ that attempts to maximize the sum of their radii.
11
+
12
+ Returns:
13
+ Tuple of (centers, radii, sum_of_radii)
14
+ centers: np.array of shape (26, 2) with (x, y) coordinates
15
+ radii: np.array of shape (26) with radius of each circle
16
+ sum_of_radii: Sum of all radii
17
+ """
18
+ # Initialize arrays for 26 circles
19
+ n = 26
20
+ centers = np.zeros((n, 2))
21
+
22
+ # Place circles in a structured pattern
23
+ # This is a simple pattern - evolution will improve this
24
+
25
+ # First, place a large circle in the center
26
+ centers[0] = [0.5, 0.5]
27
+
28
+ # Place 8 circles around it in a ring
29
+ for i in range(8):
30
+ angle = 2 * np.pi * i / 8
31
+ centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
32
+
33
+ # Place 16 more circles in an outer ring
34
+ for i in range(16):
35
+ angle = 2 * np.pi * i / 16
36
+ centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
37
+
38
+ # Additional positioning adjustment to make sure all circles
39
+ # are inside the square and don't overlap
40
+ # Clip to ensure everything is inside the unit square
41
+ centers = np.clip(centers, 0.01, 0.99)
42
+
43
+ # Compute maximum valid radii for this configuration
44
+ radii = compute_max_radii(centers)
45
+ return centers, radii
46
+
47
+
48
+ def compute_max_radii(centers):
49
+ """
50
+ Compute the maximum possible radii for each circle position
51
+ such that they don't overlap and stay within the unit square.
52
+
53
+ Args:
54
+ centers: np.array of shape (n, 2) with (x, y) coordinates
55
+
56
+ Returns:
57
+ np.array of shape (n) with radius of each circle
58
+ """
59
+ n = centers.shape[0]
60
+ radii = np.ones(n)
61
+
62
+ # First, limit by distance to square borders
63
+ for i in range(n):
64
+ x, y = centers[i]
65
+ # Distance to borders
66
+ radii[i] = min(x, y, 1 - x, 1 - y)
67
+
68
+ # Then, limit by distance to other circles
69
+ # Each pair of circles with centers at distance d can have
70
+ # sum of radii at most d to avoid overlap
71
+ for i in range(n):
72
+ for j in range(i + 1, n):
73
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
74
+
75
+ # If current radii would cause overlap
76
+ if radii[i] + radii[j] > dist:
77
+ # Scale both radii proportionally
78
+ scale = dist / (radii[i] + radii[j])
79
+ radii[i] *= scale
80
+ radii[j] *= scale
81
+
82
+ return radii
83
+
84
+
85
+ # EVOLVE-BLOCK-END
86
+
87
+
88
+ # This part remains fixed (not evolved)
89
+ def run_packing():
90
+ """Run the circle packing constructor for n=26"""
91
+ centers, radii = construct_packing()
92
+ # Calculate the sum of radii
93
+ sum_radii = np.sum(radii)
94
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/edit.diff ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,94 +1,108 @@
4
+ # EVOLVE-BLOCK-START
5
+ """Constructor-based circle packing for n=26 circles"""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def construct_packing():
11
+ """
12
+ Construct a specific arrangement of 26 circles in a unit square
13
+ that attempts to maximize the sum of their radii.
14
+
15
+ Returns:
16
+ Tuple of (centers, radii, sum_of_radii)
17
+ centers: np.array of shape (26, 2) with (x, y) coordinates
18
+ radii: np.array of shape (26) with radius of each circle
19
+ sum_of_radii: Sum of all radii
20
+ """
21
+ # Initialize arrays for 26 circles
22
+ n = 26
23
+ centers = np.zeros((n, 2))
24
+
25
+ # Place circles in a structured pattern
26
+ # This is a simple pattern - evolution will improve this
27
+
28
+ - # First, place a large circle in the center
29
+ - centers[0] = [0.5, 0.5]
30
+ + n = 26
31
+ + centers = np.zeros((n, 2))
32
+ + idx = 0
33
+
34
+ - # Place 8 circles around it in a ring
35
+ - for i in range(8):
36
+ - angle = 2 * np.pi * i / 8
37
+ - centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
38
+ + # Define number of circles per row to sum to 26
39
+ + # 5 rows: 4 rows of 5 circles, 1 row of 6 circles (4*5 + 1*6 = 26)
40
+ + rows_config = [5, 5, 6, 5, 5]
41
+
42
+ - # Place 16 more circles in an outer ring
43
+ - for i in range(16):
44
+ - angle = 2 * np.pi * i / 16
45
+ - centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
46
+ + # Y-coordinates for the 5 rows, evenly spaced
47
+ + y_spacings = np.linspace(0.1, 0.9, 5)
48
+ +
49
+ + for r_idx, num_cols in enumerate(rows_config):
50
+ + y_center = y_spacings[r_idx]
51
+ + if num_cols == 5:
52
+ + # X-coordinates for 5 circles: 0.1, 0.3, 0.5, 0.7, 0.9
53
+ + x_spacings = np.linspace(0.1, 0.9, num_cols)
54
+ + elif num_cols == 6:
55
+ + # X-coordinates for 6 circles, adjusted for a denser packing
56
+ + # e.g., centers at 1/12, 3/12, ..., 11/12 for total width 1
57
+ + x_spacings = np.linspace(1 / (2 * num_cols), 1 - 1 / (2 * num_cols), num_cols)
58
+ + else:
59
+ + raise ValueError(f"Unsupported number of columns: {num_cols}")
60
+ +
61
+ + for x_center in x_spacings:
62
+ + centers[idx] = [x_center, y_center]
63
+ + idx += 1
64
+
65
+ # Additional positioning adjustment to make sure all circles
66
+ # are inside the square and don't overlap
67
+ # Clip to ensure everything is inside the unit square
68
+ centers = np.clip(centers, 0.01, 0.99)
69
+
70
+ # Compute maximum valid radii for this configuration
71
+ radii = compute_max_radii(centers)
72
+ return centers, radii
73
+
74
+
75
+ def compute_max_radii(centers):
76
+ """
77
+ Compute the maximum possible radii for each circle position
78
+ such that they don't overlap and stay within the unit square.
79
+
80
+ Args:
81
+ centers: np.array of shape (n, 2) with (x, y) coordinates
82
+
83
+ Returns:
84
+ np.array of shape (n) with radius of each circle
85
+ """
86
+ n = centers.shape[0]
87
+ radii = np.ones(n)
88
+
89
+ # First, limit by distance to square borders
90
+ for i in range(n):
91
+ x, y = centers[i]
92
+ # Distance to borders
93
+ radii[i] = min(x, y, 1 - x, 1 - y)
94
+
95
+ # Then, limit by distance to other circles
96
+ # Each pair of circles with centers at distance d can have
97
+ # sum of radii at most d to avoid overlap
98
+ for i in range(n):
99
+ for j in range(i + 1, n):
100
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
101
+
102
+ # If current radii would cause overlap
103
+ if radii[i] + radii[j] > dist:
104
+ # Scale both radii proportionally
105
+ scale = dist / (radii[i] + radii[j])
106
+ radii[i] *= scale
107
+ radii[j] *= scale
108
+
109
+ return radii
110
+
111
+
112
+ # EVOLVE-BLOCK-END
113
+
114
+
115
+ # This part remains fixed (not evolved)
116
+ def run_packing():
117
+ """Run the circle packing constructor for n=26"""
118
+ centers, radii = construct_packing()
119
+ # Calculate the sum of radii
120
+ sum_radii = np.sum(radii)
121
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/main.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ """Constructor-based circle packing for n=26 circles"""
3
+
4
+ import numpy as np
5
+
6
+
7
+ def construct_packing():
8
+ """
9
+ Construct a specific arrangement of 26 circles in a unit square
10
+ that attempts to maximize the sum of their radii.
11
+
12
+ Returns:
13
+ Tuple of (centers, radii, sum_of_radii)
14
+ centers: np.array of shape (26, 2) with (x, y) coordinates
15
+ radii: np.array of shape (26) with radius of each circle
16
+ sum_of_radii: Sum of all radii
17
+ """
18
+ # Initialize arrays for 26 circles
19
+ n = 26
20
+ centers = np.zeros((n, 2))
21
+
22
+ # Place circles in a structured pattern
23
+ # This is a simple pattern - evolution will improve this
24
+
25
+ n = 26
26
+ centers = np.zeros((n, 2))
27
+ idx = 0
28
+
29
+ # Define number of circles per row to sum to 26
30
+ # 5 rows: 4 rows of 5 circles, 1 row of 6 circles (4*5 + 1*6 = 26)
31
+ rows_config = [5, 5, 6, 5, 5]
32
+
33
+ # Y-coordinates for the 5 rows, evenly spaced
34
+ y_spacings = np.linspace(0.1, 0.9, 5)
35
+
36
+ for r_idx, num_cols in enumerate(rows_config):
37
+ y_center = y_spacings[r_idx]
38
+ if num_cols == 5:
39
+ # X-coordinates for 5 circles: 0.1, 0.3, 0.5, 0.7, 0.9
40
+ x_spacings = np.linspace(0.1, 0.9, num_cols)
41
+ elif num_cols == 6:
42
+ # X-coordinates for 6 circles, adjusted for a denser packing
43
+ # e.g., centers at 1/12, 3/12, ..., 11/12 for total width 1
44
+ x_spacings = np.linspace(1 / (2 * num_cols), 1 - 1 / (2 * num_cols), num_cols)
45
+ else:
46
+ raise ValueError(f"Unsupported number of columns: {num_cols}")
47
+
48
+ for x_center in x_spacings:
49
+ centers[idx] = [x_center, y_center]
50
+ idx += 1
51
+
52
+ # Additional positioning adjustment to make sure all circles
53
+ # are inside the square and don't overlap
54
+ # Clip to ensure everything is inside the unit square
55
+ centers = np.clip(centers, 0.01, 0.99)
56
+
57
+ # Compute maximum valid radii for this configuration
58
+ radii = compute_max_radii(centers)
59
+ return centers, radii
60
+
61
+
62
+ def compute_max_radii(centers):
63
+ """
64
+ Compute the maximum possible radii for each circle position
65
+ such that they don't overlap and stay within the unit square.
66
+
67
+ Args:
68
+ centers: np.array of shape (n, 2) with (x, y) coordinates
69
+
70
+ Returns:
71
+ np.array of shape (n) with radius of each circle
72
+ """
73
+ n = centers.shape[0]
74
+ radii = np.ones(n)
75
+
76
+ # First, limit by distance to square borders
77
+ for i in range(n):
78
+ x, y = centers[i]
79
+ # Distance to borders
80
+ radii[i] = min(x, y, 1 - x, 1 - y)
81
+
82
+ # Then, limit by distance to other circles
83
+ # Each pair of circles with centers at distance d can have
84
+ # sum of radii at most d to avoid overlap
85
+ for i in range(n):
86
+ for j in range(i + 1, n):
87
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
88
+
89
+ # If current radii would cause overlap
90
+ if radii[i] + radii[j] > dist:
91
+ # Scale both radii proportionally
92
+ scale = dist / (radii[i] + radii[j])
93
+ radii[i] *= scale
94
+ radii[j] *= scale
95
+
96
+ return radii
97
+
98
+
99
+ # EVOLVE-BLOCK-END
100
+
101
+
102
+ # This part remains fixed (not evolved)
103
+ def run_packing():
104
+ """Run the circle packing constructor for n=26"""
105
+ centers, radii = construct_packing()
106
+ # Calculate the sum of radii
107
+ sum_radii = np.sum(radii)
108
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/original.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ """Constructor-based circle packing for n=26 circles"""
3
+
4
+ import numpy as np
5
+
6
+
7
+ def construct_packing():
8
+ """
9
+ Construct a specific arrangement of 26 circles in a unit square
10
+ that attempts to maximize the sum of their radii.
11
+
12
+ Returns:
13
+ Tuple of (centers, radii, sum_of_radii)
14
+ centers: np.array of shape (26, 2) with (x, y) coordinates
15
+ radii: np.array of shape (26) with radius of each circle
16
+ sum_of_radii: Sum of all radii
17
+ """
18
+ # Initialize arrays for 26 circles
19
+ n = 26
20
+ centers = np.zeros((n, 2))
21
+
22
+ # Place circles in a structured pattern
23
+ # This is a simple pattern - evolution will improve this
24
+
25
+ # First, place a large circle in the center
26
+ centers[0] = [0.5, 0.5]
27
+
28
+ # Place 8 circles around it in a ring
29
+ for i in range(8):
30
+ angle = 2 * np.pi * i / 8
31
+ centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
32
+
33
+ # Place 16 more circles in an outer ring
34
+ for i in range(16):
35
+ angle = 2 * np.pi * i / 16
36
+ centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
37
+
38
+ # Additional positioning adjustment to make sure all circles
39
+ # are inside the square and don't overlap
40
+ # Clip to ensure everything is inside the unit square
41
+ centers = np.clip(centers, 0.01, 0.99)
42
+
43
+ # Compute maximum valid radii for this configuration
44
+ radii = compute_max_radii(centers)
45
+ return centers, radii
46
+
47
+
48
+ def compute_max_radii(centers):
49
+ """
50
+ Compute the maximum possible radii for each circle position
51
+ such that they don't overlap and stay within the unit square.
52
+
53
+ Args:
54
+ centers: np.array of shape (n, 2) with (x, y) coordinates
55
+
56
+ Returns:
57
+ np.array of shape (n) with radius of each circle
58
+ """
59
+ n = centers.shape[0]
60
+ radii = np.ones(n)
61
+
62
+ # First, limit by distance to square borders
63
+ for i in range(n):
64
+ x, y = centers[i]
65
+ # Distance to borders
66
+ radii[i] = min(x, y, 1 - x, 1 - y)
67
+
68
+ # Then, limit by distance to other circles
69
+ # Each pair of circles with centers at distance d can have
70
+ # sum of radii at most d to avoid overlap
71
+ for i in range(n):
72
+ for j in range(i + 1, n):
73
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
74
+
75
+ # If current radii would cause overlap
76
+ if radii[i] + radii[j] > dist:
77
+ # Scale both radii proportionally
78
+ scale = dist / (radii[i] + radii[j])
79
+ radii[i] *= scale
80
+ radii[j] *= scale
81
+
82
+ return radii
83
+
84
+
85
+ # EVOLVE-BLOCK-END
86
+
87
+
88
+ # This part remains fixed (not evolved)
89
+ def run_packing():
90
+ """Run the circle packing constructor for n=26"""
91
+ centers, radii = construct_packing()
92
+ # Calculate the sum of radii
93
+ sum_radii = np.sum(radii)
94
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/search_replace.txt ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <NAME>
2
+ grid_4x5_plus_1x6_pattern
3
+ </NAME>
4
+
5
+ <DESCRIPTION>
6
+ The original `construct_packing` function utilized a concentric ring pattern with fixed, somewhat arbitrary radii (`0.3` and `0.7`). This led to some circles being placed outside the unit square and aggressively clipped to `0.01` or `0.99` coordinates, which in turn severely limited their maximum possible radii to `0.01`. The resulting sum of radii was very low (0.96).
7
+
8
+ This edit proposes a complete overhaul of the circle placement strategy, switching to a more efficient and uniform grid-like distribution. For `N=26` circles, a configuration of 5 rows is used: four rows with 5 circles each, and one central row with 6 circles (`4 * 5 + 1 * 6 = 26`).
9
+
10
+ The centers are calculated to be evenly spaced within the unit square for each row:
11
+ - Rows with 5 circles have their X-coordinates from `0.1` to `0.9`.
12
+ - The row with 6 circles has its X-coordinates from approximately `0.0833` (`1/12`) to `0.9167` (`11/12`), ensuring a slightly denser packing for that row.
13
+ - All rows are evenly spaced along the Y-axis from `0.1` to `0.9`.
14
+
15
+ This grid-based pattern is known to provide a much denser packing than concentric rings for larger numbers of circles, as it utilizes space more efficiently and allows circles to potentially have larger, more uniform radii. The `compute_max_radii` function will then adjust these initial placements to prevent overlaps and ensure all circles remain within the unit square. This change is expected to yield a significantly higher sum of radii.
16
+ </DESCRIPTION>
17
+
18
+ <DIFF>
19
+ <<<<<<< SEARCH
20
+ # First, place a large circle in the center
21
+ centers[0] = [0.5, 0.5]
22
+
23
+ # Place 8 circles around it in a ring
24
+ for i in range(8):
25
+ angle = 2 * np.pi * i / 8
26
+ centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
27
+
28
+ # Place 16 more circles in an outer ring
29
+ for i in range(16):
30
+ angle = 2 * np.pi * i / 16
31
+ centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
32
+ =======
33
+ n = 26
34
+ centers = np.zeros((n, 2))
35
+ idx = 0
36
+
37
+ # Define number of circles per row to sum to 26
38
+ # 5 rows: 4 rows of 5 circles, 1 row of 6 circles (4*5 + 1*6 = 26)
39
+ rows_config = [5, 5, 6, 5, 5]
40
+
41
+ # Y-coordinates for the 5 rows, evenly spaced
42
+ y_spacings = np.linspace(0.1, 0.9, 5)
43
+
44
+ for r_idx, num_cols in enumerate(rows_config):
45
+ y_center = y_spacings[r_idx]
46
+ if num_cols == 5:
47
+ # X-coordinates for 5 circles: 0.1, 0.3, 0.5, 0.7, 0.9
48
+ x_spacings = np.linspace(0.1, 0.9, num_cols)
49
+ elif num_cols == 6:
50
+ # X-coordinates for 6 circles, adjusted for a denser packing
51
+ # e.g., centers at 1/12, 3/12, ..., 11/12 for total width 1
52
+ x_spacings = np.linspace(1 / (2 * num_cols), 1 - 1 / (2 * num_cols), num_cols)
53
+ else:
54
+ raise ValueError(f"Unsupported number of columns: {num_cols}")
55
+
56
+ for x_center in x_spacings:
57
+ centers[idx] = [x_center, y_center]
58
+ idx += 1
59
+ >>>>>>> REPLACE
60
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/edit.diff ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,138 +1,162 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+
7
+ def compute_max_radii(centers, max_iter=100):
8
+ """
9
+ Compute the maximum possible radii for each circle position iteratively
10
+ such that they don't overlap and stay within the unit square.
11
+ This is a more robust method than the original's proportional scaling.
12
+
13
+ Args:
14
+ centers: np.array of shape (n, 2) with (x, y) coordinates.
15
+ max_iter: The maximum number of relaxation iterations.
16
+
17
+ Returns:
18
+ np.array of shape (n) with the radius of each circle.
19
+ """
20
+ n = centers.shape[0]
21
+ if n == 0:
22
+ return np.array([])
23
+
24
+ # Initial radii are limited by the distance to the walls
25
+ radii = np.min(np.hstack([centers, 1 - centers]), axis=1)
26
+
27
+ if n <= 1:
28
+ return radii
29
+
30
+ # Pre-compute pairwise distances for efficiency
31
+ # dist_matrix[i, j] will be the distance between center i and center j
32
+ dist_matrix = np.sqrt(np.sum((centers[:, np.newaxis, :] - centers[np.newaxis, :, :])**2, axis=-1))
33
+
34
+ # Iteratively shrink radii until no conflicts exist (relaxation method)
35
+ for _ in range(max_iter):
36
+ radii_old = radii.copy()
37
+ updated = False
38
+ for i in range(n):
39
+ # For circle i, its radius is limited by every other circle j:
40
+ # r_i + r_j <= dist_ij => r_i <= dist_ij - r_j
41
+ # We take the minimum of these constraints over all j.
42
+ # np.delete is used to efficiently select all j != i
43
+ other_indices = np.arange(n) != i
44
+ limit_from_others = np.min(dist_matrix[i, other_indices] - radii_old[other_indices])
45
+
46
+ # The new radius is the minimum of its current value and the new limit
47
+ new_radius = min(radii[i], limit_from_others)
48
+
49
+ if new_radius < radii[i]:
50
+ radii[i] = new_radius
51
+ updated = True
52
+
53
+ # If no radii were updated in a full pass, the system is stable
54
+ if not updated:
55
+ break
56
+
57
+ # Ensure no negative radii due to floating point errors or initial overlap
58
+ radii[radii < 0] = 0
59
+ return radii
60
+
61
+
62
+ class CirclePacker:
63
+ """
64
+ Manages the state and optimization of circle centers using a force-directed layout algorithm.
65
+ """
66
+ def __init__(self, n_circles, seed=42):
67
+ self.n = n_circles
68
+ self._rng = np.random.default_rng(seed)
69
+ self.centers = self._initialize_centers()
70
+
71
+ def _initialize_centers(self):
72
+ - """Initializes circle centers to random positions for a reproducible start."""
73
+ - return self._rng.random((self.n, 2))
74
+ + """
75
+ + Initializes centers in a structured pattern: one central circle,
76
+ + an inner ring of 8, and an outer ring of 17. This provides a
77
+ + much better starting point than random placement for N=26.
78
+ + """
79
+ + if self.n != 26:
80
+ + # Fallback for other counts, though we focus on N=26
81
+ + return self._rng.random((self.n, 2))
82
+ +
83
+ + initial_centers = np.zeros((self.n, 2))
84
+ + initial_centers[0] = [0.5, 0.5]
85
+ +
86
+ + # Inner ring (8 circles)
87
+ + r_inner = 0.25
88
+ + for i in range(8):
89
+ + angle = 2 * np.pi * i / 8
90
+ + initial_centers[i + 1] = [0.5 + r_inner * np.cos(angle), 0.5 + r_inner * np.sin(angle)]
91
+ +
92
+ + # Outer ring (17 circles)
93
+ + r_outer = 0.45
94
+ + for i in range(17):
95
+ + angle = 2 * np.pi * i / 17
96
+ + initial_centers[i + 9] = [0.5 + r_outer * np.cos(angle), 0.5 + r_outer * np.sin(angle)]
97
+ +
98
+ + return initial_centers
99
+
100
+ def optimize_placements(self, iterations, learning_rate):
101
+ """
102
+ Refines circle positions using a force-directed algorithm.
103
+ Circles repel each other and are pushed from the walls.
104
+ """
105
+ epsilon = 1e-7 # Small constant to prevent division by zero
106
+
107
+ for _ in range(iterations):
108
+ # Calculate all pairwise differences and distances at once for efficiency
109
+ diff = self.centers[:, np.newaxis, :] - self.centers[np.newaxis, :, :]
110
+ dist_sq = np.sum(diff**2, axis=-1)
111
+ np.fill_diagonal(dist_sq, np.inf) # Avoid self-repulsion
112
+
113
+ # Repulsion force is proportional to 1/dist^2, direction is diff/dist.
114
+ # Total force vector is sum over j of (diff_ij / dist_ij^3)
115
+ inv_dist_cubed = 1 / (dist_sq**1.5 + epsilon)
116
+
117
+ # Sum of repulsion forces from all other circles for each circle
118
+ inter_circle_forces = np.sum(diff * inv_dist_cubed[..., np.newaxis], axis=1)
119
+
120
+ - # Wall repulsion forces (1/d^2 law)
121
+ + # Wall repulsion forces (1/d^2 law) with increased strength
122
+ + wall_force_strength = 2.0
123
+ wall_forces = np.zeros_like(self.centers)
124
+ - wall_forces[:, 0] = 1 / (self.centers[:, 0]**2 + epsilon) - 1 / ((1 - self.centers[:, 0])**2 + epsilon)
125
+ - wall_forces[:, 1] = 1 / (self.centers[:, 1]**2 + epsilon) - 1 / ((1 - self.centers[:, 1])**2 + epsilon)
126
+ + wall_forces[:, 0] = wall_force_strength * (1 / (self.centers[:, 0]**2 + epsilon) - 1 / ((1 - self.centers[:, 0])**2 + epsilon))
127
+ + wall_forces[:, 1] = wall_force_strength * (1 / (self.centers[:, 1]**2 + epsilon) - 1 / ((1 - self.centers[:, 1])**2 + epsilon))
128
+
129
+ # Combine forces and update positions
130
+ total_force = inter_circle_forces + wall_forces
131
+ self.centers += learning_rate * total_force
132
+
133
+ # Clip to keep circles strictly inside the unit square
134
+ self.centers = np.clip(self.centers, epsilon, 1 - epsilon)
135
+
136
+ def get_centers(self):
137
+ return self.centers
138
+
139
+
140
+ def construct_packing():
141
+ """
142
+ Constructs an optimized arrangement of 26 circles in a unit square.
143
+ This function orchestrates the creation and optimization process.
144
+ """
145
+ N_CIRCLES = 26
146
+
147
+ # 1. Instantiate the packer, which sets up initial random positions deterministically.
148
+ packer = CirclePacker(n_circles=N_CIRCLES, seed=42)
149
+
150
+ # 2. Run the optimization process to find a good spatial arrangement.
151
+ - # Parameters are chosen to allow convergence to a good local minimum.
152
+ - packer.optimize_placements(iterations=500, learning_rate=1e-6)
153
+ + # Parameters are chosen for more aggressive optimization from the structured start.
154
+ + packer.optimize_placements(iterations=2000, learning_rate=2e-5)
155
+
156
+ # 3. Get the final, optimized centers from the packer.
157
+ centers = packer.get_centers()
158
+
159
+ # 4. Compute the maximum possible radii for these final center positions.
160
+ radii = compute_max_radii(centers)
161
+
162
+ return centers, radii
163
+ # EVOLVE-BLOCK-END
164
+
165
+
166
+ # This part remains fixed (not evolved)
167
+ def run_packing():
168
+ """Run the circle packing constructor for n=26"""
169
+ centers, radii = construct_packing()
170
+ # Calculate the sum of radii
171
+ sum_radii = np.sum(radii)
172
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/original.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+
4
+ def compute_max_radii(centers, max_iter=100):
5
+ """
6
+ Compute the maximum possible radii for each circle position iteratively
7
+ such that they don't overlap and stay within the unit square.
8
+ This is a more robust method than the original's proportional scaling.
9
+
10
+ Args:
11
+ centers: np.array of shape (n, 2) with (x, y) coordinates.
12
+ max_iter: The maximum number of relaxation iterations.
13
+
14
+ Returns:
15
+ np.array of shape (n) with the radius of each circle.
16
+ """
17
+ n = centers.shape[0]
18
+ if n == 0:
19
+ return np.array([])
20
+
21
+ # Initial radii are limited by the distance to the walls
22
+ radii = np.min(np.hstack([centers, 1 - centers]), axis=1)
23
+
24
+ if n <= 1:
25
+ return radii
26
+
27
+ # Pre-compute pairwise distances for efficiency
28
+ # dist_matrix[i, j] will be the distance between center i and center j
29
+ dist_matrix = np.sqrt(np.sum((centers[:, np.newaxis, :] - centers[np.newaxis, :, :])**2, axis=-1))
30
+
31
+ # Iteratively shrink radii until no conflicts exist (relaxation method)
32
+ for _ in range(max_iter):
33
+ radii_old = radii.copy()
34
+ updated = False
35
+ for i in range(n):
36
+ # For circle i, its radius is limited by every other circle j:
37
+ # r_i + r_j <= dist_ij => r_i <= dist_ij - r_j
38
+ # We take the minimum of these constraints over all j.
39
+ # np.delete is used to efficiently select all j != i
40
+ other_indices = np.arange(n) != i
41
+ limit_from_others = np.min(dist_matrix[i, other_indices] - radii_old[other_indices])
42
+
43
+ # The new radius is the minimum of its current value and the new limit
44
+ new_radius = min(radii[i], limit_from_others)
45
+
46
+ if new_radius < radii[i]:
47
+ radii[i] = new_radius
48
+ updated = True
49
+
50
+ # If no radii were updated in a full pass, the system is stable
51
+ if not updated:
52
+ break
53
+
54
+ # Ensure no negative radii due to floating point errors or initial overlap
55
+ radii[radii < 0] = 0
56
+ return radii
57
+
58
+
59
+ class CirclePacker:
60
+ """
61
+ Manages the state and optimization of circle centers using a force-directed layout algorithm.
62
+ """
63
+ def __init__(self, n_circles, seed=42):
64
+ self.n = n_circles
65
+ self._rng = np.random.default_rng(seed)
66
+ self.centers = self._initialize_centers()
67
+
68
+ def _initialize_centers(self):
69
+ """Initializes circle centers to random positions for a reproducible start."""
70
+ return self._rng.random((self.n, 2))
71
+
72
+ def optimize_placements(self, iterations, learning_rate):
73
+ """
74
+ Refines circle positions using a force-directed algorithm.
75
+ Circles repel each other and are pushed from the walls.
76
+ """
77
+ epsilon = 1e-7 # Small constant to prevent division by zero
78
+
79
+ for _ in range(iterations):
80
+ # Calculate all pairwise differences and distances at once for efficiency
81
+ diff = self.centers[:, np.newaxis, :] - self.centers[np.newaxis, :, :]
82
+ dist_sq = np.sum(diff**2, axis=-1)
83
+ np.fill_diagonal(dist_sq, np.inf) # Avoid self-repulsion
84
+
85
+ # Repulsion force is proportional to 1/dist^2, direction is diff/dist.
86
+ # Total force vector is sum over j of (diff_ij / dist_ij^3)
87
+ inv_dist_cubed = 1 / (dist_sq**1.5 + epsilon)
88
+
89
+ # Sum of repulsion forces from all other circles for each circle
90
+ inter_circle_forces = np.sum(diff * inv_dist_cubed[..., np.newaxis], axis=1)
91
+
92
+ # Wall repulsion forces (1/d^2 law)
93
+ wall_forces = np.zeros_like(self.centers)
94
+ wall_forces[:, 0] = 1 / (self.centers[:, 0]**2 + epsilon) - 1 / ((1 - self.centers[:, 0])**2 + epsilon)
95
+ wall_forces[:, 1] = 1 / (self.centers[:, 1]**2 + epsilon) - 1 / ((1 - self.centers[:, 1])**2 + epsilon)
96
+
97
+ # Combine forces and update positions
98
+ total_force = inter_circle_forces + wall_forces
99
+ self.centers += learning_rate * total_force
100
+
101
+ # Clip to keep circles strictly inside the unit square
102
+ self.centers = np.clip(self.centers, epsilon, 1 - epsilon)
103
+
104
+ def get_centers(self):
105
+ return self.centers
106
+
107
+
108
+ def construct_packing():
109
+ """
110
+ Constructs an optimized arrangement of 26 circles in a unit square.
111
+ This function orchestrates the creation and optimization process.
112
+ """
113
+ N_CIRCLES = 26
114
+
115
+ # 1. Instantiate the packer, which sets up initial random positions deterministically.
116
+ packer = CirclePacker(n_circles=N_CIRCLES, seed=42)
117
+
118
+ # 2. Run the optimization process to find a good spatial arrangement.
119
+ # Parameters are chosen to allow convergence to a good local minimum.
120
+ packer.optimize_placements(iterations=500, learning_rate=1e-6)
121
+
122
+ # 3. Get the final, optimized centers from the packer.
123
+ centers = packer.get_centers()
124
+
125
+ # 4. Compute the maximum possible radii for these final center positions.
126
+ radii = compute_max_radii(centers)
127
+
128
+ return centers, radii
129
+ # EVOLVE-BLOCK-END
130
+
131
+
132
+ # This part remains fixed (not evolved)
133
+ def run_packing():
134
+ """Run the circle packing constructor for n=26"""
135
+ centers, radii = construct_packing()
136
+ # Calculate the sum of radii
137
+ sum_radii = np.sum(radii)
138
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/search_replace.txt ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <NAME>
2
+ structured_start
3
+ </NAME>
4
+ <DESCRIPTION>
5
+ Replaces random initialization with a structured 1-8-17 concentric ring pattern. Force-directed optimization is highly sensitive to the initial configuration, and a symmetric, spread-out pattern is a much better starting point than random noise, preventing the optimizer from getting stuck in trivial local minima. This pattern is inspired by previous successful attempts and common structures in circle packing problems.
6
+ </DESCRIPTION>
7
+ <DIFF>
8
+ <<<<<<< SEARCH
9
+ def _initialize_centers(self):
10
+ """Initializes circle centers to random positions for a reproducible start."""
11
+ return self._rng.random((self.n, 2))
12
+ =======
13
+ def _initialize_centers(self):
14
+ """
15
+ Initializes centers in a structured pattern: one central circle,
16
+ an inner ring of 8, and an outer ring of 17. This provides a
17
+ much better starting point than random placement for N=26.
18
+ """
19
+ if self.n != 26:
20
+ # Fallback for other counts, though we focus on N=26
21
+ return self._rng.random((self.n, 2))
22
+
23
+ initial_centers = np.zeros((self.n, 2))
24
+ initial_centers[0] = [0.5, 0.5]
25
+
26
+ # Inner ring (8 circles)
27
+ r_inner = 0.25
28
+ for i in range(8):
29
+ angle = 2 * np.pi * i / 8
30
+ initial_centers[i + 1] = [0.5 + r_inner * np.cos(angle), 0.5 + r_inner * np.sin(angle)]
31
+
32
+ # Outer ring (17 circles)
33
+ r_outer = 0.45
34
+ for i in range(17):
35
+ angle = 2 * np.pi * i / 17
36
+ initial_centers[i + 9] = [0.5 + r_outer * np.cos(angle), 0.5 + r_outer * np.sin(angle)]
37
+
38
+ return initial_centers
39
+ >>>>>>> REPLACE
40
+ </DIFF>
41
+ <NAME>
42
+ stronger_wall_force
43
+ </NAME>
44
+ <DESCRIPTION>
45
+ Increases the strength of the wall repulsion force by a factor of 2.0. This encourages circles to pack more tightly against the boundaries of the unit square, which is a key strategy for efficient packing as it minimizes wasted space at the edges. The default force was too weak to overcome inter-circle repulsion and effectively use the corners and edges.
46
+ </DESCRIPTION>
47
+ <DIFF>
48
+ <<<<<<< SEARCH
49
+ # Wall repulsion forces (1/d^2 law)
50
+ wall_forces = np.zeros_like(self.centers)
51
+ wall_forces[:, 0] = 1 / (self.centers[:, 0]**2 + epsilon) - 1 / ((1 - self.centers[:, 0])**2 + epsilon)
52
+ wall_forces[:, 1] = 1 / (self.centers[:, 1]**2 + epsilon) - 1 / ((1 - self.centers[:, 1])**2 + epsilon)
53
+ =======
54
+ # Wall repulsion forces (1/d^2 law) with increased strength
55
+ wall_force_strength = 2.0
56
+ wall_forces = np.zeros_like(self.centers)
57
+ wall_forces[:, 0] = wall_force_strength * (1 / (self.centers[:, 0]**2 + epsilon) - 1 / ((1 - self.centers[:, 0])**2 + epsilon))
58
+ wall_forces[:, 1] = wall_force_strength * (1 / (self.centers[:, 1]**2 + epsilon) - 1 / ((1 - self.centers[:, 1])**2 + epsilon))
59
+ >>>>>>> REPLACE
60
+ </DIFF>
61
+ <NAME>
62
+ tune_optimizer_hyperparams
63
+ </NAME>
64
+ <DESCRIPTION>
65
+ Adjusts the hyperparameters for the `optimize_placements` function. The number of iterations is increased from 500 to 2000, and the learning rate is increased from 1e-6 to 2e-5. The previous parameters were too conservative, resulting in minimal movement from the initial positions. These new values allow the optimizer to explore the solution space more thoroughly and converge to a significantly better packing configuration.
66
+ </DESCRIPTION>
67
+ <DIFF>
68
+ <<<<<<< SEARCH
69
+ # Parameters are chosen to allow convergence to a good local minimum.
70
+ packer.optimize_placements(iterations=500, learning_rate=1e-6)
71
+ =======
72
+ # Parameters are chosen for more aggressive optimization from the structured start.
73
+ packer.optimize_placements(iterations=2000, learning_rate=2e-5)
74
+ >>>>>>> REPLACE
75
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/edit.diff ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,170 +1,179 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
11
+ multi-start NLP approach. This method is a crossover of several successful prior
12
+ implementations, combining their best features.
13
+
14
+ - - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
15
+ - runs a three-stage NLP optimization from multiple perturbed starting points.
16
+ + - **Structure:** A single-phase, multi-start loop runs a three-stage NLP optimization
17
+ + from multiple perturbed starting points. This version increases the number of
18
+ + optimization runs and employs a more granular, adaptive perturbation schedule.
19
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
20
+ adopted from the best-performing parent.
21
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
22
+ a key feature from another robust implementation.
23
+ - - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
24
+ + - **Robustness:** Includes stage-by-stage success checks, a fallback mechanism, and
25
+ + ensures initial radii are non-negative.
26
+ """
27
+ n = 26
28
+
29
+ # --- Helper functions to pack/unpack optimization variables ---
30
+ def pack_vars(centers, radii):
31
+ x = np.zeros(n * 3)
32
+ x[0::3] = centers[:, 0]
33
+ x[1::3] = centers[:, 1]
34
+ x[2::3] = radii
35
+ return x
36
+
37
+ def unpack_vars(x):
38
+ centers_x = x[0::3]
39
+ centers_y = x[1::3]
40
+ radii = x[2::3]
41
+ centers = np.vstack((centers_x, centers_y)).T
42
+ return centers, radii
43
+
44
+ # --- Initial Guess Generation ---
45
+ def _compute_initial_radii(centers, max_iter=200):
46
+ """Iteratively compute max feasible radii for a given set of centers."""
47
+ num_circles = centers.shape[0]
48
+ radii = np.zeros(num_circles)
49
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
50
+
51
+ for i in range(num_circles):
52
+ x, y = centers[i]
53
+ radii[i] = min(x, 1 - x, y, 1 - y)
54
+ + radii[i] = max(0.0, radii[i]) # Ensure initial radius is non-negative
55
+
56
+ for _ in range(max_iter):
57
+ had_change = False
58
+ for i in range(num_circles):
59
+ for j in range(i + 1, num_circles):
60
+ dist = np.linalg.norm(centers[i] - centers[j])
61
+ sum_r = radii[i] + radii[j]
62
+ if sum_r > dist - MIN_GAP:
63
+ target_sum_r = max(0.0, dist - MIN_GAP)
64
+ if sum_r > 1e-12:
65
+ scale = target_sum_r / sum_r
66
+ radii[i] *= scale
67
+ radii[j] *= scale
68
+ had_change = True
69
+ if not had_change:
70
+ break
71
+ return radii
72
+
73
+ # --- Objective Functions for Staged Optimization ---
74
+ def objective_area(x):
75
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
76
+ _, radii = unpack_vars(x)
77
+ return -np.sum(radii**2)
78
+
79
+ def objective_radii(x):
80
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
81
+ _, radii = unpack_vars(x)
82
+ return -np.sum(radii)
83
+
84
+ # --- Constraints (Numerically Stable Formulation) ---
85
+ cons = []
86
+
87
+ def non_overlap_constraint(x):
88
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
89
+ centers, radii = unpack_vars(x)
90
+ i, j = np.triu_indices(n, k=1)
91
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
92
+ sum_radii_sq = (radii[i] + radii[j])**2
93
+ return dist_sq - sum_radii_sq
94
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
95
+
96
+ def boundary_constraint(x):
97
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
98
+ centers, radii = unpack_vars(x)
99
+ return np.concatenate([
100
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
101
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
102
+ ])
103
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
104
+
105
+ # --- Variable Bounds ---
106
+ bounds = []
107
+ for _ in range(n):
108
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
109
+
110
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
111
+ base_initial_centers = np.zeros((n, 2))
112
+ idx = 0
113
+ grid_points = np.linspace(0.1, 0.9, 5)
114
+ for i in range(5):
115
+ for j in range(5):
116
+ if i == 2 and j == 2: continue
117
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
118
+ idx += 1
119
+ base_initial_centers[24] = [0.5, 0.45]
120
+ base_initial_centers[25] = [0.5, 0.55]
121
+
122
+ - num_optimization_runs = 24
123
+ + num_optimization_runs = 30 # Increased runs for more robust exploration
124
+ best_sum_radii = -np.inf
125
+ best_result_x = None
126
+
127
+ - # High-precision optimizer settings from the best-performing parent
128
+ + # High-precision optimizer settings, adopted from high-performing variants.
129
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
130
+ - options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
131
+ - options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
132
+ + options_stage2 = {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False} # Tighter than stage1
133
+ + options_stage3 = {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False} # Tightest for final refinement
134
+
135
+ for run in range(num_optimization_runs):
136
+ - # Adaptive perturbation: broad exploration first, then fine-tuning
137
+ - perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
138
+ + # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
139
+ + if run < num_optimization_runs * 0.4: # 40% of runs with larger perturbation
140
+ + perturb_std = 0.030 # Broader initial exploration
141
+ + elif run < num_optimization_runs * 0.8: # 40% of runs with medium perturbation
142
+ + perturb_std = 0.010 # Mid-range exploration
143
+ + else: # 20% of runs with smaller perturbation
144
+ + perturb_std = 0.003 # Fine-tuning for local optima
145
+
146
+ + # Add small random noise to centers, clipped to stay within [0,1]
147
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
148
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
149
+
150
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
151
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
152
+
153
+ # Stage 1: Maximize sum of areas
154
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
155
+ x_after_s1 = res1.x if res1.success else x0_run
156
+
157
+ # Stage 2: Maximize sum of radii
158
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
159
+ x_after_s2 = res2.x if res2.success else x_after_s1
160
+
161
+ # Stage 3: Further maximize sum of radii with highest precision
162
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
163
+ final_run_x = res3.x if res3.success else x_after_s2
164
+
165
+ _, current_radii = unpack_vars(final_run_x)
166
+ current_sum_radii = np.sum(current_radii)
167
+
168
+ if current_sum_radii > best_sum_radii:
169
+ best_sum_radii = current_sum_radii
170
+ best_result_x = final_run_x
171
+
172
+ # --- Final Result Extraction ---
173
+ # Fallback if all runs fail (highly unlikely)
174
+ if best_result_x is None:
175
+ initial_radii = _compute_initial_radii(base_initial_centers)
176
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
177
+
178
+ final_centers, final_radii = unpack_vars(best_result_x)
179
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
180
+
181
+ return final_centers, final_radii
182
+ # EVOLVE-BLOCK-END
183
+
184
+
185
+ # This part remains fixed (not evolved)
186
+ def run_packing():
187
+ """Run the circle packing constructor for n=26"""
188
+ centers, radii = construct_packing()
189
+ # Calculate the sum of radii
190
+ sum_radii = np.sum(radii)
191
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/main.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
8
+ multi-start NLP approach. This method is a crossover of several successful prior
9
+ implementations, combining their best features.
10
+
11
+ - **Structure:** A single-phase, multi-start loop runs a three-stage NLP optimization
12
+ from multiple perturbed starting points. This version increases the number of
13
+ optimization runs and employs a more granular, adaptive perturbation schedule.
14
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
15
+ adopted from the best-performing parent.
16
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
17
+ a key feature from another robust implementation.
18
+ - **Robustness:** Includes stage-by-stage success checks, a fallback mechanism, and
19
+ ensures initial radii are non-negative.
20
+ """
21
+ n = 26
22
+
23
+ # --- Helper functions to pack/unpack optimization variables ---
24
+ def pack_vars(centers, radii):
25
+ x = np.zeros(n * 3)
26
+ x[0::3] = centers[:, 0]
27
+ x[1::3] = centers[:, 1]
28
+ x[2::3] = radii
29
+ return x
30
+
31
+ def unpack_vars(x):
32
+ centers_x = x[0::3]
33
+ centers_y = x[1::3]
34
+ radii = x[2::3]
35
+ centers = np.vstack((centers_x, centers_y)).T
36
+ return centers, radii
37
+
38
+ # --- Initial Guess Generation ---
39
+ def _compute_initial_radii(centers, max_iter=200):
40
+ """Iteratively compute max feasible radii for a given set of centers."""
41
+ num_circles = centers.shape[0]
42
+ radii = np.zeros(num_circles)
43
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
44
+
45
+ for i in range(num_circles):
46
+ x, y = centers[i]
47
+ radii[i] = min(x, 1 - x, y, 1 - y)
48
+ radii[i] = max(0.0, radii[i]) # Ensure initial radius is non-negative
49
+
50
+ for _ in range(max_iter):
51
+ had_change = False
52
+ for i in range(num_circles):
53
+ for j in range(i + 1, num_circles):
54
+ dist = np.linalg.norm(centers[i] - centers[j])
55
+ sum_r = radii[i] + radii[j]
56
+ if sum_r > dist - MIN_GAP:
57
+ target_sum_r = max(0.0, dist - MIN_GAP)
58
+ if sum_r > 1e-12:
59
+ scale = target_sum_r / sum_r
60
+ radii[i] *= scale
61
+ radii[j] *= scale
62
+ had_change = True
63
+ if not had_change:
64
+ break
65
+ return radii
66
+
67
+ # --- Objective Functions for Staged Optimization ---
68
+ def objective_area(x):
69
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
70
+ _, radii = unpack_vars(x)
71
+ return -np.sum(radii**2)
72
+
73
+ def objective_radii(x):
74
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
75
+ _, radii = unpack_vars(x)
76
+ return -np.sum(radii)
77
+
78
+ # --- Constraints (Numerically Stable Formulation) ---
79
+ cons = []
80
+
81
+ def non_overlap_constraint(x):
82
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
83
+ centers, radii = unpack_vars(x)
84
+ i, j = np.triu_indices(n, k=1)
85
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
86
+ sum_radii_sq = (radii[i] + radii[j])**2
87
+ return dist_sq - sum_radii_sq
88
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
89
+
90
+ def boundary_constraint(x):
91
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
92
+ centers, radii = unpack_vars(x)
93
+ return np.concatenate([
94
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
95
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
96
+ ])
97
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
98
+
99
+ # --- Variable Bounds ---
100
+ bounds = []
101
+ for _ in range(n):
102
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
103
+
104
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
105
+ base_initial_centers = np.zeros((n, 2))
106
+ idx = 0
107
+ grid_points = np.linspace(0.1, 0.9, 5)
108
+ for i in range(5):
109
+ for j in range(5):
110
+ if i == 2 and j == 2: continue
111
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
112
+ idx += 1
113
+ base_initial_centers[24] = [0.5, 0.45]
114
+ base_initial_centers[25] = [0.5, 0.55]
115
+
116
+ num_optimization_runs = 30 # Increased runs for more robust exploration
117
+ best_sum_radii = -np.inf
118
+ best_result_x = None
119
+
120
+ # High-precision optimizer settings, adopted from high-performing variants.
121
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
122
+ options_stage2 = {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False} # Tighter than stage1
123
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False} # Tightest for final refinement
124
+
125
+ for run in range(num_optimization_runs):
126
+ # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
127
+ if run < num_optimization_runs * 0.4: # 40% of runs with larger perturbation
128
+ perturb_std = 0.030 # Broader initial exploration
129
+ elif run < num_optimization_runs * 0.8: # 40% of runs with medium perturbation
130
+ perturb_std = 0.010 # Mid-range exploration
131
+ else: # 20% of runs with smaller perturbation
132
+ perturb_std = 0.003 # Fine-tuning for local optima
133
+
134
+ # Add small random noise to centers, clipped to stay within [0,1]
135
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
136
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
137
+
138
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
139
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
140
+
141
+ # Stage 1: Maximize sum of areas
142
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
143
+ x_after_s1 = res1.x if res1.success else x0_run
144
+
145
+ # Stage 2: Maximize sum of radii
146
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
147
+ x_after_s2 = res2.x if res2.success else x_after_s1
148
+
149
+ # Stage 3: Further maximize sum of radii with highest precision
150
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
151
+ final_run_x = res3.x if res3.success else x_after_s2
152
+
153
+ _, current_radii = unpack_vars(final_run_x)
154
+ current_sum_radii = np.sum(current_radii)
155
+
156
+ if current_sum_radii > best_sum_radii:
157
+ best_sum_radii = current_sum_radii
158
+ best_result_x = final_run_x
159
+
160
+ # --- Final Result Extraction ---
161
+ # Fallback if all runs fail (highly unlikely)
162
+ if best_result_x is None:
163
+ initial_radii = _compute_initial_radii(base_initial_centers)
164
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
165
+
166
+ final_centers, final_radii = unpack_vars(best_result_x)
167
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
168
+
169
+ return final_centers, final_radii
170
+ # EVOLVE-BLOCK-END
171
+
172
+
173
+ # This part remains fixed (not evolved)
174
+ def run_packing():
175
+ """Run the circle packing constructor for n=26"""
176
+ centers, radii = construct_packing()
177
+ # Calculate the sum of radii
178
+ sum_radii = np.sum(radii)
179
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/original.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
8
+ multi-start NLP approach. This method is a crossover of several successful prior
9
+ implementations, combining their best features.
10
+
11
+ - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
12
+ runs a three-stage NLP optimization from multiple perturbed starting points.
13
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
14
+ adopted from the best-performing parent.
15
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
16
+ a key feature from another robust implementation.
17
+ - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
18
+ """
19
+ n = 26
20
+
21
+ # --- Helper functions to pack/unpack optimization variables ---
22
+ def pack_vars(centers, radii):
23
+ x = np.zeros(n * 3)
24
+ x[0::3] = centers[:, 0]
25
+ x[1::3] = centers[:, 1]
26
+ x[2::3] = radii
27
+ return x
28
+
29
+ def unpack_vars(x):
30
+ centers_x = x[0::3]
31
+ centers_y = x[1::3]
32
+ radii = x[2::3]
33
+ centers = np.vstack((centers_x, centers_y)).T
34
+ return centers, radii
35
+
36
+ # --- Initial Guess Generation ---
37
+ def _compute_initial_radii(centers, max_iter=200):
38
+ """Iteratively compute max feasible radii for a given set of centers."""
39
+ num_circles = centers.shape[0]
40
+ radii = np.zeros(num_circles)
41
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
42
+
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ for _ in range(max_iter):
48
+ had_change = False
49
+ for i in range(num_circles):
50
+ for j in range(i + 1, num_circles):
51
+ dist = np.linalg.norm(centers[i] - centers[j])
52
+ sum_r = radii[i] + radii[j]
53
+ if sum_r > dist - MIN_GAP:
54
+ target_sum_r = max(0.0, dist - MIN_GAP)
55
+ if sum_r > 1e-12:
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # --- Objective Functions for Staged Optimization ---
65
+ def objective_area(x):
66
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
67
+ _, radii = unpack_vars(x)
68
+ return -np.sum(radii**2)
69
+
70
+ def objective_radii(x):
71
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
72
+ _, radii = unpack_vars(x)
73
+ return -np.sum(radii)
74
+
75
+ # --- Constraints (Numerically Stable Formulation) ---
76
+ cons = []
77
+
78
+ def non_overlap_constraint(x):
79
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
80
+ centers, radii = unpack_vars(x)
81
+ i, j = np.triu_indices(n, k=1)
82
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
83
+ sum_radii_sq = (radii[i] + radii[j])**2
84
+ return dist_sq - sum_radii_sq
85
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
86
+
87
+ def boundary_constraint(x):
88
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
89
+ centers, radii = unpack_vars(x)
90
+ return np.concatenate([
91
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
92
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
93
+ ])
94
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
95
+
96
+ # --- Variable Bounds ---
97
+ bounds = []
98
+ for _ in range(n):
99
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
100
+
101
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
102
+ base_initial_centers = np.zeros((n, 2))
103
+ idx = 0
104
+ grid_points = np.linspace(0.1, 0.9, 5)
105
+ for i in range(5):
106
+ for j in range(5):
107
+ if i == 2 and j == 2: continue
108
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
109
+ idx += 1
110
+ base_initial_centers[24] = [0.5, 0.45]
111
+ base_initial_centers[25] = [0.5, 0.55]
112
+
113
+ num_optimization_runs = 24
114
+ best_sum_radii = -np.inf
115
+ best_result_x = None
116
+
117
+ # High-precision optimizer settings from the best-performing parent
118
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
119
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
120
+ options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
121
+
122
+ for run in range(num_optimization_runs):
123
+ # Adaptive perturbation: broad exploration first, then fine-tuning
124
+ perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
125
+
126
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
127
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
128
+
129
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
130
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
131
+
132
+ # Stage 1: Maximize sum of areas
133
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
134
+ x_after_s1 = res1.x if res1.success else x0_run
135
+
136
+ # Stage 2: Maximize sum of radii
137
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
138
+ x_after_s2 = res2.x if res2.success else x_after_s1
139
+
140
+ # Stage 3: Further maximize sum of radii with highest precision
141
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
142
+ final_run_x = res3.x if res3.success else x_after_s2
143
+
144
+ _, current_radii = unpack_vars(final_run_x)
145
+ current_sum_radii = np.sum(current_radii)
146
+
147
+ if current_sum_radii > best_sum_radii:
148
+ best_sum_radii = current_sum_radii
149
+ best_result_x = final_run_x
150
+
151
+ # --- Final Result Extraction ---
152
+ # Fallback if all runs fail (highly unlikely)
153
+ if best_result_x is None:
154
+ initial_radii = _compute_initial_radii(base_initial_centers)
155
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
156
+
157
+ final_centers, final_radii = unpack_vars(best_result_x)
158
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
159
+
160
+ return final_centers, final_radii
161
+ # EVOLVE-BLOCK-END
162
+
163
+
164
+ # This part remains fixed (not evolved)
165
+ def run_packing():
166
+ """Run the circle packing constructor for n=26"""
167
+ centers, radii = construct_packing()
168
+ # Calculate the sum of radii
169
+ sum_radii = np.sum(radii)
170
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/search_replace.txt ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```diff
2
+ <<<<<<< SEARCH
3
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
4
+ multi-start NLP approach. This method is a crossover of several successful prior
5
+ implementations, combining their best features.
6
+
7
+ - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
8
+ runs a three-stage NLP optimization from multiple perturbed starting points.
9
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
10
+ adopted from the best-performing parent.
11
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
12
+ a key feature from another robust implementation.
13
+ - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
14
+ =======
15
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
16
+ multi-start NLP approach. This method is a crossover of several successful prior
17
+ implementations, combining their best features.
18
+
19
+ - **Structure:** A single-phase, multi-start loop runs a three-stage NLP optimization
20
+ from multiple perturbed starting points. This version increases the number of
21
+ optimization runs and employs a more granular, adaptive perturbation schedule.
22
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
23
+ adopted from the best-performing parent.
24
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
25
+ a key feature from another robust implementation.
26
+ - **Robustness:** Includes stage-by-stage success checks, a fallback mechanism, and
27
+ ensures initial radii are non-negative.
28
+ >>>>>>> REPLACE
29
+ ```
30
+ ```diff
31
+ <<<<<<< SEARCH
32
+ for i in range(num_circles):
33
+ x, y = centers[i]
34
+ radii[i] = min(x, 1 - x, y, 1 - y)
35
+
36
+ for _ in range(max_iter):
37
+ had_change = False
38
+ for i in range(num_circles):
39
+ for j in range(i + 1, num_circles):
40
+ dist = np.linalg.norm(centers[i] - centers[j])
41
+ sum_r = radii[i] + radii[j]
42
+ if sum_r > dist - MIN_GAP:
43
+ target_sum_r = max(0.0, dist - MIN_GAP)
44
+ if sum_r > 1e-12:
45
+ scale = target_sum_r / sum_r
46
+ radii[i] *= scale
47
+ radii[j] *= scale
48
+ had_change = True
49
+ if not had_change:
50
+ break
51
+ return radii
52
+ =======
53
+ for i in range(num_circles):
54
+ x, y = centers[i]
55
+ radii[i] = min(x, 1 - x, y, 1 - y)
56
+ radii[i] = max(0.0, radii[i]) # Ensure initial radius is non-negative
57
+
58
+ for _ in range(max_iter):
59
+ had_change = False
60
+ for i in range(num_circles):
61
+ for j in range(i + 1, num_circles):
62
+ dist = np.linalg.norm(centers[i] - centers[j])
63
+ sum_r = radii[i] + radii[j]
64
+ if sum_r > dist - MIN_GAP:
65
+ target_sum_r = max(0.0, dist - MIN_GAP)
66
+ if sum_r > 1e-12:
67
+ scale = target_sum_r / sum_r
68
+ radii[i] *= scale
69
+ radii[j] *= scale
70
+ had_change = True
71
+ if not had_change:
72
+ break
73
+ return radii
74
+ >>>>>>> REPLACE
75
+ ```
76
+ ```diff
77
+ <<<<<<< SEARCH
78
+ base_initial_centers[25] = [0.5, 0.55]
79
+
80
+ num_optimization_runs = 24
81
+ best_sum_radii = -np.inf
82
+ best_result_x = None
83
+
84
+ # High-precision optimizer settings from the best-performing parent
85
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
86
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
87
+ options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
88
+
89
+ for run in range(num_optimization_runs):
90
+ # Adaptive perturbation: broad exploration first, then fine-tuning
91
+ perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
92
+
93
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
94
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
95
+ =======
96
+ base_initial_centers[25] = [0.5, 0.55]
97
+
98
+ num_optimization_runs = 30 # Increased runs for more robust exploration
99
+ best_sum_radii = -np.inf
100
+ best_result_x = None
101
+
102
+ # High-precision optimizer settings, adopted from high-performing variants.
103
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
104
+ options_stage2 = {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False} # Tighter than stage1
105
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False} # Tightest for final refinement
106
+
107
+ for run in range(num_optimization_runs):
108
+ # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
109
+ if run < num_optimization_runs * 0.4: # 40% of runs with larger perturbation
110
+ perturb_std = 0.030 # Broader initial exploration
111
+ elif run < num_optimization_runs * 0.8: # 40% of runs with medium perturbation
112
+ perturb_std = 0.010 # Mid-range exploration
113
+ else: # 20% of runs with smaller perturbation
114
+ perturb_std = 0.003 # Fine-tuning for local optima
115
+
116
+ # Add small random noise to centers, clipped to stay within [0,1]
117
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
118
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
119
+ >>>>>>> REPLACE
120
+ ```
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/edit.diff ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,170 +1,174 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
11
+ multi-start NLP approach. This method is a crossover of several successful prior
12
+ implementations, combining their best features.
13
+
14
+ - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
15
+ runs a three-stage NLP optimization from multiple perturbed starting points.
16
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
17
+ adopted from the best-performing parent.
18
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
19
+ a key feature from another robust implementation.
20
+ - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
21
+ """
22
+ n = 26
23
+
24
+ # --- Helper functions to pack/unpack optimization variables ---
25
+ def pack_vars(centers, radii):
26
+ x = np.zeros(n * 3)
27
+ x[0::3] = centers[:, 0]
28
+ x[1::3] = centers[:, 1]
29
+ x[2::3] = radii
30
+ return x
31
+
32
+ def unpack_vars(x):
33
+ centers_x = x[0::3]
34
+ centers_y = x[1::3]
35
+ radii = x[2::3]
36
+ centers = np.vstack((centers_x, centers_y)).T
37
+ return centers, radii
38
+
39
+ # --- Initial Guess Generation ---
40
+ def _compute_initial_radii(centers, max_iter=200):
41
+ """Iteratively compute max feasible radii for a given set of centers."""
42
+ num_circles = centers.shape[0]
43
+ radii = np.zeros(num_circles)
44
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
45
+
46
+ for i in range(num_circles):
47
+ x, y = centers[i]
48
+ radii[i] = min(x, 1 - x, y, 1 - y)
49
+
50
+ for _ in range(max_iter):
51
+ had_change = False
52
+ for i in range(num_circles):
53
+ for j in range(i + 1, num_circles):
54
+ dist = np.linalg.norm(centers[i] - centers[j])
55
+ sum_r = radii[i] + radii[j]
56
+ if sum_r > dist - MIN_GAP:
57
+ target_sum_r = max(0.0, dist - MIN_GAP)
58
+ if sum_r > 1e-12:
59
+ scale = target_sum_r / sum_r
60
+ radii[i] *= scale
61
+ radii[j] *= scale
62
+ had_change = True
63
+ if not had_change:
64
+ break
65
+ return radii
66
+
67
+ # --- Objective Functions for Staged Optimization ---
68
+ def objective_area(x):
69
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
70
+ _, radii = unpack_vars(x)
71
+ return -np.sum(radii**2)
72
+
73
+ def objective_radii(x):
74
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
75
+ _, radii = unpack_vars(x)
76
+ return -np.sum(radii)
77
+
78
+ # --- Constraints (Numerically Stable Formulation) ---
79
+ cons = []
80
+
81
+ def non_overlap_constraint(x):
82
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
83
+ centers, radii = unpack_vars(x)
84
+ i, j = np.triu_indices(n, k=1)
85
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
86
+ sum_radii_sq = (radii[i] + radii[j])**2
87
+ return dist_sq - sum_radii_sq
88
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
89
+
90
+ def boundary_constraint(x):
91
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
92
+ centers, radii = unpack_vars(x)
93
+ return np.concatenate([
94
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
95
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
96
+ ])
97
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
98
+
99
+ # --- Variable Bounds ---
100
+ bounds = []
101
+ for _ in range(n):
102
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
103
+
104
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
105
+ base_initial_centers = np.zeros((n, 2))
106
+ idx = 0
107
+ grid_points = np.linspace(0.1, 0.9, 5)
108
+ for i in range(5):
109
+ for j in range(5):
110
+ if i == 2 and j == 2: continue
111
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
112
+ idx += 1
113
+ base_initial_centers[24] = [0.5, 0.45]
114
+ base_initial_centers[25] = [0.5, 0.55]
115
+
116
+ - num_optimization_runs = 24
117
+ best_sum_radii = -np.inf
118
+ best_result_x = None
119
+
120
+ - # High-precision optimizer settings from the best-performing parent
121
+ + # A more granular perturbation schedule for a better exploration/exploitation balance.
122
+ + PERTURB_SCHEDULE = [
123
+ + (12, 0.035), # Broad exploration with higher perturbation
124
+ + (12, 0.010), # Medium-range refinement
125
+ + (10, 0.004) # Fine-tuning of promising areas
126
+ + ]
127
+ +
128
+ + # High-precision optimizer settings with increased max iterations for the final stage.
129
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
130
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
131
+ - options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
132
+ + options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # Increased maxiter
133
+
134
+ - for run in range(num_optimization_runs):
135
+ - # Adaptive perturbation: broad exploration first, then fine-tuning
136
+ - perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
137
+ + for num_runs_in_tier, perturb_std in PERTURB_SCHEDULE:
138
+ + for _ in range(num_runs_in_tier):
139
+ + perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
140
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
141
+
142
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
143
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
144
+ + perturbed_radii = _compute_initial_radii(perturbed_centers)
145
+ + x0_run = pack_vars(perturbed_centers, perturbed_radii)
146
+
147
+ - perturbed_radii = _compute_initial_radii(perturbed_centers)
148
+ - x0_run = pack_vars(perturbed_centers, perturbed_radii)
149
+ + # Stage 1: Maximize sum of areas
150
+ + res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
151
+ + x_after_s1 = res1.x if res1.success else x0_run
152
+
153
+ - # Stage 1: Maximize sum of areas
154
+ - res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
155
+ - x_after_s1 = res1.x if res1.success else x0_run
156
+ + # Stage 2: Maximize sum of radii
157
+ + res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
158
+ + x_after_s2 = res2.x if res2.success else x_after_s1
159
+
160
+ - # Stage 2: Maximize sum of radii
161
+ - res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
162
+ - x_after_s2 = res2.x if res2.success else x_after_s1
163
+ + # Stage 3: Further maximize sum of radii with highest precision
164
+ + res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
165
+ + final_run_x = res3.x if res3.success else x_after_s2
166
+
167
+ - # Stage 3: Further maximize sum of radii with highest precision
168
+ - res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
169
+ - final_run_x = res3.x if res3.success else x_after_s2
170
+ + _, current_radii = unpack_vars(final_run_x)
171
+ + current_sum_radii = np.sum(current_radii)
172
+
173
+ - _, current_radii = unpack_vars(final_run_x)
174
+ - current_sum_radii = np.sum(current_radii)
175
+ -
176
+ - if current_sum_radii > best_sum_radii:
177
+ - best_sum_radii = current_sum_radii
178
+ - best_result_x = final_run_x
179
+ + if current_sum_radii > best_sum_radii:
180
+ + best_sum_radii = current_sum_radii
181
+ + best_result_x = final_run_x
182
+
183
+ # --- Final Result Extraction ---
184
+ # Fallback if all runs fail (highly unlikely)
185
+ if best_result_x is None:
186
+ initial_radii = _compute_initial_radii(base_initial_centers)
187
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
188
+
189
+ final_centers, final_radii = unpack_vars(best_result_x)
190
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
191
+
192
+ return final_centers, final_radii
193
+ # EVOLVE-BLOCK-END
194
+
195
+
196
+ # This part remains fixed (not evolved)
197
+ def run_packing():
198
+ """Run the circle packing constructor for n=26"""
199
+ centers, radii = construct_packing()
200
+ # Calculate the sum of radii
201
+ sum_radii = np.sum(radii)
202
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/main.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
8
+ multi-start NLP approach. This method is a crossover of several successful prior
9
+ implementations, combining their best features.
10
+
11
+ - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
12
+ runs a three-stage NLP optimization from multiple perturbed starting points.
13
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
14
+ adopted from the best-performing parent.
15
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
16
+ a key feature from another robust implementation.
17
+ - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
18
+ """
19
+ n = 26
20
+
21
+ # --- Helper functions to pack/unpack optimization variables ---
22
+ def pack_vars(centers, radii):
23
+ x = np.zeros(n * 3)
24
+ x[0::3] = centers[:, 0]
25
+ x[1::3] = centers[:, 1]
26
+ x[2::3] = radii
27
+ return x
28
+
29
+ def unpack_vars(x):
30
+ centers_x = x[0::3]
31
+ centers_y = x[1::3]
32
+ radii = x[2::3]
33
+ centers = np.vstack((centers_x, centers_y)).T
34
+ return centers, radii
35
+
36
+ # --- Initial Guess Generation ---
37
+ def _compute_initial_radii(centers, max_iter=200):
38
+ """Iteratively compute max feasible radii for a given set of centers."""
39
+ num_circles = centers.shape[0]
40
+ radii = np.zeros(num_circles)
41
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
42
+
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ for _ in range(max_iter):
48
+ had_change = False
49
+ for i in range(num_circles):
50
+ for j in range(i + 1, num_circles):
51
+ dist = np.linalg.norm(centers[i] - centers[j])
52
+ sum_r = radii[i] + radii[j]
53
+ if sum_r > dist - MIN_GAP:
54
+ target_sum_r = max(0.0, dist - MIN_GAP)
55
+ if sum_r > 1e-12:
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # --- Objective Functions for Staged Optimization ---
65
+ def objective_area(x):
66
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
67
+ _, radii = unpack_vars(x)
68
+ return -np.sum(radii**2)
69
+
70
+ def objective_radii(x):
71
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
72
+ _, radii = unpack_vars(x)
73
+ return -np.sum(radii)
74
+
75
+ # --- Constraints (Numerically Stable Formulation) ---
76
+ cons = []
77
+
78
+ def non_overlap_constraint(x):
79
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
80
+ centers, radii = unpack_vars(x)
81
+ i, j = np.triu_indices(n, k=1)
82
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
83
+ sum_radii_sq = (radii[i] + radii[j])**2
84
+ return dist_sq - sum_radii_sq
85
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
86
+
87
+ def boundary_constraint(x):
88
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
89
+ centers, radii = unpack_vars(x)
90
+ return np.concatenate([
91
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
92
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
93
+ ])
94
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
95
+
96
+ # --- Variable Bounds ---
97
+ bounds = []
98
+ for _ in range(n):
99
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
100
+
101
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
102
+ base_initial_centers = np.zeros((n, 2))
103
+ idx = 0
104
+ grid_points = np.linspace(0.1, 0.9, 5)
105
+ for i in range(5):
106
+ for j in range(5):
107
+ if i == 2 and j == 2: continue
108
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
109
+ idx += 1
110
+ base_initial_centers[24] = [0.5, 0.45]
111
+ base_initial_centers[25] = [0.5, 0.55]
112
+
113
+ best_sum_radii = -np.inf
114
+ best_result_x = None
115
+
116
+ # A more granular perturbation schedule for a better exploration/exploitation balance.
117
+ PERTURB_SCHEDULE = [
118
+ (12, 0.035), # Broad exploration with higher perturbation
119
+ (12, 0.010), # Medium-range refinement
120
+ (10, 0.004) # Fine-tuning of promising areas
121
+ ]
122
+
123
+ # High-precision optimizer settings with increased max iterations for the final stage.
124
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
125
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
126
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # Increased maxiter
127
+
128
+ for num_runs_in_tier, perturb_std in PERTURB_SCHEDULE:
129
+ for _ in range(num_runs_in_tier):
130
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
131
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
132
+
133
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
134
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
135
+
136
+ # Stage 1: Maximize sum of areas
137
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
138
+ x_after_s1 = res1.x if res1.success else x0_run
139
+
140
+ # Stage 2: Maximize sum of radii
141
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
142
+ x_after_s2 = res2.x if res2.success else x_after_s1
143
+
144
+ # Stage 3: Further maximize sum of radii with highest precision
145
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
146
+ final_run_x = res3.x if res3.success else x_after_s2
147
+
148
+ _, current_radii = unpack_vars(final_run_x)
149
+ current_sum_radii = np.sum(current_radii)
150
+
151
+ if current_sum_radii > best_sum_radii:
152
+ best_sum_radii = current_sum_radii
153
+ best_result_x = final_run_x
154
+
155
+ # --- Final Result Extraction ---
156
+ # Fallback if all runs fail (highly unlikely)
157
+ if best_result_x is None:
158
+ initial_radii = _compute_initial_radii(base_initial_centers)
159
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
160
+
161
+ final_centers, final_radii = unpack_vars(best_result_x)
162
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
163
+
164
+ return final_centers, final_radii
165
+ # EVOLVE-BLOCK-END
166
+
167
+
168
+ # This part remains fixed (not evolved)
169
+ def run_packing():
170
+ """Run the circle packing constructor for n=26"""
171
+ centers, radii = construct_packing()
172
+ # Calculate the sum of radii
173
+ sum_radii = np.sum(radii)
174
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/original.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid, high-precision,
8
+ multi-start NLP approach. This method is a crossover of several successful prior
9
+ implementations, combining their best features.
10
+
11
+ - **Structure:** A single-phase, multi-start loop (inspired by high-scoring variants)
12
+ runs a three-stage NLP optimization from multiple perturbed starting points.
13
+ - **Precision:** Employs extremely tight solver tolerances for high-precision refinement,
14
+ adopted from the best-performing parent.
15
+ - **Stability:** Utilizes a numerically stable squared-distance non-overlap constraint,
16
+ a key feature from another robust implementation.
17
+ - **Robustness:** Includes stage-by-stage success checks and a fallback mechanism.
18
+ """
19
+ n = 26
20
+
21
+ # --- Helper functions to pack/unpack optimization variables ---
22
+ def pack_vars(centers, radii):
23
+ x = np.zeros(n * 3)
24
+ x[0::3] = centers[:, 0]
25
+ x[1::3] = centers[:, 1]
26
+ x[2::3] = radii
27
+ return x
28
+
29
+ def unpack_vars(x):
30
+ centers_x = x[0::3]
31
+ centers_y = x[1::3]
32
+ radii = x[2::3]
33
+ centers = np.vstack((centers_x, centers_y)).T
34
+ return centers, radii
35
+
36
+ # --- Initial Guess Generation ---
37
+ def _compute_initial_radii(centers, max_iter=200):
38
+ """Iteratively compute max feasible radii for a given set of centers."""
39
+ num_circles = centers.shape[0]
40
+ radii = np.zeros(num_circles)
41
+ MIN_GAP = 1e-8 # Use a small gap for numerical robustness
42
+
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ for _ in range(max_iter):
48
+ had_change = False
49
+ for i in range(num_circles):
50
+ for j in range(i + 1, num_circles):
51
+ dist = np.linalg.norm(centers[i] - centers[j])
52
+ sum_r = radii[i] + radii[j]
53
+ if sum_r > dist - MIN_GAP:
54
+ target_sum_r = max(0.0, dist - MIN_GAP)
55
+ if sum_r > 1e-12:
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # --- Objective Functions for Staged Optimization ---
65
+ def objective_area(x):
66
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
67
+ _, radii = unpack_vars(x)
68
+ return -np.sum(radii**2)
69
+
70
+ def objective_radii(x):
71
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
72
+ _, radii = unpack_vars(x)
73
+ return -np.sum(radii)
74
+
75
+ # --- Constraints (Numerically Stable Formulation) ---
76
+ cons = []
77
+
78
+ def non_overlap_constraint(x):
79
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. This squared formulation avoids sqrt."""
80
+ centers, radii = unpack_vars(x)
81
+ i, j = np.triu_indices(n, k=1)
82
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
83
+ sum_radii_sq = (radii[i] + radii[j])**2
84
+ return dist_sq - sum_radii_sq
85
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
86
+
87
+ def boundary_constraint(x):
88
+ """Constraint: All circles must be inside the [0,1]x[0,1] square."""
89
+ centers, radii = unpack_vars(x)
90
+ return np.concatenate([
91
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
92
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
93
+ ])
94
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
95
+
96
+ # --- Variable Bounds ---
97
+ bounds = []
98
+ for _ in range(n):
99
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
100
+
101
+ # --- Multi-Start Optimizer with Iterative Perturbation ---
102
+ base_initial_centers = np.zeros((n, 2))
103
+ idx = 0
104
+ grid_points = np.linspace(0.1, 0.9, 5)
105
+ for i in range(5):
106
+ for j in range(5):
107
+ if i == 2 and j == 2: continue
108
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
109
+ idx += 1
110
+ base_initial_centers[24] = [0.5, 0.45]
111
+ base_initial_centers[25] = [0.5, 0.55]
112
+
113
+ num_optimization_runs = 24
114
+ best_sum_radii = -np.inf
115
+ best_result_x = None
116
+
117
+ # High-precision optimizer settings from the best-performing parent
118
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
119
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
120
+ options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
121
+
122
+ for run in range(num_optimization_runs):
123
+ # Adaptive perturbation: broad exploration first, then fine-tuning
124
+ perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
125
+
126
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
127
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
128
+
129
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
130
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
131
+
132
+ # Stage 1: Maximize sum of areas
133
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
134
+ x_after_s1 = res1.x if res1.success else x0_run
135
+
136
+ # Stage 2: Maximize sum of radii
137
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
138
+ x_after_s2 = res2.x if res2.success else x_after_s1
139
+
140
+ # Stage 3: Further maximize sum of radii with highest precision
141
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
142
+ final_run_x = res3.x if res3.success else x_after_s2
143
+
144
+ _, current_radii = unpack_vars(final_run_x)
145
+ current_sum_radii = np.sum(current_radii)
146
+
147
+ if current_sum_radii > best_sum_radii:
148
+ best_sum_radii = current_sum_radii
149
+ best_result_x = final_run_x
150
+
151
+ # --- Final Result Extraction ---
152
+ # Fallback if all runs fail (highly unlikely)
153
+ if best_result_x is None:
154
+ initial_radii = _compute_initial_radii(base_initial_centers)
155
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
156
+
157
+ final_centers, final_radii = unpack_vars(best_result_x)
158
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
159
+
160
+ return final_centers, final_radii
161
+ # EVOLVE-BLOCK-END
162
+
163
+
164
+ # This part remains fixed (not evolved)
165
+ def run_packing():
166
+ """Run the circle packing constructor for n=26"""
167
+ centers, radii = construct_packing()
168
+ # Calculate the sum of radii
169
+ sum_radii = np.sum(radii)
170
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/search_replace.txt ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <NAME>
2
+ enhanced_multistart_schedule
3
+ </NAME>
4
+ <DESCRIPTION>
5
+ This change enhances the multi-start optimization strategy by replacing the current two-tier perturbation schedule with a more granular three-tier schedule. The total number of optimization runs is increased from 24 to 34, and the perturbation standard deviations are adjusted for broader exploration (`0.035`), intermediate refinement (`0.010`), and fine-tuning (`0.004`).
6
+
7
+ Additionally, the `maxiter` for the final, highest-precision optimization stage is increased from 3500 to 5000. This allows the solver more iterations to converge under the extremely tight tolerances, increasing the likelihood of finding a higher-quality local optimum.
8
+
9
+ This combined approach invests more computational effort into a more structured search, balancing wide exploration with deep refinement, aiming to push the sum of radii closer to the theoretical maximum by more effectively navigating the solution space.
10
+ </DESCRIPTION>
11
+ <DIFF>
12
+ <<<<<<< SEARCH
13
+ num_optimization_runs = 24
14
+ best_sum_radii = -np.inf
15
+ best_result_x = None
16
+
17
+ # High-precision optimizer settings from the best-performing parent
18
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
19
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
20
+ options_stage3 = {'maxiter': 3500, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
21
+
22
+ for run in range(num_optimization_runs):
23
+ # Adaptive perturbation: broad exploration first, then fine-tuning
24
+ perturb_std = 0.02 if run < num_optimization_runs / 2 else 0.005
25
+
26
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
27
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
28
+
29
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
30
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
31
+
32
+ # Stage 1: Maximize sum of areas
33
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
34
+ x_after_s1 = res1.x if res1.success else x0_run
35
+
36
+ # Stage 2: Maximize sum of radii
37
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
38
+ x_after_s2 = res2.x if res2.success else x_after_s1
39
+
40
+ # Stage 3: Further maximize sum of radii with highest precision
41
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
42
+ final_run_x = res3.x if res3.success else x_after_s2
43
+
44
+ _, current_radii = unpack_vars(final_run_x)
45
+ current_sum_radii = np.sum(current_radii)
46
+
47
+ if current_sum_radii > best_sum_radii:
48
+ best_sum_radii = current_sum_radii
49
+ best_result_x = final_run_x
50
+ =======
51
+ best_sum_radii = -np.inf
52
+ best_result_x = None
53
+
54
+ # A more granular perturbation schedule for a better exploration/exploitation balance.
55
+ PERTURB_SCHEDULE = [
56
+ (12, 0.035), # Broad exploration with higher perturbation
57
+ (12, 0.010), # Medium-range refinement
58
+ (10, 0.004) # Fine-tuning of promising areas
59
+ ]
60
+
61
+ # High-precision optimizer settings with increased max iterations for the final stage.
62
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
63
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
64
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # Increased maxiter
65
+
66
+ for num_runs_in_tier, perturb_std in PERTURB_SCHEDULE:
67
+ for _ in range(num_runs_in_tier):
68
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std, base_initial_centers.shape)
69
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
70
+
71
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
72
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
73
+
74
+ # Stage 1: Maximize sum of areas
75
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
76
+ x_after_s1 = res1.x if res1.success else x0_run
77
+
78
+ # Stage 2: Maximize sum of radii
79
+ res2 = minimize(objective_radii, x_after_s1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
80
+ x_after_s2 = res2.x if res2.success else x_after_s1
81
+
82
+ # Stage 3: Further maximize sum of radii with highest precision
83
+ res3 = minimize(objective_radii, x_after_s2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
84
+ final_run_x = res3.x if res3.success else x_after_s2
85
+
86
+ _, current_radii = unpack_vars(final_run_x)
87
+ current_sum_radii = np.sum(current_radii)
88
+
89
+ if current_sum_radii > best_sum_radii:
90
+ best_sum_radii = current_sum_radii
91
+ best_result_x = final_run_x
92
+ >>>>>>> REPLACE
93
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/edit.diff ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,180 +1,233 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ Constructs an optimized arrangement of 26 circles by formulating the problem
11
+ as a nonlinear program and solving it with scipy.optimize.minimize.
12
+ """
13
+ n = 26
14
+
15
+ # Helper functions to convert between the flat optimization vector and
16
+ # the structured centers/radii arrays. These are defined inside to keep
17
+ # the evolution block self-contained.
18
+ def pack_vars(centers, radii):
19
+ x = np.zeros(n * 3)
20
+ x[0::3] = centers[:, 0]
21
+ x[1::3] = centers[:, 1]
22
+ x[2::3] = radii
23
+ return x
24
+
25
+ def unpack_vars(x):
26
+ centers_x = x[0::3]
27
+ centers_y = x[1::3]
28
+ radii = x[2::3]
29
+ centers = np.vstack((centers_x, centers_y)).T
30
+ return centers, radii
31
+
32
+ # --- 1. Initial Guess Generation ---
33
+ def _compute_initial_radii(centers, max_iter=200):
34
+ """
35
+ Iteratively computes the maximum possible non-overlapping radii for a
36
+ given fixed set of centers, ensuring a small minimum gap.
37
+ """
38
+ num_circles = centers.shape[0]
39
+ radii = np.zeros(num_circles)
40
+ - MIN_GAP_THRESHOLD = 1e-8
41
+ + MIN_GAP_THRESHOLD = 1e-7 # Slightly larger initial gap for more exploration to help initial radii computation
42
+
43
+ # Initialize radii based on the minimum distance to the walls.
44
+ for i in range(num_circles):
45
+ x, y = centers[i]
46
+ radii[i] = min(x, 1 - x, y, 1 - y)
47
+
48
+ # Iteratively shrink radii to resolve overlaps.
49
+ for _ in range(max_iter):
50
+ had_change = False
51
+ for i in range(num_circles):
52
+ for j in range(i + 1, num_circles):
53
+ dist = np.linalg.norm(centers[i] - centers[j])
54
+ sum_r = radii[i] + radii[j]
55
+ if sum_r > dist - MIN_GAP_THRESHOLD:
56
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
57
+ if sum_r > 1e-12: # Avoid division by zero
58
+ scale = target_sum_r / sum_r
59
+ radii[i] *= scale
60
+ radii[j] *= scale
61
+ had_change = True
62
+ if not had_change:
63
+ break
64
+ return radii
65
+
66
+ - # Base layout: Seed the search with a previously found high-quality solution.
67
+ - # This focuses the optimizer on a promising region of the solution space.
68
+ - base_initial_centers = np.array([
69
+ + # --- Define Multiple Initial Base Layouts ---
70
+ + # Guess 1: Proven 5x5 grid with a split center.
71
+ + base_centers_grid = np.zeros((n, 2))
72
+ + idx = 0
73
+ + for i in range(5):
74
+ + for j in range(5):
75
+ + if i == 2 and j == 2:
76
+ + continue
77
+ + base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
78
+ + idx += 1
79
+ + base_centers_grid[24] = [0.5, 0.45]
80
+ + base_centers_grid[25] = [0.5, 0.55]
81
+ +
82
+ + # Guess 2: Dense hexagonal-like grid.
83
+ + def _get_hexagonal_initial_centers():
84
+ + centers_raw = []
85
+ + rows_config = [5, 6, 5, 6, 4] # Approx 26 circles
86
+ + r_base = 0.1 # Base radius for initial hex packing
87
+ + dx = 2 * r_base
88
+ + dy = r_base * np.sqrt(3)
89
+ + current_y = 0.0
90
+ + for r_idx, num_cols in enumerate(rows_config):
91
+ + row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
92
+ + for col_idx in range(num_cols):
93
+ + if len(centers_raw) < n: # Ensure we don't create more than n circles
94
+ + centers_raw.append([row_x_offset + col_idx * dx, current_y])
95
+ + current_y += dy
96
+ + centers_raw = np.array(centers_raw)
97
+ +
98
+ + # Scale and center the hexagonal pattern
99
+ + if centers_raw.size == 0: # Handle edge case if n is too small
100
+ + return np.zeros((n, 2))
101
+ + x_min, y_min = np.min(centers_raw, axis=0)
102
+ + x_max, y_max = np.max(centers_raw, axis=0)
103
+ + scale = 0.99 / max(x_max - x_min, y_max - y_min) if max(x_max - x_min, y_max - y_min) > 0 else 1.0
104
+ + centers = (centers_raw - np.array([x_min, y_min])) * scale
105
+ + current_x_max, current_y_max = np.max(centers, axis=0)
106
+ + offset = (1.0 - np.array([current_x_max, current_y_max])) / 2.0
107
+ + centers += offset
108
+ + return centers[:n] # Trim to exactly n circles if more were generated
109
+ + base_centers_hex = _get_hexagonal_initial_centers()
110
+ +
111
+ + # Guess 3: Seed with a known high-quality result (from prior best).
112
+ + base_centers_best_known = np.array([
113
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
114
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
115
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
116
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
117
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
118
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
119
+ [0.5173, 0.4172], [0.4888, 0.5875]
120
+ ])
121
+
122
+ # --- 2. Define Objective Functions for Staged Optimization ---
123
+ # Stage 1: Maximize total area (sum of radii squared) for a dense packing.
124
+ def objective_area(x):
125
+ _, radii = unpack_vars(x)
126
+ return -np.sum(radii**2)
127
+
128
+ # Stage 2 & 3: Maximize sum of radii (the primary goal).
129
+ def objective_radii(x):
130
+ _, radii = unpack_vars(x)
131
+ return -np.sum(radii)
132
+
133
+ # --- 3. Define Constraints ---
134
+ cons = []
135
+
136
+ # Constraint 1: Non-overlapping circles using numerically stable squared distances.
137
+ def non_overlap_constraint(x):
138
+ centers, radii = unpack_vars(x)
139
+ i, j = np.triu_indices(n, k=1)
140
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
141
+ sum_radii_sq = (radii[i] + radii[j])**2
142
+ return dist_sq - sum_radii_sq
143
+
144
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
145
+
146
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
147
+ def boundary_constraint(x):
148
+ centers, radii = unpack_vars(x)
149
+ return np.concatenate([
150
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
151
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
152
+ ])
153
+
154
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
155
+
156
+ # --- 4. Define Bounds for each variable ---
157
+ + MIN_RADIUS = 1e-6 # Enforce a minimum positive radius to prevent degenerate solutions
158
+ bounds = []
159
+ for _ in range(n):
160
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
161
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)]) # Apply MIN_RADIUS bound
162
+
163
+ # --- 5. Run a Multi-Run, Three-Stage Optimization Strategy ---
164
+ num_optimization_runs = 30 # Increased number of runs for robust exploration
165
+ best_sum_radii = -np.inf
166
+ best_result_x = None
167
+
168
+ # Define progressively tighter optimizer settings for each stage.
169
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
170
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
171
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
172
+
173
+ for run in range(num_optimization_runs):
174
+ - # Use an adaptive perturbation schedule: broad exploration first, then fine-tuning.
175
+ - if run < num_optimization_runs // 2:
176
+ - perturbation_std_dev = 0.020
177
+ + # Cycle through the three base layouts for initial guess.
178
+ + if run % 3 == 0:
179
+ + current_base_centers = base_centers_grid
180
+ + elif run % 3 == 1:
181
+ + current_base_centers = base_centers_hex
182
+ else:
183
+ - perturbation_std_dev = 0.005
184
+ + current_base_centers = base_centers_best_known
185
+ +
186
+ + # Apply a continuous adaptive perturbation schedule.
187
+ + max_perturbation_std_dev = 0.030 # Broader initial exploration
188
+ + min_perturbation_std_dev = 0.001 # Finer refinement towards the end
189
+ + if num_optimization_runs > 1:
190
+ + perturbation_std_dev = max_perturbation_std_dev - (run / (num_optimization_runs - 1)) * \
191
+ + (max_perturbation_std_dev - min_perturbation_std_dev)
192
+ + else: # Handle case of single run gracefully
193
+ + perturbation_std_dev = min_perturbation_std_dev
194
+
195
+ # Create a perturbed starting point for this run
196
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
197
+ + perturbed_centers = current_base_centers + np.random.normal(0, perturbation_std_dev, current_base_centers.shape)
198
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
199
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
200
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
201
+
202
+ # Stage 1: Maximize sum of areas (r^2) for a dense packing.
203
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
204
+ if not result_stage1.success:
205
+ continue # Skip to next run if stage 1 fails
206
+
207
+ # Stage 2: Maximize sum of radii (r) with tight tolerances.
208
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
209
+ if not result_stage2.success:
210
+ continue # Skip if stage 2 fails
211
+
212
+ # Stage 3: Final refinement of radii sum with the tightest tolerances.
213
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
214
+
215
+ # Check the result of the final stage
216
+ _, current_radii = unpack_vars(result_stage3.x)
217
+ current_sum_radii = np.sum(current_radii)
218
+
219
+ # If this run is the best so far, save its result
220
+ if current_sum_radii > best_sum_radii:
221
+ best_sum_radii = current_sum_radii
222
+ best_result_x = result_stage3.x
223
+
224
+ # --- 6. Extract and Return the Best Result ---
225
+ # If no run was successful, fall back to a default computed from the base layout.
226
+ if best_result_x is None:
227
+ - initial_radii = _compute_initial_radii(base_initial_centers)
228
+ - best_result_x = pack_vars(base_initial_centers, initial_radii)
229
+ + initial_radii = _compute_initial_radii(base_centers_best_known) # Use a proven base for fallback
230
+ + best_result_x = pack_vars(base_centers_best_known, initial_radii)
231
+
232
+ final_x = best_result_x
233
+ final_centers, final_radii = unpack_vars(final_x)
234
+
235
+ # Clean up any potential floating point inaccuracies (e.g., small negative radii).
236
+ final_radii = np.maximum(final_radii, 0)
237
+
238
+ return final_centers, final_radii
239
+ # EVOLVE-BLOCK-END
240
+
241
+
242
+ # This part remains fixed (not evolved)
243
+ def run_packing():
244
+ """Run the circle packing constructor for n=26"""
245
+ centers, radii = construct_packing()
246
+ # Calculate the sum of radii
247
+ sum_radii = np.sum(radii)
248
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/main.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by formulating the problem
8
+ as a nonlinear program and solving it with scipy.optimize.minimize.
9
+ """
10
+ n = 26
11
+
12
+ # Helper functions to convert between the flat optimization vector and
13
+ # the structured centers/radii arrays. These are defined inside to keep
14
+ # the evolution block self-contained.
15
+ def pack_vars(centers, radii):
16
+ x = np.zeros(n * 3)
17
+ x[0::3] = centers[:, 0]
18
+ x[1::3] = centers[:, 1]
19
+ x[2::3] = radii
20
+ return x
21
+
22
+ def unpack_vars(x):
23
+ centers_x = x[0::3]
24
+ centers_y = x[1::3]
25
+ radii = x[2::3]
26
+ centers = np.vstack((centers_x, centers_y)).T
27
+ return centers, radii
28
+
29
+ # --- 1. Initial Guess Generation ---
30
+ def _compute_initial_radii(centers, max_iter=200):
31
+ """
32
+ Iteratively computes the maximum possible non-overlapping radii for a
33
+ given fixed set of centers, ensuring a small minimum gap.
34
+ """
35
+ num_circles = centers.shape[0]
36
+ radii = np.zeros(num_circles)
37
+ MIN_GAP_THRESHOLD = 1e-7 # Slightly larger initial gap for more exploration to help initial radii computation
38
+
39
+ # Initialize radii based on the minimum distance to the walls.
40
+ for i in range(num_circles):
41
+ x, y = centers[i]
42
+ radii[i] = min(x, 1 - x, y, 1 - y)
43
+
44
+ # Iteratively shrink radii to resolve overlaps.
45
+ for _ in range(max_iter):
46
+ had_change = False
47
+ for i in range(num_circles):
48
+ for j in range(i + 1, num_circles):
49
+ dist = np.linalg.norm(centers[i] - centers[j])
50
+ sum_r = radii[i] + radii[j]
51
+ if sum_r > dist - MIN_GAP_THRESHOLD:
52
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
53
+ if sum_r > 1e-12: # Avoid division by zero
54
+ scale = target_sum_r / sum_r
55
+ radii[i] *= scale
56
+ radii[j] *= scale
57
+ had_change = True
58
+ if not had_change:
59
+ break
60
+ return radii
61
+
62
+ # --- Define Multiple Initial Base Layouts ---
63
+ # Guess 1: Proven 5x5 grid with a split center.
64
+ base_centers_grid = np.zeros((n, 2))
65
+ idx = 0
66
+ for i in range(5):
67
+ for j in range(5):
68
+ if i == 2 and j == 2:
69
+ continue
70
+ base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
71
+ idx += 1
72
+ base_centers_grid[24] = [0.5, 0.45]
73
+ base_centers_grid[25] = [0.5, 0.55]
74
+
75
+ # Guess 2: Dense hexagonal-like grid.
76
+ def _get_hexagonal_initial_centers():
77
+ centers_raw = []
78
+ rows_config = [5, 6, 5, 6, 4] # Approx 26 circles
79
+ r_base = 0.1 # Base radius for initial hex packing
80
+ dx = 2 * r_base
81
+ dy = r_base * np.sqrt(3)
82
+ current_y = 0.0
83
+ for r_idx, num_cols in enumerate(rows_config):
84
+ row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
85
+ for col_idx in range(num_cols):
86
+ if len(centers_raw) < n: # Ensure we don't create more than n circles
87
+ centers_raw.append([row_x_offset + col_idx * dx, current_y])
88
+ current_y += dy
89
+ centers_raw = np.array(centers_raw)
90
+
91
+ # Scale and center the hexagonal pattern
92
+ if centers_raw.size == 0: # Handle edge case if n is too small
93
+ return np.zeros((n, 2))
94
+ x_min, y_min = np.min(centers_raw, axis=0)
95
+ x_max, y_max = np.max(centers_raw, axis=0)
96
+ scale = 0.99 / max(x_max - x_min, y_max - y_min) if max(x_max - x_min, y_max - y_min) > 0 else 1.0
97
+ centers = (centers_raw - np.array([x_min, y_min])) * scale
98
+ current_x_max, current_y_max = np.max(centers, axis=0)
99
+ offset = (1.0 - np.array([current_x_max, current_y_max])) / 2.0
100
+ centers += offset
101
+ return centers[:n] # Trim to exactly n circles if more were generated
102
+ base_centers_hex = _get_hexagonal_initial_centers()
103
+
104
+ # Guess 3: Seed with a known high-quality result (from prior best).
105
+ base_centers_best_known = np.array([
106
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
107
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
108
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
109
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
110
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
111
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
112
+ [0.5173, 0.4172], [0.4888, 0.5875]
113
+ ])
114
+
115
+ # --- 2. Define Objective Functions for Staged Optimization ---
116
+ # Stage 1: Maximize total area (sum of radii squared) for a dense packing.
117
+ def objective_area(x):
118
+ _, radii = unpack_vars(x)
119
+ return -np.sum(radii**2)
120
+
121
+ # Stage 2 & 3: Maximize sum of radii (the primary goal).
122
+ def objective_radii(x):
123
+ _, radii = unpack_vars(x)
124
+ return -np.sum(radii)
125
+
126
+ # --- 3. Define Constraints ---
127
+ cons = []
128
+
129
+ # Constraint 1: Non-overlapping circles using numerically stable squared distances.
130
+ def non_overlap_constraint(x):
131
+ centers, radii = unpack_vars(x)
132
+ i, j = np.triu_indices(n, k=1)
133
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
134
+ sum_radii_sq = (radii[i] + radii[j])**2
135
+ return dist_sq - sum_radii_sq
136
+
137
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
138
+
139
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
140
+ def boundary_constraint(x):
141
+ centers, radii = unpack_vars(x)
142
+ return np.concatenate([
143
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
144
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
145
+ ])
146
+
147
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
148
+
149
+ # --- 4. Define Bounds for each variable ---
150
+ MIN_RADIUS = 1e-6 # Enforce a minimum positive radius to prevent degenerate solutions
151
+ bounds = []
152
+ for _ in range(n):
153
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)]) # Apply MIN_RADIUS bound
154
+
155
+ # --- 5. Run a Multi-Run, Three-Stage Optimization Strategy ---
156
+ num_optimization_runs = 30 # Increased number of runs for robust exploration
157
+ best_sum_radii = -np.inf
158
+ best_result_x = None
159
+
160
+ # Define progressively tighter optimizer settings for each stage.
161
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
162
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
163
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
164
+
165
+ for run in range(num_optimization_runs):
166
+ # Cycle through the three base layouts for initial guess.
167
+ if run % 3 == 0:
168
+ current_base_centers = base_centers_grid
169
+ elif run % 3 == 1:
170
+ current_base_centers = base_centers_hex
171
+ else:
172
+ current_base_centers = base_centers_best_known
173
+
174
+ # Apply a continuous adaptive perturbation schedule.
175
+ max_perturbation_std_dev = 0.030 # Broader initial exploration
176
+ min_perturbation_std_dev = 0.001 # Finer refinement towards the end
177
+ if num_optimization_runs > 1:
178
+ perturbation_std_dev = max_perturbation_std_dev - (run / (num_optimization_runs - 1)) * \
179
+ (max_perturbation_std_dev - min_perturbation_std_dev)
180
+ else: # Handle case of single run gracefully
181
+ perturbation_std_dev = min_perturbation_std_dev
182
+
183
+ # Create a perturbed starting point for this run
184
+ perturbed_centers = current_base_centers + np.random.normal(0, perturbation_std_dev, current_base_centers.shape)
185
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
186
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
187
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
188
+
189
+ # Stage 1: Maximize sum of areas (r^2) for a dense packing.
190
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
191
+ if not result_stage1.success:
192
+ continue # Skip to next run if stage 1 fails
193
+
194
+ # Stage 2: Maximize sum of radii (r) with tight tolerances.
195
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
196
+ if not result_stage2.success:
197
+ continue # Skip if stage 2 fails
198
+
199
+ # Stage 3: Final refinement of radii sum with the tightest tolerances.
200
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
201
+
202
+ # Check the result of the final stage
203
+ _, current_radii = unpack_vars(result_stage3.x)
204
+ current_sum_radii = np.sum(current_radii)
205
+
206
+ # If this run is the best so far, save its result
207
+ if current_sum_radii > best_sum_radii:
208
+ best_sum_radii = current_sum_radii
209
+ best_result_x = result_stage3.x
210
+
211
+ # --- 6. Extract and Return the Best Result ---
212
+ # If no run was successful, fall back to a default computed from the base layout.
213
+ if best_result_x is None:
214
+ initial_radii = _compute_initial_radii(base_centers_best_known) # Use a proven base for fallback
215
+ best_result_x = pack_vars(base_centers_best_known, initial_radii)
216
+
217
+ final_x = best_result_x
218
+ final_centers, final_radii = unpack_vars(final_x)
219
+
220
+ # Clean up any potential floating point inaccuracies (e.g., small negative radii).
221
+ final_radii = np.maximum(final_radii, 0)
222
+
223
+ return final_centers, final_radii
224
+ # EVOLVE-BLOCK-END
225
+
226
+
227
+ # This part remains fixed (not evolved)
228
+ def run_packing():
229
+ """Run the circle packing constructor for n=26"""
230
+ centers, radii = construct_packing()
231
+ # Calculate the sum of radii
232
+ sum_radii = np.sum(radii)
233
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/original.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by formulating the problem
8
+ as a nonlinear program and solving it with scipy.optimize.minimize.
9
+ """
10
+ n = 26
11
+
12
+ # Helper functions to convert between the flat optimization vector and
13
+ # the structured centers/radii arrays. These are defined inside to keep
14
+ # the evolution block self-contained.
15
+ def pack_vars(centers, radii):
16
+ x = np.zeros(n * 3)
17
+ x[0::3] = centers[:, 0]
18
+ x[1::3] = centers[:, 1]
19
+ x[2::3] = radii
20
+ return x
21
+
22
+ def unpack_vars(x):
23
+ centers_x = x[0::3]
24
+ centers_y = x[1::3]
25
+ radii = x[2::3]
26
+ centers = np.vstack((centers_x, centers_y)).T
27
+ return centers, radii
28
+
29
+ # --- 1. Initial Guess Generation ---
30
+ def _compute_initial_radii(centers, max_iter=200):
31
+ """
32
+ Iteratively computes the maximum possible non-overlapping radii for a
33
+ given fixed set of centers, ensuring a small minimum gap.
34
+ """
35
+ num_circles = centers.shape[0]
36
+ radii = np.zeros(num_circles)
37
+ MIN_GAP_THRESHOLD = 1e-8
38
+
39
+ # Initialize radii based on the minimum distance to the walls.
40
+ for i in range(num_circles):
41
+ x, y = centers[i]
42
+ radii[i] = min(x, 1 - x, y, 1 - y)
43
+
44
+ # Iteratively shrink radii to resolve overlaps.
45
+ for _ in range(max_iter):
46
+ had_change = False
47
+ for i in range(num_circles):
48
+ for j in range(i + 1, num_circles):
49
+ dist = np.linalg.norm(centers[i] - centers[j])
50
+ sum_r = radii[i] + radii[j]
51
+ if sum_r > dist - MIN_GAP_THRESHOLD:
52
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
53
+ if sum_r > 1e-12: # Avoid division by zero
54
+ scale = target_sum_r / sum_r
55
+ radii[i] *= scale
56
+ radii[j] *= scale
57
+ had_change = True
58
+ if not had_change:
59
+ break
60
+ return radii
61
+
62
+ # Base layout: Seed the search with a previously found high-quality solution.
63
+ # This focuses the optimizer on a promising region of the solution space.
64
+ base_initial_centers = np.array([
65
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
66
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
67
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
68
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
69
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
70
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
71
+ [0.5173, 0.4172], [0.4888, 0.5875]
72
+ ])
73
+
74
+ # --- 2. Define Objective Functions for Staged Optimization ---
75
+ # Stage 1: Maximize total area (sum of radii squared) for a dense packing.
76
+ def objective_area(x):
77
+ _, radii = unpack_vars(x)
78
+ return -np.sum(radii**2)
79
+
80
+ # Stage 2 & 3: Maximize sum of radii (the primary goal).
81
+ def objective_radii(x):
82
+ _, radii = unpack_vars(x)
83
+ return -np.sum(radii)
84
+
85
+ # --- 3. Define Constraints ---
86
+ cons = []
87
+
88
+ # Constraint 1: Non-overlapping circles using numerically stable squared distances.
89
+ def non_overlap_constraint(x):
90
+ centers, radii = unpack_vars(x)
91
+ i, j = np.triu_indices(n, k=1)
92
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
93
+ sum_radii_sq = (radii[i] + radii[j])**2
94
+ return dist_sq - sum_radii_sq
95
+
96
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
97
+
98
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
99
+ def boundary_constraint(x):
100
+ centers, radii = unpack_vars(x)
101
+ return np.concatenate([
102
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
103
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
104
+ ])
105
+
106
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
107
+
108
+ # --- 4. Define Bounds for each variable ---
109
+ bounds = []
110
+ for _ in range(n):
111
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
112
+
113
+ # --- 5. Run a Multi-Run, Three-Stage Optimization Strategy ---
114
+ num_optimization_runs = 30 # Increased number of runs for robust exploration
115
+ best_sum_radii = -np.inf
116
+ best_result_x = None
117
+
118
+ # Define progressively tighter optimizer settings for each stage.
119
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
120
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
121
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
122
+
123
+ for run in range(num_optimization_runs):
124
+ # Use an adaptive perturbation schedule: broad exploration first, then fine-tuning.
125
+ if run < num_optimization_runs // 2:
126
+ perturbation_std_dev = 0.020
127
+ else:
128
+ perturbation_std_dev = 0.005
129
+
130
+ # Create a perturbed starting point for this run
131
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
132
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
133
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
134
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
135
+
136
+ # Stage 1: Maximize sum of areas (r^2) for a dense packing.
137
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
138
+ if not result_stage1.success:
139
+ continue # Skip to next run if stage 1 fails
140
+
141
+ # Stage 2: Maximize sum of radii (r) with tight tolerances.
142
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
143
+ if not result_stage2.success:
144
+ continue # Skip if stage 2 fails
145
+
146
+ # Stage 3: Final refinement of radii sum with the tightest tolerances.
147
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
148
+
149
+ # Check the result of the final stage
150
+ _, current_radii = unpack_vars(result_stage3.x)
151
+ current_sum_radii = np.sum(current_radii)
152
+
153
+ # If this run is the best so far, save its result
154
+ if current_sum_radii > best_sum_radii:
155
+ best_sum_radii = current_sum_radii
156
+ best_result_x = result_stage3.x
157
+
158
+ # --- 6. Extract and Return the Best Result ---
159
+ # If no run was successful, fall back to a default computed from the base layout.
160
+ if best_result_x is None:
161
+ initial_radii = _compute_initial_radii(base_initial_centers)
162
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
163
+
164
+ final_x = best_result_x
165
+ final_centers, final_radii = unpack_vars(final_x)
166
+
167
+ # Clean up any potential floating point inaccuracies (e.g., small negative radii).
168
+ final_radii = np.maximum(final_radii, 0)
169
+
170
+ return final_centers, final_radii
171
+ # EVOLVE-BLOCK-END
172
+
173
+
174
+ # This part remains fixed (not evolved)
175
+ def run_packing():
176
+ """Run the circle packing constructor for n=26"""
177
+ centers, radii = construct_packing()
178
+ # Calculate the sum of radii
179
+ sum_radii = np.sum(radii)
180
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/search_replace.txt ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <NAME>
2
+ revert_to_multi_base_and_smooth_perturbation
3
+ </NAME>
4
+
5
+ <DESCRIPTION>
6
+ This edit re-integrates successful strategies from prior generations to improve the robustness and exploration capabilities of the optimizer.
7
+
8
+ 1. **Multiple Initial Base Layouts**: Instead of relying on a single 'best-known' initial configuration, the optimizer now cycles through three distinct base layouts: a grid, a hexagonal pattern, and the previously identified best-known solution. This ensures a more diverse set of starting points, increasing the likelihood of finding a global optimum by exploring different regions of the solution space. This addresses the "Diversify Base Initial Layouts" recommendation.
9
+
10
+ 2. **Smoother Adaptive Perturbation Schedule**: The perturbation applied to initial centers now follows a continuous linear decay from a broader exploration (0.030 standard deviation) to a finer refinement (0.001 standard deviation) over the course of the optimization runs. This replaces the previous two-tiered perturbation and allows for a more gradual transition from global exploration to local search, potentially improving convergence. This directly implements the "Smoother, Continuous Adaptive Perturbation Schedule" recommendation.
11
+
12
+ 3. **Non-Zero Minimum Radius Constraint**: A `MIN_RADIUS = 1e-6` bound is re-introduced for all circle radii during optimization. This prevents degenerate solutions where radii might approach zero due to numerical issues, ensuring more stable and physically meaningful results. This implements the "Enforce a Non-Zero Minimum Radius Constraint" recommendation.
13
+
14
+ 4. **Adjusted Initial Radii Gap**: The `MIN_GAP_THRESHOLD` in `_compute_initial_radii` is slightly increased from `1e-8` to `1e-7`. This provides a bit more 'breathing room' in the initial radii calculation, which can sometimes help the NLP solver avoid getting stuck in overly tight configurations early on.
15
+ </DESCRIPTION>
16
+
17
+ <DIFF>
18
+ <<<<<<< SEARCH
19
+ MIN_GAP_THRESHOLD = 1e-8
20
+
21
+ # Initialize radii based on the minimum distance to the walls.
22
+ for i in range(num_circles):
23
+ x, y = centers[i]
24
+ radii[i] = min(x, 1 - x, y, 1 - y)
25
+
26
+ # Iteratively shrink radii to resolve overlaps.
27
+ for _ in range(max_iter):
28
+ had_change = False
29
+ for i in range(num_circles):
30
+ for j in range(i + 1, num_circles):
31
+ dist = np.linalg.norm(centers[i] - centers[j])
32
+ sum_r = radii[i] + radii[j]
33
+ if sum_r > dist - MIN_GAP_THRESHOLD:
34
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
35
+ if sum_r > 1e-12: # Avoid division by zero
36
+ scale = target_sum_r / sum_r
37
+ radii[i] *= scale
38
+ radii[j] *= scale
39
+ had_change = True
40
+ if not had_change:
41
+ break
42
+ return radii
43
+
44
+ # Base layout: Seed the search with a previously found high-quality solution.
45
+ # This focuses the optimizer on a promising region of the solution space.
46
+ base_initial_centers = np.array([
47
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
48
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
49
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
50
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
51
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
52
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
53
+ [0.5173, 0.4172], [0.4888, 0.5875]
54
+ ])
55
+
56
+ # --- 2. Define Objective Functions for Staged Optimization ---
57
+ # Stage 1: Maximize total area (sum of radii squared) for a dense packing.
58
+ def objective_area(x):
59
+ _, radii = unpack_vars(x)
60
+ return -np.sum(radii**2)
61
+
62
+ # Stage 2 & 3: Maximize sum of radii (the primary goal).
63
+ def objective_radii(x):
64
+ _, radii = unpack_vars(x)
65
+ return -np.sum(radii)
66
+
67
+ # --- 3. Define Constraints ---
68
+ cons = []
69
+
70
+ # Constraint 1: Non-overlapping circles using numerically stable squared distances.
71
+ def non_overlap_constraint(x):
72
+ centers, radii = unpack_vars(x)
73
+ i, j = np.triu_indices(n, k=1)
74
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
75
+ sum_radii_sq = (radii[i] + radii[j])**2
76
+ return dist_sq - sum_radii_sq
77
+
78
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
79
+
80
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
81
+ def boundary_constraint(x):
82
+ centers, radii = unpack_vars(x)
83
+ return np.concatenate([
84
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
85
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
86
+ ])
87
+
88
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
89
+
90
+ # --- 4. Define Bounds for each variable ---
91
+ bounds = []
92
+ for _ in range(n):
93
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
94
+
95
+ # --- 5. Run a Multi-Run, Three-Stage Optimization Strategy ---
96
+ num_optimization_runs = 30 # Increased number of runs for robust exploration
97
+ best_sum_radii = -np.inf
98
+ best_result_x = None
99
+
100
+ # Define progressively tighter optimizer settings for each stage.
101
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
102
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
103
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
104
+
105
+ for run in range(num_optimization_runs):
106
+ # Use an adaptive perturbation schedule: broad exploration first, then fine-tuning.
107
+ if run < num_optimization_runs // 2:
108
+ perturbation_std_dev = 0.020
109
+ else:
110
+ perturbation_std_dev = 0.005
111
+
112
+ # Create a perturbed starting point for this run
113
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
114
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
115
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
116
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
117
+
118
+ # Stage 1: Maximize sum of areas (r^2) for a dense packing.
119
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
120
+ if not result_stage1.success:
121
+ continue # Skip to next run if stage 1 fails
122
+
123
+ # Stage 2: Maximize sum of radii (r) with tight tolerances.
124
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
125
+ if not result_stage2.success:
126
+ continue # Skip if stage 2 fails
127
+
128
+ # Stage 3: Final refinement of radii sum with the tightest tolerances.
129
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
130
+
131
+ # Check the result of the final stage
132
+ _, current_radii = unpack_vars(result_stage3.x)
133
+ current_sum_radii = np.sum(current_radii)
134
+
135
+ # If this run is the best so far, save its result
136
+ if current_sum_radii > best_sum_radii:
137
+ best_sum_radii = current_sum_radii
138
+ best_result_x = result_stage3.x
139
+
140
+ # --- 6. Extract and Return the Best Result ---
141
+ # If no run was successful, fall back to a default computed from the base layout.
142
+ if best_result_x is None:
143
+ initial_radii = _compute_initial_radii(base_initial_centers)
144
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
145
+ =======
146
+ MIN_GAP_THRESHOLD = 1e-7 # Slightly larger initial gap for more exploration to help initial radii computation
147
+
148
+ # Initialize radii based on the minimum distance to the walls.
149
+ for i in range(num_circles):
150
+ x, y = centers[i]
151
+ radii[i] = min(x, 1 - x, y, 1 - y)
152
+
153
+ # Iteratively shrink radii to resolve overlaps.
154
+ for _ in range(max_iter):
155
+ had_change = False
156
+ for i in range(num_circles):
157
+ for j in range(i + 1, num_circles):
158
+ dist = np.linalg.norm(centers[i] - centers[j])
159
+ sum_r = radii[i] + radii[j]
160
+ if sum_r > dist - MIN_GAP_THRESHOLD:
161
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
162
+ if sum_r > 1e-12: # Avoid division by zero
163
+ scale = target_sum_r / sum_r
164
+ radii[i] *= scale
165
+ radii[j] *= scale
166
+ had_change = True
167
+ if not had_change:
168
+ break
169
+ return radii
170
+
171
+ # --- Define Multiple Initial Base Layouts ---
172
+ # Guess 1: Proven 5x5 grid with a split center.
173
+ base_centers_grid = np.zeros((n, 2))
174
+ idx = 0
175
+ for i in range(5):
176
+ for j in range(5):
177
+ if i == 2 and j == 2:
178
+ continue
179
+ base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
180
+ idx += 1
181
+ base_centers_grid[24] = [0.5, 0.45]
182
+ base_centers_grid[25] = [0.5, 0.55]
183
+
184
+ # Guess 2: Dense hexagonal-like grid.
185
+ def _get_hexagonal_initial_centers():
186
+ centers_raw = []
187
+ rows_config = [5, 6, 5, 6, 4] # Approx 26 circles
188
+ r_base = 0.1 # Base radius for initial hex packing
189
+ dx = 2 * r_base
190
+ dy = r_base * np.sqrt(3)
191
+ current_y = 0.0
192
+ for r_idx, num_cols in enumerate(rows_config):
193
+ row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
194
+ for col_idx in range(num_cols):
195
+ if len(centers_raw) < n: # Ensure we don't create more than n circles
196
+ centers_raw.append([row_x_offset + col_idx * dx, current_y])
197
+ current_y += dy
198
+ centers_raw = np.array(centers_raw)
199
+
200
+ # Scale and center the hexagonal pattern
201
+ if centers_raw.size == 0: # Handle edge case if n is too small
202
+ return np.zeros((n, 2))
203
+ x_min, y_min = np.min(centers_raw, axis=0)
204
+ x_max, y_max = np.max(centers_raw, axis=0)
205
+ scale = 0.99 / max(x_max - x_min, y_max - y_min) if max(x_max - x_min, y_max - y_min) > 0 else 1.0
206
+ centers = (centers_raw - np.array([x_min, y_min])) * scale
207
+ current_x_max, current_y_max = np.max(centers, axis=0)
208
+ offset = (1.0 - np.array([current_x_max, current_y_max])) / 2.0
209
+ centers += offset
210
+ return centers[:n] # Trim to exactly n circles if more were generated
211
+ base_centers_hex = _get_hexagonal_initial_centers()
212
+
213
+ # Guess 3: Seed with a known high-quality result (from prior best).
214
+ base_centers_best_known = np.array([
215
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
216
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
217
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
218
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
219
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
220
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
221
+ [0.5173, 0.4172], [0.4888, 0.5875]
222
+ ])
223
+
224
+ # --- 2. Define Objective Functions for Staged Optimization ---
225
+ # Stage 1: Maximize total area (sum of radii squared) for a dense packing.
226
+ def objective_area(x):
227
+ _, radii = unpack_vars(x)
228
+ return -np.sum(radii**2)
229
+
230
+ # Stage 2 & 3: Maximize sum of radii (the primary goal).
231
+ def objective_radii(x):
232
+ _, radii = unpack_vars(x)
233
+ return -np.sum(radii)
234
+
235
+ # --- 3. Define Constraints ---
236
+ cons = []
237
+
238
+ # Constraint 1: Non-overlapping circles using numerically stable squared distances.
239
+ def non_overlap_constraint(x):
240
+ centers, radii = unpack_vars(x)
241
+ i, j = np.triu_indices(n, k=1)
242
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
243
+ sum_radii_sq = (radii[i] + radii[j])**2
244
+ return dist_sq - sum_radii_sq
245
+
246
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
247
+
248
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
249
+ def boundary_constraint(x):
250
+ centers, radii = unpack_vars(x)
251
+ return np.concatenate([
252
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
253
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
254
+ ])
255
+
256
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
257
+
258
+ # --- 4. Define Bounds for each variable ---
259
+ MIN_RADIUS = 1e-6 # Enforce a minimum positive radius to prevent degenerate solutions
260
+ bounds = []
261
+ for _ in range(n):
262
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)]) # Apply MIN_RADIUS bound
263
+
264
+ # --- 5. Run a Multi-Run, Three-Stage Optimization Strategy ---
265
+ num_optimization_runs = 30 # Increased number of runs for robust exploration
266
+ best_sum_radii = -np.inf
267
+ best_result_x = None
268
+
269
+ # Define progressively tighter optimizer settings for each stage.
270
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
271
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
272
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
273
+
274
+ for run in range(num_optimization_runs):
275
+ # Cycle through the three base layouts for initial guess.
276
+ if run % 3 == 0:
277
+ current_base_centers = base_centers_grid
278
+ elif run % 3 == 1:
279
+ current_base_centers = base_centers_hex
280
+ else:
281
+ current_base_centers = base_centers_best_known
282
+
283
+ # Apply a continuous adaptive perturbation schedule.
284
+ max_perturbation_std_dev = 0.030 # Broader initial exploration
285
+ min_perturbation_std_dev = 0.001 # Finer refinement towards the end
286
+ if num_optimization_runs > 1:
287
+ perturbation_std_dev = max_perturbation_std_dev - (run / (num_optimization_runs - 1)) * \
288
+ (max_perturbation_std_dev - min_perturbation_std_dev)
289
+ else: # Handle case of single run gracefully
290
+ perturbation_std_dev = min_perturbation_std_dev
291
+
292
+ # Create a perturbed starting point for this run
293
+ perturbed_centers = current_base_centers + np.random.normal(0, perturbation_std_dev, current_base_centers.shape)
294
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
295
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
296
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
297
+
298
+ # Stage 1: Maximize sum of areas (r^2) for a dense packing.
299
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
300
+ if not result_stage1.success:
301
+ continue # Skip to next run if stage 1 fails
302
+
303
+ # Stage 2: Maximize sum of radii (r) with tight tolerances.
304
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
305
+ if not result_stage2.success:
306
+ continue # Skip if stage 2 fails
307
+
308
+ # Stage 3: Final refinement of radii sum with the tightest tolerances.
309
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
310
+
311
+ # Check the result of the final stage
312
+ _, current_radii = unpack_vars(result_stage3.x)
313
+ current_sum_radii = np.sum(current_radii)
314
+
315
+ # If this run is the best so far, save its result
316
+ if current_sum_radii > best_sum_radii:
317
+ best_sum_radii = current_sum_radii
318
+ best_result_x = result_stage3.x
319
+
320
+ # --- 6. Extract and Return the Best Result ---
321
+ # If no run was successful, fall back to a default computed from the base layout.
322
+ if best_result_x is None:
323
+ initial_radii = _compute_initial_radii(base_centers_best_known) # Use a proven base for fallback
324
+ best_result_x = pack_vars(base_centers_best_known, initial_radii)
325
+ >>>>>>> REPLACE
326
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/edit.diff ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,168 +1,264 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ - Constructs an optimized arrangement of 26 circles by seeding a three-stage
11
+ - NLP with a known high-quality solution. This method leverages expert knowledge
12
+ - by starting its search from a proven configuration, uses numerically stable
13
+ - constraints, and intensifies the search with more runs and iterations.
14
+ + Constructs an optimized arrangement of 26 circles using a Randomized Hybrid
15
+ + Seeded Optimization (RHSO) approach. This method dynamically generates diverse
16
+ + initial center configurations, employs a continuous adaptive perturbation
17
+ + schedule, uses a hybrid objective for the first NLP stage, and enforces
18
+ + non-zero radii, leading to a robust and high-precision search.
19
+ """
20
+ n = 26
21
+
22
+ - # Helper functions to convert between the flat optimization vector and
23
+ - # the structured centers/radii arrays.
24
+ + # --- Helper functions to pack/unpack optimization variables ---
25
+ def pack_vars(centers, radii):
26
+ x = np.zeros(n * 3)
27
+ x[0::3] = centers[:, 0]
28
+ x[1::3] = centers[:, 1]
29
+ x[2::3] = radii
30
+ return x
31
+
32
+ def unpack_vars(x):
33
+ centers_x = x[0::3]
34
+ centers_y = x[1::3]
35
+ radii = x[2::3]
36
+ centers = np.vstack((centers_x, centers_y)).T
37
+ return centers, radii
38
+
39
+ - # --- 1. Initial Guess Generation ---
40
+ - def _compute_initial_radii(centers, max_iter=200):
41
+ - """
42
+ - Iteratively computes the maximum possible non-overlapping radii for a
43
+ - given fixed set of centers, ensuring a small minimum gap.
44
+ - """
45
+ - num_circles = centers.shape[0]
46
+ - radii = np.zeros(num_circles)
47
+ - MIN_GAP_THRESHOLD = 1e-8
48
+ -
49
+ - for i in range(num_circles):
50
+ - x, y = centers[i]
51
+ - radii[i] = min(x, 1 - x, y, 1 - y)
52
+ -
53
+ - for _ in range(max_iter):
54
+ - had_change = False
55
+ + # --- Initial Guess Generation Functions ---
56
+ +
57
+ + def _generate_random_grid_centers(num_circles, perturb_std_dev):
58
+ + grid_dim = int(np.ceil(np.sqrt(num_circles)))
59
+ + x_coords = np.linspace(0.1, 0.9, grid_dim)
60
+ + y_coords = np.linspace(0.1, 0.9, grid_dim)
61
+ + centers_base = np.array([[x, y] for x in x_coords for y in y_coords])
62
+ +
63
+ + # If the grid is too small, fill with random, if too large, trim
64
+ + if len(centers_base) < num_circles:
65
+ + centers_base = np.vstack([centers_base, np.random.rand(num_circles - len(centers_base), 2)])
66
+ + centers_base = centers_base[:num_circles]
67
+ +
68
+ + perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
69
+ + return np.clip(perturbed_centers, 0.0, 1.0)
70
+ +
71
+ + def _generate_hexagonal_centers(num_circles, perturb_std_dev):
72
+ + centers_raw = []
73
+ + # Adjusted rows config to better fit 26 circles
74
+ + # Example: 5+6+5+6+4 = 26
75
+ + rows_config = [5, 6, 5, 6, 4]
76
+ + r_approx = 0.11 # Approximate radius for initial spacing
77
+ + dx = 2 * r_approx * 0.9 # Spacing between centers in a row
78
+ + dy = r_approx * np.sqrt(3) * 0.9 # Spacing between rows
79
+ +
80
+ + current_y = 0.0
81
+ + for r_idx, num_cols in enumerate(rows_config):
82
+ + if len(centers_raw) >= num_circles:
83
+ + break
84
+ +
85
+ + # Alternate offset for hexagonal packing
86
+ + row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
87
+ +
88
+ + # Adjust starting x to roughly center the pattern horizontally
89
+ + total_row_width = (num_cols - 1) * dx
90
+ + start_x = (1.0 - total_row_width) / 2.0
91
+ +
92
+ + for col_idx in range(num_cols):
93
+ + if len(centers_raw) >= num_circles:
94
+ + break
95
+ + centers_raw.append([start_x + row_x_offset + col_idx * dx, current_y])
96
+ + current_y += dy
97
+ +
98
+ + centers_base = np.array(centers_raw[:num_circles])
99
+ +
100
+ + # Scale and center the generated pattern within [0,1]
101
+ + if len(centers_base) > 0:
102
+ + x_min, y_min = np.min(centers_base, axis=0)
103
+ + x_max, y_max = np.max(centers_base, axis=0)
104
+ + scale_factor = 0.95 / max(x_max - x_min, y_max - y_min, 1e-6)
105
+ + centers_base = (centers_base - np.array([x_min, y_min])) * scale_factor
106
+ + offset = (1.0 - np.array([np.max(centers_base[:,0]), np.max(centers_base[:,1])])) / 2.0
107
+ + centers_base += offset
108
+ + else: # Fallback if for some reason hex generation fails
109
+ + centers_base = np.random.rand(num_circles, 2) * 0.8 + 0.1 # Random, slightly inward
110
+ +
111
+ + perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
112
+ + return np.clip(perturbed_centers, 0.0, 1.0)
113
+ +
114
+ + def _generate_repulsed_random_centers(num_circles, perturb_std_dev, repulsion_iters=10):
115
+ + centers_base = np.random.rand(num_circles, 2)
116
+ +
117
+ + for _ in range(repulsion_iters):
118
+ + forces = np.zeros_like(centers_base)
119
+ for i in range(num_circles):
120
+ for j in range(i + 1, num_circles):
121
+ - dist = np.linalg.norm(centers[i] - centers[j])
122
+ - sum_r = radii[i] + radii[j]
123
+ - if sum_r > dist - MIN_GAP_THRESHOLD:
124
+ - target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
125
+ - if sum_r > 1e-12:
126
+ - scale = target_sum_r / sum_r
127
+ - radii[i] *= scale
128
+ - radii[j] *= scale
129
+ - had_change = True
130
+ - if not had_change:
131
+ - break
132
+ - return radii
133
+ -
134
+ - # Base layout: Seed with the best-known previous result to focus the search.
135
+ - base_initial_centers = np.array([
136
+ + diff = centers_base[i] - centers_base[j]
137
+ + dist_sq = np.sum(diff**2)
138
+ + if dist_sq < 1e-6: dist_sq = 1e-6
139
+ + force_magnitude = 0.005 / dist_sq # Inverse square repulsion
140
+ + forces[i] += diff * force_magnitude
141
+ + forces[j] -= diff * force_magnitude
142
+ +
143
+ + # Boundary repulsion (stronger closer to edge)
144
+ + forces[:,0] += 0.01 * (1 / (centers_base[:,0] + 1e-6)**2 - 1 / (1 - centers_base[:,0] + 1e-6)**2)
145
+ + forces[:,1] += 0.01 * (1 / (centers_base[:,1] + 1e-6)**2 - 1 / (1 - centers_base[:,1] + 1e-6)**2)
146
+ +
147
+ + centers_base = centers_base + forces * 0.01
148
+ + centers_base = np.clip(centers_base, 0.0, 1.0)
149
+ +
150
+ + perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
151
+ + return np.clip(perturbed_centers, 0.0, 1.0)
152
+ +
153
+ + # Base layout: Hardcoded best-known previous result
154
+ + hardcoded_best_known_centers = np.array([
155
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
156
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
157
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
158
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
159
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
160
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
161
+ [0.5173, 0.4172], [0.4888, 0.5875]
162
+ ])
163
+
164
+ - # --- 2. Define Objective Functions for Staged Optimization ---
165
+ - def objective_area(x):
166
+ + # --- Radii Computation for Initial Guess ---
167
+ + def _compute_initial_radii(centers, max_iter=200):
168
+ + num_circles = centers.shape[0]
169
+ + radii = np.zeros(num_circles)
170
+ +
171
+ + for i in range(num_circles):
172
+ + x, y = centers[i]
173
+ + radii[i] = min(x, 1 - x, y, 1 - y)
174
+ +
175
+ + for iter_step in range(max_iter):
176
+ + # Gradually reduce the minimum gap threshold for tighter packing
177
+ + # Starts from 1e-5 and smoothly decays to 1e-9
178
+ + MIN_GAP_THRESHOLD = 1e-5 * (1 - iter_step / max_iter) + 1e-9 * (iter_step / max_iter)
179
+ +
180
+ + had_change = False
181
+ + for i in range(num_circles):
182
+ + for j in range(i + 1, num_circles):
183
+ + dist = np.linalg.norm(centers[i] - centers[j])
184
+ + sum_r = radii[i] + radii[j]
185
+ + if sum_r > dist - MIN_GAP_THRESHOLD:
186
+ + target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
187
+ + if sum_r > 1e-12: # Avoid division by zero
188
+ + scale = target_sum_r / sum_r
189
+ + radii[i] *= scale
190
+ + radii[j] *= scale
191
+ + had_change = True
192
+ + if not had_change and iter_step > max_iter / 2:
193
+ + break
194
+ + return radii
195
+ +
196
+ + # --- Objective Functions for Staged Optimization ---
197
+ + def objective_hybrid(x, alpha=0.2): # Hybrid objective: balance area and sum of radii
198
+ _, radii = unpack_vars(x)
199
+ - return -np.sum(radii**2)
200
+ -
201
+ - def objective_radii(x):
202
+ + return - (alpha * np.sum(radii**2) + (1 - alpha) * np.sum(radii))
203
+ +
204
+ + def objective_radii(x): # Main objective: maximize sum of radii
205
+ _, radii = unpack_vars(x)
206
+ return -np.sum(radii)
207
+
208
+ - # --- 3. Define Constraints ---
209
+ + # --- Constraints (Numerically Stable Formulation) ---
210
+ cons = []
211
+
212
+ - # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
213
+ def non_overlap_constraint(x):
214
+ centers, radii = unpack_vars(x)
215
+ i, j = np.triu_indices(n, k=1)
216
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
217
+ sum_radii_sq = (radii[i] + radii[j])**2
218
+ return dist_sq - sum_radii_sq
219
+ -
220
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
221
+
222
+ - # Constraint 2: Circles must be within the [0,1] x [0,1] square.
223
+ def boundary_constraint(x):
224
+ centers, radii = unpack_vars(x)
225
+ return np.concatenate([
226
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
227
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
228
+ ])
229
+ -
230
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
231
+
232
+ - # --- 4. Define Bounds for each variable ---
233
+ + # --- Variable Bounds ---
234
+ + MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for stability
235
+ bounds = []
236
+ for _ in range(n):
237
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
238
+ -
239
+ - # --- 5. Run the Optimizer with an Intensified Search Strategy ---
240
+ - num_optimization_runs = 30
241
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
242
+ +
243
+ + # --- Multi-Start Optimizer with Dynamic Initial Guesses and Adaptive Perturbation ---
244
+ + num_optimization_runs = 40 # Increased runs for broader search
245
+ best_sum_radii = -np.inf
246
+ best_result_x = None
247
+ + current_best_centers_found = None # Stores centers of the best solution found so far
248
+
249
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
250
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
251
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
252
+
253
+ for run in range(num_optimization_runs):
254
+ - if run < num_optimization_runs // 2:
255
+ - perturbation_std_dev = 0.020
256
+ - else:
257
+ - perturbation_std_dev = 0.005
258
+ -
259
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
260
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
261
+ -
262
+ + # Continuous adaptive perturbation schedule
263
+ + max_perturbation_std_dev = 0.030 # Broader initial perturbation
264
+ + min_perturbation_std_dev = 0.001 # Finer final perturbation
265
+ + perturbation_std_dev = max_perturbation_std_dev * (1 - run / (num_optimization_runs - 1)) + \
266
+ + min_perturbation_std_dev * (run / (num_optimization_runs - 1))
267
+ +
268
+ + # Dynamic initial center generation based on run phase
269
+ + if run < num_optimization_runs * 0.25: # First 25%: Random Grid
270
+ + perturbed_centers = _generate_random_grid_centers(n, perturbation_std_dev)
271
+ + elif run < num_optimization_runs * 0.5: # Next 25%: Repulsed Random
272
+ + perturbed_centers = _generate_repulsed_random_centers(n, perturbation_std_dev)
273
+ + elif run < num_optimization_runs * 0.75: # Next 25%: Hexagonal
274
+ + perturbed_centers = _generate_hexagonal_centers(n, perturbation_std_dev)
275
+ + else: # Last 25%: Perturb best-known (either hardcoded or dynamically found)
276
+ + if current_best_centers_found is not None:
277
+ + centers_base_for_perturb = current_best_centers_found
278
+ + else:
279
+ + centers_base_for_perturb = hardcoded_best_known_centers
280
+ + perturbed_centers = centers_base_for_perturb + np.random.normal(0, perturbation_std_dev, centers_base_for_perturb.shape)
281
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
282
+ +
283
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
284
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
285
+
286
+ - # Stage 1: Maximize sum of areas (r^2).
287
+ - result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
288
+ + # Stage 1: Maximize hybrid objective (area and radii)
289
+ + result_stage1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
290
+ if not result_stage1.success:
291
+ continue
292
+
293
+ - # Stage 2: Maximize sum of radii (r).
294
+ + # Stage 2: Maximize sum of radii
295
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
296
+ if not result_stage2.success:
297
+ continue
298
+
299
+ - # Stage 3: Final refinement with the tightest tolerances.
300
+ + # Stage 3: Final refinement with tightest tolerances
301
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
302
+
303
+ _, current_radii = unpack_vars(result_stage3.x)
304
+ current_sum_radii = np.sum(current_radii)
305
+
306
+ if current_sum_radii > best_sum_radii:
307
+ best_sum_radii = current_sum_radii
308
+ best_result_x = result_stage3.x
309
+ -
310
+ - # --- 6. Extract and Return the Best Result ---
311
+ + # Update the best centers found so far for future perturbations
312
+ + current_best_centers_found, _ = unpack_vars(best_result_x)
313
+ +
314
+ + # --- Final Result Extraction ---
315
+ if best_result_x is None:
316
+ - initial_radii = _compute_initial_radii(base_initial_centers)
317
+ - best_result_x = pack_vars(base_initial_centers, initial_radii)
318
+ + # Fallback if all runs failed to converge meaningfully
319
+ + initial_radii = _compute_initial_radii(hardcoded_best_known_centers)
320
+ + best_result_x = pack_vars(hardcoded_best_known_centers, initial_radii)
321
+
322
+ final_centers, final_radii = unpack_vars(best_result_x)
323
+ - final_radii = np.maximum(final_radii, 0)
324
+ + final_radii = np.maximum(final_radii, 0) # Ensure no negative radii
325
+
326
+ return final_centers, final_radii
327
+ # EVOLVE-BLOCK-END
328
+
329
+
330
+ # This part remains fixed (not evolved)
331
+ def run_packing():
332
+ """Run the circle packing constructor for n=26"""
333
+ centers, radii = construct_packing()
334
+ # Calculate the sum of radii
335
+ sum_radii = np.sum(radii)
336
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/main.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a Randomized Hybrid
8
+ Seeded Optimization (RHSO) approach. This method dynamically generates diverse
9
+ initial center configurations, employs a continuous adaptive perturbation
10
+ schedule, uses a hybrid objective for the first NLP stage, and enforces
11
+ non-zero radii, leading to a robust and high-precision search.
12
+ """
13
+ n = 26
14
+
15
+ # --- Helper functions to pack/unpack optimization variables ---
16
+ def pack_vars(centers, radii):
17
+ x = np.zeros(n * 3)
18
+ x[0::3] = centers[:, 0]
19
+ x[1::3] = centers[:, 1]
20
+ x[2::3] = radii
21
+ return x
22
+
23
+ def unpack_vars(x):
24
+ centers_x = x[0::3]
25
+ centers_y = x[1::3]
26
+ radii = x[2::3]
27
+ centers = np.vstack((centers_x, centers_y)).T
28
+ return centers, radii
29
+
30
+ # --- Initial Guess Generation Functions ---
31
+
32
+ def _generate_random_grid_centers(num_circles, perturb_std_dev):
33
+ grid_dim = int(np.ceil(np.sqrt(num_circles)))
34
+ x_coords = np.linspace(0.1, 0.9, grid_dim)
35
+ y_coords = np.linspace(0.1, 0.9, grid_dim)
36
+ centers_base = np.array([[x, y] for x in x_coords for y in y_coords])
37
+
38
+ # If the grid is too small, fill with random, if too large, trim
39
+ if len(centers_base) < num_circles:
40
+ centers_base = np.vstack([centers_base, np.random.rand(num_circles - len(centers_base), 2)])
41
+ centers_base = centers_base[:num_circles]
42
+
43
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
44
+ return np.clip(perturbed_centers, 0.0, 1.0)
45
+
46
+ def _generate_hexagonal_centers(num_circles, perturb_std_dev):
47
+ centers_raw = []
48
+ # Adjusted rows config to better fit 26 circles
49
+ # Example: 5+6+5+6+4 = 26
50
+ rows_config = [5, 6, 5, 6, 4]
51
+ r_approx = 0.11 # Approximate radius for initial spacing
52
+ dx = 2 * r_approx * 0.9 # Spacing between centers in a row
53
+ dy = r_approx * np.sqrt(3) * 0.9 # Spacing between rows
54
+
55
+ current_y = 0.0
56
+ for r_idx, num_cols in enumerate(rows_config):
57
+ if len(centers_raw) >= num_circles:
58
+ break
59
+
60
+ # Alternate offset for hexagonal packing
61
+ row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
62
+
63
+ # Adjust starting x to roughly center the pattern horizontally
64
+ total_row_width = (num_cols - 1) * dx
65
+ start_x = (1.0 - total_row_width) / 2.0
66
+
67
+ for col_idx in range(num_cols):
68
+ if len(centers_raw) >= num_circles:
69
+ break
70
+ centers_raw.append([start_x + row_x_offset + col_idx * dx, current_y])
71
+ current_y += dy
72
+
73
+ centers_base = np.array(centers_raw[:num_circles])
74
+
75
+ # Scale and center the generated pattern within [0,1]
76
+ if len(centers_base) > 0:
77
+ x_min, y_min = np.min(centers_base, axis=0)
78
+ x_max, y_max = np.max(centers_base, axis=0)
79
+ scale_factor = 0.95 / max(x_max - x_min, y_max - y_min, 1e-6)
80
+ centers_base = (centers_base - np.array([x_min, y_min])) * scale_factor
81
+ offset = (1.0 - np.array([np.max(centers_base[:,0]), np.max(centers_base[:,1])])) / 2.0
82
+ centers_base += offset
83
+ else: # Fallback if for some reason hex generation fails
84
+ centers_base = np.random.rand(num_circles, 2) * 0.8 + 0.1 # Random, slightly inward
85
+
86
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
87
+ return np.clip(perturbed_centers, 0.0, 1.0)
88
+
89
+ def _generate_repulsed_random_centers(num_circles, perturb_std_dev, repulsion_iters=10):
90
+ centers_base = np.random.rand(num_circles, 2)
91
+
92
+ for _ in range(repulsion_iters):
93
+ forces = np.zeros_like(centers_base)
94
+ for i in range(num_circles):
95
+ for j in range(i + 1, num_circles):
96
+ diff = centers_base[i] - centers_base[j]
97
+ dist_sq = np.sum(diff**2)
98
+ if dist_sq < 1e-6: dist_sq = 1e-6
99
+ force_magnitude = 0.005 / dist_sq # Inverse square repulsion
100
+ forces[i] += diff * force_magnitude
101
+ forces[j] -= diff * force_magnitude
102
+
103
+ # Boundary repulsion (stronger closer to edge)
104
+ forces[:,0] += 0.01 * (1 / (centers_base[:,0] + 1e-6)**2 - 1 / (1 - centers_base[:,0] + 1e-6)**2)
105
+ forces[:,1] += 0.01 * (1 / (centers_base[:,1] + 1e-6)**2 - 1 / (1 - centers_base[:,1] + 1e-6)**2)
106
+
107
+ centers_base = centers_base + forces * 0.01
108
+ centers_base = np.clip(centers_base, 0.0, 1.0)
109
+
110
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
111
+ return np.clip(perturbed_centers, 0.0, 1.0)
112
+
113
+ # Base layout: Hardcoded best-known previous result
114
+ hardcoded_best_known_centers = np.array([
115
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
116
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
117
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
118
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
119
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
120
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
121
+ [0.5173, 0.4172], [0.4888, 0.5875]
122
+ ])
123
+
124
+ # --- Radii Computation for Initial Guess ---
125
+ def _compute_initial_radii(centers, max_iter=200):
126
+ num_circles = centers.shape[0]
127
+ radii = np.zeros(num_circles)
128
+
129
+ for i in range(num_circles):
130
+ x, y = centers[i]
131
+ radii[i] = min(x, 1 - x, y, 1 - y)
132
+
133
+ for iter_step in range(max_iter):
134
+ # Gradually reduce the minimum gap threshold for tighter packing
135
+ # Starts from 1e-5 and smoothly decays to 1e-9
136
+ MIN_GAP_THRESHOLD = 1e-5 * (1 - iter_step / max_iter) + 1e-9 * (iter_step / max_iter)
137
+
138
+ had_change = False
139
+ for i in range(num_circles):
140
+ for j in range(i + 1, num_circles):
141
+ dist = np.linalg.norm(centers[i] - centers[j])
142
+ sum_r = radii[i] + radii[j]
143
+ if sum_r > dist - MIN_GAP_THRESHOLD:
144
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
145
+ if sum_r > 1e-12: # Avoid division by zero
146
+ scale = target_sum_r / sum_r
147
+ radii[i] *= scale
148
+ radii[j] *= scale
149
+ had_change = True
150
+ if not had_change and iter_step > max_iter / 2:
151
+ break
152
+ return radii
153
+
154
+ # --- Objective Functions for Staged Optimization ---
155
+ def objective_hybrid(x, alpha=0.2): # Hybrid objective: balance area and sum of radii
156
+ _, radii = unpack_vars(x)
157
+ return - (alpha * np.sum(radii**2) + (1 - alpha) * np.sum(radii))
158
+
159
+ def objective_radii(x): # Main objective: maximize sum of radii
160
+ _, radii = unpack_vars(x)
161
+ return -np.sum(radii)
162
+
163
+ # --- Constraints (Numerically Stable Formulation) ---
164
+ cons = []
165
+
166
+ def non_overlap_constraint(x):
167
+ centers, radii = unpack_vars(x)
168
+ i, j = np.triu_indices(n, k=1)
169
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
170
+ sum_radii_sq = (radii[i] + radii[j])**2
171
+ return dist_sq - sum_radii_sq
172
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
173
+
174
+ def boundary_constraint(x):
175
+ centers, radii = unpack_vars(x)
176
+ return np.concatenate([
177
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
178
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
179
+ ])
180
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
181
+
182
+ # --- Variable Bounds ---
183
+ MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for stability
184
+ bounds = []
185
+ for _ in range(n):
186
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
187
+
188
+ # --- Multi-Start Optimizer with Dynamic Initial Guesses and Adaptive Perturbation ---
189
+ num_optimization_runs = 40 # Increased runs for broader search
190
+ best_sum_radii = -np.inf
191
+ best_result_x = None
192
+ current_best_centers_found = None # Stores centers of the best solution found so far
193
+
194
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
195
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
196
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
197
+
198
+ for run in range(num_optimization_runs):
199
+ # Continuous adaptive perturbation schedule
200
+ max_perturbation_std_dev = 0.030 # Broader initial perturbation
201
+ min_perturbation_std_dev = 0.001 # Finer final perturbation
202
+ perturbation_std_dev = max_perturbation_std_dev * (1 - run / (num_optimization_runs - 1)) + \
203
+ min_perturbation_std_dev * (run / (num_optimization_runs - 1))
204
+
205
+ # Dynamic initial center generation based on run phase
206
+ if run < num_optimization_runs * 0.25: # First 25%: Random Grid
207
+ perturbed_centers = _generate_random_grid_centers(n, perturbation_std_dev)
208
+ elif run < num_optimization_runs * 0.5: # Next 25%: Repulsed Random
209
+ perturbed_centers = _generate_repulsed_random_centers(n, perturbation_std_dev)
210
+ elif run < num_optimization_runs * 0.75: # Next 25%: Hexagonal
211
+ perturbed_centers = _generate_hexagonal_centers(n, perturbation_std_dev)
212
+ else: # Last 25%: Perturb best-known (either hardcoded or dynamically found)
213
+ if current_best_centers_found is not None:
214
+ centers_base_for_perturb = current_best_centers_found
215
+ else:
216
+ centers_base_for_perturb = hardcoded_best_known_centers
217
+ perturbed_centers = centers_base_for_perturb + np.random.normal(0, perturbation_std_dev, centers_base_for_perturb.shape)
218
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
219
+
220
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
221
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
222
+
223
+ # Stage 1: Maximize hybrid objective (area and radii)
224
+ result_stage1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
225
+ if not result_stage1.success:
226
+ continue
227
+
228
+ # Stage 2: Maximize sum of radii
229
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
230
+ if not result_stage2.success:
231
+ continue
232
+
233
+ # Stage 3: Final refinement with tightest tolerances
234
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
235
+
236
+ _, current_radii = unpack_vars(result_stage3.x)
237
+ current_sum_radii = np.sum(current_radii)
238
+
239
+ if current_sum_radii > best_sum_radii:
240
+ best_sum_radii = current_sum_radii
241
+ best_result_x = result_stage3.x
242
+ # Update the best centers found so far for future perturbations
243
+ current_best_centers_found, _ = unpack_vars(best_result_x)
244
+
245
+ # --- Final Result Extraction ---
246
+ if best_result_x is None:
247
+ # Fallback if all runs failed to converge meaningfully
248
+ initial_radii = _compute_initial_radii(hardcoded_best_known_centers)
249
+ best_result_x = pack_vars(hardcoded_best_known_centers, initial_radii)
250
+
251
+ final_centers, final_radii = unpack_vars(best_result_x)
252
+ final_radii = np.maximum(final_radii, 0) # Ensure no negative radii
253
+
254
+ return final_centers, final_radii
255
+ # EVOLVE-BLOCK-END
256
+
257
+
258
+ # This part remains fixed (not evolved)
259
+ def run_packing():
260
+ """Run the circle packing constructor for n=26"""
261
+ centers, radii = construct_packing()
262
+ # Calculate the sum of radii
263
+ sum_radii = np.sum(radii)
264
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/original.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by seeding a three-stage
8
+ NLP with a known high-quality solution. This method leverages expert knowledge
9
+ by starting its search from a proven configuration, uses numerically stable
10
+ constraints, and intensifies the search with more runs and iterations.
11
+ """
12
+ n = 26
13
+
14
+ # Helper functions to convert between the flat optimization vector and
15
+ # the structured centers/radii arrays.
16
+ def pack_vars(centers, radii):
17
+ x = np.zeros(n * 3)
18
+ x[0::3] = centers[:, 0]
19
+ x[1::3] = centers[:, 1]
20
+ x[2::3] = radii
21
+ return x
22
+
23
+ def unpack_vars(x):
24
+ centers_x = x[0::3]
25
+ centers_y = x[1::3]
26
+ radii = x[2::3]
27
+ centers = np.vstack((centers_x, centers_y)).T
28
+ return centers, radii
29
+
30
+ # --- 1. Initial Guess Generation ---
31
+ def _compute_initial_radii(centers, max_iter=200):
32
+ """
33
+ Iteratively computes the maximum possible non-overlapping radii for a
34
+ given fixed set of centers, ensuring a small minimum gap.
35
+ """
36
+ num_circles = centers.shape[0]
37
+ radii = np.zeros(num_circles)
38
+ MIN_GAP_THRESHOLD = 1e-8
39
+
40
+ for i in range(num_circles):
41
+ x, y = centers[i]
42
+ radii[i] = min(x, 1 - x, y, 1 - y)
43
+
44
+ for _ in range(max_iter):
45
+ had_change = False
46
+ for i in range(num_circles):
47
+ for j in range(i + 1, num_circles):
48
+ dist = np.linalg.norm(centers[i] - centers[j])
49
+ sum_r = radii[i] + radii[j]
50
+ if sum_r > dist - MIN_GAP_THRESHOLD:
51
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
52
+ if sum_r > 1e-12:
53
+ scale = target_sum_r / sum_r
54
+ radii[i] *= scale
55
+ radii[j] *= scale
56
+ had_change = True
57
+ if not had_change:
58
+ break
59
+ return radii
60
+
61
+ # Base layout: Seed with the best-known previous result to focus the search.
62
+ base_initial_centers = np.array([
63
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
64
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
65
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
66
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
67
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
68
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
69
+ [0.5173, 0.4172], [0.4888, 0.5875]
70
+ ])
71
+
72
+ # --- 2. Define Objective Functions for Staged Optimization ---
73
+ def objective_area(x):
74
+ _, radii = unpack_vars(x)
75
+ return -np.sum(radii**2)
76
+
77
+ def objective_radii(x):
78
+ _, radii = unpack_vars(x)
79
+ return -np.sum(radii)
80
+
81
+ # --- 3. Define Constraints ---
82
+ cons = []
83
+
84
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
85
+ def non_overlap_constraint(x):
86
+ centers, radii = unpack_vars(x)
87
+ i, j = np.triu_indices(n, k=1)
88
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
89
+ sum_radii_sq = (radii[i] + radii[j])**2
90
+ return dist_sq - sum_radii_sq
91
+
92
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
93
+
94
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
95
+ def boundary_constraint(x):
96
+ centers, radii = unpack_vars(x)
97
+ return np.concatenate([
98
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
99
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
100
+ ])
101
+
102
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
103
+
104
+ # --- 4. Define Bounds for each variable ---
105
+ bounds = []
106
+ for _ in range(n):
107
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
108
+
109
+ # --- 5. Run the Optimizer with an Intensified Search Strategy ---
110
+ num_optimization_runs = 30
111
+ best_sum_radii = -np.inf
112
+ best_result_x = None
113
+
114
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
115
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
116
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
117
+
118
+ for run in range(num_optimization_runs):
119
+ if run < num_optimization_runs // 2:
120
+ perturbation_std_dev = 0.020
121
+ else:
122
+ perturbation_std_dev = 0.005
123
+
124
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
125
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
126
+
127
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
128
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
129
+
130
+ # Stage 1: Maximize sum of areas (r^2).
131
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
132
+ if not result_stage1.success:
133
+ continue
134
+
135
+ # Stage 2: Maximize sum of radii (r).
136
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
137
+ if not result_stage2.success:
138
+ continue
139
+
140
+ # Stage 3: Final refinement with the tightest tolerances.
141
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
142
+
143
+ _, current_radii = unpack_vars(result_stage3.x)
144
+ current_sum_radii = np.sum(current_radii)
145
+
146
+ if current_sum_radii > best_sum_radii:
147
+ best_sum_radii = current_sum_radii
148
+ best_result_x = result_stage3.x
149
+
150
+ # --- 6. Extract and Return the Best Result ---
151
+ if best_result_x is None:
152
+ initial_radii = _compute_initial_radii(base_initial_centers)
153
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
154
+
155
+ final_centers, final_radii = unpack_vars(best_result_x)
156
+ final_radii = np.maximum(final_radii, 0)
157
+
158
+ return final_centers, final_radii
159
+ # EVOLVE-BLOCK-END
160
+
161
+
162
+ # This part remains fixed (not evolved)
163
+ def run_packing():
164
+ """Run the circle packing constructor for n=26"""
165
+ centers, radii = construct_packing()
166
+ # Calculate the sum of radii
167
+ sum_radii = np.sum(radii)
168
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/rewrite.txt ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a Randomized Hybrid
8
+ Seeded Optimization (RHSO) approach. This method dynamically generates diverse
9
+ initial center configurations, employs a continuous adaptive perturbation
10
+ schedule, uses a hybrid objective for the first NLP stage, and enforces
11
+ non-zero radii, leading to a robust and high-precision search.
12
+ """
13
+ n = 26
14
+
15
+ # --- Helper functions to pack/unpack optimization variables ---
16
+ def pack_vars(centers, radii):
17
+ x = np.zeros(n * 3)
18
+ x[0::3] = centers[:, 0]
19
+ x[1::3] = centers[:, 1]
20
+ x[2::3] = radii
21
+ return x
22
+
23
+ def unpack_vars(x):
24
+ centers_x = x[0::3]
25
+ centers_y = x[1::3]
26
+ radii = x[2::3]
27
+ centers = np.vstack((centers_x, centers_y)).T
28
+ return centers, radii
29
+
30
+ # --- Initial Guess Generation Functions ---
31
+
32
+ def _generate_random_grid_centers(num_circles, perturb_std_dev):
33
+ grid_dim = int(np.ceil(np.sqrt(num_circles)))
34
+ x_coords = np.linspace(0.1, 0.9, grid_dim)
35
+ y_coords = np.linspace(0.1, 0.9, grid_dim)
36
+ centers_base = np.array([[x, y] for x in x_coords for y in y_coords])
37
+
38
+ # If the grid is too small, fill with random, if too large, trim
39
+ if len(centers_base) < num_circles:
40
+ centers_base = np.vstack([centers_base, np.random.rand(num_circles - len(centers_base), 2)])
41
+ centers_base = centers_base[:num_circles]
42
+
43
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
44
+ return np.clip(perturbed_centers, 0.0, 1.0)
45
+
46
+ def _generate_hexagonal_centers(num_circles, perturb_std_dev):
47
+ centers_raw = []
48
+ # Adjusted rows config to better fit 26 circles
49
+ # Example: 5+6+5+6+4 = 26
50
+ rows_config = [5, 6, 5, 6, 4]
51
+ r_approx = 0.11 # Approximate radius for initial spacing
52
+ dx = 2 * r_approx * 0.9 # Spacing between centers in a row
53
+ dy = r_approx * np.sqrt(3) * 0.9 # Spacing between rows
54
+
55
+ current_y = 0.0
56
+ for r_idx, num_cols in enumerate(rows_config):
57
+ if len(centers_raw) >= num_circles:
58
+ break
59
+
60
+ # Alternate offset for hexagonal packing
61
+ row_x_offset = dx / 2.0 if r_idx % 2 != 0 else 0.0
62
+
63
+ # Adjust starting x to roughly center the pattern horizontally
64
+ total_row_width = (num_cols - 1) * dx
65
+ start_x = (1.0 - total_row_width) / 2.0
66
+
67
+ for col_idx in range(num_cols):
68
+ if len(centers_raw) >= num_circles:
69
+ break
70
+ centers_raw.append([start_x + row_x_offset + col_idx * dx, current_y])
71
+ current_y += dy
72
+
73
+ centers_base = np.array(centers_raw[:num_circles])
74
+
75
+ # Scale and center the generated pattern within [0,1]
76
+ if len(centers_base) > 0:
77
+ x_min, y_min = np.min(centers_base, axis=0)
78
+ x_max, y_max = np.max(centers_base, axis=0)
79
+ scale_factor = 0.95 / max(x_max - x_min, y_max - y_min, 1e-6)
80
+ centers_base = (centers_base - np.array([x_min, y_min])) * scale_factor
81
+ offset = (1.0 - np.array([np.max(centers_base[:,0]), np.max(centers_base[:,1])])) / 2.0
82
+ centers_base += offset
83
+ else: # Fallback if for some reason hex generation fails
84
+ centers_base = np.random.rand(num_circles, 2) * 0.8 + 0.1 # Random, slightly inward
85
+
86
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
87
+ return np.clip(perturbed_centers, 0.0, 1.0)
88
+
89
+ def _generate_repulsed_random_centers(num_circles, perturb_std_dev, repulsion_iters=10):
90
+ centers_base = np.random.rand(num_circles, 2)
91
+
92
+ for _ in range(repulsion_iters):
93
+ forces = np.zeros_like(centers_base)
94
+ for i in range(num_circles):
95
+ for j in range(i + 1, num_circles):
96
+ diff = centers_base[i] - centers_base[j]
97
+ dist_sq = np.sum(diff**2)
98
+ if dist_sq < 1e-6: dist_sq = 1e-6
99
+ force_magnitude = 0.005 / dist_sq # Inverse square repulsion
100
+ forces[i] += diff * force_magnitude
101
+ forces[j] -= diff * force_magnitude
102
+
103
+ # Boundary repulsion (stronger closer to edge)
104
+ forces[:,0] += 0.01 * (1 / (centers_base[:,0] + 1e-6)**2 - 1 / (1 - centers_base[:,0] + 1e-6)**2)
105
+ forces[:,1] += 0.01 * (1 / (centers_base[:,1] + 1e-6)**2 - 1 / (1 - centers_base[:,1] + 1e-6)**2)
106
+
107
+ centers_base = centers_base + forces * 0.01
108
+ centers_base = np.clip(centers_base, 0.0, 1.0)
109
+
110
+ perturbed_centers = centers_base + np.random.normal(0, perturb_std_dev, centers_base.shape)
111
+ return np.clip(perturbed_centers, 0.0, 1.0)
112
+
113
+ # Base layout: Hardcoded best-known previous result
114
+ hardcoded_best_known_centers = np.array([
115
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
116
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
117
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
118
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
119
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
120
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
121
+ [0.5173, 0.4172], [0.4888, 0.5875]
122
+ ])
123
+
124
+ # --- Radii Computation for Initial Guess ---
125
+ def _compute_initial_radii(centers, max_iter=200):
126
+ num_circles = centers.shape[0]
127
+ radii = np.zeros(num_circles)
128
+
129
+ for i in range(num_circles):
130
+ x, y = centers[i]
131
+ radii[i] = min(x, 1 - x, y, 1 - y)
132
+
133
+ for iter_step in range(max_iter):
134
+ # Gradually reduce the minimum gap threshold for tighter packing
135
+ # Starts from 1e-5 and smoothly decays to 1e-9
136
+ MIN_GAP_THRESHOLD = 1e-5 * (1 - iter_step / max_iter) + 1e-9 * (iter_step / max_iter)
137
+
138
+ had_change = False
139
+ for i in range(num_circles):
140
+ for j in range(i + 1, num_circles):
141
+ dist = np.linalg.norm(centers[i] - centers[j])
142
+ sum_r = radii[i] + radii[j]
143
+ if sum_r > dist - MIN_GAP_THRESHOLD:
144
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
145
+ if sum_r > 1e-12: # Avoid division by zero
146
+ scale = target_sum_r / sum_r
147
+ radii[i] *= scale
148
+ radii[j] *= scale
149
+ had_change = True
150
+ if not had_change and iter_step > max_iter / 2:
151
+ break
152
+ return radii
153
+
154
+ # --- Objective Functions for Staged Optimization ---
155
+ def objective_hybrid(x, alpha=0.2): # Hybrid objective: balance area and sum of radii
156
+ _, radii = unpack_vars(x)
157
+ return - (alpha * np.sum(radii**2) + (1 - alpha) * np.sum(radii))
158
+
159
+ def objective_radii(x): # Main objective: maximize sum of radii
160
+ _, radii = unpack_vars(x)
161
+ return -np.sum(radii)
162
+
163
+ # --- Constraints (Numerically Stable Formulation) ---
164
+ cons = []
165
+
166
+ def non_overlap_constraint(x):
167
+ centers, radii = unpack_vars(x)
168
+ i, j = np.triu_indices(n, k=1)
169
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
170
+ sum_radii_sq = (radii[i] + radii[j])**2
171
+ return dist_sq - sum_radii_sq
172
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
173
+
174
+ def boundary_constraint(x):
175
+ centers, radii = unpack_vars(x)
176
+ return np.concatenate([
177
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
178
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
179
+ ])
180
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
181
+
182
+ # --- Variable Bounds ---
183
+ MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for stability
184
+ bounds = []
185
+ for _ in range(n):
186
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
187
+
188
+ # --- Multi-Start Optimizer with Dynamic Initial Guesses and Adaptive Perturbation ---
189
+ num_optimization_runs = 40 # Increased runs for broader search
190
+ best_sum_radii = -np.inf
191
+ best_result_x = None
192
+ current_best_centers_found = None # Stores centers of the best solution found so far
193
+
194
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
195
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
196
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
197
+
198
+ for run in range(num_optimization_runs):
199
+ # Continuous adaptive perturbation schedule
200
+ max_perturbation_std_dev = 0.030 # Broader initial perturbation
201
+ min_perturbation_std_dev = 0.001 # Finer final perturbation
202
+ perturbation_std_dev = max_perturbation_std_dev * (1 - run / (num_optimization_runs - 1)) + \
203
+ min_perturbation_std_dev * (run / (num_optimization_runs - 1))
204
+
205
+ # Dynamic initial center generation based on run phase
206
+ if run < num_optimization_runs * 0.25: # First 25%: Random Grid
207
+ perturbed_centers = _generate_random_grid_centers(n, perturbation_std_dev)
208
+ elif run < num_optimization_runs * 0.5: # Next 25%: Repulsed Random
209
+ perturbed_centers = _generate_repulsed_random_centers(n, perturbation_std_dev)
210
+ elif run < num_optimization_runs * 0.75: # Next 25%: Hexagonal
211
+ perturbed_centers = _generate_hexagonal_centers(n, perturbation_std_dev)
212
+ else: # Last 25%: Perturb best-known (either hardcoded or dynamically found)
213
+ if current_best_centers_found is not None:
214
+ centers_base_for_perturb = current_best_centers_found
215
+ else:
216
+ centers_base_for_perturb = hardcoded_best_known_centers
217
+ perturbed_centers = centers_base_for_perturb + np.random.normal(0, perturbation_std_dev, centers_base_for_perturb.shape)
218
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
219
+
220
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
221
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
222
+
223
+ # Stage 1: Maximize hybrid objective (area and radii)
224
+ result_stage1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
225
+ if not result_stage1.success:
226
+ continue
227
+
228
+ # Stage 2: Maximize sum of radii
229
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
230
+ if not result_stage2.success:
231
+ continue
232
+
233
+ # Stage 3: Final refinement with tightest tolerances
234
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
235
+
236
+ _, current_radii = unpack_vars(result_stage3.x)
237
+ current_sum_radii = np.sum(current_radii)
238
+
239
+ if current_sum_radii > best_sum_radii:
240
+ best_sum_radii = current_sum_radii
241
+ best_result_x = result_stage3.x
242
+ # Update the best centers found so far for future perturbations
243
+ current_best_centers_found, _ = unpack_vars(best_result_x)
244
+
245
+ # --- Final Result Extraction ---
246
+ if best_result_x is None:
247
+ # Fallback if all runs failed to converge meaningfully
248
+ initial_radii = _compute_initial_radii(hardcoded_best_known_centers)
249
+ best_result_x = pack_vars(hardcoded_best_known_centers, initial_radii)
250
+
251
+ final_centers, final_radii = unpack_vars(best_result_x)
252
+ final_radii = np.maximum(final_radii, 0) # Ensure no negative radii
253
+
254
+ return final_centers, final_radii
255
+ # EVOLVE-BLOCK-END
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/edit.diff ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,173 +1,188 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ Constructs an optimized arrangement of 26 circles using a three-stage NLP.
11
+ This method enhances previous strategies by increasing the number of optimization
12
+ runs, using an adaptive perturbation schedule for the initial guess, employing
13
+ progressively tighter solver tolerances across three stages, and using a
14
+ numerically stable squared-distance constraint.
15
+ """
16
+ n = 26
17
+
18
+ # Helper functions to convert between the flat optimization vector and
19
+ # the structured centers/radii arrays.
20
+ def pack_vars(centers, radii):
21
+ x = np.zeros(n * 3)
22
+ x[0::3] = centers[:, 0]
23
+ x[1::3] = centers[:, 1]
24
+ x[2::3] = radii
25
+ return x
26
+
27
+ def unpack_vars(x):
28
+ centers_x = x[0::3]
29
+ centers_y = x[1::3]
30
+ radii = x[2::3]
31
+ centers = np.vstack((centers_x, centers_y)).T
32
+ return centers, radii
33
+
34
+ # --- 1. Initial Guess ---
35
+ def _compute_initial_radii(centers, max_iter=200):
36
+ - """Iteratively compute max non-overlapping radii for a given set of centers."""
37
+ + """
38
+ + Iteratively compute max non-overlapping radii for a given set of centers.
39
+ + Uses an adaptive MIN_GAP and ensures strictly positive radii.
40
+ + """
41
+ num_circles = centers.shape[0]
42
+ radii = np.zeros(num_circles)
43
+ - MIN_GAP = 1e-8 # Use a small gap for robustness
44
+ + # Adaptive MIN_GAP: scales with the number of circles to allow tighter packing
45
+ + # or more robust initial state depending on N, aligning with Recommendation 3.
46
+ + MIN_GAP = 1e-7 / np.sqrt(num_circles)
47
+
48
+ # Initialize radii based on distance to walls
49
+ for i in range(num_circles):
50
+ x, y = centers[i]
51
+ radii[i] = min(x, 1 - x, y, 1 - y)
52
+
53
+ # Iteratively shrink radii to resolve overlaps
54
+ for _ in range(max_iter):
55
+ had_change = False
56
+ for i in range(num_circles):
57
+ for j in range(i + 1, num_circles):
58
+ dist = np.linalg.norm(centers[i] - centers[j])
59
+ sum_r = radii[i] + radii[j]
60
+ - if sum_r > dist - MIN_GAP:
61
+ + if sum_r > dist - MIN_GAP: # Check for overlap with the adaptive gap
62
+ target_sum_r = max(0.0, dist - MIN_GAP)
63
+ if sum_r > 1e-12:
64
+ scale = target_sum_r / sum_r
65
+ radii[i] *= scale
66
+ radii[j] *= scale
67
+ had_change = True
68
+ if not had_change:
69
+ break
70
+ - return radii
71
+ + # Ensure radii are strictly positive to prevent numerical issues with zero radii.
72
+ + return np.maximum(radii, 1e-10)
73
+
74
+ # --- 2. Define Objective Functions for Staged Optimization ---
75
+ def objective_area(x):
76
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
77
+ _, radii = unpack_vars(x)
78
+ return -np.sum(radii**2)
79
+
80
+ def objective_radii(x):
81
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
82
+ _, radii = unpack_vars(x)
83
+ return -np.sum(radii)
84
+
85
+ # --- 3. Define Constraints ---
86
+ cons = []
87
+
88
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
89
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
90
+ def non_overlap_constraint(x):
91
+ centers, radii = unpack_vars(x)
92
+ i, j = np.triu_indices(n, k=1)
93
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
94
+ sum_radii_sq = (radii[i] + radii[j])**2
95
+ return dist_sq - sum_radii_sq
96
+
97
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
98
+
99
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
100
+ def boundary_constraint(x):
101
+ centers, radii = unpack_vars(x)
102
+ return np.concatenate([
103
+ centers[:, 0] - radii, # x - r >= 0
104
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
105
+ centers[:, 1] - radii, # y - r >= 0
106
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
107
+ ])
108
+
109
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
110
+
111
+ # --- 4. Define Bounds for each variable ---
112
+ bounds = []
113
+ + MIN_RADIUS_BOUND = 1e-7 # Enforce a small positive minimum radius for numerical stability
114
+ for _ in range(n):
115
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
116
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
117
+
118
+ # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
119
+ base_initial_centers = np.zeros((n, 2))
120
+ idx = 0
121
+ for i in range(5):
122
+ for j in range(5):
123
+ if i == 2 and j == 2: continue
124
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
125
+ idx += 1
126
+ base_initial_centers[24] = [0.5, 0.45]
127
+ base_initial_centers[25] = [0.5, 0.55]
128
+
129
+ - num_optimization_runs = 24
130
+ + num_optimization_runs = 30 # Increased runs for more robust exploration
131
+ best_sum_radii = -np.inf
132
+ best_result_x = None
133
+
134
+ # Progressively aggressive optimizer settings for each stage
135
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
136
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
137
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
138
+
139
+ for run in range(num_optimization_runs):
140
+ - # Adaptive perturbation: broad exploration first, then fine-tuning.
141
+ - if run < num_optimization_runs // 2:
142
+ - perturbation_std_dev = 0.020 # Broader exploration
143
+ - else:
144
+ - perturbation_std_dev = 0.005 # Finer refinement
145
+ + # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
146
+ + # This schedule divides runs into three tiers for more gradual decay.
147
+ + if run < num_optimization_runs * 0.4: # First 40% of runs (e.g., first 12 runs)
148
+ + perturbation_std_dev = 0.030 # Broader initial exploration
149
+ + elif run < num_optimization_runs * 0.8: # Next 40% of runs (e.g., next 12 runs)
150
+ + perturbation_std_dev = 0.010 # Mid-range exploration
151
+ + else: # Last 20% of runs (e.g., last 6 runs)
152
+ + perturbation_std_dev = 0.003 # Fine-tuning for local optima
153
+
154
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
155
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
156
+
157
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
158
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
159
+
160
+ # Stage 1: Maximize sum of areas (r^2)
161
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
162
+ - if not result_stage1.success: continue
163
+ + # Proceed with result_stage1.x if successful, else use x0_run
164
+ + x_after_stage1 = result_stage1.x if result_stage1.success else x0_run
165
+
166
+ # Stage 2: Maximize sum of radii (r)
167
+ - result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
168
+ - if not result_stage2.success: continue
169
+ + result_stage2 = minimize(objective_radii, x_after_stage1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
170
+ + # Proceed with result_stage2.x if successful, else use x_after_stage1
171
+ + x_after_stage2 = result_stage2.x if result_stage2.success else x_after_stage1
172
+
173
+ # Stage 3: Further maximize sum of radii with tighter options
174
+ - result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
175
+ + result_stage3 = minimize(objective_radii, x_after_stage2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
176
+
177
+ - _, current_radii = unpack_vars(result_stage3.x)
178
+ + # Use the result from the last successful stage. Prioritize Stage 3 if successful.
179
+ + final_run_x = result_stage3.x if result_stage3.success else x_after_stage2
180
+ +
181
+ + _, current_radii = unpack_vars(final_run_x)
182
+ current_sum_radii = np.sum(current_radii)
183
+
184
+ if current_sum_radii > best_sum_radii:
185
+ best_sum_radii = current_sum_radii
186
+ best_result_x = result_stage3.x
187
+
188
+ # --- 6. Extract and Return Results ---
189
+ # Fallback if all runs fail (highly unlikely)
190
+ if best_result_x is None:
191
+ initial_radii = _compute_initial_radii(base_initial_centers)
192
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
193
+
194
+ final_centers, final_radii = unpack_vars(best_result_x)
195
+ final_radii = np.maximum(final_radii, 0)
196
+
197
+ return final_centers, final_radii
198
+ # EVOLVE-BLOCK-END
199
+
200
+
201
+ # This part remains fixed (not evolved)
202
+ def run_packing():
203
+ """Run the circle packing constructor for n=26"""
204
+ centers, radii = construct_packing()
205
+ # Calculate the sum of radii
206
+ sum_radii = np.sum(radii)
207
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/main.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a three-stage NLP.
8
+ This method enhances previous strategies by increasing the number of optimization
9
+ runs, using an adaptive perturbation schedule for the initial guess, employing
10
+ progressively tighter solver tolerances across three stages, and using a
11
+ numerically stable squared-distance constraint.
12
+ """
13
+ n = 26
14
+
15
+ # Helper functions to convert between the flat optimization vector and
16
+ # the structured centers/radii arrays.
17
+ def pack_vars(centers, radii):
18
+ x = np.zeros(n * 3)
19
+ x[0::3] = centers[:, 0]
20
+ x[1::3] = centers[:, 1]
21
+ x[2::3] = radii
22
+ return x
23
+
24
+ def unpack_vars(x):
25
+ centers_x = x[0::3]
26
+ centers_y = x[1::3]
27
+ radii = x[2::3]
28
+ centers = np.vstack((centers_x, centers_y)).T
29
+ return centers, radii
30
+
31
+ # --- 1. Initial Guess ---
32
+ def _compute_initial_radii(centers, max_iter=200):
33
+ """
34
+ Iteratively compute max non-overlapping radii for a given set of centers.
35
+ Uses an adaptive MIN_GAP and ensures strictly positive radii.
36
+ """
37
+ num_circles = centers.shape[0]
38
+ radii = np.zeros(num_circles)
39
+ # Adaptive MIN_GAP: scales with the number of circles to allow tighter packing
40
+ # or more robust initial state depending on N, aligning with Recommendation 3.
41
+ MIN_GAP = 1e-7 / np.sqrt(num_circles)
42
+
43
+ # Initialize radii based on distance to walls
44
+ for i in range(num_circles):
45
+ x, y = centers[i]
46
+ radii[i] = min(x, 1 - x, y, 1 - y)
47
+
48
+ # Iteratively shrink radii to resolve overlaps
49
+ for _ in range(max_iter):
50
+ had_change = False
51
+ for i in range(num_circles):
52
+ for j in range(i + 1, num_circles):
53
+ dist = np.linalg.norm(centers[i] - centers[j])
54
+ sum_r = radii[i] + radii[j]
55
+ if sum_r > dist - MIN_GAP: # Check for overlap with the adaptive gap
56
+ target_sum_r = max(0.0, dist - MIN_GAP)
57
+ if sum_r > 1e-12:
58
+ scale = target_sum_r / sum_r
59
+ radii[i] *= scale
60
+ radii[j] *= scale
61
+ had_change = True
62
+ if not had_change:
63
+ break
64
+ # Ensure radii are strictly positive to prevent numerical issues with zero radii.
65
+ return np.maximum(radii, 1e-10)
66
+
67
+ # --- 2. Define Objective Functions for Staged Optimization ---
68
+ def objective_area(x):
69
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
70
+ _, radii = unpack_vars(x)
71
+ return -np.sum(radii**2)
72
+
73
+ def objective_radii(x):
74
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
75
+ _, radii = unpack_vars(x)
76
+ return -np.sum(radii)
77
+
78
+ # --- 3. Define Constraints ---
79
+ cons = []
80
+
81
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
82
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
83
+ def non_overlap_constraint(x):
84
+ centers, radii = unpack_vars(x)
85
+ i, j = np.triu_indices(n, k=1)
86
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
87
+ sum_radii_sq = (radii[i] + radii[j])**2
88
+ return dist_sq - sum_radii_sq
89
+
90
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
91
+
92
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
93
+ def boundary_constraint(x):
94
+ centers, radii = unpack_vars(x)
95
+ return np.concatenate([
96
+ centers[:, 0] - radii, # x - r >= 0
97
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
98
+ centers[:, 1] - radii, # y - r >= 0
99
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
100
+ ])
101
+
102
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
103
+
104
+ # --- 4. Define Bounds for each variable ---
105
+ bounds = []
106
+ MIN_RADIUS_BOUND = 1e-7 # Enforce a small positive minimum radius for numerical stability
107
+ for _ in range(n):
108
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
109
+
110
+ # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
111
+ base_initial_centers = np.zeros((n, 2))
112
+ idx = 0
113
+ for i in range(5):
114
+ for j in range(5):
115
+ if i == 2 and j == 2: continue
116
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
117
+ idx += 1
118
+ base_initial_centers[24] = [0.5, 0.45]
119
+ base_initial_centers[25] = [0.5, 0.55]
120
+
121
+ num_optimization_runs = 30 # Increased runs for more robust exploration
122
+ best_sum_radii = -np.inf
123
+ best_result_x = None
124
+
125
+ # Progressively aggressive optimizer settings for each stage
126
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
127
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
128
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
129
+
130
+ for run in range(num_optimization_runs):
131
+ # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
132
+ # This schedule divides runs into three tiers for more gradual decay.
133
+ if run < num_optimization_runs * 0.4: # First 40% of runs (e.g., first 12 runs)
134
+ perturbation_std_dev = 0.030 # Broader initial exploration
135
+ elif run < num_optimization_runs * 0.8: # Next 40% of runs (e.g., next 12 runs)
136
+ perturbation_std_dev = 0.010 # Mid-range exploration
137
+ else: # Last 20% of runs (e.g., last 6 runs)
138
+ perturbation_std_dev = 0.003 # Fine-tuning for local optima
139
+
140
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
141
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
142
+
143
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
144
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
145
+
146
+ # Stage 1: Maximize sum of areas (r^2)
147
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
148
+ # Proceed with result_stage1.x if successful, else use x0_run
149
+ x_after_stage1 = result_stage1.x if result_stage1.success else x0_run
150
+
151
+ # Stage 2: Maximize sum of radii (r)
152
+ result_stage2 = minimize(objective_radii, x_after_stage1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
153
+ # Proceed with result_stage2.x if successful, else use x_after_stage1
154
+ x_after_stage2 = result_stage2.x if result_stage2.success else x_after_stage1
155
+
156
+ # Stage 3: Further maximize sum of radii with tighter options
157
+ result_stage3 = minimize(objective_radii, x_after_stage2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
158
+
159
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
160
+ final_run_x = result_stage3.x if result_stage3.success else x_after_stage2
161
+
162
+ _, current_radii = unpack_vars(final_run_x)
163
+ current_sum_radii = np.sum(current_radii)
164
+
165
+ if current_sum_radii > best_sum_radii:
166
+ best_sum_radii = current_sum_radii
167
+ best_result_x = result_stage3.x
168
+
169
+ # --- 6. Extract and Return Results ---
170
+ # Fallback if all runs fail (highly unlikely)
171
+ if best_result_x is None:
172
+ initial_radii = _compute_initial_radii(base_initial_centers)
173
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
174
+
175
+ final_centers, final_radii = unpack_vars(best_result_x)
176
+ final_radii = np.maximum(final_radii, 0)
177
+
178
+ return final_centers, final_radii
179
+ # EVOLVE-BLOCK-END
180
+
181
+
182
+ # This part remains fixed (not evolved)
183
+ def run_packing():
184
+ """Run the circle packing constructor for n=26"""
185
+ centers, radii = construct_packing()
186
+ # Calculate the sum of radii
187
+ sum_radii = np.sum(radii)
188
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/original.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a three-stage NLP.
8
+ This method enhances previous strategies by increasing the number of optimization
9
+ runs, using an adaptive perturbation schedule for the initial guess, employing
10
+ progressively tighter solver tolerances across three stages, and using a
11
+ numerically stable squared-distance constraint.
12
+ """
13
+ n = 26
14
+
15
+ # Helper functions to convert between the flat optimization vector and
16
+ # the structured centers/radii arrays.
17
+ def pack_vars(centers, radii):
18
+ x = np.zeros(n * 3)
19
+ x[0::3] = centers[:, 0]
20
+ x[1::3] = centers[:, 1]
21
+ x[2::3] = radii
22
+ return x
23
+
24
+ def unpack_vars(x):
25
+ centers_x = x[0::3]
26
+ centers_y = x[1::3]
27
+ radii = x[2::3]
28
+ centers = np.vstack((centers_x, centers_y)).T
29
+ return centers, radii
30
+
31
+ # --- 1. Initial Guess ---
32
+ def _compute_initial_radii(centers, max_iter=200):
33
+ """Iteratively compute max non-overlapping radii for a given set of centers."""
34
+ num_circles = centers.shape[0]
35
+ radii = np.zeros(num_circles)
36
+ MIN_GAP = 1e-8 # Use a small gap for robustness
37
+
38
+ # Initialize radii based on distance to walls
39
+ for i in range(num_circles):
40
+ x, y = centers[i]
41
+ radii[i] = min(x, 1 - x, y, 1 - y)
42
+
43
+ # Iteratively shrink radii to resolve overlaps
44
+ for _ in range(max_iter):
45
+ had_change = False
46
+ for i in range(num_circles):
47
+ for j in range(i + 1, num_circles):
48
+ dist = np.linalg.norm(centers[i] - centers[j])
49
+ sum_r = radii[i] + radii[j]
50
+ if sum_r > dist - MIN_GAP:
51
+ target_sum_r = max(0.0, dist - MIN_GAP)
52
+ if sum_r > 1e-12:
53
+ scale = target_sum_r / sum_r
54
+ radii[i] *= scale
55
+ radii[j] *= scale
56
+ had_change = True
57
+ if not had_change:
58
+ break
59
+ return radii
60
+
61
+ # --- 2. Define Objective Functions for Staged Optimization ---
62
+ def objective_area(x):
63
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
64
+ _, radii = unpack_vars(x)
65
+ return -np.sum(radii**2)
66
+
67
+ def objective_radii(x):
68
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
69
+ _, radii = unpack_vars(x)
70
+ return -np.sum(radii)
71
+
72
+ # --- 3. Define Constraints ---
73
+ cons = []
74
+
75
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
76
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
77
+ def non_overlap_constraint(x):
78
+ centers, radii = unpack_vars(x)
79
+ i, j = np.triu_indices(n, k=1)
80
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
81
+ sum_radii_sq = (radii[i] + radii[j])**2
82
+ return dist_sq - sum_radii_sq
83
+
84
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
85
+
86
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
87
+ def boundary_constraint(x):
88
+ centers, radii = unpack_vars(x)
89
+ return np.concatenate([
90
+ centers[:, 0] - radii, # x - r >= 0
91
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
92
+ centers[:, 1] - radii, # y - r >= 0
93
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
94
+ ])
95
+
96
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
97
+
98
+ # --- 4. Define Bounds for each variable ---
99
+ bounds = []
100
+ for _ in range(n):
101
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
102
+
103
+ # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
104
+ base_initial_centers = np.zeros((n, 2))
105
+ idx = 0
106
+ for i in range(5):
107
+ for j in range(5):
108
+ if i == 2 and j == 2: continue
109
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
110
+ idx += 1
111
+ base_initial_centers[24] = [0.5, 0.45]
112
+ base_initial_centers[25] = [0.5, 0.55]
113
+
114
+ num_optimization_runs = 24
115
+ best_sum_radii = -np.inf
116
+ best_result_x = None
117
+
118
+ # Progressively aggressive optimizer settings for each stage
119
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
120
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
121
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
122
+
123
+ for run in range(num_optimization_runs):
124
+ # Adaptive perturbation: broad exploration first, then fine-tuning.
125
+ if run < num_optimization_runs // 2:
126
+ perturbation_std_dev = 0.020 # Broader exploration
127
+ else:
128
+ perturbation_std_dev = 0.005 # Finer refinement
129
+
130
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
131
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
132
+
133
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
134
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
135
+
136
+ # Stage 1: Maximize sum of areas (r^2)
137
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
138
+ if not result_stage1.success: continue
139
+
140
+ # Stage 2: Maximize sum of radii (r)
141
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
142
+ if not result_stage2.success: continue
143
+
144
+ # Stage 3: Further maximize sum of radii with tighter options
145
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
146
+
147
+ _, current_radii = unpack_vars(result_stage3.x)
148
+ current_sum_radii = np.sum(current_radii)
149
+
150
+ if current_sum_radii > best_sum_radii:
151
+ best_sum_radii = current_sum_radii
152
+ best_result_x = result_stage3.x
153
+
154
+ # --- 6. Extract and Return Results ---
155
+ # Fallback if all runs fail (highly unlikely)
156
+ if best_result_x is None:
157
+ initial_radii = _compute_initial_radii(base_initial_centers)
158
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
159
+
160
+ final_centers, final_radii = unpack_vars(best_result_x)
161
+ final_radii = np.maximum(final_radii, 0)
162
+
163
+ return final_centers, final_radii
164
+ # EVOLVE-BLOCK-END
165
+
166
+
167
+ # This part remains fixed (not evolved)
168
+ def run_packing():
169
+ """Run the circle packing constructor for n=26"""
170
+ centers, radii = construct_packing()
171
+ # Calculate the sum of radii
172
+ sum_radii = np.sum(radii)
173
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/search_replace.txt ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <NAME>
2
+ perturbation_tiers_and_runs
3
+ </NAME>
4
+
5
+ <DESCRIPTION>
6
+ This edit refines the multi-start optimization strategy by:
7
+ 1. **Increasing `num_optimization_runs` from 24 to 30**: More runs provide a higher chance of exploring the solution space effectively and escaping local optima.
8
+ 2. **Implementing a three-tiered adaptive perturbation schedule**: Instead of two broad tiers, the perturbation standard deviation (`perturbation_std_dev`) now gradually decreases across three phases (40% broad exploration, 40% medium exploration, 20% fine-tuning). This allows for a more nuanced search, starting with larger perturbations to explore diverse configurations and progressively narrowing the focus to refine promising solutions. The specific values (0.030, 0.010, 0.003) are adopted from successful prior generations.
9
+ </DESCRIPTION>
10
+
11
+ <DIFF>
12
+ <<<<<<< SEARCH
13
+ base_initial_centers[25] = [0.5, 0.55]
14
+
15
+ num_optimization_runs = 24
16
+ best_sum_radii = -np.inf
17
+ best_result_x = None
18
+
19
+ # Progressively aggressive optimizer settings for each stage
20
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
21
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
22
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
23
+
24
+ for run in range(num_optimization_runs):
25
+ # Adaptive perturbation: broad exploration first, then fine-tuning.
26
+ if run < num_optimization_runs // 2:
27
+ perturbation_std_dev = 0.020 # Broader exploration
28
+ else:
29
+ perturbation_std_dev = 0.005 # Finer refinement
30
+
31
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
32
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
33
+
34
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
35
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
36
+
37
+ # Stage 1: Maximize sum of areas (r^2)
38
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
39
+ if not result_stage1.success: continue
40
+
41
+ # Stage 2: Maximize sum of radii (r)
42
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
43
+ if not result_stage2.success: continue
44
+
45
+ # Stage 3: Further maximize sum of radii with tighter options
46
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
47
+
48
+ _, current_radii = unpack_vars(result_stage3.x)
49
+ current_sum_radii = np.sum(current_radii)
50
+ =======
51
+ base_initial_centers[25] = [0.5, 0.55]
52
+
53
+ num_optimization_runs = 30 # Increased runs for more robust exploration
54
+ best_sum_radii = -np.inf
55
+ best_result_x = None
56
+
57
+ # Progressively aggressive optimizer settings for each stage
58
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
59
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
60
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
61
+
62
+ for run in range(num_optimization_runs):
63
+ # Adaptive perturbation schedule: broader exploration first, then fine-tuning.
64
+ # This schedule divides runs into three tiers for more gradual decay.
65
+ if run < num_optimization_runs * 0.4: # First 40% of runs (e.g., first 12 runs)
66
+ perturbation_std_dev = 0.030 # Broader initial exploration
67
+ elif run < num_optimization_runs * 0.8: # Next 40% of runs (e.g., next 12 runs)
68
+ perturbation_std_dev = 0.010 # Mid-range exploration
69
+ else: # Last 20% of runs (e.g., last 6 runs)
70
+ perturbation_std_dev = 0.003 # Fine-tuning for local optima
71
+
72
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
73
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
74
+
75
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
76
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
77
+
78
+ # Stage 1: Maximize sum of areas (r^2)
79
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
80
+ # Proceed with result_stage1.x if successful, else use x0_run
81
+ x_after_stage1 = result_stage1.x if result_stage1.success else x0_run
82
+
83
+ # Stage 2: Maximize sum of radii (r)
84
+ result_stage2 = minimize(objective_radii, x_after_stage1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
85
+ # Proceed with result_stage2.x if successful, else use x_after_stage1
86
+ x_after_stage2 = result_stage2.x if result_stage2.success else x_after_stage1
87
+
88
+ # Stage 3: Further maximize sum of radii with tighter options
89
+ result_stage3 = minimize(objective_radii, x_after_stage2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
90
+
91
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
92
+ final_run_x = result_stage3.x if result_stage3.success else x_after_stage2
93
+
94
+ _, current_radii = unpack_vars(final_run_x)
95
+ current_sum_radii = np.sum(current_radii)
96
+ >>>>>>> REPLACE
97
+ </DIFF>
98
+ <NAME>
99
+ min_radius_bound
100
+ </NAME>
101
+
102
+ <DESCRIPTION>
103
+ This change enhances numerical stability and constraint adherence by:
104
+ 1. **Enforcing a non-zero minimum radius in the solver bounds**: The radius bounds are changed from `(0.0, 0.5)` to `(1e-7, 0.5)`. This explicitly tells the optimizer that radii must be at least `1e-7`, preventing them from collapsing to exactly zero due to floating-point inaccuracies, which can lead to numerical instability or degenerate solutions, especially with very tight `ftol`/`gtol` settings. This aligns with Recommendation 5.
105
+ </DESCRIPTION>
106
+
107
+ <DIFF>
108
+ <<<<<<< SEARCH
109
+ bounds = []
110
+ for _ in range(n):
111
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
112
+ =======
113
+ bounds = []
114
+ MIN_RADIUS_BOUND = 1e-7 # Enforce a small positive minimum radius for numerical stability
115
+ for _ in range(n):
116
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
117
+ >>>>>>> REPLACE
118
+ </DIFF>
119
+ <NAME>
120
+ adaptive_min_gap_and_positive_radii
121
+ </NAME>
122
+
123
+ <DESCRIPTION>
124
+ This edit improves the initial radii calculation (`_compute_initial_radii`) by:
125
+ 1. **Implementing an adaptive `MIN_GAP`**: The `MIN_GAP` used to resolve initial overlaps is now dynamically scaled by the number of circles (`1e-7 / np.sqrt(num_circles)`). This makes the initial packing tighter for larger `N` and more robust for smaller `N`, as recommended (Recommendation 3). It allows the solver to start from a more finely tuned initial feasible state.
126
+ 2. **Ensuring strictly positive radii**: A final `np.maximum(radii, 1e-10)` is applied to the computed radii before returning. This explicitly ensures that all initial radii are strictly positive, complementing the `MIN_RADIUS_BOUND` change in the solver's variable bounds and preventing potential numerical issues with zero radii.
127
+ </DESCRIPTION>
128
+
129
+ <DIFF>
130
+ <<<<<<< SEARCH
131
+ def _compute_initial_radii(centers, max_iter=200):
132
+ """Iteratively compute max non-overlapping radii for a given set of centers."""
133
+ num_circles = centers.shape[0]
134
+ radii = np.zeros(num_circles)
135
+ MIN_GAP = 1e-8 # Use a small gap for robustness
136
+
137
+ # Initialize radii based on distance to walls
138
+ for i in range(num_circles):
139
+ x, y = centers[i]
140
+ radii[i] = min(x, 1 - x, y, 1 - y)
141
+
142
+ # Iteratively shrink radii to resolve overlaps
143
+ for _ in range(max_iter):
144
+ had_change = False
145
+ for i in range(num_circles):
146
+ for j in range(i + 1, num_circles):
147
+ dist = np.linalg.norm(centers[i] - centers[j])
148
+ sum_r = radii[i] + radii[j]
149
+ if sum_r > dist - MIN_GAP:
150
+ target_sum_r = max(0.0, dist - MIN_GAP)
151
+ if sum_r > 1e-12:
152
+ scale = target_sum_r / sum_r
153
+ radii[i] *= scale
154
+ radii[j] *= scale
155
+ had_change = True
156
+ if not had_change:
157
+ break
158
+ return radii
159
+ =======
160
+ def _compute_initial_radii(centers, max_iter=200):
161
+ """
162
+ Iteratively compute max non-overlapping radii for a given set of centers.
163
+ Uses an adaptive MIN_GAP and ensures strictly positive radii.
164
+ """
165
+ num_circles = centers.shape[0]
166
+ radii = np.zeros(num_circles)
167
+ # Adaptive MIN_GAP: scales with the number of circles to allow tighter packing
168
+ # or more robust initial state depending on N, aligning with Recommendation 3.
169
+ MIN_GAP = 1e-7 / np.sqrt(num_circles)
170
+
171
+ # Initialize radii based on distance to walls
172
+ for i in range(num_circles):
173
+ x, y = centers[i]
174
+ radii[i] = min(x, 1 - x, y, 1 - y)
175
+
176
+ # Iteratively shrink radii to resolve overlaps
177
+ for _ in range(max_iter):
178
+ had_change = False
179
+ for i in range(num_circles):
180
+ for j in range(i + 1, num_circles):
181
+ dist = np.linalg.norm(centers[i] - centers[j])
182
+ sum_r = radii[i] + radii[j]
183
+ if sum_r > dist - MIN_GAP: # Check for overlap with the adaptive gap
184
+ target_sum_r = max(0.0, dist - MIN_GAP)
185
+ if sum_r > 1e-12:
186
+ scale = target_sum_r / sum_r
187
+ radii[i] *= scale
188
+ radii[j] *= scale
189
+ had_change = True
190
+ if not had_change:
191
+ break
192
+ # Ensure radii are strictly positive to prevent numerical issues with zero radii.
193
+ return np.maximum(radii, 1e-10)
194
+ >>>>>>> REPLACE
195
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/edit.diff ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,205 +1,206 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ def construct_packing():
9
+ """
10
+ - Constructs an optimized arrangement of 26 circles by formulating the problem
11
+ - as a nonlinear program and solving it with scipy.optimize.minimize. This approach
12
+ - reverts to a proven high-performance strategy, abandoning the less effective
13
+ - Simulated Annealing method, and re-implements the successful two-stage NLP.
14
+ + Constructs an optimized arrangement of 26 circles by leveraging a hybrid of the
15
+ + best strategies: diverse initial layouts, a novel hybrid objective function,
16
+ + and a three-stage NLP with a tiered perturbation schedule.
17
+ + - **Hybrid Initialization**: Rotates between three distinct initial layouts (grid,
18
+ + hexagonal, and a known best) to explore different regions of the solution space.
19
+ + - **Hybrid Objective**: Uses a weighted objective in the first stage to balance
20
+ + area maximization (for density) and radii sum maximization (the primary goal).
21
+ + - **Intensified Search**: Employs a high number of runs with a tiered perturbation
22
+ + schedule and very aggressive solver settings for deep refinement.
23
+ """
24
+ n = 26
25
+
26
+ - # Helper functions to convert between the flat optimization vector and
27
+ - # the structured centers/radii arrays.
28
+ + # Helper functions to pack/unpack optimization variables
29
+ def pack_vars(centers, radii):
30
+ x = np.zeros(n * 3)
31
+ x[0::3] = centers[:, 0]
32
+ x[1::3] = centers[:, 1]
33
+ x[2::3] = radii
34
+ return x
35
+
36
+ def unpack_vars(x):
37
+ centers_x = x[0::3]
38
+ centers_y = x[1::3]
39
+ radii = x[2::3]
40
+ centers = np.vstack((centers_x, centers_y)).T
41
+ return centers, radii
42
+
43
+ - # --- 1. Initial Guess ---
44
+ - # A good initial guess is crucial for the optimizer to find a high-quality solution.
45
+ - def _compute_initial_radii(centers, max_iter=200):
46
+ - """Iteratively compute max radii for a given set of centers."""
47
+ + # --- 1. Initial Guess Generation ---
48
+ + def _compute_initial_radii(centers, max_iter=250):
49
+ + """
50
+ + Iteratively computes max feasible radii for a given set of centers.
51
+ + """
52
+ num_circles = centers.shape[0]
53
+ radii = np.zeros(num_circles)
54
+ -
55
+ - # Initialize radii based on distance to walls
56
+ + MIN_GAP_THRESHOLD = 1e-8
57
+ +
58
+ for i in range(num_circles):
59
+ x, y = centers[i]
60
+ radii[i] = min(x, 1 - x, y, 1 - y)
61
+
62
+ - # Iteratively shrink radii based on proximity to other circles
63
+ - # Adaptive MIN_GAP_THRESHOLD based on the number of circles
64
+ - MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(num_circles)
65
+ for _ in range(max_iter):
66
+ had_change = False
67
+ for i in range(num_circles):
68
+ for j in range(i + 1, num_circles):
69
+ dist = np.linalg.norm(centers[i] - centers[j])
70
+ sum_r = radii[i] + radii[j]
71
+ - # Check if circles overlap or are within MIN_GAP_THRESHOLD
72
+ if sum_r > dist - MIN_GAP_THRESHOLD:
73
+ - # Calculate the new sum of radii needed to maintain a MIN_GAP_THRESHOLD.
74
+ - # Ensure target_sum_r is non-negative.
75
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
76
+ - if sum_r > 1e-12: # Avoid division by zero
77
+ + if sum_r > 1e-12:
78
+ scale = target_sum_r / sum_r
79
+ radii[i] *= scale
80
+ radii[j] *= scale
81
+ had_change = True
82
+ if not had_change:
83
+ break
84
+ return radii
85
+
86
+ - # --- 2. Define Objective Functions for Staged Optimization ---
87
+ - # Stage 1 objective: Maximize sum of areas (r^2) to find a globally dense packing.
88
+ - def objective_area(x):
89
+ - _, radii = unpack_vars(x)
90
+ - return -np.sum(radii**2)
91
+ -
92
+ - # Stage 2 objective: Maximize sum of radii (r), the primary goal.
93
+ - def objective_radii(x):
94
+ - _, radii = unpack_vars(x)
95
+ - return -np.sum(radii)
96
+ -
97
+ - # --- 3. Define Constraints ---
98
+ - # All constraint functions must be of the form f(x) >= 0.
99
+ - cons = []
100
+ -
101
+ - # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
102
+ - # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
103
+ - def non_overlap_constraint(x):
104
+ - centers, radii = unpack_vars(x)
105
+ -
106
+ - # Get indices for the upper triangle of the pairwise matrix to avoid redundant checks.
107
+ - i, j = np.triu_indices(n, k=1)
108
+ -
109
+ - # Calculate squared Euclidean distance for all unique pairs.
110
+ - dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
111
+ -
112
+ - # Calculate squared sum of radii for corresponding pairs.
113
+ - sum_radii_sq = (radii[i] + radii[j])**2
114
+ -
115
+ - return dist_sq - sum_radii_sq
116
+ -
117
+ - cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
118
+ -
119
+ - # Constraint 2: Circles must be within the [0,1] x [0,1] square.
120
+ - def boundary_constraint(x):
121
+ - centers, radii = unpack_vars(x)
122
+ - # Return a flat array of all boundary constraint values
123
+ - return np.concatenate([
124
+ - centers[:, 0] - radii, # x - r >= 0
125
+ - 1 - centers[:, 0] - radii, # 1 - x - r >= 0
126
+ - centers[:, 1] - radii, # y - r >= 0
127
+ - 1 - centers[:, 1] - radii # 1 - y - r >= 0
128
+ - ])
129
+ -
130
+ - cons.append({'type': 'ineq', 'fun': boundary_constraint})
131
+ -
132
+ - # --- 4. Define Bounds for each variable ---
133
+ - # 0 <= center_x, center_y <= 1
134
+ - # MIN_RADIUS <= radius <= 0.5 (a single circle cannot have a radius > 0.5, and must be positive)
135
+ - MIN_RADIUS = 1e-6 # Enforce a minimum positive radius
136
+ - bounds = []
137
+ - for _ in range(n):
138
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
139
+ -
140
+ - # --- 5. Run the Optimizer with Iterative Perturbation ---
141
+ - # Define the base initial centers from a previously found high-quality solution.
142
+ - base_initial_centers = np.array([
143
+ + # Guess 1: Proven 5x5 grid with a split center.
144
+ + base_centers_grid = np.zeros((n, 2))
145
+ + idx = 0
146
+ + for i in range(5):
147
+ + for j in range(5):
148
+ + if i == 2 and j == 2: continue
149
+ + base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
150
+ + idx += 1
151
+ + base_centers_grid[24] = [0.5, 0.45]
152
+ + base_centers_grid[25] = [0.5, 0.55]
153
+ +
154
+ + # Guess 2: Dense hexagonal-like grid.
155
+ + def _get_hexagonal_initial_centers():
156
+ + centers = []
157
+ + rows = [5, 6, 5, 6, 4] # Total 26
158
+ + y = 0.05
159
+ + for i, count in enumerate(rows):
160
+ + x_offset = 0.05 if i % 2 == 1 else 0.15 # Stagger rows
161
+ + for j in range(count):
162
+ + if len(centers) < n:
163
+ + centers.append([x_offset + j * 0.18, y])
164
+ + y += 0.16
165
+ + centers = np.array(centers)
166
+ + centers = np.clip(centers, 0.01, 0.99)
167
+ + return centers
168
+ + base_centers_hex = _get_hexagonal_initial_centers()
169
+ +
170
+ + # Guess 3: Seed with the best recent result.
171
+ + base_centers_best_known = np.array([
172
+ [0.1130, 0.1130], [0.0698, 0.2906], [0.1251, 0.4775], [0.0782, 0.6753],
173
+ [0.1261, 0.8739], [0.3283, 0.1026], [0.2391, 0.2840], [0.3517, 0.4523],
174
+ [0.2854, 0.6746], [0.3545, 0.8966], [0.5307, 0.0998], [0.4346, 0.2709],
175
+ [0.4682, 0.7614], [0.5515, 0.9062], [0.7314, 0.1009], [0.6292, 0.2717],
176
+ [0.7104, 0.5149], [0.6403, 0.7324], [0.7426, 0.9027], [0.9158, 0.0842],
177
+ [0.8629, 0.2992], [0.9187, 0.5103], [0.8705, 0.7154], [0.9196, 0.9196],
178
+ [0.5296, 0.4174], [0.4978, 0.5917]
179
+ ])
180
+ -
181
+ - num_optimization_runs = 30 # Increased runs for more robust exploration
182
+ +
183
+ + initial_layouts = [base_centers_best_known, base_centers_grid, base_centers_hex]
184
+ +
185
+ + # --- 2. Define Objective Functions for Staged Optimization ---
186
+ + def objective_hybrid(x, alpha=0.2):
187
+ + """Stage 1: Maximize a weighted sum of total area and total radii."""
188
+ + _, radii = unpack_vars(x)
189
+ + sum_radii_sq = np.sum(radii**2)
190
+ + sum_radii = np.sum(radii)
191
+ + return - (alpha * sum_radii_sq + (1 - alpha) * sum_radii)
192
+ +
193
+ + def objective_radii(x):
194
+ + """Stage 2 & 3: Maximize sum of radii (the primary goal)."""
195
+ + _, radii = unpack_vars(x)
196
+ + return -np.sum(radii)
197
+ +
198
+ + # --- 3. Define Constraints ---
199
+ + cons = []
200
+ +
201
+ + def non_overlap_constraint(x):
202
+ + """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. Numerically stable."""
203
+ + centers, radii = unpack_vars(x)
204
+ + i, j = np.triu_indices(n, k=1)
205
+ + dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
206
+ + sum_radii_sq = (radii[i] + radii[j])**2
207
+ + return dist_sq - sum_radii_sq
208
+ + cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
209
+ +
210
+ + def boundary_constraint(x):
211
+ + centers, radii = unpack_vars(x)
212
+ + return np.concatenate([
213
+ + centers[:, 0] - radii, 1 - centers[:, 0] - radii,
214
+ + centers[:, 1] - radii, 1 - centers[:, 1] - radii
215
+ + ])
216
+ + cons.append({'type': 'ineq', 'fun': boundary_constraint})
217
+ +
218
+ + # --- 4. Define Bounds ---
219
+ + MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for numerical stability
220
+ + bounds = []
221
+ + for _ in range(n):
222
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
223
+ +
224
+ + # --- 5. Run the Optimizer with Hybrid Strategy ---
225
+ best_sum_radii = -np.inf
226
+ best_result_x = None
227
+
228
+ - # Aggressive optimizer settings
229
+ - options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
230
+ - options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
231
+ - options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # More iterations for final refinement
232
+ -
233
+ - for run in range(num_optimization_runs):
234
+ - # Apply dynamic perturbation to initial centers: wider at start, narrower at end
235
+ - max_perturbation_std_dev = 0.02 # Focus perturbation around the good starting point
236
+ - min_perturbation_std_dev = 0.001
237
+ - if num_optimization_runs > 1:
238
+ - perturbation_std_dev = max_perturbation_std_dev - (run / (num_optimization_runs - 1)) * \
239
+ - (max_perturbation_std_dev - min_perturbation_std_dev)
240
+ - else:
241
+ - perturbation_std_dev = min_perturbation_std_dev
242
+ -
243
+ - # Add small random noise to centers, clipped to stay within [0,1]
244
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
245
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
246
+ -
247
+ - # Compute the maximum possible radii for the perturbed centers.
248
+ - perturbed_radii = _compute_initial_radii(perturbed_centers)
249
+ -
250
+ - # Create the initial optimization vector `x0` for this run.
251
+ - x0_run = pack_vars(perturbed_centers, perturbed_radii)
252
+ -
253
+ - # Stage 1: Maximize sum of *areas* (r^2)
254
+ - result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
255
+ - if not result_stage1.success:
256
+ - continue
257
+ -
258
+ - # Stage 2: Maximize sum of *radii* (r)
259
+ - result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
260
+ - if not result_stage2.success:
261
+ - continue
262
+ -
263
+ - # Stage 3: Further maximize sum of *radii* (r) with tighter options
264
+ - result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
265
+ -
266
+ - # Extract results for this run from the final stage
267
+ - _, current_radii = unpack_vars(result_stage3.x)
268
+ - current_sum_radii = np.sum(current_radii)
269
+ -
270
+ - # Keep track of the best result found so far
271
+ - if current_sum_radii > best_sum_radii:
272
+ - best_sum_radii = current_sum_radii
273
+ - best_result_x = result_stage3.x
274
+ + # Tiered perturbation schedule for exploration/exploitation balance
275
+ + PERTURB_SCHEDULE = [(0.030, 12), (0.010, 12), (0.003, 12)] # (std_dev, num_runs)
276
+ +
277
+ + # Aggressive solver settings with more iterations in the final stage
278
+ + options_stage1 = {'maxiter': 1500, 'ftol': 1e-9, 'gtol': 1e-7, 'disp': False}
279
+ + options_stage2 = {'maxiter': 3000, 'ftol': 1e-11, 'gtol': 1e-9, 'disp': False}
280
+ + options_stage3 = {'maxiter': 6000, 'ftol': 1e-13, 'gtol': 1e-11, 'disp': False}
281
+ +
282
+ + run_count = 0
283
+ + for std_dev, num_runs in PERTURB_SCHEDULE:
284
+ + for _ in range(num_runs):
285
+ + # Cycle through the three base layouts
286
+ + current_base_centers = initial_layouts[run_count % len(initial_layouts)]
287
+ +
288
+ + # Apply perturbation
289
+ + perturbed_centers = current_base_centers + np.random.normal(0, std_dev, current_base_centers.shape)
290
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
291
+ +
292
+ + perturbed_radii = _compute_initial_radii(perturbed_centers)
293
+ + x0_run = pack_vars(perturbed_centers, perturbed_radii)
294
+ +
295
+ + # Stage 1: Maximize hybrid objective
296
+ + res1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
297
+ + if not res1.success: continue
298
+ +
299
+ + # Stage 2: Maximize sum of radii
300
+ + res2 = minimize(objective_radii, res1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
301
+ + if not res2.success: continue
302
+ +
303
+ + # Stage 3: Final refinement of radii sum with highest precision
304
+ + res3 = minimize(objective_radii, res2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
305
+ +
306
+ + _, current_radii = unpack_vars(res3.x)
307
+ + current_sum_radii = np.sum(current_radii)
308
+ +
309
+ + if current_sum_radii > best_sum_radii:
310
+ + best_sum_radii = current_sum_radii
311
+ + best_result_x = res3.x
312
+ +
313
+ + run_count += 1
314
+
315
+ # --- 6. Extract and Return Results ---
316
+ - # Use the best result found across all perturbed runs.
317
+ if best_result_x is None:
318
+ - # Fallback to a valid result if all runs failed
319
+ - initial_radii = _compute_initial_radii(base_initial_centers)
320
+ - best_result_x = pack_vars(base_initial_centers, initial_radii)
321
+ -
322
+ - final_x = best_result_x
323
+ - final_centers, final_radii = unpack_vars(final_x)
324
+ -
325
+ - # Clean up any potential floating point inaccuracies (e.g., small negative radii).
326
+ - final_radii = np.maximum(final_radii, 0)
327
+ + initial_radii = _compute_initial_radii(base_centers_best_known)
328
+ + best_result_x = pack_vars(base_centers_best_known, initial_radii)
329
+ +
330
+ + final_centers, final_radii = unpack_vars(best_result_x)
331
+ + final_radii = np.maximum(final_radii, 0) # Final cleanup
332
+
333
+ return final_centers, final_radii
334
+ # EVOLVE-BLOCK-END
335
+
336
+
337
+ # This part remains fixed (not evolved)
338
+ def run_packing():
339
+ """Run the circle packing constructor for n=26"""
340
+ centers, radii = construct_packing()
341
+ # Calculate the sum of radii
342
+ sum_radii = np.sum(radii)
343
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/main.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by leveraging a hybrid of the
8
+ best strategies: diverse initial layouts, a novel hybrid objective function,
9
+ and a three-stage NLP with a tiered perturbation schedule.
10
+ - **Hybrid Initialization**: Rotates between three distinct initial layouts (grid,
11
+ hexagonal, and a known best) to explore different regions of the solution space.
12
+ - **Hybrid Objective**: Uses a weighted objective in the first stage to balance
13
+ area maximization (for density) and radii sum maximization (the primary goal).
14
+ - **Intensified Search**: Employs a high number of runs with a tiered perturbation
15
+ schedule and very aggressive solver settings for deep refinement.
16
+ """
17
+ n = 26
18
+
19
+ # Helper functions to pack/unpack optimization variables
20
+ def pack_vars(centers, radii):
21
+ x = np.zeros(n * 3)
22
+ x[0::3] = centers[:, 0]
23
+ x[1::3] = centers[:, 1]
24
+ x[2::3] = radii
25
+ return x
26
+
27
+ def unpack_vars(x):
28
+ centers_x = x[0::3]
29
+ centers_y = x[1::3]
30
+ radii = x[2::3]
31
+ centers = np.vstack((centers_x, centers_y)).T
32
+ return centers, radii
33
+
34
+ # --- 1. Initial Guess Generation ---
35
+ def _compute_initial_radii(centers, max_iter=250):
36
+ """
37
+ Iteratively computes max feasible radii for a given set of centers.
38
+ """
39
+ num_circles = centers.shape[0]
40
+ radii = np.zeros(num_circles)
41
+ MIN_GAP_THRESHOLD = 1e-8
42
+
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ for _ in range(max_iter):
48
+ had_change = False
49
+ for i in range(num_circles):
50
+ for j in range(i + 1, num_circles):
51
+ dist = np.linalg.norm(centers[i] - centers[j])
52
+ sum_r = radii[i] + radii[j]
53
+ if sum_r > dist - MIN_GAP_THRESHOLD:
54
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
55
+ if sum_r > 1e-12:
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # Guess 1: Proven 5x5 grid with a split center.
65
+ base_centers_grid = np.zeros((n, 2))
66
+ idx = 0
67
+ for i in range(5):
68
+ for j in range(5):
69
+ if i == 2 and j == 2: continue
70
+ base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
71
+ idx += 1
72
+ base_centers_grid[24] = [0.5, 0.45]
73
+ base_centers_grid[25] = [0.5, 0.55]
74
+
75
+ # Guess 2: Dense hexagonal-like grid.
76
+ def _get_hexagonal_initial_centers():
77
+ centers = []
78
+ rows = [5, 6, 5, 6, 4] # Total 26
79
+ y = 0.05
80
+ for i, count in enumerate(rows):
81
+ x_offset = 0.05 if i % 2 == 1 else 0.15 # Stagger rows
82
+ for j in range(count):
83
+ if len(centers) < n:
84
+ centers.append([x_offset + j * 0.18, y])
85
+ y += 0.16
86
+ centers = np.array(centers)
87
+ centers = np.clip(centers, 0.01, 0.99)
88
+ return centers
89
+ base_centers_hex = _get_hexagonal_initial_centers()
90
+
91
+ # Guess 3: Seed with the best recent result.
92
+ base_centers_best_known = np.array([
93
+ [0.1130, 0.1130], [0.0698, 0.2906], [0.1251, 0.4775], [0.0782, 0.6753],
94
+ [0.1261, 0.8739], [0.3283, 0.1026], [0.2391, 0.2840], [0.3517, 0.4523],
95
+ [0.2854, 0.6746], [0.3545, 0.8966], [0.5307, 0.0998], [0.4346, 0.2709],
96
+ [0.4682, 0.7614], [0.5515, 0.9062], [0.7314, 0.1009], [0.6292, 0.2717],
97
+ [0.7104, 0.5149], [0.6403, 0.7324], [0.7426, 0.9027], [0.9158, 0.0842],
98
+ [0.8629, 0.2992], [0.9187, 0.5103], [0.8705, 0.7154], [0.9196, 0.9196],
99
+ [0.5296, 0.4174], [0.4978, 0.5917]
100
+ ])
101
+
102
+ initial_layouts = [base_centers_best_known, base_centers_grid, base_centers_hex]
103
+
104
+ # --- 2. Define Objective Functions for Staged Optimization ---
105
+ def objective_hybrid(x, alpha=0.2):
106
+ """Stage 1: Maximize a weighted sum of total area and total radii."""
107
+ _, radii = unpack_vars(x)
108
+ sum_radii_sq = np.sum(radii**2)
109
+ sum_radii = np.sum(radii)
110
+ return - (alpha * sum_radii_sq + (1 - alpha) * sum_radii)
111
+
112
+ def objective_radii(x):
113
+ """Stage 2 & 3: Maximize sum of radii (the primary goal)."""
114
+ _, radii = unpack_vars(x)
115
+ return -np.sum(radii)
116
+
117
+ # --- 3. Define Constraints ---
118
+ cons = []
119
+
120
+ def non_overlap_constraint(x):
121
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. Numerically stable."""
122
+ centers, radii = unpack_vars(x)
123
+ i, j = np.triu_indices(n, k=1)
124
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
125
+ sum_radii_sq = (radii[i] + radii[j])**2
126
+ return dist_sq - sum_radii_sq
127
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
128
+
129
+ def boundary_constraint(x):
130
+ centers, radii = unpack_vars(x)
131
+ return np.concatenate([
132
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
133
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
134
+ ])
135
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
136
+
137
+ # --- 4. Define Bounds ---
138
+ MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for numerical stability
139
+ bounds = []
140
+ for _ in range(n):
141
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
142
+
143
+ # --- 5. Run the Optimizer with Hybrid Strategy ---
144
+ best_sum_radii = -np.inf
145
+ best_result_x = None
146
+
147
+ # Tiered perturbation schedule for exploration/exploitation balance
148
+ PERTURB_SCHEDULE = [(0.030, 12), (0.010, 12), (0.003, 12)] # (std_dev, num_runs)
149
+
150
+ # Aggressive solver settings with more iterations in the final stage
151
+ options_stage1 = {'maxiter': 1500, 'ftol': 1e-9, 'gtol': 1e-7, 'disp': False}
152
+ options_stage2 = {'maxiter': 3000, 'ftol': 1e-11, 'gtol': 1e-9, 'disp': False}
153
+ options_stage3 = {'maxiter': 6000, 'ftol': 1e-13, 'gtol': 1e-11, 'disp': False}
154
+
155
+ run_count = 0
156
+ for std_dev, num_runs in PERTURB_SCHEDULE:
157
+ for _ in range(num_runs):
158
+ # Cycle through the three base layouts
159
+ current_base_centers = initial_layouts[run_count % len(initial_layouts)]
160
+
161
+ # Apply perturbation
162
+ perturbed_centers = current_base_centers + np.random.normal(0, std_dev, current_base_centers.shape)
163
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
164
+
165
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
166
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
167
+
168
+ # Stage 1: Maximize hybrid objective
169
+ res1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
170
+ if not res1.success: continue
171
+
172
+ # Stage 2: Maximize sum of radii
173
+ res2 = minimize(objective_radii, res1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
174
+ if not res2.success: continue
175
+
176
+ # Stage 3: Final refinement of radii sum with highest precision
177
+ res3 = minimize(objective_radii, res2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
178
+
179
+ _, current_radii = unpack_vars(res3.x)
180
+ current_sum_radii = np.sum(current_radii)
181
+
182
+ if current_sum_radii > best_sum_radii:
183
+ best_sum_radii = current_sum_radii
184
+ best_result_x = res3.x
185
+
186
+ run_count += 1
187
+
188
+ # --- 6. Extract and Return Results ---
189
+ if best_result_x is None:
190
+ initial_radii = _compute_initial_radii(base_centers_best_known)
191
+ best_result_x = pack_vars(base_centers_best_known, initial_radii)
192
+
193
+ final_centers, final_radii = unpack_vars(best_result_x)
194
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
195
+
196
+ return final_centers, final_radii
197
+ # EVOLVE-BLOCK-END
198
+
199
+
200
+ # This part remains fixed (not evolved)
201
+ def run_packing():
202
+ """Run the circle packing constructor for n=26"""
203
+ centers, radii = construct_packing()
204
+ # Calculate the sum of radii
205
+ sum_radii = np.sum(radii)
206
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/original.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by formulating the problem
8
+ as a nonlinear program and solving it with scipy.optimize.minimize. This approach
9
+ reverts to a proven high-performance strategy, abandoning the less effective
10
+ Simulated Annealing method, and re-implements the successful two-stage NLP.
11
+ """
12
+ n = 26
13
+
14
+ # Helper functions to convert between the flat optimization vector and
15
+ # the structured centers/radii arrays.
16
+ def pack_vars(centers, radii):
17
+ x = np.zeros(n * 3)
18
+ x[0::3] = centers[:, 0]
19
+ x[1::3] = centers[:, 1]
20
+ x[2::3] = radii
21
+ return x
22
+
23
+ def unpack_vars(x):
24
+ centers_x = x[0::3]
25
+ centers_y = x[1::3]
26
+ radii = x[2::3]
27
+ centers = np.vstack((centers_x, centers_y)).T
28
+ return centers, radii
29
+
30
+ # --- 1. Initial Guess ---
31
+ # A good initial guess is crucial for the optimizer to find a high-quality solution.
32
+ def _compute_initial_radii(centers, max_iter=200):
33
+ """Iteratively compute max radii for a given set of centers."""
34
+ num_circles = centers.shape[0]
35
+ radii = np.zeros(num_circles)
36
+
37
+ # Initialize radii based on distance to walls
38
+ for i in range(num_circles):
39
+ x, y = centers[i]
40
+ radii[i] = min(x, 1 - x, y, 1 - y)
41
+
42
+ # Iteratively shrink radii based on proximity to other circles
43
+ # Adaptive MIN_GAP_THRESHOLD based on the number of circles
44
+ MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(num_circles)
45
+ for _ in range(max_iter):
46
+ had_change = False
47
+ for i in range(num_circles):
48
+ for j in range(i + 1, num_circles):
49
+ dist = np.linalg.norm(centers[i] - centers[j])
50
+ sum_r = radii[i] + radii[j]
51
+ # Check if circles overlap or are within MIN_GAP_THRESHOLD
52
+ if sum_r > dist - MIN_GAP_THRESHOLD:
53
+ # Calculate the new sum of radii needed to maintain a MIN_GAP_THRESHOLD.
54
+ # Ensure target_sum_r is non-negative.
55
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
56
+ if sum_r > 1e-12: # Avoid division by zero
57
+ scale = target_sum_r / sum_r
58
+ radii[i] *= scale
59
+ radii[j] *= scale
60
+ had_change = True
61
+ if not had_change:
62
+ break
63
+ return radii
64
+
65
+ # --- 2. Define Objective Functions for Staged Optimization ---
66
+ # Stage 1 objective: Maximize sum of areas (r^2) to find a globally dense packing.
67
+ def objective_area(x):
68
+ _, radii = unpack_vars(x)
69
+ return -np.sum(radii**2)
70
+
71
+ # Stage 2 objective: Maximize sum of radii (r), the primary goal.
72
+ def objective_radii(x):
73
+ _, radii = unpack_vars(x)
74
+ return -np.sum(radii)
75
+
76
+ # --- 3. Define Constraints ---
77
+ # All constraint functions must be of the form f(x) >= 0.
78
+ cons = []
79
+
80
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
81
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
82
+ def non_overlap_constraint(x):
83
+ centers, radii = unpack_vars(x)
84
+
85
+ # Get indices for the upper triangle of the pairwise matrix to avoid redundant checks.
86
+ i, j = np.triu_indices(n, k=1)
87
+
88
+ # Calculate squared Euclidean distance for all unique pairs.
89
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
90
+
91
+ # Calculate squared sum of radii for corresponding pairs.
92
+ sum_radii_sq = (radii[i] + radii[j])**2
93
+
94
+ return dist_sq - sum_radii_sq
95
+
96
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
97
+
98
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
99
+ def boundary_constraint(x):
100
+ centers, radii = unpack_vars(x)
101
+ # Return a flat array of all boundary constraint values
102
+ return np.concatenate([
103
+ centers[:, 0] - radii, # x - r >= 0
104
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
105
+ centers[:, 1] - radii, # y - r >= 0
106
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
107
+ ])
108
+
109
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
110
+
111
+ # --- 4. Define Bounds for each variable ---
112
+ # 0 <= center_x, center_y <= 1
113
+ # MIN_RADIUS <= radius <= 0.5 (a single circle cannot have a radius > 0.5, and must be positive)
114
+ MIN_RADIUS = 1e-6 # Enforce a minimum positive radius
115
+ bounds = []
116
+ for _ in range(n):
117
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
118
+
119
+ # --- 5. Run the Optimizer with Iterative Perturbation ---
120
+ # Define the base initial centers from a previously found high-quality solution.
121
+ base_initial_centers = np.array([
122
+ [0.1130, 0.1130], [0.0698, 0.2906], [0.1251, 0.4775], [0.0782, 0.6753],
123
+ [0.1261, 0.8739], [0.3283, 0.1026], [0.2391, 0.2840], [0.3517, 0.4523],
124
+ [0.2854, 0.6746], [0.3545, 0.8966], [0.5307, 0.0998], [0.4346, 0.2709],
125
+ [0.4682, 0.7614], [0.5515, 0.9062], [0.7314, 0.1009], [0.6292, 0.2717],
126
+ [0.7104, 0.5149], [0.6403, 0.7324], [0.7426, 0.9027], [0.9158, 0.0842],
127
+ [0.8629, 0.2992], [0.9187, 0.5103], [0.8705, 0.7154], [0.9196, 0.9196],
128
+ [0.5296, 0.4174], [0.4978, 0.5917]
129
+ ])
130
+
131
+ num_optimization_runs = 30 # Increased runs for more robust exploration
132
+ best_sum_radii = -np.inf
133
+ best_result_x = None
134
+
135
+ # Aggressive optimizer settings
136
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
137
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
138
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # More iterations for final refinement
139
+
140
+ for run in range(num_optimization_runs):
141
+ # Apply dynamic perturbation to initial centers: wider at start, narrower at end
142
+ max_perturbation_std_dev = 0.02 # Focus perturbation around the good starting point
143
+ min_perturbation_std_dev = 0.001
144
+ if num_optimization_runs > 1:
145
+ perturbation_std_dev = max_perturbation_std_dev - (run / (num_optimization_runs - 1)) * \
146
+ (max_perturbation_std_dev - min_perturbation_std_dev)
147
+ else:
148
+ perturbation_std_dev = min_perturbation_std_dev
149
+
150
+ # Add small random noise to centers, clipped to stay within [0,1]
151
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
152
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
153
+
154
+ # Compute the maximum possible radii for the perturbed centers.
155
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
156
+
157
+ # Create the initial optimization vector `x0` for this run.
158
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
159
+
160
+ # Stage 1: Maximize sum of *areas* (r^2)
161
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
162
+ if not result_stage1.success:
163
+ continue
164
+
165
+ # Stage 2: Maximize sum of *radii* (r)
166
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
167
+ if not result_stage2.success:
168
+ continue
169
+
170
+ # Stage 3: Further maximize sum of *radii* (r) with tighter options
171
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
172
+
173
+ # Extract results for this run from the final stage
174
+ _, current_radii = unpack_vars(result_stage3.x)
175
+ current_sum_radii = np.sum(current_radii)
176
+
177
+ # Keep track of the best result found so far
178
+ if current_sum_radii > best_sum_radii:
179
+ best_sum_radii = current_sum_radii
180
+ best_result_x = result_stage3.x
181
+
182
+ # --- 6. Extract and Return Results ---
183
+ # Use the best result found across all perturbed runs.
184
+ if best_result_x is None:
185
+ # Fallback to a valid result if all runs failed
186
+ initial_radii = _compute_initial_radii(base_initial_centers)
187
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
188
+
189
+ final_x = best_result_x
190
+ final_centers, final_radii = unpack_vars(final_x)
191
+
192
+ # Clean up any potential floating point inaccuracies (e.g., small negative radii).
193
+ final_radii = np.maximum(final_radii, 0)
194
+
195
+ return final_centers, final_radii
196
+ # EVOLVE-BLOCK-END
197
+
198
+
199
+ # This part remains fixed (not evolved)
200
+ def run_packing():
201
+ """Run the circle packing constructor for n=26"""
202
+ centers, radii = construct_packing()
203
+ # Calculate the sum of radii
204
+ sum_radii = np.sum(radii)
205
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/rewrite.txt ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by leveraging a hybrid of the
8
+ best strategies: diverse initial layouts, a novel hybrid objective function,
9
+ and a three-stage NLP with a tiered perturbation schedule.
10
+ - **Hybrid Initialization**: Rotates between three distinct initial layouts (grid,
11
+ hexagonal, and a known best) to explore different regions of the solution space.
12
+ - **Hybrid Objective**: Uses a weighted objective in the first stage to balance
13
+ area maximization (for density) and radii sum maximization (the primary goal).
14
+ - **Intensified Search**: Employs a high number of runs with a tiered perturbation
15
+ schedule and very aggressive solver settings for deep refinement.
16
+ """
17
+ n = 26
18
+
19
+ # Helper functions to pack/unpack optimization variables
20
+ def pack_vars(centers, radii):
21
+ x = np.zeros(n * 3)
22
+ x[0::3] = centers[:, 0]
23
+ x[1::3] = centers[:, 1]
24
+ x[2::3] = radii
25
+ return x
26
+
27
+ def unpack_vars(x):
28
+ centers_x = x[0::3]
29
+ centers_y = x[1::3]
30
+ radii = x[2::3]
31
+ centers = np.vstack((centers_x, centers_y)).T
32
+ return centers, radii
33
+
34
+ # --- 1. Initial Guess Generation ---
35
+ def _compute_initial_radii(centers, max_iter=250):
36
+ """
37
+ Iteratively computes max feasible radii for a given set of centers.
38
+ """
39
+ num_circles = centers.shape[0]
40
+ radii = np.zeros(num_circles)
41
+ MIN_GAP_THRESHOLD = 1e-8
42
+
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ for _ in range(max_iter):
48
+ had_change = False
49
+ for i in range(num_circles):
50
+ for j in range(i + 1, num_circles):
51
+ dist = np.linalg.norm(centers[i] - centers[j])
52
+ sum_r = radii[i] + radii[j]
53
+ if sum_r > dist - MIN_GAP_THRESHOLD:
54
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
55
+ if sum_r > 1e-12:
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # Guess 1: Proven 5x5 grid with a split center.
65
+ base_centers_grid = np.zeros((n, 2))
66
+ idx = 0
67
+ for i in range(5):
68
+ for j in range(5):
69
+ if i == 2 and j == 2: continue
70
+ base_centers_grid[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
71
+ idx += 1
72
+ base_centers_grid[24] = [0.5, 0.45]
73
+ base_centers_grid[25] = [0.5, 0.55]
74
+
75
+ # Guess 2: Dense hexagonal-like grid.
76
+ def _get_hexagonal_initial_centers():
77
+ centers = []
78
+ rows = [5, 6, 5, 6, 4] # Total 26
79
+ y = 0.05
80
+ for i, count in enumerate(rows):
81
+ x_offset = 0.05 if i % 2 == 1 else 0.15 # Stagger rows
82
+ for j in range(count):
83
+ if len(centers) < n:
84
+ centers.append([x_offset + j * 0.18, y])
85
+ y += 0.16
86
+ centers = np.array(centers)
87
+ centers = np.clip(centers, 0.01, 0.99)
88
+ return centers
89
+ base_centers_hex = _get_hexagonal_initial_centers()
90
+
91
+ # Guess 3: Seed with the best recent result.
92
+ base_centers_best_known = np.array([
93
+ [0.1130, 0.1130], [0.0698, 0.2906], [0.1251, 0.4775], [0.0782, 0.6753],
94
+ [0.1261, 0.8739], [0.3283, 0.1026], [0.2391, 0.2840], [0.3517, 0.4523],
95
+ [0.2854, 0.6746], [0.3545, 0.8966], [0.5307, 0.0998], [0.4346, 0.2709],
96
+ [0.4682, 0.7614], [0.5515, 0.9062], [0.7314, 0.1009], [0.6292, 0.2717],
97
+ [0.7104, 0.5149], [0.6403, 0.7324], [0.7426, 0.9027], [0.9158, 0.0842],
98
+ [0.8629, 0.2992], [0.9187, 0.5103], [0.8705, 0.7154], [0.9196, 0.9196],
99
+ [0.5296, 0.4174], [0.4978, 0.5917]
100
+ ])
101
+
102
+ initial_layouts = [base_centers_best_known, base_centers_grid, base_centers_hex]
103
+
104
+ # --- 2. Define Objective Functions for Staged Optimization ---
105
+ def objective_hybrid(x, alpha=0.2):
106
+ """Stage 1: Maximize a weighted sum of total area and total radii."""
107
+ _, radii = unpack_vars(x)
108
+ sum_radii_sq = np.sum(radii**2)
109
+ sum_radii = np.sum(radii)
110
+ return - (alpha * sum_radii_sq + (1 - alpha) * sum_radii)
111
+
112
+ def objective_radii(x):
113
+ """Stage 2 & 3: Maximize sum of radii (the primary goal)."""
114
+ _, radii = unpack_vars(x)
115
+ return -np.sum(radii)
116
+
117
+ # --- 3. Define Constraints ---
118
+ cons = []
119
+
120
+ def non_overlap_constraint(x):
121
+ """Constraint: dist_ij^2 - (ri + rj)^2 >= 0. Numerically stable."""
122
+ centers, radii = unpack_vars(x)
123
+ i, j = np.triu_indices(n, k=1)
124
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
125
+ sum_radii_sq = (radii[i] + radii[j])**2
126
+ return dist_sq - sum_radii_sq
127
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
128
+
129
+ def boundary_constraint(x):
130
+ centers, radii = unpack_vars(x)
131
+ return np.concatenate([
132
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
133
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
134
+ ])
135
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
136
+
137
+ # --- 4. Define Bounds ---
138
+ MIN_RADIUS = 1e-7 # Enforce a minimum positive radius for numerical stability
139
+ bounds = []
140
+ for _ in range(n):
141
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS, 0.5)])
142
+
143
+ # --- 5. Run the Optimizer with Hybrid Strategy ---
144
+ best_sum_radii = -np.inf
145
+ best_result_x = None
146
+
147
+ # Tiered perturbation schedule for exploration/exploitation balance
148
+ PERTURB_SCHEDULE = [(0.030, 12), (0.010, 12), (0.003, 12)] # (std_dev, num_runs)
149
+
150
+ # Aggressive solver settings with more iterations in the final stage
151
+ options_stage1 = {'maxiter': 1500, 'ftol': 1e-9, 'gtol': 1e-7, 'disp': False}
152
+ options_stage2 = {'maxiter': 3000, 'ftol': 1e-11, 'gtol': 1e-9, 'disp': False}
153
+ options_stage3 = {'maxiter': 6000, 'ftol': 1e-13, 'gtol': 1e-11, 'disp': False}
154
+
155
+ run_count = 0
156
+ for std_dev, num_runs in PERTURB_SCHEDULE:
157
+ for _ in range(num_runs):
158
+ # Cycle through the three base layouts
159
+ current_base_centers = initial_layouts[run_count % len(initial_layouts)]
160
+
161
+ # Apply perturbation
162
+ perturbed_centers = current_base_centers + np.random.normal(0, std_dev, current_base_centers.shape)
163
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
164
+
165
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
166
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
167
+
168
+ # Stage 1: Maximize hybrid objective
169
+ res1 = minimize(objective_hybrid, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
170
+ if not res1.success: continue
171
+
172
+ # Stage 2: Maximize sum of radii
173
+ res2 = minimize(objective_radii, res1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
174
+ if not res2.success: continue
175
+
176
+ # Stage 3: Final refinement of radii sum with highest precision
177
+ res3 = minimize(objective_radii, res2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
178
+
179
+ _, current_radii = unpack_vars(res3.x)
180
+ current_sum_radii = np.sum(current_radii)
181
+
182
+ if current_sum_radii > best_sum_radii:
183
+ best_sum_radii = current_sum_radii
184
+ best_result_x = res3.x
185
+
186
+ run_count += 1
187
+
188
+ # --- 6. Extract and Return Results ---
189
+ if best_result_x is None:
190
+ initial_radii = _compute_initial_radii(base_centers_best_known)
191
+ best_result_x = pack_vars(base_centers_best_known, initial_radii)
192
+
193
+ final_centers, final_radii = unpack_vars(best_result_x)
194
+ final_radii = np.maximum(final_radii, 0) # Final cleanup
195
+
196
+ return final_centers, final_radii
197
+ # EVOLVE-BLOCK-END
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/edit.diff ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,195 +1,188 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ -from scipy.optimize import minimize
7
+ +from scipy.optimize import minimize, differential_evolution, NonlinearConstraint
8
+
9
+ def construct_packing():
10
+ """
11
+ - Constructs an optimized arrangement of 26 circles by formulating the problem
12
+ - as a nonlinear program and solving it with scipy.optimize.minimize. This approach
13
+ - reverts to a proven high-performance strategy, abandoning the less effective
14
+ - Simulated Annealing method, and re-implements the successful two-stage NLP.
15
+ + Constructs an optimized arrangement of 26 circles using a hybrid global-local
16
+ + optimization approach. It employs Differential Evolution for broad global search
17
+ + and then refines the best solution with a high-precision SLSQP local optimizer.
18
+ """
19
+ n = 26
20
+
21
+ # Helper functions to convert between the flat optimization vector and
22
+ # the structured centers/radii arrays.
23
+ def pack_vars(centers, radii):
24
+ x = np.zeros(n * 3)
25
+ x[0::3] = centers[:, 0]
26
+ x[1::3] = centers[:, 1]
27
+ x[2::3] = radii
28
+ return x
29
+
30
+ def unpack_vars(x):
31
+ centers_x = x[0::3]
32
+ centers_y = x[1::3]
33
+ radii = x[2::3]
34
+ centers = np.vstack((centers_x, centers_y)).T
35
+ return centers, radii
36
+
37
+ - # --- 1. Initial Guess ---
38
+ - # A good initial guess is crucial for the optimizer to find a high-quality solution.
39
+ + # --- 1. Initial Radii Computation for Feasible Starting Points ---
40
+ def _compute_initial_radii(centers, max_iter=200):
41
+ - """Iteratively compute max radii for a given set of centers."""
42
+ + """
43
+ + Iteratively computes the maximum possible non-overlapping radii for a
44
+ + given fixed set of centers, ensuring a small minimum gap. This is used
45
+ + to generate feasible starting points for the optimization.
46
+ + """
47
+ num_circles = centers.shape[0]
48
+ radii = np.zeros(num_circles)
49
+ + MIN_GAP_THRESHOLD = 1e-8 # Small gap for numerical robustness
50
+
51
+ - # Initialize radii based on distance to walls
52
+ + # Initialize radii based on the minimum distance to the walls.
53
+ for i in range(num_circles):
54
+ x, y = centers[i]
55
+ radii[i] = min(x, 1 - x, y, 1 - y)
56
+
57
+ - # Iteratively shrink radii based on proximity to other circles
58
+ + # Iteratively shrink radii to resolve overlaps while maintaining MIN_GAP_THRESHOLD.
59
+ for _ in range(max_iter):
60
+ had_change = False
61
+ for i in range(num_circles):
62
+ for j in range(i + 1, num_circles):
63
+ dist = np.linalg.norm(centers[i] - centers[j])
64
+ sum_r = radii[i] + radii[j]
65
+ - # Define a minimum separation buffer to ensure strict non-overlap initially.
66
+ - MIN_GAP = 1e-7
67
+ - if sum_r > dist - MIN_GAP: # Check if circles overlap or are within MIN_GAP
68
+ - # Calculate the new sum of radii needed to maintain a MIN_GAP.
69
+ - # Ensure target_sum_r is non-negative.
70
+ - target_sum_r = max(0.0, dist - MIN_GAP)
71
+ - if sum_r > 1e-12: # Avoid division by zero
72
+ + if sum_r > dist - MIN_GAP_THRESHOLD:
73
+ + target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
74
+ + if sum_r > 1e-12: # Avoid division by zero for scaling
75
+ scale = target_sum_r / sum_r
76
+ radii[i] *= scale
77
+ radii[j] *= scale
78
+ had_change = True
79
+ if not had_change:
80
+ break
81
+ return radii
82
+
83
+ - # --- 2. Define Objective Functions for Staged Optimization ---
84
+ - # Stage 1 objective: Maximize sum of areas (r^2) to find a globally dense packing.
85
+ - def objective_area(x):
86
+ - _, radii = unpack_vars(x)
87
+ - return -np.sum(radii**2)
88
+ -
89
+ - # Stage 2 objective: Maximize sum of radii (r), the primary goal.
90
+ + # --- 2. Objective Function: Maximize Sum of Radii ---
91
+ + # Differential Evolution and SLSQP both minimize, so we negate the sum of radii.
92
+ def objective_radii(x):
93
+ _, radii = unpack_vars(x)
94
+ return -np.sum(radii)
95
+
96
+ - # --- 3. Define Constraints ---
97
+ - # All constraint functions must be of the form f(x) >= 0.
98
+ - cons = []
99
+ + # --- 3. Constraint Functions ---
100
+ + # These functions define the conditions for non-overlap and staying within boundaries.
101
+ + # They return values that must be >= 0 for a feasible solution.
102
+
103
+ - # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
104
+ - # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
105
+ - def non_overlap_constraint(x):
106
+ + def non_overlap_constraint_func(x):
107
+ centers, radii = unpack_vars(x)
108
+ + # Calculate pairwise squared distances to avoid sqrt and maintain numerical stability
109
+ + i, j = np.triu_indices(n, k=1)
110
+ + dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
111
+ + sum_radii_sq = (radii[i] + radii[j])**2
112
+ + return dist_sq - sum_radii_sq # Must be >= 0
113
+
114
+ - # Get indices for the upper triangle of the pairwise matrix to avoid redundant checks.
115
+ - i, j = np.triu_indices(n, k=1)
116
+ + def boundary_constraint_func(x):
117
+ + centers, radii = unpack_vars(x)
118
+ + return np.concatenate([
119
+ + centers[:, 0] - radii, # x_center - radius >= 0
120
+ + 1 - centers[:, 0] - radii, # 1 - x_center - radius >= 0
121
+ + centers[:, 1] - radii, # y_center - radius >= 0
122
+ + 1 - centers[:, 1] - radii # 1 - y_center - radius >= 0
123
+ + ]) # All must be >= 0
124
+
125
+ - # Calculate squared Euclidean distance for all unique pairs.
126
+ - dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
127
+ + # --- 4. Define Constraints for Different Solvers ---
128
+ + # For scipy.optimize.minimize (SLSQP), constraints are a list of dicts.
129
+ + cons_slsqp = [
130
+ + {'type': 'ineq', 'fun': non_overlap_constraint_func},
131
+ + {'type': 'ineq', 'fun': boundary_constraint_func}
132
+ + ]
133
+
134
+ - # Calculate squared sum of radii for corresponding pairs.
135
+ - sum_radii_sq = (radii[i] + radii[j])**2
136
+ + # For scipy.optimize.differential_evolution, constraints are NonlinearConstraint objects.
137
+ + # The bounds for the constraint functions are [lower_bound, upper_bound].
138
+ + # For f(x) >= 0, we use [0, np.inf].
139
+ + non_overlap_nlc = NonlinearConstraint(non_overlap_constraint_func, 0, np.inf)
140
+ + boundary_nlc = NonlinearConstraint(boundary_constraint_func, 0, np.inf)
141
+ + cons_de = (non_overlap_nlc, boundary_nlc)
142
+
143
+ - return dist_sq - sum_radii_sq
144
+ -
145
+ - cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
146
+ -
147
+ - # Constraint 2: Circles must be within the [0,1] x [0,1] square.
148
+ - def boundary_constraint(x):
149
+ - centers, radii = unpack_vars(x)
150
+ - # Return a flat array of all boundary constraint values
151
+ - return np.concatenate([
152
+ - centers[:, 0] - radii, # x - r >= 0
153
+ - 1 - centers[:, 0] - radii, # 1 - x - r >= 0
154
+ - centers[:, 1] - radii, # y - r >= 0
155
+ - 1 - centers[:, 1] - radii # 1 - y - r >= 0
156
+ - ])
157
+ -
158
+ - cons.append({'type': 'ineq', 'fun': boundary_constraint})
159
+ -
160
+ - # --- 4. Define Bounds for each variable ---
161
+ - # 0 <= center_x, center_y <= 1
162
+ - # 0 <= radius <= 0.5 (a single circle cannot have a radius > 0.5)
163
+ + # --- 5. Define Bounds for all Variables ---
164
+ + # Center coordinates (x, y) must be between 0 and 1.
165
+ + # Radii must be positive (MIN_RADIUS_BOUND) and cannot exceed 0.5 (max possible in unit square).
166
+ + MIN_RADIUS_BOUND = 1e-7 # Ensure radii are strictly positive for numerical stability
167
+ bounds = []
168
+ for _ in range(n):
169
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
170
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
171
+
172
+ - # --- 5. Run the Optimizer with Iterative Perturbation ---
173
+ - # Define the base initial centers (proven 5x5 grid with a split center).
174
+ - base_initial_centers = np.zeros((n, 2))
175
+ - idx = 0
176
+ - for i in range(5):
177
+ - for j in range(5):
178
+ - if i == 2 and j == 2:
179
+ - continue
180
+ - base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
181
+ - idx += 1
182
+ - base_initial_centers[24] = [0.5, 0.45]
183
+ - base_initial_centers[25] = [0.5, 0.55]
184
+ + # --- 6. Differential Evolution (Global Search Stage) ---
185
+ + # Initialize DE with a known high-quality solution, perturbed, to start from a good region.
186
+ + base_initial_centers_best_known = np.array([
187
+ + [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
188
+ + [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
189
+ + [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
190
+ + [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
191
+ + [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
192
+ + [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
193
+ + [0.5173, 0.4172], [0.4888, 0.5875]
194
+ + ])
195
+
196
+ - num_optimization_runs = 30 # Further increased runs for more robust exploration
197
+ - best_sum_radii = -np.inf
198
+ - best_result_x = None
199
+ + DE_POPSIZE = 50 # Number of individuals in the population
200
+ + DE_MAXITER = 500 # Maximum iterations for the global search
201
+ + PERTURB_STD_DEV_DE_INIT = 0.03 # Std dev for perturbing initial centers for DE population
202
+
203
+ - # Aggressive optimizer settings
204
+ - options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
205
+ - options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
206
+ - options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # More iterations for the final refinement stage
207
+ + # Generate an initial population for Differential Evolution. Each member starts
208
+ + # from a perturbed version of the best-known solution, ensuring initial feasibility.
209
+ + initial_population_de = []
210
+ + for _ in range(DE_POPSIZE):
211
+ + perturbed_centers = base_initial_centers_best_known + np.random.normal(0, PERTURB_STD_DEV_DE_INIT, base_initial_centers_best_known.shape)
212
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
213
+ +
214
+ + # Compute feasible radii for these perturbed centers
215
+ + initial_radii_for_de_member = _compute_initial_radii(perturbed_centers)
216
+ + initial_population_de.append(pack_vars(perturbed_centers, initial_radii_for_de_member))
217
+ + initial_population_de = np.array(initial_population_de)
218
+
219
+ - for run in range(num_optimization_runs):
220
+ - # Apply adaptive perturbation to initial centers for each run
221
+ - # Use larger std_dev for initial runs to explore broadly, then smaller for refinement
222
+ - if run < num_optimization_runs / 2:
223
+ - perturbation_std_dev = 0.02 # Broader exploration
224
+ - else:
225
+ - perturbation_std_dev = 0.005 # Finer refinement
226
+ + # Run Differential Evolution for global optimization
227
+ + result_de = differential_evolution(
228
+ + objective_radii,
229
+ + bounds,
230
+ + constraints=cons_de,
231
+ + maxiter=DE_MAXITER,
232
+ + popsize=DE_POPSIZE,
233
+ + init=initial_population_de, # Provide the seeded initial population
234
+ + polish=False, # Disable DE's internal polish; we'll do a separate SLSQP polish
235
+ + disp=False, # Set to True to see DE's progress
236
+ + workers=-1, # Use all available CPU cores for parallelization
237
+ + # seed=42 # Uncomment for reproducibility
238
+ + )
239
+
240
+ - # Add small random noise to centers, clipped to stay within [0,1]
241
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
242
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
243
+ + x_global_opt = result_de.x # The best solution found by Differential Evolution
244
+
245
+ - # Compute the maximum possible radii for the perturbed centers.
246
+ - perturbed_radii = _compute_initial_radii(perturbed_centers)
247
+ + # --- 7. Local Search Refinement (SLSQP Stage) ---
248
+ + # Use SLSQP to fine-tune the globally optimized solution for higher precision.
249
+ + options_slsqp = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
250
+
251
+ - # Create the initial optimization vector `x0` for this run.
252
+ - x0_run = pack_vars(perturbed_centers, perturbed_radii)
253
+ + result_slsqp = minimize(
254
+ + objective_radii,
255
+ + x_global_opt, # Start SLSQP from DE's best result
256
+ + method='SLSQP',
257
+ + bounds=bounds,
258
+ + constraints=cons_slsqp,
259
+ + options=options_slsqp
260
+ + )
261
+ +
262
+ + # Use the SLSQP result if successful, otherwise fall back to DE's result
263
+ + final_x = result_slsqp.x if result_slsqp.success else x_global_opt
264
+
265
+ - # Stage 1: Maximize sum of *areas* (r^2)
266
+ - result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
267
+ -
268
+ - # Stage 2: Maximize sum of *radii* (r)
269
+ - x_stage1 = result_stage1.x
270
+ - result_stage2 = minimize(objective_radii, x_stage1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
271
+ -
272
+ - # Stage 3: Further maximize sum of *radii* (r) with tighter options
273
+ - x_stage2 = result_stage2.x
274
+ - result_stage3 = minimize(objective_radii, x_stage2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
275
+ -
276
+ - # Extract results for this run from the final stage
277
+ - _, current_radii = unpack_vars(result_stage3.x)
278
+ - current_sum_radii = np.sum(current_radii)
279
+ -
280
+ - # Keep track of the best result found so far
281
+ - if current_sum_radii > best_sum_radii:
282
+ - best_sum_radii = current_sum_radii
283
+ - best_result_x = result_stage3.x
284
+ -
285
+ - # --- 6. Extract and Return Results ---
286
+ - # Use the best result found across all perturbed runs.
287
+ - final_x = best_result_x
288
+ + # --- 8. Final Result Extraction ---
289
+ final_centers, final_radii = unpack_vars(final_x)
290
+ -
291
+ - # Clean up any potential floating point inaccuracies (e.g., small negative radii).
292
+ - final_radii = np.maximum(final_radii, 0)
293
+ +
294
+ + # Final cleanup: ensure no radii are negative due to numerical inaccuracies
295
+ + final_radii = np.maximum(final_radii, MIN_RADIUS_BOUND)
296
+
297
+ return final_centers, final_radii
298
+ # EVOLVE-BLOCK-END
299
+
300
+
301
+ # This part remains fixed (not evolved)
302
+ def run_packing():
303
+ """Run the circle packing constructor for n=26"""
304
+ centers, radii = construct_packing()
305
+ # Calculate the sum of radii
306
+ sum_radii = np.sum(radii)
307
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/main.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize, differential_evolution, NonlinearConstraint
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid global-local
8
+ optimization approach. It employs Differential Evolution for broad global search
9
+ and then refines the best solution with a high-precision SLSQP local optimizer.
10
+ """
11
+ n = 26
12
+
13
+ # Helper functions to convert between the flat optimization vector and
14
+ # the structured centers/radii arrays.
15
+ def pack_vars(centers, radii):
16
+ x = np.zeros(n * 3)
17
+ x[0::3] = centers[:, 0]
18
+ x[1::3] = centers[:, 1]
19
+ x[2::3] = radii
20
+ return x
21
+
22
+ def unpack_vars(x):
23
+ centers_x = x[0::3]
24
+ centers_y = x[1::3]
25
+ radii = x[2::3]
26
+ centers = np.vstack((centers_x, centers_y)).T
27
+ return centers, radii
28
+
29
+ # --- 1. Initial Radii Computation for Feasible Starting Points ---
30
+ def _compute_initial_radii(centers, max_iter=200):
31
+ """
32
+ Iteratively computes the maximum possible non-overlapping radii for a
33
+ given fixed set of centers, ensuring a small minimum gap. This is used
34
+ to generate feasible starting points for the optimization.
35
+ """
36
+ num_circles = centers.shape[0]
37
+ radii = np.zeros(num_circles)
38
+ MIN_GAP_THRESHOLD = 1e-8 # Small gap for numerical robustness
39
+
40
+ # Initialize radii based on the minimum distance to the walls.
41
+ for i in range(num_circles):
42
+ x, y = centers[i]
43
+ radii[i] = min(x, 1 - x, y, 1 - y)
44
+
45
+ # Iteratively shrink radii to resolve overlaps while maintaining MIN_GAP_THRESHOLD.
46
+ for _ in range(max_iter):
47
+ had_change = False
48
+ for i in range(num_circles):
49
+ for j in range(i + 1, num_circles):
50
+ dist = np.linalg.norm(centers[i] - centers[j])
51
+ sum_r = radii[i] + radii[j]
52
+ if sum_r > dist - MIN_GAP_THRESHOLD:
53
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
54
+ if sum_r > 1e-12: # Avoid division by zero for scaling
55
+ scale = target_sum_r / sum_r
56
+ radii[i] *= scale
57
+ radii[j] *= scale
58
+ had_change = True
59
+ if not had_change:
60
+ break
61
+ return radii
62
+
63
+ # --- 2. Objective Function: Maximize Sum of Radii ---
64
+ # Differential Evolution and SLSQP both minimize, so we negate the sum of radii.
65
+ def objective_radii(x):
66
+ _, radii = unpack_vars(x)
67
+ return -np.sum(radii)
68
+
69
+ # --- 3. Constraint Functions ---
70
+ # These functions define the conditions for non-overlap and staying within boundaries.
71
+ # They return values that must be >= 0 for a feasible solution.
72
+
73
+ def non_overlap_constraint_func(x):
74
+ centers, radii = unpack_vars(x)
75
+ # Calculate pairwise squared distances to avoid sqrt and maintain numerical stability
76
+ i, j = np.triu_indices(n, k=1)
77
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
78
+ sum_radii_sq = (radii[i] + radii[j])**2
79
+ return dist_sq - sum_radii_sq # Must be >= 0
80
+
81
+ def boundary_constraint_func(x):
82
+ centers, radii = unpack_vars(x)
83
+ return np.concatenate([
84
+ centers[:, 0] - radii, # x_center - radius >= 0
85
+ 1 - centers[:, 0] - radii, # 1 - x_center - radius >= 0
86
+ centers[:, 1] - radii, # y_center - radius >= 0
87
+ 1 - centers[:, 1] - radii # 1 - y_center - radius >= 0
88
+ ]) # All must be >= 0
89
+
90
+ # --- 4. Define Constraints for Different Solvers ---
91
+ # For scipy.optimize.minimize (SLSQP), constraints are a list of dicts.
92
+ cons_slsqp = [
93
+ {'type': 'ineq', 'fun': non_overlap_constraint_func},
94
+ {'type': 'ineq', 'fun': boundary_constraint_func}
95
+ ]
96
+
97
+ # For scipy.optimize.differential_evolution, constraints are NonlinearConstraint objects.
98
+ # The bounds for the constraint functions are [lower_bound, upper_bound].
99
+ # For f(x) >= 0, we use [0, np.inf].
100
+ non_overlap_nlc = NonlinearConstraint(non_overlap_constraint_func, 0, np.inf)
101
+ boundary_nlc = NonlinearConstraint(boundary_constraint_func, 0, np.inf)
102
+ cons_de = (non_overlap_nlc, boundary_nlc)
103
+
104
+ # --- 5. Define Bounds for all Variables ---
105
+ # Center coordinates (x, y) must be between 0 and 1.
106
+ # Radii must be positive (MIN_RADIUS_BOUND) and cannot exceed 0.5 (max possible in unit square).
107
+ MIN_RADIUS_BOUND = 1e-7 # Ensure radii are strictly positive for numerical stability
108
+ bounds = []
109
+ for _ in range(n):
110
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
111
+
112
+ # --- 6. Differential Evolution (Global Search Stage) ---
113
+ # Initialize DE with a known high-quality solution, perturbed, to start from a good region.
114
+ base_initial_centers_best_known = np.array([
115
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
116
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
117
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
118
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
119
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
120
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
121
+ [0.5173, 0.4172], [0.4888, 0.5875]
122
+ ])
123
+
124
+ DE_POPSIZE = 50 # Number of individuals in the population
125
+ DE_MAXITER = 500 # Maximum iterations for the global search
126
+ PERTURB_STD_DEV_DE_INIT = 0.03 # Std dev for perturbing initial centers for DE population
127
+
128
+ # Generate an initial population for Differential Evolution. Each member starts
129
+ # from a perturbed version of the best-known solution, ensuring initial feasibility.
130
+ initial_population_de = []
131
+ for _ in range(DE_POPSIZE):
132
+ perturbed_centers = base_initial_centers_best_known + np.random.normal(0, PERTURB_STD_DEV_DE_INIT, base_initial_centers_best_known.shape)
133
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
134
+
135
+ # Compute feasible radii for these perturbed centers
136
+ initial_radii_for_de_member = _compute_initial_radii(perturbed_centers)
137
+ initial_population_de.append(pack_vars(perturbed_centers, initial_radii_for_de_member))
138
+ initial_population_de = np.array(initial_population_de)
139
+
140
+ # Run Differential Evolution for global optimization
141
+ result_de = differential_evolution(
142
+ objective_radii,
143
+ bounds,
144
+ constraints=cons_de,
145
+ maxiter=DE_MAXITER,
146
+ popsize=DE_POPSIZE,
147
+ init=initial_population_de, # Provide the seeded initial population
148
+ polish=False, # Disable DE's internal polish; we'll do a separate SLSQP polish
149
+ disp=False, # Set to True to see DE's progress
150
+ workers=-1, # Use all available CPU cores for parallelization
151
+ # seed=42 # Uncomment for reproducibility
152
+ )
153
+
154
+ x_global_opt = result_de.x # The best solution found by Differential Evolution
155
+
156
+ # --- 7. Local Search Refinement (SLSQP Stage) ---
157
+ # Use SLSQP to fine-tune the globally optimized solution for higher precision.
158
+ options_slsqp = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
159
+
160
+ result_slsqp = minimize(
161
+ objective_radii,
162
+ x_global_opt, # Start SLSQP from DE's best result
163
+ method='SLSQP',
164
+ bounds=bounds,
165
+ constraints=cons_slsqp,
166
+ options=options_slsqp
167
+ )
168
+
169
+ # Use the SLSQP result if successful, otherwise fall back to DE's result
170
+ final_x = result_slsqp.x if result_slsqp.success else x_global_opt
171
+
172
+ # --- 8. Final Result Extraction ---
173
+ final_centers, final_radii = unpack_vars(final_x)
174
+
175
+ # Final cleanup: ensure no radii are negative due to numerical inaccuracies
176
+ final_radii = np.maximum(final_radii, MIN_RADIUS_BOUND)
177
+
178
+ return final_centers, final_radii
179
+ # EVOLVE-BLOCK-END
180
+
181
+
182
+ # This part remains fixed (not evolved)
183
+ def run_packing():
184
+ """Run the circle packing constructor for n=26"""
185
+ centers, radii = construct_packing()
186
+ # Calculate the sum of radii
187
+ sum_radii = np.sum(radii)
188
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/original.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by formulating the problem
8
+ as a nonlinear program and solving it with scipy.optimize.minimize. This approach
9
+ reverts to a proven high-performance strategy, abandoning the less effective
10
+ Simulated Annealing method, and re-implements the successful two-stage NLP.
11
+ """
12
+ n = 26
13
+
14
+ # Helper functions to convert between the flat optimization vector and
15
+ # the structured centers/radii arrays.
16
+ def pack_vars(centers, radii):
17
+ x = np.zeros(n * 3)
18
+ x[0::3] = centers[:, 0]
19
+ x[1::3] = centers[:, 1]
20
+ x[2::3] = radii
21
+ return x
22
+
23
+ def unpack_vars(x):
24
+ centers_x = x[0::3]
25
+ centers_y = x[1::3]
26
+ radii = x[2::3]
27
+ centers = np.vstack((centers_x, centers_y)).T
28
+ return centers, radii
29
+
30
+ # --- 1. Initial Guess ---
31
+ # A good initial guess is crucial for the optimizer to find a high-quality solution.
32
+ def _compute_initial_radii(centers, max_iter=200):
33
+ """Iteratively compute max radii for a given set of centers."""
34
+ num_circles = centers.shape[0]
35
+ radii = np.zeros(num_circles)
36
+
37
+ # Initialize radii based on distance to walls
38
+ for i in range(num_circles):
39
+ x, y = centers[i]
40
+ radii[i] = min(x, 1 - x, y, 1 - y)
41
+
42
+ # Iteratively shrink radii based on proximity to other circles
43
+ for _ in range(max_iter):
44
+ had_change = False
45
+ for i in range(num_circles):
46
+ for j in range(i + 1, num_circles):
47
+ dist = np.linalg.norm(centers[i] - centers[j])
48
+ sum_r = radii[i] + radii[j]
49
+ # Define a minimum separation buffer to ensure strict non-overlap initially.
50
+ MIN_GAP = 1e-7
51
+ if sum_r > dist - MIN_GAP: # Check if circles overlap or are within MIN_GAP
52
+ # Calculate the new sum of radii needed to maintain a MIN_GAP.
53
+ # Ensure target_sum_r is non-negative.
54
+ target_sum_r = max(0.0, dist - MIN_GAP)
55
+ if sum_r > 1e-12: # Avoid division by zero
56
+ scale = target_sum_r / sum_r
57
+ radii[i] *= scale
58
+ radii[j] *= scale
59
+ had_change = True
60
+ if not had_change:
61
+ break
62
+ return radii
63
+
64
+ # --- 2. Define Objective Functions for Staged Optimization ---
65
+ # Stage 1 objective: Maximize sum of areas (r^2) to find a globally dense packing.
66
+ def objective_area(x):
67
+ _, radii = unpack_vars(x)
68
+ return -np.sum(radii**2)
69
+
70
+ # Stage 2 objective: Maximize sum of radii (r), the primary goal.
71
+ def objective_radii(x):
72
+ _, radii = unpack_vars(x)
73
+ return -np.sum(radii)
74
+
75
+ # --- 3. Define Constraints ---
76
+ # All constraint functions must be of the form f(x) >= 0.
77
+ cons = []
78
+
79
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
80
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
81
+ def non_overlap_constraint(x):
82
+ centers, radii = unpack_vars(x)
83
+
84
+ # Get indices for the upper triangle of the pairwise matrix to avoid redundant checks.
85
+ i, j = np.triu_indices(n, k=1)
86
+
87
+ # Calculate squared Euclidean distance for all unique pairs.
88
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
89
+
90
+ # Calculate squared sum of radii for corresponding pairs.
91
+ sum_radii_sq = (radii[i] + radii[j])**2
92
+
93
+ return dist_sq - sum_radii_sq
94
+
95
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
96
+
97
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
98
+ def boundary_constraint(x):
99
+ centers, radii = unpack_vars(x)
100
+ # Return a flat array of all boundary constraint values
101
+ return np.concatenate([
102
+ centers[:, 0] - radii, # x - r >= 0
103
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
104
+ centers[:, 1] - radii, # y - r >= 0
105
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
106
+ ])
107
+
108
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
109
+
110
+ # --- 4. Define Bounds for each variable ---
111
+ # 0 <= center_x, center_y <= 1
112
+ # 0 <= radius <= 0.5 (a single circle cannot have a radius > 0.5)
113
+ bounds = []
114
+ for _ in range(n):
115
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
116
+
117
+ # --- 5. Run the Optimizer with Iterative Perturbation ---
118
+ # Define the base initial centers (proven 5x5 grid with a split center).
119
+ base_initial_centers = np.zeros((n, 2))
120
+ idx = 0
121
+ for i in range(5):
122
+ for j in range(5):
123
+ if i == 2 and j == 2:
124
+ continue
125
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
126
+ idx += 1
127
+ base_initial_centers[24] = [0.5, 0.45]
128
+ base_initial_centers[25] = [0.5, 0.55]
129
+
130
+ num_optimization_runs = 30 # Further increased runs for more robust exploration
131
+ best_sum_radii = -np.inf
132
+ best_result_x = None
133
+
134
+ # Aggressive optimizer settings
135
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
136
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
137
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False} # More iterations for the final refinement stage
138
+
139
+ for run in range(num_optimization_runs):
140
+ # Apply adaptive perturbation to initial centers for each run
141
+ # Use larger std_dev for initial runs to explore broadly, then smaller for refinement
142
+ if run < num_optimization_runs / 2:
143
+ perturbation_std_dev = 0.02 # Broader exploration
144
+ else:
145
+ perturbation_std_dev = 0.005 # Finer refinement
146
+
147
+ # Add small random noise to centers, clipped to stay within [0,1]
148
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
149
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
150
+
151
+ # Compute the maximum possible radii for the perturbed centers.
152
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
153
+
154
+ # Create the initial optimization vector `x0` for this run.
155
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
156
+
157
+ # Stage 1: Maximize sum of *areas* (r^2)
158
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
159
+
160
+ # Stage 2: Maximize sum of *radii* (r)
161
+ x_stage1 = result_stage1.x
162
+ result_stage2 = minimize(objective_radii, x_stage1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
163
+
164
+ # Stage 3: Further maximize sum of *radii* (r) with tighter options
165
+ x_stage2 = result_stage2.x
166
+ result_stage3 = minimize(objective_radii, x_stage2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
167
+
168
+ # Extract results for this run from the final stage
169
+ _, current_radii = unpack_vars(result_stage3.x)
170
+ current_sum_radii = np.sum(current_radii)
171
+
172
+ # Keep track of the best result found so far
173
+ if current_sum_radii > best_sum_radii:
174
+ best_sum_radii = current_sum_radii
175
+ best_result_x = result_stage3.x
176
+
177
+ # --- 6. Extract and Return Results ---
178
+ # Use the best result found across all perturbed runs.
179
+ final_x = best_result_x
180
+ final_centers, final_radii = unpack_vars(final_x)
181
+
182
+ # Clean up any potential floating point inaccuracies (e.g., small negative radii).
183
+ final_radii = np.maximum(final_radii, 0)
184
+
185
+ return final_centers, final_radii
186
+ # EVOLVE-BLOCK-END
187
+
188
+
189
+ # This part remains fixed (not evolved)
190
+ def run_packing():
191
+ """Run the circle packing constructor for n=26"""
192
+ centers, radii = construct_packing()
193
+ # Calculate the sum of radii
194
+ sum_radii = np.sum(radii)
195
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/rewrite.txt ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize, differential_evolution, NonlinearConstraint
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a hybrid global-local
8
+ optimization approach. It employs Differential Evolution for broad global search
9
+ and then refines the best solution with a high-precision SLSQP local optimizer.
10
+ """
11
+ n = 26
12
+
13
+ # Helper functions to convert between the flat optimization vector and
14
+ # the structured centers/radii arrays.
15
+ def pack_vars(centers, radii):
16
+ x = np.zeros(n * 3)
17
+ x[0::3] = centers[:, 0]
18
+ x[1::3] = centers[:, 1]
19
+ x[2::3] = radii
20
+ return x
21
+
22
+ def unpack_vars(x):
23
+ centers_x = x[0::3]
24
+ centers_y = x[1::3]
25
+ radii = x[2::3]
26
+ centers = np.vstack((centers_x, centers_y)).T
27
+ return centers, radii
28
+
29
+ # --- 1. Initial Radii Computation for Feasible Starting Points ---
30
+ def _compute_initial_radii(centers, max_iter=200):
31
+ """
32
+ Iteratively computes the maximum possible non-overlapping radii for a
33
+ given fixed set of centers, ensuring a small minimum gap. This is used
34
+ to generate feasible starting points for the optimization.
35
+ """
36
+ num_circles = centers.shape[0]
37
+ radii = np.zeros(num_circles)
38
+ MIN_GAP_THRESHOLD = 1e-8 # Small gap for numerical robustness
39
+
40
+ # Initialize radii based on the minimum distance to the walls.
41
+ for i in range(num_circles):
42
+ x, y = centers[i]
43
+ radii[i] = min(x, 1 - x, y, 1 - y)
44
+
45
+ # Iteratively shrink radii to resolve overlaps while maintaining MIN_GAP_THRESHOLD.
46
+ for _ in range(max_iter):
47
+ had_change = False
48
+ for i in range(num_circles):
49
+ for j in range(i + 1, num_circles):
50
+ dist = np.linalg.norm(centers[i] - centers[j])
51
+ sum_r = radii[i] + radii[j]
52
+ if sum_r > dist - MIN_GAP_THRESHOLD:
53
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
54
+ if sum_r > 1e-12: # Avoid division by zero for scaling
55
+ scale = target_sum_r / sum_r
56
+ radii[i] *= scale
57
+ radii[j] *= scale
58
+ had_change = True
59
+ if not had_change:
60
+ break
61
+ return radii
62
+
63
+ # --- 2. Objective Function: Maximize Sum of Radii ---
64
+ # Differential Evolution and SLSQP both minimize, so we negate the sum of radii.
65
+ def objective_radii(x):
66
+ _, radii = unpack_vars(x)
67
+ return -np.sum(radii)
68
+
69
+ # --- 3. Constraint Functions ---
70
+ # These functions define the conditions for non-overlap and staying within boundaries.
71
+ # They return values that must be >= 0 for a feasible solution.
72
+
73
+ def non_overlap_constraint_func(x):
74
+ centers, radii = unpack_vars(x)
75
+ # Calculate pairwise squared distances to avoid sqrt and maintain numerical stability
76
+ i, j = np.triu_indices(n, k=1)
77
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
78
+ sum_radii_sq = (radii[i] + radii[j])**2
79
+ return dist_sq - sum_radii_sq # Must be >= 0
80
+
81
+ def boundary_constraint_func(x):
82
+ centers, radii = unpack_vars(x)
83
+ return np.concatenate([
84
+ centers[:, 0] - radii, # x_center - radius >= 0
85
+ 1 - centers[:, 0] - radii, # 1 - x_center - radius >= 0
86
+ centers[:, 1] - radii, # y_center - radius >= 0
87
+ 1 - centers[:, 1] - radii # 1 - y_center - radius >= 0
88
+ ]) # All must be >= 0
89
+
90
+ # --- 4. Define Constraints for Different Solvers ---
91
+ # For scipy.optimize.minimize (SLSQP), constraints are a list of dicts.
92
+ cons_slsqp = [
93
+ {'type': 'ineq', 'fun': non_overlap_constraint_func},
94
+ {'type': 'ineq', 'fun': boundary_constraint_func}
95
+ ]
96
+
97
+ # For scipy.optimize.differential_evolution, constraints are NonlinearConstraint objects.
98
+ # The bounds for the constraint functions are [lower_bound, upper_bound].
99
+ # For f(x) >= 0, we use [0, np.inf].
100
+ non_overlap_nlc = NonlinearConstraint(non_overlap_constraint_func, 0, np.inf)
101
+ boundary_nlc = NonlinearConstraint(boundary_constraint_func, 0, np.inf)
102
+ cons_de = (non_overlap_nlc, boundary_nlc)
103
+
104
+ # --- 5. Define Bounds for all Variables ---
105
+ # Center coordinates (x, y) must be between 0 and 1.
106
+ # Radii must be positive (MIN_RADIUS_BOUND) and cannot exceed 0.5 (max possible in unit square).
107
+ MIN_RADIUS_BOUND = 1e-7 # Ensure radii are strictly positive for numerical stability
108
+ bounds = []
109
+ for _ in range(n):
110
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
111
+
112
+ # --- 6. Differential Evolution (Global Search Stage) ---
113
+ # Initialize DE with a known high-quality solution, perturbed, to start from a good region.
114
+ base_initial_centers_best_known = np.array([
115
+ [0.1121, 0.1121], [0.0690, 0.2880], [0.1230, 0.4722], [0.0778, 0.6679],
116
+ [0.1305, 0.8695], [0.3263, 0.1024], [0.2364, 0.2820], [0.3455, 0.4486],
117
+ [0.2794, 0.6632], [0.3554, 0.9032], [0.5298, 0.1011], [0.4305, 0.2712],
118
+ [0.4555, 0.7606], [0.5462, 0.9060], [0.7325, 0.1016], [0.6301, 0.2798],
119
+ [0.7042, 0.5040], [0.6336, 0.7288], [0.7388, 0.9014], [0.9166, 0.0834],
120
+ [0.8668, 0.2942], [0.9182, 0.5031], [0.8682, 0.7108], [0.9183, 0.9183],
121
+ [0.5173, 0.4172], [0.4888, 0.5875]
122
+ ])
123
+
124
+ DE_POPSIZE = 50 # Number of individuals in the population
125
+ DE_MAXITER = 500 # Maximum iterations for the global search
126
+ PERTURB_STD_DEV_DE_INIT = 0.03 # Std dev for perturbing initial centers for DE population
127
+
128
+ # Generate an initial population for Differential Evolution. Each member starts
129
+ # from a perturbed version of the best-known solution, ensuring initial feasibility.
130
+ initial_population_de = []
131
+ for _ in range(DE_POPSIZE):
132
+ perturbed_centers = base_initial_centers_best_known + np.random.normal(0, PERTURB_STD_DEV_DE_INIT, base_initial_centers_best_known.shape)
133
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
134
+
135
+ # Compute feasible radii for these perturbed centers
136
+ initial_radii_for_de_member = _compute_initial_radii(perturbed_centers)
137
+ initial_population_de.append(pack_vars(perturbed_centers, initial_radii_for_de_member))
138
+ initial_population_de = np.array(initial_population_de)
139
+
140
+ # Run Differential Evolution for global optimization
141
+ result_de = differential_evolution(
142
+ objective_radii,
143
+ bounds,
144
+ constraints=cons_de,
145
+ maxiter=DE_MAXITER,
146
+ popsize=DE_POPSIZE,
147
+ init=initial_population_de, # Provide the seeded initial population
148
+ polish=False, # Disable DE's internal polish; we'll do a separate SLSQP polish
149
+ disp=False, # Set to True to see DE's progress
150
+ workers=-1, # Use all available CPU cores for parallelization
151
+ # seed=42 # Uncomment for reproducibility
152
+ )
153
+
154
+ x_global_opt = result_de.x # The best solution found by Differential Evolution
155
+
156
+ # --- 7. Local Search Refinement (SLSQP Stage) ---
157
+ # Use SLSQP to fine-tune the globally optimized solution for higher precision.
158
+ options_slsqp = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
159
+
160
+ result_slsqp = minimize(
161
+ objective_radii,
162
+ x_global_opt, # Start SLSQP from DE's best result
163
+ method='SLSQP',
164
+ bounds=bounds,
165
+ constraints=cons_slsqp,
166
+ options=options_slsqp
167
+ )
168
+
169
+ # Use the SLSQP result if successful, otherwise fall back to DE's result
170
+ final_x = result_slsqp.x if result_slsqp.success else x_global_opt
171
+
172
+ # --- 8. Final Result Extraction ---
173
+ final_centers, final_radii = unpack_vars(final_x)
174
+
175
+ # Final cleanup: ensure no radii are negative due to numerical inaccuracies
176
+ final_radii = np.maximum(final_radii, MIN_RADIUS_BOUND)
177
+
178
+ return final_centers, final_radii
179
+ # EVOLVE-BLOCK-END
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/edit.diff ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,173 +1,310 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ -def construct_packing():
9
+ - """
10
+ - Constructs an optimized arrangement of 26 circles using a three-stage NLP.
11
+ - This method enhances previous strategies by increasing the number of optimization
12
+ - runs, using an adaptive perturbation schedule for the initial guess, employing
13
+ - progressively tighter solver tolerances across three stages, and using a
14
+ - numerically stable squared-distance constraint.
15
+ - """
16
+ - n = 26
17
+ -
18
+ - # Helper functions to convert between the flat optimization vector and
19
+ - # the structured centers/radii arrays.
20
+ - def pack_vars(centers, radii):
21
+ - x = np.zeros(n * 3)
22
+ +class CirclePackingProblem:
23
+ + """
24
+ + Encapsulates the definition of the circle packing problem for N circles
25
+ + within a unit square. Defines objectives, constraints, and variable packing/unpacking.
26
+ + """
27
+ + def __init__(self, n_circles):
28
+ + self.n = n_circles
29
+ + self.bounds = self._define_bounds()
30
+ + self.constraints = self._define_constraints()
31
+ +
32
+ + def _define_bounds(self):
33
+ + """Define bounds for each variable: center_x, center_y, radius."""
34
+ + bounds = []
35
+ + for _ in range(self.n):
36
+ + # Recommendation 5: Enforce a non-zero minimum radius to prevent degenerate solutions.
37
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (1e-7, 0.5)])
38
+ + return bounds
39
+ +
40
+ + def _define_constraints(self):
41
+ + """Define non-overlap and boundary constraints."""
42
+ + cons = []
43
+ +
44
+ + # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
45
+ + # Using squared distances for numerical stability and avoiding sqrt in derivatives.
46
+ + def non_overlap_constraint(x_vars):
47
+ + centers, radii = self._unpack_vars(x_vars)
48
+ + # Vectorized computation of pairwise squared Euclidean distances.
49
+ + i, j = np.triu_indices(self.n, k=1)
50
+ + dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
51
+ +
52
+ + # Squared sum of radii for corresponding pairs.
53
+ + radii_sums_sq = (radii[i] + radii[j])**2
54
+ +
55
+ + return dist_sq - radii_sums_sq
56
+ +
57
+ + cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
58
+ +
59
+ + # Constraint 2: Circles must be within the [0,1] x [0,1] square.
60
+ + def boundary_constraint(x_vars):
61
+ + centers, radii = self._unpack_vars(x_vars)
62
+ + return np.concatenate([
63
+ + centers[:, 0] - radii, # x - r >= 0
64
+ + 1 - centers[:, 0] - radii, # 1 - x - r >= 0
65
+ + centers[:, 1] - radii, # y - r >= 0
66
+ + 1 - centers[:, 1] - radii # 1 - y - r >= 0
67
+ + ])
68
+ +
69
+ + cons.append({'type': 'ineq', 'fun': boundary_constraint})
70
+ + return cons
71
+ +
72
+ + def objective_area(self, x_vars):
73
+ + """Objective for Stage 1: Maximize sum of areas (r^2)."""
74
+ + _, radii = self._unpack_vars(x_vars)
75
+ + return -np.sum(radii**2)
76
+ +
77
+ + def objective_radii(self, x_vars):
78
+ + """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
79
+ + _, radii = self._unpack_vars(x_vars)
80
+ + return -np.sum(radii)
81
+ +
82
+ + def _pack_vars(self, centers, radii):
83
+ + """Converts structured centers/radii into a flat optimization vector."""
84
+ + x = np.zeros(self.n * 3)
85
+ x[0::3] = centers[:, 0]
86
+ x[1::3] = centers[:, 1]
87
+ x[2::3] = radii
88
+ return x
89
+
90
+ - def unpack_vars(x):
91
+ - centers_x = x[0::3]
92
+ - centers_y = x[1::3]
93
+ - radii = x[2::3]
94
+ + def _unpack_vars(self, x_vars):
95
+ + """Converts a flat optimization vector into structured centers/radii."""
96
+ + centers_x = x_vars[0::3]
97
+ + centers_y = x_vars[1::3]
98
+ + radii = x_vars[2::3]
99
+ centers = np.vstack((centers_x, centers_y)).T
100
+ return centers, radii
101
+
102
+ - # --- 1. Initial Guess ---
103
+ - def _compute_initial_radii(centers, max_iter=200):
104
+ - """Iteratively compute max non-overlapping radii for a given set of centers."""
105
+ - num_circles = centers.shape[0]
106
+ - radii = np.zeros(num_circles)
107
+ - MIN_GAP = 1e-8 # Use a small gap for robustness
108
+ -
109
+ - # Initialize radii based on distance to walls
110
+ - for i in range(num_circles):
111
+ + def compute_initial_radii(self, centers, max_iter=200):
112
+ + """
113
+ + Iteratively computes the maximum possible non-overlapping radii for a
114
+ + given fixed set of centers, ensuring a small minimum gap.
115
+ + (Recommendation 3: Dynamic MIN_GAP)
116
+ + """
117
+ + radii = np.zeros(self.n)
118
+ + # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
119
+ + # or more robust initial state for fewer circles.
120
+ + MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
121
+ +
122
+ + # Initialize radii based on the minimum distance to the walls.
123
+ + for i in range(self.n):
124
+ x, y = centers[i]
125
+ radii[i] = min(x, 1 - x, y, 1 - y)
126
+ -
127
+ - # Iteratively shrink radii to resolve overlaps
128
+ + radii[i] = max(radii[i], 1e-7) # Ensure initial radii are at least the minimum bound
129
+ +
130
+ + # Iteratively shrink radii to resolve overlaps.
131
+ for _ in range(max_iter):
132
+ had_change = False
133
+ - for i in range(num_circles):
134
+ - for j in range(i + 1, num_circles):
135
+ + for i in range(self.n):
136
+ + for j in range(i + 1, self.n):
137
+ dist = np.linalg.norm(centers[i] - centers[j])
138
+ sum_r = radii[i] + radii[j]
139
+ - if sum_r > dist - MIN_GAP:
140
+ - target_sum_r = max(0.0, dist - MIN_GAP)
141
+ - if sum_r > 1e-12:
142
+ + if sum_r > dist - MIN_GAP_THRESHOLD:
143
+ + target_sum_r = max(2 * 1e-7, dist - MIN_GAP_THRESHOLD) # Ensure sum is at least 2*min_radius
144
+ + if sum_r > 1e-12: # Avoid division by zero
145
+ scale = target_sum_r / sum_r
146
+ radii[i] *= scale
147
+ radii[j] *= scale
148
+ had_change = True
149
+ if not had_change:
150
+ - break
151
+ + break # Converged
152
+ return radii
153
+
154
+ - # --- 2. Define Objective Functions for Staged Optimization ---
155
+ - def objective_area(x):
156
+ - """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
157
+ - _, radii = unpack_vars(x)
158
+ - return -np.sum(radii**2)
159
+ -
160
+ - def objective_radii(x):
161
+ - """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
162
+ - _, radii = unpack_vars(x)
163
+ - return -np.sum(radii)
164
+ -
165
+ - # --- 3. Define Constraints ---
166
+ - cons = []
167
+ -
168
+ - # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
169
+ - # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
170
+ - def non_overlap_constraint(x):
171
+ - centers, radii = unpack_vars(x)
172
+ - i, j = np.triu_indices(n, k=1)
173
+ - dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
174
+ - sum_radii_sq = (radii[i] + radii[j])**2
175
+ - return dist_sq - sum_radii_sq
176
+ -
177
+ - cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
178
+ -
179
+ - # Constraint 2: Circles must be within the [0,1] x [0,1] square.
180
+ - def boundary_constraint(x):
181
+ - centers, radii = unpack_vars(x)
182
+ - return np.concatenate([
183
+ - centers[:, 0] - radii, # x - r >= 0
184
+ - 1 - centers[:, 0] - radii, # 1 - x - r >= 0
185
+ - centers[:, 1] - radii, # y - r >= 0
186
+ - 1 - centers[:, 1] - radii # 1 - y - r >= 0
187
+ - ])
188
+ -
189
+ - cons.append({'type': 'ineq', 'fun': boundary_constraint})
190
+ -
191
+ - # --- 4. Define Bounds for each variable ---
192
+ - bounds = []
193
+ - for _ in range(n):
194
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
195
+ -
196
+ - # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
197
+ - base_initial_centers = np.zeros((n, 2))
198
+ - idx = 0
199
+ - for i in range(5):
200
+ - for j in range(5):
201
+ - if i == 2 and j == 2: continue
202
+ - base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
203
+ +
204
+ +class InitialGuesser:
205
+ + """
206
+ + Generates diverse initial configurations of circle centers for the NLP solver.
207
+ + (Recommendation 1: Diversify Base Initial Layouts)
208
+ + """
209
+ + def __init__(self, n_circles, problem_instance):
210
+ + self.n = n_circles
211
+ + self.problem = problem_instance
212
+ +
213
+ + def _get_grid_split_5x5_centers(self):
214
+ + """Returns the proven 5x5 grid with a split center configuration."""
215
+ + centers = np.zeros((self.n, 2))
216
+ + idx = 0
217
+ + grid_points = np.linspace(0.1, 0.9, 5)
218
+ + for i in range(5):
219
+ + for j in range(5):
220
+ + if i == 2 and j == 2: # Skip center of the 5x5 grid
221
+ + continue
222
+ + if idx < self.n: # Ensure we don't exceed n_circles
223
+ + centers[idx] = [grid_points[i], grid_points[j]]
224
+ + idx += 1
225
+ + # Place the remaining two circles in the "split center" configuration.
226
+ + # This assumes N=26 fits (25-1+2=26).
227
+ + if self.n > 24 and idx < self.n:
228
+ + centers[idx] = [0.5, 0.45]
229
+ idx += 1
230
+ - base_initial_centers[24] = [0.5, 0.45]
231
+ - base_initial_centers[25] = [0.5, 0.55]
232
+ -
233
+ - num_optimization_runs = 24
234
+ - best_sum_radii = -np.inf
235
+ - best_result_x = None
236
+ -
237
+ - # Progressively aggressive optimizer settings for each stage
238
+ - options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
239
+ - options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
240
+ - options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
241
+ -
242
+ - for run in range(num_optimization_runs):
243
+ - # Adaptive perturbation: broad exploration first, then fine-tuning.
244
+ - if run < num_optimization_runs // 2:
245
+ - perturbation_std_dev = 0.020 # Broader exploration
246
+ + if self.n > 25 and idx < self.n:
247
+ + centers[idx] = [0.5, 0.55]
248
+ + idx += 1
249
+ + return centers[:idx] # Return exactly N circles actually filled
250
+ +
251
+ + def _get_uniform_grid_centers(self):
252
+ + """
253
+ + Generates centers in a more uniform grid pattern for N circles.
254
+ + Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
255
+ + """
256
+ + side_len = int(np.ceil(np.sqrt(self.n)))
257
+ + # Adjust spacing to fit `side_len` circles with some margin.
258
+ + spacing = 1.0 / (side_len + 1)
259
+ + centers = []
260
+ + for i in range(side_len):
261
+ + for j in range(side_len):
262
+ + if len(centers) < self.n:
263
+ + centers.append([spacing * (i + 1), spacing * (j + 1)])
264
+ +
265
+ + # If N is not a perfect square, or if N is less than side_len*side_len,
266
+ + # fill remaining positions (up to N) at or near the center to ensure N circles.
267
+ + # Adding a slight random offset to these central points to prevent them from
268
+ + # being exactly on top of each other and aiding initial perturbation.
269
+ + while len(centers) < self.n:
270
+ + offset_x = np.random.uniform(-0.05, 0.05)
271
+ + offset_y = np.random.uniform(-0.05, 0.05)
272
+ + centers.append([0.5 + offset_x, 0.5 + offset_y])
273
+ +
274
+ + return np.array(centers[:self.n]) # Ensure exactly N circles
275
+ +
276
+ + def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
277
+ + """
278
+ + Generates an initial optimization vector `x0` based on the specified strategy
279
+ + and applies perturbation.
280
+ + """
281
+ + if strategy == 'grid_split_5x5':
282
+ + base_centers = self._get_grid_split_5x5_centers()
283
+ + elif strategy == 'uniform_grid':
284
+ + base_centers = self._get_uniform_grid_centers()
285
+ else:
286
+ - perturbation_std_dev = 0.005 # Finer refinement
287
+ -
288
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
289
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
290
+ -
291
+ - perturbed_radii = _compute_initial_radii(perturbed_centers)
292
+ - x0_run = pack_vars(perturbed_centers, perturbed_radii)
293
+ -
294
+ - # Stage 1: Maximize sum of areas (r^2)
295
+ - result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
296
+ - if not result_stage1.success: continue
297
+ -
298
+ - # Stage 2: Maximize sum of radii (r)
299
+ - result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
300
+ - if not result_stage2.success: continue
301
+ -
302
+ - # Stage 3: Further maximize sum of radii with tighter options
303
+ - result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
304
+ -
305
+ - _, current_radii = unpack_vars(result_stage3.x)
306
+ - current_sum_radii = np.sum(current_radii)
307
+ -
308
+ - if current_sum_radii > best_sum_radii:
309
+ - best_sum_radii = current_sum_radii
310
+ - best_result_x = result_stage3.x
311
+ -
312
+ - # --- 6. Extract and Return Results ---
313
+ - # Fallback if all runs fail (highly unlikely)
314
+ - if best_result_x is None:
315
+ - initial_radii = _compute_initial_radii(base_initial_centers)
316
+ - best_result_x = pack_vars(base_initial_centers, initial_radii)
317
+ -
318
+ - final_centers, final_radii = unpack_vars(best_result_x)
319
+ - final_radii = np.maximum(final_radii, 0)
320
+ + raise ValueError(f"Unknown initial guess strategy: {strategy}")
321
+ +
322
+ + # Apply perturbation
323
+ + perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
324
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
325
+ +
326
+ + # Compute initial radii for the perturbed centers
327
+ + perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
328
+ +
329
+ + return self.problem._pack_vars(perturbed_centers, perturbed_radii)
330
+ +
331
+ +
332
+ +class PackingOptimizer:
333
+ + """
334
+ + Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
335
+ + (Recommendation 2: Smoother Adaptive Perturbation Schedule, Recommendation 3: Three-Stage NLP)
336
+ + """
337
+ + def __init__(self, problem_instance, optimizer_config):
338
+ + self.problem = problem_instance
339
+ + self.config = optimizer_config
340
+ + self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
341
+ +
342
+ + def optimize(self):
343
+ + """Runs the optimization process and returns the best result found."""
344
+ + best_sum_radii = -np.inf
345
+ + best_result_x = None
346
+ +
347
+ + num_runs = self.config['num_optimization_runs']
348
+ + # Recommendation 1: Hybrid initial guess strategies distributed across runs
349
+ + run_strategies = self.config.get('run_strategies', ['grid_split_5x5'] * num_runs)
350
+ +
351
+ + for run_idx in range(num_runs):
352
+ + # Recommendation 2: Adaptive perturbation schedule (three-tier)
353
+ + if run_idx < num_runs * 0.4: # 40% of runs with larger perturbation for global exploration
354
+ + perturbation_std_dev = self.config['perturbation_std_dev_large']
355
+ + elif run_idx < num_runs * 0.8: # 40% of runs with medium perturbation for intermediate exploration
356
+ + perturbation_std_dev = self.config['perturbation_std_dev_medium']
357
+ + else: # 20% of runs with smaller perturbation for local refinement
358
+ + perturbation_std_dev = self.config['perturbation_std_dev_small']
359
+ +
360
+ + # Select initial guess strategy for this run. Cycle through strategies if fewer strategies than runs.
361
+ + strategy_for_run = run_strategies[run_idx % len(run_strategies)]
362
+ + x0_run = self.initial_guesser.generate_initial_x0(strategy=strategy_for_run, perturbation_std_dev=perturbation_std_dev)
363
+ +
364
+ + # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
365
+ + result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
366
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
367
+ + options=self.config['options_stage1'])
368
+ + # If Stage 1 fails, skip to next run (no good starting point found)
369
+ + if not result_stage1.success:
370
+ + continue
371
+ +
372
+ + # Stage 2: Maximize sum of radii (r) with moderately tight options
373
+ + result_stage2 = minimize(self.problem.objective_radii, result_stage1.x, method='SLSQP',
374
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
375
+ + options=self.config['options_stage2'])
376
+ + # If Stage 2 fails, use Stage 1's result for next stage.
377
+ + x_after_s2 = result_stage2.x if result_stage2.success else result_stage1.x
378
+ +
379
+ + # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
380
+ + result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
381
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
382
+ + options=self.config['options_stage3'])
383
+ +
384
+ + # Use the result from the last successful stage. Prioritize Stage 3 if successful.
385
+ + final_run_x = result_stage3.x if result_stage3.success else x_after_s2
386
+ +
387
+ + _, current_radii = self.problem._unpack_vars(final_run_x)
388
+ + current_sum_radii = np.sum(current_radii)
389
+ +
390
+ + if current_sum_radii > best_sum_radii:
391
+ + best_sum_radii = current_sum_radii
392
+ + best_result_x = final_run_x
393
+ +
394
+ + # Fallback if no run was successful, use initial guess from the first strategy with no perturbation.
395
+ + if best_result_x is None:
396
+ + # Fallback for N=26 using the grid_split_5x5 strategy with no perturbation
397
+ + best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
398
+ +
399
+ + final_centers, final_radii = self.problem._unpack_vars(best_result_x)
400
+ + # Clean up potential floating point inaccuracies (e.g., small negative radii).
401
+ + final_radii = np.maximum(final_radii, 0)
402
+ +
403
+ + return final_centers, final_radii
404
+ +
405
+ +def construct_packing():
406
+ + """
407
+ + Main function to construct an optimized packing of N=26 circles.
408
+ + This uses a structurally redesigned program with clear separation of concerns
409
+ + into Problem, InitialGuesser, and Optimizer classes. It incorporates
410
+ + adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
411
+ + with progressive solver tightness.
412
+ + """
413
+ + n_circles = 26
414
+ +
415
+ + # 1. Initialize the problem definition
416
+ + problem = CirclePackingProblem(n_circles)
417
+ +
418
+ + # 2. Define optimizer configurations
419
+ + optimizer_config = {
420
+ + 'num_optimization_runs': 30, # Increased runs for more robust exploration
421
+ + 'perturbation_std_dev_large': 0.03, # Broader initial exploration
422
+ + 'perturbation_std_dev_medium': 0.01, # Mid-range exploration
423
+ + 'perturbation_std_dev_small': 0.003, # Fine-tuning for local optima
424
+ + 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
425
+ + # Stage 2 uses slightly relaxed options than final stage
426
+ + 'options_stage2': {'maxiter': 2500, 'ftol': 1e-10, 'gtol': 5e-7, 'disp': False},
427
+ + # Stage 3 uses aggressive options for final refinement
428
+ + 'options_stage3': {'maxiter': 5000, 'ftol': 1e-12, 'gtol': 1e-8, 'disp': False},
429
+ + # Hybrid initial guess strategies distributed across runs
430
+ + 'run_strategies': ['grid_split_5x5'] * 15 + ['uniform_grid'] * 15
431
+ + }
432
+ +
433
+ + # 3. Instantiate and run the optimizer
434
+ + optimizer = PackingOptimizer(problem, optimizer_config)
435
+ + final_centers, final_radii = optimizer.optimize()
436
+
437
+ return final_centers, final_radii
438
+ # EVOLVE-BLOCK-END
439
+
440
+
441
+ # This part remains fixed (not evolved)
442
+ def run_packing():
443
+ """Run the circle packing constructor for n=26"""
444
+ centers, radii = construct_packing()
445
+ # Calculate the sum of radii
446
+ sum_radii = np.sum(radii)
447
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/main.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ class CirclePackingProblem:
6
+ """
7
+ Encapsulates the definition of the circle packing problem for N circles
8
+ within a unit square. Defines objectives, constraints, and variable packing/unpacking.
9
+ """
10
+ def __init__(self, n_circles):
11
+ self.n = n_circles
12
+ self.bounds = self._define_bounds()
13
+ self.constraints = self._define_constraints()
14
+
15
+ def _define_bounds(self):
16
+ """Define bounds for each variable: center_x, center_y, radius."""
17
+ bounds = []
18
+ for _ in range(self.n):
19
+ # Recommendation 5: Enforce a non-zero minimum radius to prevent degenerate solutions.
20
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (1e-7, 0.5)])
21
+ return bounds
22
+
23
+ def _define_constraints(self):
24
+ """Define non-overlap and boundary constraints."""
25
+ cons = []
26
+
27
+ # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
28
+ # Using squared distances for numerical stability and avoiding sqrt in derivatives.
29
+ def non_overlap_constraint(x_vars):
30
+ centers, radii = self._unpack_vars(x_vars)
31
+ # Vectorized computation of pairwise squared Euclidean distances.
32
+ i, j = np.triu_indices(self.n, k=1)
33
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
34
+
35
+ # Squared sum of radii for corresponding pairs.
36
+ radii_sums_sq = (radii[i] + radii[j])**2
37
+
38
+ return dist_sq - radii_sums_sq
39
+
40
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
41
+
42
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
43
+ def boundary_constraint(x_vars):
44
+ centers, radii = self._unpack_vars(x_vars)
45
+ return np.concatenate([
46
+ centers[:, 0] - radii, # x - r >= 0
47
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
48
+ centers[:, 1] - radii, # y - r >= 0
49
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
50
+ ])
51
+
52
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
53
+ return cons
54
+
55
+ def objective_area(self, x_vars):
56
+ """Objective for Stage 1: Maximize sum of areas (r^2)."""
57
+ _, radii = self._unpack_vars(x_vars)
58
+ return -np.sum(radii**2)
59
+
60
+ def objective_radii(self, x_vars):
61
+ """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
62
+ _, radii = self._unpack_vars(x_vars)
63
+ return -np.sum(radii)
64
+
65
+ def _pack_vars(self, centers, radii):
66
+ """Converts structured centers/radii into a flat optimization vector."""
67
+ x = np.zeros(self.n * 3)
68
+ x[0::3] = centers[:, 0]
69
+ x[1::3] = centers[:, 1]
70
+ x[2::3] = radii
71
+ return x
72
+
73
+ def _unpack_vars(self, x_vars):
74
+ """Converts a flat optimization vector into structured centers/radii."""
75
+ centers_x = x_vars[0::3]
76
+ centers_y = x_vars[1::3]
77
+ radii = x_vars[2::3]
78
+ centers = np.vstack((centers_x, centers_y)).T
79
+ return centers, radii
80
+
81
+ def compute_initial_radii(self, centers, max_iter=200):
82
+ """
83
+ Iteratively computes the maximum possible non-overlapping radii for a
84
+ given fixed set of centers, ensuring a small minimum gap.
85
+ (Recommendation 3: Dynamic MIN_GAP)
86
+ """
87
+ radii = np.zeros(self.n)
88
+ # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
89
+ # or more robust initial state for fewer circles.
90
+ MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
91
+
92
+ # Initialize radii based on the minimum distance to the walls.
93
+ for i in range(self.n):
94
+ x, y = centers[i]
95
+ radii[i] = min(x, 1 - x, y, 1 - y)
96
+ radii[i] = max(radii[i], 1e-7) # Ensure initial radii are at least the minimum bound
97
+
98
+ # Iteratively shrink radii to resolve overlaps.
99
+ for _ in range(max_iter):
100
+ had_change = False
101
+ for i in range(self.n):
102
+ for j in range(i + 1, self.n):
103
+ dist = np.linalg.norm(centers[i] - centers[j])
104
+ sum_r = radii[i] + radii[j]
105
+ if sum_r > dist - MIN_GAP_THRESHOLD:
106
+ target_sum_r = max(2 * 1e-7, dist - MIN_GAP_THRESHOLD) # Ensure sum is at least 2*min_radius
107
+ if sum_r > 1e-12: # Avoid division by zero
108
+ scale = target_sum_r / sum_r
109
+ radii[i] *= scale
110
+ radii[j] *= scale
111
+ had_change = True
112
+ if not had_change:
113
+ break # Converged
114
+ return radii
115
+
116
+
117
+ class InitialGuesser:
118
+ """
119
+ Generates diverse initial configurations of circle centers for the NLP solver.
120
+ (Recommendation 1: Diversify Base Initial Layouts)
121
+ """
122
+ def __init__(self, n_circles, problem_instance):
123
+ self.n = n_circles
124
+ self.problem = problem_instance
125
+
126
+ def _get_grid_split_5x5_centers(self):
127
+ """Returns the proven 5x5 grid with a split center configuration."""
128
+ centers = np.zeros((self.n, 2))
129
+ idx = 0
130
+ grid_points = np.linspace(0.1, 0.9, 5)
131
+ for i in range(5):
132
+ for j in range(5):
133
+ if i == 2 and j == 2: # Skip center of the 5x5 grid
134
+ continue
135
+ if idx < self.n: # Ensure we don't exceed n_circles
136
+ centers[idx] = [grid_points[i], grid_points[j]]
137
+ idx += 1
138
+ # Place the remaining two circles in the "split center" configuration.
139
+ # This assumes N=26 fits (25-1+2=26).
140
+ if self.n > 24 and idx < self.n:
141
+ centers[idx] = [0.5, 0.45]
142
+ idx += 1
143
+ if self.n > 25 and idx < self.n:
144
+ centers[idx] = [0.5, 0.55]
145
+ idx += 1
146
+ return centers[:idx] # Return exactly N circles actually filled
147
+
148
+ def _get_uniform_grid_centers(self):
149
+ """
150
+ Generates centers in a more uniform grid pattern for N circles.
151
+ Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
152
+ """
153
+ side_len = int(np.ceil(np.sqrt(self.n)))
154
+ # Adjust spacing to fit `side_len` circles with some margin.
155
+ spacing = 1.0 / (side_len + 1)
156
+ centers = []
157
+ for i in range(side_len):
158
+ for j in range(side_len):
159
+ if len(centers) < self.n:
160
+ centers.append([spacing * (i + 1), spacing * (j + 1)])
161
+
162
+ # If N is not a perfect square, or if N is less than side_len*side_len,
163
+ # fill remaining positions (up to N) at or near the center to ensure N circles.
164
+ # Adding a slight random offset to these central points to prevent them from
165
+ # being exactly on top of each other and aiding initial perturbation.
166
+ while len(centers) < self.n:
167
+ offset_x = np.random.uniform(-0.05, 0.05)
168
+ offset_y = np.random.uniform(-0.05, 0.05)
169
+ centers.append([0.5 + offset_x, 0.5 + offset_y])
170
+
171
+ return np.array(centers[:self.n]) # Ensure exactly N circles
172
+
173
+ def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
174
+ """
175
+ Generates an initial optimization vector `x0` based on the specified strategy
176
+ and applies perturbation.
177
+ """
178
+ if strategy == 'grid_split_5x5':
179
+ base_centers = self._get_grid_split_5x5_centers()
180
+ elif strategy == 'uniform_grid':
181
+ base_centers = self._get_uniform_grid_centers()
182
+ else:
183
+ raise ValueError(f"Unknown initial guess strategy: {strategy}")
184
+
185
+ # Apply perturbation
186
+ perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
187
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
188
+
189
+ # Compute initial radii for the perturbed centers
190
+ perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
191
+
192
+ return self.problem._pack_vars(perturbed_centers, perturbed_radii)
193
+
194
+
195
+ class PackingOptimizer:
196
+ """
197
+ Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
198
+ (Recommendation 2: Smoother Adaptive Perturbation Schedule, Recommendation 3: Three-Stage NLP)
199
+ """
200
+ def __init__(self, problem_instance, optimizer_config):
201
+ self.problem = problem_instance
202
+ self.config = optimizer_config
203
+ self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
204
+
205
+ def optimize(self):
206
+ """Runs the optimization process and returns the best result found."""
207
+ best_sum_radii = -np.inf
208
+ best_result_x = None
209
+
210
+ num_runs = self.config['num_optimization_runs']
211
+ # Recommendation 1: Hybrid initial guess strategies distributed across runs
212
+ run_strategies = self.config.get('run_strategies', ['grid_split_5x5'] * num_runs)
213
+
214
+ for run_idx in range(num_runs):
215
+ # Recommendation 2: Adaptive perturbation schedule (three-tier)
216
+ if run_idx < num_runs * 0.4: # 40% of runs with larger perturbation for global exploration
217
+ perturbation_std_dev = self.config['perturbation_std_dev_large']
218
+ elif run_idx < num_runs * 0.8: # 40% of runs with medium perturbation for intermediate exploration
219
+ perturbation_std_dev = self.config['perturbation_std_dev_medium']
220
+ else: # 20% of runs with smaller perturbation for local refinement
221
+ perturbation_std_dev = self.config['perturbation_std_dev_small']
222
+
223
+ # Select initial guess strategy for this run. Cycle through strategies if fewer strategies than runs.
224
+ strategy_for_run = run_strategies[run_idx % len(run_strategies)]
225
+ x0_run = self.initial_guesser.generate_initial_x0(strategy=strategy_for_run, perturbation_std_dev=perturbation_std_dev)
226
+
227
+ # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
228
+ result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
229
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
230
+ options=self.config['options_stage1'])
231
+ # If Stage 1 fails, skip to next run (no good starting point found)
232
+ if not result_stage1.success:
233
+ continue
234
+
235
+ # Stage 2: Maximize sum of radii (r) with moderately tight options
236
+ result_stage2 = minimize(self.problem.objective_radii, result_stage1.x, method='SLSQP',
237
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
238
+ options=self.config['options_stage2'])
239
+ # If Stage 2 fails, use Stage 1's result for next stage.
240
+ x_after_s2 = result_stage2.x if result_stage2.success else result_stage1.x
241
+
242
+ # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
243
+ result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
244
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
245
+ options=self.config['options_stage3'])
246
+
247
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
248
+ final_run_x = result_stage3.x if result_stage3.success else x_after_s2
249
+
250
+ _, current_radii = self.problem._unpack_vars(final_run_x)
251
+ current_sum_radii = np.sum(current_radii)
252
+
253
+ if current_sum_radii > best_sum_radii:
254
+ best_sum_radii = current_sum_radii
255
+ best_result_x = final_run_x
256
+
257
+ # Fallback if no run was successful, use initial guess from the first strategy with no perturbation.
258
+ if best_result_x is None:
259
+ # Fallback for N=26 using the grid_split_5x5 strategy with no perturbation
260
+ best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
261
+
262
+ final_centers, final_radii = self.problem._unpack_vars(best_result_x)
263
+ # Clean up potential floating point inaccuracies (e.g., small negative radii).
264
+ final_radii = np.maximum(final_radii, 0)
265
+
266
+ return final_centers, final_radii
267
+
268
+ def construct_packing():
269
+ """
270
+ Main function to construct an optimized packing of N=26 circles.
271
+ This uses a structurally redesigned program with clear separation of concerns
272
+ into Problem, InitialGuesser, and Optimizer classes. It incorporates
273
+ adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
274
+ with progressive solver tightness.
275
+ """
276
+ n_circles = 26
277
+
278
+ # 1. Initialize the problem definition
279
+ problem = CirclePackingProblem(n_circles)
280
+
281
+ # 2. Define optimizer configurations
282
+ optimizer_config = {
283
+ 'num_optimization_runs': 30, # Increased runs for more robust exploration
284
+ 'perturbation_std_dev_large': 0.03, # Broader initial exploration
285
+ 'perturbation_std_dev_medium': 0.01, # Mid-range exploration
286
+ 'perturbation_std_dev_small': 0.003, # Fine-tuning for local optima
287
+ 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
288
+ # Stage 2 uses slightly relaxed options than final stage
289
+ 'options_stage2': {'maxiter': 2500, 'ftol': 1e-10, 'gtol': 5e-7, 'disp': False},
290
+ # Stage 3 uses aggressive options for final refinement
291
+ 'options_stage3': {'maxiter': 5000, 'ftol': 1e-12, 'gtol': 1e-8, 'disp': False},
292
+ # Hybrid initial guess strategies distributed across runs
293
+ 'run_strategies': ['grid_split_5x5'] * 15 + ['uniform_grid'] * 15
294
+ }
295
+
296
+ # 3. Instantiate and run the optimizer
297
+ optimizer = PackingOptimizer(problem, optimizer_config)
298
+ final_centers, final_radii = optimizer.optimize()
299
+
300
+ return final_centers, final_radii
301
+ # EVOLVE-BLOCK-END
302
+
303
+
304
+ # This part remains fixed (not evolved)
305
+ def run_packing():
306
+ """Run the circle packing constructor for n=26"""
307
+ centers, radii = construct_packing()
308
+ # Calculate the sum of radii
309
+ sum_radii = np.sum(radii)
310
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/original.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles using a three-stage NLP.
8
+ This method enhances previous strategies by increasing the number of optimization
9
+ runs, using an adaptive perturbation schedule for the initial guess, employing
10
+ progressively tighter solver tolerances across three stages, and using a
11
+ numerically stable squared-distance constraint.
12
+ """
13
+ n = 26
14
+
15
+ # Helper functions to convert between the flat optimization vector and
16
+ # the structured centers/radii arrays.
17
+ def pack_vars(centers, radii):
18
+ x = np.zeros(n * 3)
19
+ x[0::3] = centers[:, 0]
20
+ x[1::3] = centers[:, 1]
21
+ x[2::3] = radii
22
+ return x
23
+
24
+ def unpack_vars(x):
25
+ centers_x = x[0::3]
26
+ centers_y = x[1::3]
27
+ radii = x[2::3]
28
+ centers = np.vstack((centers_x, centers_y)).T
29
+ return centers, radii
30
+
31
+ # --- 1. Initial Guess ---
32
+ def _compute_initial_radii(centers, max_iter=200):
33
+ """Iteratively compute max non-overlapping radii for a given set of centers."""
34
+ num_circles = centers.shape[0]
35
+ radii = np.zeros(num_circles)
36
+ MIN_GAP = 1e-8 # Use a small gap for robustness
37
+
38
+ # Initialize radii based on distance to walls
39
+ for i in range(num_circles):
40
+ x, y = centers[i]
41
+ radii[i] = min(x, 1 - x, y, 1 - y)
42
+
43
+ # Iteratively shrink radii to resolve overlaps
44
+ for _ in range(max_iter):
45
+ had_change = False
46
+ for i in range(num_circles):
47
+ for j in range(i + 1, num_circles):
48
+ dist = np.linalg.norm(centers[i] - centers[j])
49
+ sum_r = radii[i] + radii[j]
50
+ if sum_r > dist - MIN_GAP:
51
+ target_sum_r = max(0.0, dist - MIN_GAP)
52
+ if sum_r > 1e-12:
53
+ scale = target_sum_r / sum_r
54
+ radii[i] *= scale
55
+ radii[j] *= scale
56
+ had_change = True
57
+ if not had_change:
58
+ break
59
+ return radii
60
+
61
+ # --- 2. Define Objective Functions for Staged Optimization ---
62
+ def objective_area(x):
63
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
64
+ _, radii = unpack_vars(x)
65
+ return -np.sum(radii**2)
66
+
67
+ def objective_radii(x):
68
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
69
+ _, radii = unpack_vars(x)
70
+ return -np.sum(radii)
71
+
72
+ # --- 3. Define Constraints ---
73
+ cons = []
74
+
75
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
76
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
77
+ def non_overlap_constraint(x):
78
+ centers, radii = unpack_vars(x)
79
+ i, j = np.triu_indices(n, k=1)
80
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
81
+ sum_radii_sq = (radii[i] + radii[j])**2
82
+ return dist_sq - sum_radii_sq
83
+
84
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
85
+
86
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
87
+ def boundary_constraint(x):
88
+ centers, radii = unpack_vars(x)
89
+ return np.concatenate([
90
+ centers[:, 0] - radii, # x - r >= 0
91
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
92
+ centers[:, 1] - radii, # y - r >= 0
93
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
94
+ ])
95
+
96
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
97
+
98
+ # --- 4. Define Bounds for each variable ---
99
+ bounds = []
100
+ for _ in range(n):
101
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
102
+
103
+ # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
104
+ base_initial_centers = np.zeros((n, 2))
105
+ idx = 0
106
+ for i in range(5):
107
+ for j in range(5):
108
+ if i == 2 and j == 2: continue
109
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
110
+ idx += 1
111
+ base_initial_centers[24] = [0.5, 0.45]
112
+ base_initial_centers[25] = [0.5, 0.55]
113
+
114
+ num_optimization_runs = 24
115
+ best_sum_radii = -np.inf
116
+ best_result_x = None
117
+
118
+ # Progressively aggressive optimizer settings for each stage
119
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
120
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
121
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
122
+
123
+ for run in range(num_optimization_runs):
124
+ # Adaptive perturbation: broad exploration first, then fine-tuning.
125
+ if run < num_optimization_runs // 2:
126
+ perturbation_std_dev = 0.020 # Broader exploration
127
+ else:
128
+ perturbation_std_dev = 0.005 # Finer refinement
129
+
130
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
131
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
132
+
133
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
134
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
135
+
136
+ # Stage 1: Maximize sum of areas (r^2)
137
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
138
+ if not result_stage1.success: continue
139
+
140
+ # Stage 2: Maximize sum of radii (r)
141
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
142
+ if not result_stage2.success: continue
143
+
144
+ # Stage 3: Further maximize sum of radii with tighter options
145
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
146
+
147
+ _, current_radii = unpack_vars(result_stage3.x)
148
+ current_sum_radii = np.sum(current_radii)
149
+
150
+ if current_sum_radii > best_sum_radii:
151
+ best_sum_radii = current_sum_radii
152
+ best_result_x = result_stage3.x
153
+
154
+ # --- 6. Extract and Return Results ---
155
+ # Fallback if all runs fail (highly unlikely)
156
+ if best_result_x is None:
157
+ initial_radii = _compute_initial_radii(base_initial_centers)
158
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
159
+
160
+ final_centers, final_radii = unpack_vars(best_result_x)
161
+ final_radii = np.maximum(final_radii, 0)
162
+
163
+ return final_centers, final_radii
164
+ # EVOLVE-BLOCK-END
165
+
166
+
167
+ # This part remains fixed (not evolved)
168
+ def run_packing():
169
+ """Run the circle packing constructor for n=26"""
170
+ centers, radii = construct_packing()
171
+ # Calculate the sum of radii
172
+ sum_radii = np.sum(radii)
173
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/search_replace.txt ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <refactor_to_classes_and_refine>
2
+ Refactor to class-based structure, introduce hybrid initial guess, adaptive perturbation schedule, adaptive MIN_GAP, and non-zero min radius bound.
3
+ </DESCRIPTION>
4
+ <DIFF>
5
+ <<<<<<< SEARCH
6
+ import numpy as np
7
+ from scipy.optimize import minimize
8
+
9
+ def construct_packing():
10
+ """
11
+ Constructs an optimized arrangement of 26 circles using a three-stage NLP.
12
+ This method enhances previous strategies by increasing the number of optimization
13
+ runs, using an adaptive perturbation schedule for the initial guess, employing
14
+ progressively tighter solver tolerances across three stages, and using a
15
+ numerically stable squared-distance constraint.
16
+ """
17
+ n = 26
18
+
19
+ # Helper functions to convert between the flat optimization vector and
20
+ # the structured centers/radii arrays.
21
+ def pack_vars(centers, radii):
22
+ x = np.zeros(n * 3)
23
+ x[0::3] = centers[:, 0]
24
+ x[1::3] = centers[:, 1]
25
+ x[2::3] = radii
26
+ return x
27
+
28
+ def unpack_vars(x):
29
+ centers_x = x[0::3]
30
+ centers_y = x[1::3]
31
+ radii = x[2::3]
32
+ centers = np.vstack((centers_x, centers_y)).T
33
+ return centers, radii
34
+
35
+ # --- 1. Initial Guess ---
36
+ def _compute_initial_radii(centers, max_iter=200):
37
+ """Iteratively compute max non-overlapping radii for a given set of centers."""
38
+ num_circles = centers.shape[0]
39
+ radii = np.zeros(num_circles)
40
+ MIN_GAP = 1e-8 # Use a small gap for robustness
41
+
42
+ # Initialize radii based on distance to walls
43
+ for i in range(num_circles):
44
+ x, y = centers[i]
45
+ radii[i] = min(x, 1 - x, y, 1 - y)
46
+
47
+ # Iteratively shrink radii to resolve overlaps
48
+ for _ in range(max_iter):
49
+ had_change = False
50
+ for i in range(num_circles):
51
+ for j in range(i + 1, num_circles):
52
+ dist = np.linalg.norm(centers[i] - centers[j])
53
+ sum_r = radii[i] + radii[j]
54
+ if sum_r > dist - MIN_GAP:
55
+ target_sum_r = max(0.0, dist - MIN_GAP)
56
+ if sum_r > 1e-12:
57
+ scale = target_sum_r / sum_r
58
+ radii[i] *= scale
59
+ radii[j] *= scale
60
+ had_change = True
61
+ if not had_change:
62
+ break
63
+ return radii
64
+
65
+ # --- 2. Define Objective Functions for Staged Optimization ---
66
+ def objective_area(x):
67
+ """Stage 1: Maximize sum of areas (r^2) for a dense packing."""
68
+ _, radii = unpack_vars(x)
69
+ return -np.sum(radii**2)
70
+
71
+ def objective_radii(x):
72
+ """Stage 2 & 3: Maximize sum of radii (r), the primary goal."""
73
+ _, radii = unpack_vars(x)
74
+ return -np.sum(radii)
75
+
76
+ # --- 3. Define Constraints ---
77
+ cons = []
78
+
79
+ # Constraint 1: Non-overlapping circles. Use squared distances for numerical stability.
80
+ # (xi - xj)^2 + (yi - yj)^2 >= (ri + rj)^2 => dist_sq - (ri+rj)^2 >= 0
81
+ def non_overlap_constraint(x):
82
+ centers, radii = unpack_vars(x)
83
+ i, j = np.triu_indices(n, k=1)
84
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
85
+ sum_radii_sq = (radii[i] + radii[j])**2
86
+ return dist_sq - sum_radii_sq
87
+
88
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
89
+
90
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
91
+ def boundary_constraint(x):
92
+ centers, radii = unpack_vars(x)
93
+ return np.concatenate([
94
+ centers[:, 0] - radii, # x - r >= 0
95
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
96
+ centers[:, 1] - radii, # y - r >= 0
97
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
98
+ ])
99
+
100
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
101
+
102
+ # --- 4. Define Bounds for each variable ---
103
+ bounds = []
104
+ for _ in range(n):
105
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
106
+
107
+ # --- 5. Run the Optimizer with Iterative Perturbation & 3 Stages ---
108
+ base_initial_centers = np.zeros((n, 2))
109
+ idx = 0
110
+ for i in range(5):
111
+ for j in range(5):
112
+ if i == 2 and j == 2: continue
113
+ base_initial_centers[idx] = [0.1 + i * 0.2, 0.1 + j * 0.2]
114
+ idx += 1
115
+ base_initial_centers[24] = [0.5, 0.45]
116
+ base_initial_centers[25] = [0.5, 0.55]
117
+
118
+ num_optimization_runs = 24
119
+ best_sum_radii = -np.inf
120
+ best_result_x = None
121
+
122
+ # Progressively aggressive optimizer settings for each stage
123
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False}
124
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
125
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-10, 'disp': False}
126
+
127
+ for run in range(num_optimization_runs):
128
+ # Adaptive perturbation: broad exploration first, then fine-tuning.
129
+ if run < num_optimization_runs // 2:
130
+ perturbation_std_dev = 0.020 # Broader exploration
131
+ else:
132
+ perturbation_std_dev = 0.005 # Finer refinement
133
+
134
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturbation_std_dev, base_initial_centers.shape)
135
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
136
+
137
+ perturbed_radii = _compute_initial_radii(perturbed_centers)
138
+ x0_run = pack_vars(perturbed_centers, perturbed_radii)
139
+
140
+ # Stage 1: Maximize sum of areas (r^2)
141
+ result_stage1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
142
+ if not result_stage1.success: continue
143
+
144
+ # Stage 2: Maximize sum of radii (r)
145
+ result_stage2 = minimize(objective_radii, result_stage1.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
146
+ if not result_stage2.success: continue
147
+
148
+ # Stage 3: Further maximize sum of radii with tighter options
149
+ result_stage3 = minimize(objective_radii, result_stage2.x, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
150
+
151
+ _, current_radii = unpack_vars(result_stage3.x)
152
+ current_sum_radii = np.sum(current_radii)
153
+
154
+ if current_sum_radii > best_sum_radii:
155
+ best_sum_radii = current_sum_radii
156
+ best_result_x = result_stage3.x
157
+
158
+ # --- 6. Extract and Return Results ---
159
+ # Fallback if all runs fail (highly unlikely)
160
+ if best_result_x is None:
161
+ initial_radii = _compute_initial_radii(base_initial_centers)
162
+ best_result_x = pack_vars(base_initial_centers, initial_radii)
163
+
164
+ final_centers, final_radii = unpack_vars(best_result_x)
165
+ final_radii = np.maximum(final_radii, 0)
166
+
167
+ return final_centers, final_radii
168
+ =======
169
+ import numpy as np
170
+ from scipy.optimize import minimize
171
+
172
+ class CirclePackingProblem:
173
+ """
174
+ Encapsulates the definition of the circle packing problem for N circles
175
+ within a unit square. Defines objectives, constraints, and variable packing/unpacking.
176
+ """
177
+ def __init__(self, n_circles):
178
+ self.n = n_circles
179
+ self.bounds = self._define_bounds()
180
+ self.constraints = self._define_constraints()
181
+
182
+ def _define_bounds(self):
183
+ """Define bounds for each variable: center_x, center_y, radius."""
184
+ bounds = []
185
+ for _ in range(self.n):
186
+ # Recommendation 5: Enforce a non-zero minimum radius to prevent degenerate solutions.
187
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (1e-7, 0.5)])
188
+ return bounds
189
+
190
+ def _define_constraints(self):
191
+ """Define non-overlap and boundary constraints."""
192
+ cons = []
193
+
194
+ # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
195
+ # Using squared distances for numerical stability and avoiding sqrt in derivatives.
196
+ def non_overlap_constraint(x_vars):
197
+ centers, radii = self._unpack_vars(x_vars)
198
+ # Vectorized computation of pairwise squared Euclidean distances.
199
+ i, j = np.triu_indices(self.n, k=1)
200
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
201
+
202
+ # Squared sum of radii for corresponding pairs.
203
+ radii_sums_sq = (radii[i] + radii[j])**2
204
+
205
+ return dist_sq - radii_sums_sq
206
+
207
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
208
+
209
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
210
+ def boundary_constraint(x_vars):
211
+ centers, radii = self._unpack_vars(x_vars)
212
+ return np.concatenate([
213
+ centers[:, 0] - radii, # x - r >= 0
214
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
215
+ centers[:, 1] - radii, # y - r >= 0
216
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
217
+ ])
218
+
219
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
220
+ return cons
221
+
222
+ def objective_area(self, x_vars):
223
+ """Objective for Stage 1: Maximize sum of areas (r^2)."""
224
+ _, radii = self._unpack_vars(x_vars)
225
+ return -np.sum(radii**2)
226
+
227
+ def objective_radii(self, x_vars):
228
+ """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
229
+ _, radii = self._unpack_vars(x_vars)
230
+ return -np.sum(radii)
231
+
232
+ def _pack_vars(self, centers, radii):
233
+ """Converts structured centers/radii into a flat optimization vector."""
234
+ x = np.zeros(self.n * 3)
235
+ x[0::3] = centers[:, 0]
236
+ x[1::3] = centers[:, 1]
237
+ x[2::3] = radii
238
+ return x
239
+
240
+ def _unpack_vars(self, x_vars):
241
+ """Converts a flat optimization vector into structured centers/radii."""
242
+ centers_x = x_vars[0::3]
243
+ centers_y = x_vars[1::3]
244
+ radii = x_vars[2::3]
245
+ centers = np.vstack((centers_x, centers_y)).T
246
+ return centers, radii
247
+
248
+ def compute_initial_radii(self, centers, max_iter=200):
249
+ """
250
+ Iteratively computes the maximum possible non-overlapping radii for a
251
+ given fixed set of centers, ensuring a small minimum gap.
252
+ (Recommendation 3: Dynamic MIN_GAP)
253
+ """
254
+ radii = np.zeros(self.n)
255
+ # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
256
+ # or more robust initial state for fewer circles.
257
+ MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
258
+
259
+ # Initialize radii based on the minimum distance to the walls.
260
+ for i in range(self.n):
261
+ x, y = centers[i]
262
+ radii[i] = min(x, 1 - x, y, 1 - y)
263
+ radii[i] = max(radii[i], 1e-7) # Ensure initial radii are at least the minimum bound
264
+
265
+ # Iteratively shrink radii to resolve overlaps.
266
+ for _ in range(max_iter):
267
+ had_change = False
268
+ for i in range(self.n):
269
+ for j in range(i + 1, self.n):
270
+ dist = np.linalg.norm(centers[i] - centers[j])
271
+ sum_r = radii[i] + radii[j]
272
+ if sum_r > dist - MIN_GAP_THRESHOLD:
273
+ target_sum_r = max(2 * 1e-7, dist - MIN_GAP_THRESHOLD) # Ensure sum is at least 2*min_radius
274
+ if sum_r > 1e-12: # Avoid division by zero
275
+ scale = target_sum_r / sum_r
276
+ radii[i] *= scale
277
+ radii[j] *= scale
278
+ had_change = True
279
+ if not had_change:
280
+ break # Converged
281
+ return radii
282
+
283
+
284
+ class InitialGuesser:
285
+ """
286
+ Generates diverse initial configurations of circle centers for the NLP solver.
287
+ (Recommendation 1: Diversify Base Initial Layouts)
288
+ """
289
+ def __init__(self, n_circles, problem_instance):
290
+ self.n = n_circles
291
+ self.problem = problem_instance
292
+
293
+ def _get_grid_split_5x5_centers(self):
294
+ """Returns the proven 5x5 grid with a split center configuration."""
295
+ centers = np.zeros((self.n, 2))
296
+ idx = 0
297
+ grid_points = np.linspace(0.1, 0.9, 5)
298
+ for i in range(5):
299
+ for j in range(5):
300
+ if i == 2 and j == 2: # Skip center of the 5x5 grid
301
+ continue
302
+ if idx < self.n: # Ensure we don't exceed n_circles
303
+ centers[idx] = [grid_points[i], grid_points[j]]
304
+ idx += 1
305
+ # Place the remaining two circles in the "split center" configuration.
306
+ # This assumes N=26 fits (25-1+2=26).
307
+ if self.n > 24 and idx < self.n:
308
+ centers[idx] = [0.5, 0.45]
309
+ idx += 1
310
+ if self.n > 25 and idx < self.n:
311
+ centers[idx] = [0.5, 0.55]
312
+ idx += 1
313
+ return centers[:idx] # Return exactly N circles actually filled
314
+
315
+ def _get_uniform_grid_centers(self):
316
+ """
317
+ Generates centers in a more uniform grid pattern for N circles.
318
+ Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
319
+ """
320
+ side_len = int(np.ceil(np.sqrt(self.n)))
321
+ # Adjust spacing to fit `side_len` circles with some margin.
322
+ spacing = 1.0 / (side_len + 1)
323
+ centers = []
324
+ for i in range(side_len):
325
+ for j in range(side_len):
326
+ if len(centers) < self.n:
327
+ centers.append([spacing * (i + 1), spacing * (j + 1)])
328
+
329
+ # If N is not a perfect square, or if N is less than side_len*side_len,
330
+ # fill remaining positions (up to N) at or near the center to ensure N circles.
331
+ # Adding a slight random offset to these central points to prevent them from
332
+ # being exactly on top of each other and aiding initial perturbation.
333
+ while len(centers) < self.n:
334
+ offset_x = np.random.uniform(-0.05, 0.05)
335
+ offset_y = np.random.uniform(-0.05, 0.05)
336
+ centers.append([0.5 + offset_x, 0.5 + offset_y])
337
+
338
+ return np.array(centers[:self.n]) # Ensure exactly N circles
339
+
340
+ def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
341
+ """
342
+ Generates an initial optimization vector `x0` based on the specified strategy
343
+ and applies perturbation.
344
+ """
345
+ if strategy == 'grid_split_5x5':
346
+ base_centers = self._get_grid_split_5x5_centers()
347
+ elif strategy == 'uniform_grid':
348
+ base_centers = self._get_uniform_grid_centers()
349
+ else:
350
+ raise ValueError(f"Unknown initial guess strategy: {strategy}")
351
+
352
+ # Apply perturbation
353
+ perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
354
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
355
+
356
+ # Compute initial radii for the perturbed centers
357
+ perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
358
+
359
+ return self.problem._pack_vars(perturbed_centers, perturbed_radii)
360
+
361
+
362
+ class PackingOptimizer:
363
+ """
364
+ Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
365
+ (Recommendation 2: Smoother Adaptive Perturbation Schedule, Recommendation 3: Three-Stage NLP)
366
+ """
367
+ def __init__(self, problem_instance, optimizer_config):
368
+ self.problem = problem_instance
369
+ self.config = optimizer_config
370
+ self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
371
+
372
+ def optimize(self):
373
+ """Runs the optimization process and returns the best result found."""
374
+ best_sum_radii = -np.inf
375
+ best_result_x = None
376
+
377
+ num_runs = self.config['num_optimization_runs']
378
+ # Recommendation 1: Hybrid initial guess strategies distributed across runs
379
+ run_strategies = self.config.get('run_strategies', ['grid_split_5x5'] * num_runs)
380
+
381
+ for run_idx in range(num_runs):
382
+ # Recommendation 2: Adaptive perturbation schedule (three-tier)
383
+ if run_idx < num_runs * 0.4: # 40% of runs with larger perturbation for global exploration
384
+ perturbation_std_dev = self.config['perturbation_std_dev_large']
385
+ elif run_idx < num_runs * 0.8: # 40% of runs with medium perturbation for intermediate exploration
386
+ perturbation_std_dev = self.config['perturbation_std_dev_medium']
387
+ else: # 20% of runs with smaller perturbation for local refinement
388
+ perturbation_std_dev = self.config['perturbation_std_dev_small']
389
+
390
+ # Select initial guess strategy for this run. Cycle through strategies if fewer strategies than runs.
391
+ strategy_for_run = run_strategies[run_idx % len(run_strategies)]
392
+ x0_run = self.initial_guesser.generate_initial_x0(strategy=strategy_for_run, perturbation_std_dev=perturbation_std_dev)
393
+
394
+ # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
395
+ result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
396
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
397
+ options=self.config['options_stage1'])
398
+ # If Stage 1 fails, skip to next run (no good starting point found)
399
+ if not result_stage1.success:
400
+ continue
401
+
402
+ # Stage 2: Maximize sum of radii (r) with moderately tight options
403
+ result_stage2 = minimize(self.problem.objective_radii, result_stage1.x, method='SLSQP',
404
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
405
+ options=self.config['options_stage2'])
406
+ # If Stage 2 fails, use Stage 1's result for next stage.
407
+ x_after_s2 = result_stage2.x if result_stage2.success else result_stage1.x
408
+
409
+ # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
410
+ result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
411
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
412
+ options=self.config['options_stage3'])
413
+
414
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
415
+ final_run_x = result_stage3.x if result_stage3.success else x_after_s2
416
+
417
+ _, current_radii = self.problem._unpack_vars(final_run_x)
418
+ current_sum_radii = np.sum(current_radii)
419
+
420
+ if current_sum_radii > best_sum_radii:
421
+ best_sum_radii = current_sum_radii
422
+ best_result_x = final_run_x
423
+
424
+ # Fallback if no run was successful, use initial guess from the first strategy with no perturbation.
425
+ if best_result_x is None:
426
+ # Fallback for N=26 using the grid_split_5x5 strategy with no perturbation
427
+ best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
428
+
429
+ final_centers, final_radii = self.problem._unpack_vars(best_result_x)
430
+ # Clean up potential floating point inaccuracies (e.g., small negative radii).
431
+ final_radii = np.maximum(final_radii, 0)
432
+
433
+ return final_centers, final_radii
434
+
435
+ def construct_packing():
436
+ """
437
+ Main function to construct an optimized packing of N=26 circles.
438
+ This uses a structurally redesigned program with clear separation of concerns
439
+ into Problem, InitialGuesser, and Optimizer classes. It incorporates
440
+ adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
441
+ with progressive solver tightness.
442
+ """
443
+ n_circles = 26
444
+
445
+ # 1. Initialize the problem definition
446
+ problem = CirclePackingProblem(n_circles)
447
+
448
+ # 2. Define optimizer configurations
449
+ optimizer_config = {
450
+ 'num_optimization_runs': 30, # Increased runs for more robust exploration
451
+ 'perturbation_std_dev_large': 0.03, # Broader initial exploration
452
+ 'perturbation_std_dev_medium': 0.01, # Mid-range exploration
453
+ 'perturbation_std_dev_small': 0.003, # Fine-tuning for local optima
454
+ 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
455
+ # Stage 2 uses slightly relaxed options than final stage
456
+ 'options_stage2': {'maxiter': 2500, 'ftol': 1e-10, 'gtol': 5e-7, 'disp': False},
457
+ # Stage 3 uses aggressive options for final refinement
458
+ 'options_stage3': {'maxiter': 5000, 'ftol': 1e-12, 'gtol': 1e-8, 'disp': False},
459
+ # Hybrid initial guess strategies distributed across runs
460
+ 'run_strategies': ['grid_split_5x5'] * 15 + ['uniform_grid'] * 15
461
+ }
462
+
463
+ # 3. Instantiate and run the optimizer
464
+ optimizer = PackingOptimizer(problem, optimizer_config)
465
+ final_centers, final_radii = optimizer.optimize()
466
+
467
+ return final_centers, final_radii
468
+ >>>>>>> REPLACE
469
+ </DIFF>
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/edit.diff ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- a/original.py
2
+ +++ b/original.py
3
+ @@ -1,194 +1,297 @@
4
+ # EVOLVE-BLOCK-START
5
+ import numpy as np
6
+ from scipy.optimize import minimize
7
+
8
+ -def construct_packing():
9
+ - """
10
+ - Constructs an optimized arrangement of 26 circles by synthesizing the most
11
+ - successful strategies from previous generations. This implementation employs a
12
+ - multi-start, three-stage optimization process designed for robust global
13
+ - exploration and high-precision local refinement.
14
+ -
15
+ - Key Features:
16
+ - 1. **Multi-Start Strategy:** Instead of refining a single solution, this runs
17
+ - many independent optimizations from different starting points, increasing
18
+ - the probability of finding the global optimum. A total of 32 runs are performed.
19
+ - 2. **Adaptive Perturbation:** A three-tier perturbation schedule is used. Initial
20
+ - runs use a large perturbation to explore diverse configurations, while later
21
+ - runs use smaller perturbations to refine promising regions of the solution space.
22
+ - 3. **Three-Stage NLP:** Each run consists of a three-stage optimization:
23
+ - - Stage 1: Maximize sum of areas (r^2) to achieve a dense initial packing.
24
+ - - Stage 2: Maximize sum of radii (r) with high precision.
25
+ - - Stage 3: Further maximize sum of radii (r) with extremely tight tolerances
26
+ - for final polishing.
27
+ - 4. **Numerical Stability:** Utilizes squared-distance constraints to avoid computationally
28
+ - expensive and potentially unstable sqrt operations, and a robust initial radius
29
+ - calculation to ensure a feasible starting point for the solver.
30
+ - """
31
+ - n = 26
32
+ -
33
+ - # --- Parameters ---
34
+ - # Perturbation schedule: [num_runs, std_dev]
35
+ - PERTURB_SCHEDULE = [
36
+ - (12, 0.030), # Broad exploration
37
+ - (12, 0.010), # Medium refinement
38
+ - (8, 0.004) # Fine-tuning
39
+ - ]
40
+ -
41
+ - # --- Helper Functions for Variable Packing/Unpacking ---
42
+ - def pack_vars(centers, radii):
43
+ - x = np.zeros(n * 3)
44
+ +class CirclePackingProblem:
45
+ + """
46
+ + Encapsulates the definition of the circle packing problem for N circles
47
+ + within a unit square. Defines objectives, constraints, and variable packing/unpacking.
48
+ + """
49
+ + def __init__(self, n_circles):
50
+ + self.n = n_circles
51
+ + self.bounds = self._define_bounds()
52
+ + self.constraints = self._define_constraints()
53
+ +
54
+ + def _define_bounds(self):
55
+ + """Define bounds for each variable: center_x, center_y, radius."""
56
+ + bounds = []
57
+ + MIN_RADIUS_BOUND = 1e-10 # Enforce a small positive minimum radius for numerical stability
58
+ + for _ in range(self.n):
59
+ + bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
60
+ + return bounds
61
+ +
62
+ + def _define_constraints(self):
63
+ + """Define non-overlap and boundary constraints."""
64
+ + cons = []
65
+ +
66
+ + # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
67
+ + # Using squared distances for numerical stability and avoiding sqrt in derivatives.
68
+ + def non_overlap_constraint(x_vars):
69
+ + centers, radii = self._unpack_vars(x_vars)
70
+ + i, j = np.triu_indices(self.n, k=1) # Get unique pairs of circles
71
+ + dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
72
+ + sum_radii_sq = (radii[i] + radii[j])**2
73
+ + return dist_sq - sum_radii_sq
74
+ +
75
+ + cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
76
+ +
77
+ + # Constraint 2: Circles must be within the [0,1] x [0,1] square.
78
+ + def boundary_constraint(x_vars):
79
+ + centers, radii = self._unpack_vars(x_vars)
80
+ + return np.concatenate([
81
+ + centers[:, 0] - radii, # x - r >= 0
82
+ + 1 - centers[:, 0] - radii, # 1 - x - r >= 0
83
+ + centers[:, 1] - radii, # y - r >= 0
84
+ + 1 - centers[:, 1] - radii # 1 - y - r >= 0
85
+ + ])
86
+ +
87
+ + cons.append({'type': 'ineq', 'fun': boundary_constraint})
88
+ + return cons
89
+ +
90
+ + def objective_area(self, x_vars):
91
+ + """Objective for Stage 1: Maximize sum of areas (r^2)."""
92
+ + _, radii = self._unpack_vars(x_vars)
93
+ + return -np.sum(radii**2)
94
+ +
95
+ + def objective_radii(self, x_vars):
96
+ + """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
97
+ + _, radii = self._unpack_vars(x_vars)
98
+ + return -np.sum(radii)
99
+ +
100
+ + def _pack_vars(self, centers, radii):
101
+ + """Converts structured centers/radii into a flat optimization vector."""
102
+ + x = np.zeros(self.n * 3)
103
+ x[0::3] = centers[:, 0]
104
+ x[1::3] = centers[:, 1]
105
+ x[2::3] = radii
106
+ return x
107
+
108
+ - def unpack_vars(x):
109
+ - centers = np.vstack((x[0::3], x[1::3])).T
110
+ - radii = x[2::3]
111
+ + def _unpack_vars(self, x_vars):
112
+ + """Converts a flat optimization vector into structured centers/radii."""
113
+ + centers_x = x_vars[0::3]
114
+ + centers_y = x_vars[1::3]
115
+ + radii = x_vars[2::3]
116
+ + centers = np.vstack((centers_x, centers_y)).T
117
+ return centers, radii
118
+
119
+ - # --- Initial Guess Generation ---
120
+ - def _compute_initial_radii(centers, max_iter=200):
121
+ - """
122
+ - Computes maximum non-overlapping radii for a fixed set of centers,
123
+ - providing a strong, feasible starting point. A small minimum gap is enforced.
124
+ - """
125
+ - num_circles = centers.shape[0]
126
+ - radii = np.zeros(num_circles)
127
+ - MIN_GAP = 1e-8 # Enforce a small gap for numerical robustness
128
+ -
129
+ - # Initialize radii based on distance to walls
130
+ - for i in range(num_circles):
131
+ + def compute_initial_radii(self, centers, max_iter=200):
132
+ + """
133
+ + Iteratively computes the maximum possible non-overlapping radii for a
134
+ + given fixed set of centers, ensuring a small minimum gap.
135
+ + """
136
+ + radii = np.zeros(self.n)
137
+ + # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
138
+ + # or more robust initial state for fewer circles.
139
+ + MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
140
+ + MIN_RADIUS_EPS = 1e-10 # Ensure radii are never exactly zero
141
+ +
142
+ + # Initialize radii based on the minimum distance to the walls.
143
+ + for i in range(self.n):
144
+ x, y = centers[i]
145
+ radii[i] = min(x, 1 - x, y, 1 - y)
146
+ -
147
+ - # Iteratively shrink radii to resolve overlaps
148
+ + radii = np.maximum(radii, MIN_RADIUS_EPS) # Ensure initial radii are positive
149
+ +
150
+ + # Iteratively shrink radii to resolve overlaps.
151
+ for _ in range(max_iter):
152
+ had_change = False
153
+ - for i in range(num_circles):
154
+ - for j in range(i + 1, num_circles):
155
+ + for i in range(self.n):
156
+ + for j in range(i + 1, self.n):
157
+ dist = np.linalg.norm(centers[i] - centers[j])
158
+ sum_r = radii[i] + radii[j]
159
+ - if sum_r > dist - MIN_GAP:
160
+ - target_sum_r = max(0.0, dist - MIN_GAP)
161
+ + if sum_r > dist - MIN_GAP_THRESHOLD:
162
+ + target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
163
+ if sum_r > 1e-12: # Avoid division by zero
164
+ scale = target_sum_r / sum_r
165
+ radii[i] *= scale
166
+ radii[j] *= scale
167
+ had_change = True
168
+ if not had_change:
169
+ break # Converged
170
+ - return radii
171
+ -
172
+ - # Base layout: A proven 5x5 grid with a split center.
173
+ - base_initial_centers = np.zeros((n, 2))
174
+ - idx = 0
175
+ - grid_points = np.linspace(0.1, 0.9, 5)
176
+ - for i in range(5):
177
+ - for j in range(5):
178
+ - if i == 2 and j == 2: continue # Skip center
179
+ - base_initial_centers[idx] = [grid_points[i], grid_points[j]]
180
+ - idx += 1
181
+ - base_initial_centers[24] = [0.5, 0.45]
182
+ - base_initial_centers[25] = [0.5, 0.55]
183
+ -
184
+ - # --- Staged Objective Functions ---
185
+ - def objective_area(x):
186
+ - """Stage 1: Maximize sum of areas (minimize -sum(r^2))."""
187
+ - _, radii = unpack_vars(x)
188
+ - return -np.sum(radii**2)
189
+ -
190
+ - def objective_radii(x):
191
+ - """Stage 2 & 3: Maximize sum of radii (minimize -sum(r))."""
192
+ - _, radii = unpack_vars(x)
193
+ - return -np.sum(radii)
194
+ -
195
+ - # --- Constraints (f(x) >= 0) ---
196
+ - def non_overlap_constraint(x):
197
+ - """Numerically stable non-overlap: dist_sq - (ri+rj)^2 >= 0."""
198
+ - centers, radii = unpack_vars(x)
199
+ - i, j = np.triu_indices(n, k=1)
200
+ - dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
201
+ - sum_radii_sq = (radii[i] + radii[j])**2
202
+ - return dist_sq - sum_radii_sq
203
+ -
204
+ - def boundary_constraint(x):
205
+ - """Circles must be within the [0,1]x[0,1] square."""
206
+ - centers, radii = unpack_vars(x)
207
+ - return np.concatenate([
208
+ - centers[:, 0] - radii, 1 - centers[:, 0] - radii,
209
+ - centers[:, 1] - radii, 1 - centers[:, 1] - radii
210
+ - ])
211
+ -
212
+ - cons = [
213
+ - {'type': 'ineq', 'fun': non_overlap_constraint},
214
+ - {'type': 'ineq', 'fun': boundary_constraint}
215
+ - ]
216
+ -
217
+ - # --- Variable Bounds ---
218
+ - bounds = []
219
+ - for _ in range(n):
220
+ - bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
221
+ -
222
+ - # --- Optimizer Settings for each Stage ---
223
+ - options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-7, 'disp': False}
224
+ - options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
225
+ - options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-9, 'disp': False}
226
+ -
227
+ - # --- Main Optimization Loop ---
228
+ - best_sum_radii = -np.inf
229
+ - best_result_x = None
230
+ -
231
+ - for num_runs_in_tier, perturb_std_dev in PERTURB_SCHEDULE:
232
+ - for _ in range(num_runs_in_tier):
233
+ - # 1. Create perturbed initial state
234
+ - perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std_dev, base_initial_centers.shape)
235
+ - perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
236
+ -
237
+ - x0_run = pack_vars(perturbed_centers, _compute_initial_radii(perturbed_centers))
238
+ -
239
+ - # 2. Run three-stage optimization
240
+ - # Stage 1: Maximize sum of areas
241
+ - res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
242
+ - x1 = res1.x if res1.success else x0_run
243
+ -
244
+ - # Stage 2: Maximize sum of radii
245
+ - res2 = minimize(objective_radii, x1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
246
+ - x2 = res2.x if res2.success else x1
247
+ -
248
+ - # Stage 3: Final polish with tightest tolerances
249
+ - res3 = minimize(objective_radii, x2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
250
+ -
251
+ - # 3. Check and update best result
252
+ - final_run_x = res3.x if res3.success else x2
253
+ - _, current_radii = unpack_vars(final_run_x)
254
+ - current_sum_radii = np.sum(current_radii)
255
+ -
256
+ - if current_sum_radii > best_sum_radii:
257
+ - best_sum_radii = current_sum_radii
258
+ - best_result_x = final_run_x
259
+ -
260
+ - # --- Final Result Extraction ---
261
+ - # Fallback if all runs fail (extremely unlikely)
262
+ - if best_result_x is None:
263
+ - x0_fallback = pack_vars(base_initial_centers, _compute_initial_radii(base_initial_centers))
264
+ - res_fallback = minimize(objective_radii, x0_fallback, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
265
+ - best_result_x = res_fallback.x
266
+ -
267
+ - final_centers, final_radii = unpack_vars(best_result_x)
268
+ -
269
+ - # Final cleanup to ensure no negative radii due to floating point inaccuracies
270
+ - final_radii = np.maximum(final_radii, 0)
271
+ + return np.maximum(radii, MIN_RADIUS_EPS) # Final check for minimum radius
272
+ +
273
+ +class InitialGuesser:
274
+ + """
275
+ + Generates diverse initial configurations of circle centers for the NLP solver.
276
+ + """
277
+ + def __init__(self, n_circles, problem_instance):
278
+ + self.n = n_circles
279
+ + self.problem = problem_instance
280
+ +
281
+ + def _get_grid_split_5x5_centers(self):
282
+ + """Returns the proven 5x5 grid with a split center, a strong start for N=26."""
283
+ + centers = np.zeros((self.n, 2))
284
+ + idx = 0
285
+ + grid_points = np.linspace(0.1, 0.9, 5) # Use linspace for cleaner grid generation
286
+ + for i in range(5):
287
+ + for j in range(5):
288
+ + if i == 2 and j == 2: continue # Skip center
289
+ + centers[idx] = [grid_points[i], grid_points[j]]
290
+ + idx += 1
291
+ + # For n=26, the remaining two circles are placed centrally.
292
+ + # This setup assumes n >= 26 for these specific placements.
293
+ + if self.n >= 25: # At least 25 circles for the grid_split 5x5
294
+ + if idx < self.n:
295
+ + centers[idx] = [0.5, 0.45] # First central circle
296
+ + idx += 1
297
+ + if idx < self.n:
298
+ + centers[idx] = [0.5, 0.55] # Second central circle
299
+ + idx += 1
300
+ +
301
+ + return centers[:self.n] # Return exactly N circles
302
+ +
303
+ + def _get_uniform_grid_centers(self):
304
+ + """
305
+ + Generates centers in a more uniform grid pattern for N circles.
306
+ + Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
307
+ + """
308
+ + side_len = int(np.ceil(np.sqrt(self.n)))
309
+ + spacing = 1.0 / (side_len + 1)
310
+ + centers = []
311
+ + for i in range(side_len):
312
+ + for j in range(side_len):
313
+ + if len(centers) < self.n:
314
+ + centers.append([spacing * (i + 1), spacing * (j + 1)])
315
+ +
316
+ + # Fill remaining positions at or near the center to ensure N circles.
317
+ + while len(centers) < self.n:
318
+ + offset_x = np.random.uniform(-0.05, 0.05)
319
+ + offset_y = np.random.uniform(-0.05, 0.05)
320
+ + centers.append(np.clip([0.5 + offset_x, 0.5 + offset_y], 0.05, 0.95)) # Clip to avoid very edge
321
+ +
322
+ + return np.array(centers[:self.n]) # Ensure exactly N circles
323
+ +
324
+ + def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
325
+ + """
326
+ + Generates an initial optimization vector `x0` based on the specified strategy
327
+ + and applies perturbation.
328
+ + """
329
+ + if strategy == 'grid_split_5x5':
330
+ + base_centers = self._get_grid_split_5x5_centers()
331
+ + elif strategy == 'uniform_grid':
332
+ + base_centers = self._get_uniform_grid_centers()
333
+ + else:
334
+ + raise ValueError(f"Unknown initial guess strategy: {strategy}")
335
+ +
336
+ + # Apply perturbation
337
+ + perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
338
+ + perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
339
+ +
340
+ + # Compute initial radii for the perturbed centers
341
+ + perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
342
+ +
343
+ + return self.problem._pack_vars(perturbed_centers, perturbed_radii)
344
+ +
345
+ +
346
+ +class PackingOptimizer:
347
+ + """
348
+ + Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
349
+ + """
350
+ + def __init__(self, problem_instance, optimizer_config):
351
+ + self.problem = problem_instance
352
+ + self.config = optimizer_config
353
+ + self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
354
+ +
355
+ + def optimize(self):
356
+ + """Runs the optimization process and returns the best result found."""
357
+ + best_sum_radii = -np.inf
358
+ + best_result_x = None
359
+ +
360
+ + total_runs = sum(tier[0] for tier in self.config['perturb_schedule'])
361
+ + run_strategies = self.config['run_strategies']
362
+ +
363
+ + run_count = 0
364
+ + for num_runs_in_tier, perturb_std_dev in self.config['perturb_schedule']:
365
+ + for _ in range(num_runs_in_tier):
366
+ + # Select initial guess strategy for this run. Cycle through strategies.
367
+ + strategy_for_run = run_strategies[run_count % len(run_strategies)]
368
+ + x0_run = self.initial_guesser.generate_initial_x0(
369
+ + strategy=strategy_for_run, perturbation_std_dev=perturb_std_dev
370
+ + )
371
+ +
372
+ + # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
373
+ + result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
374
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
375
+ + options=self.config['options_stage1'])
376
+ + x_after_s1 = result_stage1.x if result_stage1.success else x0_run
377
+ +
378
+ + # Stage 2: Maximize sum of radii (r) with moderately tight options
379
+ + result_stage2 = minimize(self.problem.objective_radii, x_after_s1, method='SLSQP',
380
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
381
+ + options=self.config['options_stage2'])
382
+ + x_after_s2 = result_stage2.x if result_stage2.success else x_after_s1
383
+ +
384
+ + # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
385
+ + result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
386
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
387
+ + options=self.config['options_stage3'])
388
+ +
389
+ + # Use the result from the last successful stage. Prioritize Stage 3 if successful.
390
+ + final_run_x = result_stage3.x if result_stage3.success else x_after_s2
391
+ +
392
+ + _, current_radii = self.problem._unpack_vars(final_run_x)
393
+ + current_sum_radii = np.sum(current_radii)
394
+ +
395
+ + if current_sum_radii > best_sum_radii:
396
+ + best_sum_radii = current_sum_radii
397
+ + best_result_x = final_run_x
398
+ +
399
+ + run_count += 1 # Increment run counter
400
+ +
401
+ + # Fallback if no run was successful (highly unlikely), use a non-perturbed initial configuration.
402
+ + if best_result_x is None:
403
+ + best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
404
+ + # Run one full optimization on this fallback to get a reasonable result
405
+ + res_fallback = minimize(self.problem.objective_radii, best_result_x, method='SLSQP',
406
+ + bounds=self.problem.bounds, constraints=self.problem.constraints,
407
+ + options=self.config['options_stage3'])
408
+ + best_result_x = res_fallback.x if res_fallback.success else best_result_x
409
+ +
410
+ +
411
+ + final_centers, final_radii = self.problem._unpack_vars(best_result_x)
412
+ + final_radii = np.maximum(final_radii, 0) # Clean up potential floating point inaccuracies (e.g., small negative radii).
413
+ +
414
+ + return final_centers, final_radii
415
+ +
416
+ +def construct_packing():
417
+ + """
418
+ + Main function to construct an optimized packing of N=26 circles.
419
+ + This uses a structurally redesigned program with clear separation of concerns
420
+ + into Problem, InitialGuesser, and Optimizer classes. It incorporates
421
+ + adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
422
+ + with progressive solver tightness.
423
+ + """
424
+ + n_circles = 26
425
+ +
426
+ + # 1. Initialize the problem definition
427
+ + problem = CirclePackingProblem(n_circles)
428
+ +
429
+ + # 2. Define optimizer configurations
430
+ + optimizer_config = {
431
+ + # Perturbation schedule: [(num_runs_in_tier, std_dev)]
432
+ + 'perturb_schedule': [
433
+ + (12, 0.030), # Broad exploration
434
+ + (12, 0.010), # Medium refinement
435
+ + (8, 0.004) # Fine-tuning
436
+ + ],
437
+ + 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
438
+ + 'options_stage2': {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False},
439
+ + 'options_stage3': {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False},
440
+ + # Hybrid initial guess strategies distributed across runs
441
+ + 'run_strategies': ['grid_split_5x5', 'uniform_grid']
442
+ + }
443
+ +
444
+ + # 3. Instantiate and run the optimizer
445
+ + optimizer = PackingOptimizer(problem, optimizer_config)
446
+ + final_centers, final_radii = optimizer.optimize()
447
+
448
+ return final_centers, final_radii
449
+ # EVOLVE-BLOCK-END
450
+
451
+
452
+ # This part remains fixed (not evolved)
453
+ def run_packing():
454
+ """Run the circle packing constructor for n=26"""
455
+ centers, radii = construct_packing()
456
+ # Calculate the sum of radii
457
+ sum_radii = np.sum(radii)
458
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/main.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ class CirclePackingProblem:
6
+ """
7
+ Encapsulates the definition of the circle packing problem for N circles
8
+ within a unit square. Defines objectives, constraints, and variable packing/unpacking.
9
+ """
10
+ def __init__(self, n_circles):
11
+ self.n = n_circles
12
+ self.bounds = self._define_bounds()
13
+ self.constraints = self._define_constraints()
14
+
15
+ def _define_bounds(self):
16
+ """Define bounds for each variable: center_x, center_y, radius."""
17
+ bounds = []
18
+ MIN_RADIUS_BOUND = 1e-10 # Enforce a small positive minimum radius for numerical stability
19
+ for _ in range(self.n):
20
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
21
+ return bounds
22
+
23
+ def _define_constraints(self):
24
+ """Define non-overlap and boundary constraints."""
25
+ cons = []
26
+
27
+ # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
28
+ # Using squared distances for numerical stability and avoiding sqrt in derivatives.
29
+ def non_overlap_constraint(x_vars):
30
+ centers, radii = self._unpack_vars(x_vars)
31
+ i, j = np.triu_indices(self.n, k=1) # Get unique pairs of circles
32
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
33
+ sum_radii_sq = (radii[i] + radii[j])**2
34
+ return dist_sq - sum_radii_sq
35
+
36
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
37
+
38
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
39
+ def boundary_constraint(x_vars):
40
+ centers, radii = self._unpack_vars(x_vars)
41
+ return np.concatenate([
42
+ centers[:, 0] - radii, # x - r >= 0
43
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
44
+ centers[:, 1] - radii, # y - r >= 0
45
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
46
+ ])
47
+
48
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
49
+ return cons
50
+
51
+ def objective_area(self, x_vars):
52
+ """Objective for Stage 1: Maximize sum of areas (r^2)."""
53
+ _, radii = self._unpack_vars(x_vars)
54
+ return -np.sum(radii**2)
55
+
56
+ def objective_radii(self, x_vars):
57
+ """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
58
+ _, radii = self._unpack_vars(x_vars)
59
+ return -np.sum(radii)
60
+
61
+ def _pack_vars(self, centers, radii):
62
+ """Converts structured centers/radii into a flat optimization vector."""
63
+ x = np.zeros(self.n * 3)
64
+ x[0::3] = centers[:, 0]
65
+ x[1::3] = centers[:, 1]
66
+ x[2::3] = radii
67
+ return x
68
+
69
+ def _unpack_vars(self, x_vars):
70
+ """Converts a flat optimization vector into structured centers/radii."""
71
+ centers_x = x_vars[0::3]
72
+ centers_y = x_vars[1::3]
73
+ radii = x_vars[2::3]
74
+ centers = np.vstack((centers_x, centers_y)).T
75
+ return centers, radii
76
+
77
+ def compute_initial_radii(self, centers, max_iter=200):
78
+ """
79
+ Iteratively computes the maximum possible non-overlapping radii for a
80
+ given fixed set of centers, ensuring a small minimum gap.
81
+ """
82
+ radii = np.zeros(self.n)
83
+ # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
84
+ # or more robust initial state for fewer circles.
85
+ MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
86
+ MIN_RADIUS_EPS = 1e-10 # Ensure radii are never exactly zero
87
+
88
+ # Initialize radii based on the minimum distance to the walls.
89
+ for i in range(self.n):
90
+ x, y = centers[i]
91
+ radii[i] = min(x, 1 - x, y, 1 - y)
92
+ radii = np.maximum(radii, MIN_RADIUS_EPS) # Ensure initial radii are positive
93
+
94
+ # Iteratively shrink radii to resolve overlaps.
95
+ for _ in range(max_iter):
96
+ had_change = False
97
+ for i in range(self.n):
98
+ for j in range(i + 1, self.n):
99
+ dist = np.linalg.norm(centers[i] - centers[j])
100
+ sum_r = radii[i] + radii[j]
101
+ if sum_r > dist - MIN_GAP_THRESHOLD:
102
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
103
+ if sum_r > 1e-12: # Avoid division by zero
104
+ scale = target_sum_r / sum_r
105
+ radii[i] *= scale
106
+ radii[j] *= scale
107
+ had_change = True
108
+ if not had_change:
109
+ break # Converged
110
+ return np.maximum(radii, MIN_RADIUS_EPS) # Final check for minimum radius
111
+
112
+ class InitialGuesser:
113
+ """
114
+ Generates diverse initial configurations of circle centers for the NLP solver.
115
+ """
116
+ def __init__(self, n_circles, problem_instance):
117
+ self.n = n_circles
118
+ self.problem = problem_instance
119
+
120
+ def _get_grid_split_5x5_centers(self):
121
+ """Returns the proven 5x5 grid with a split center, a strong start for N=26."""
122
+ centers = np.zeros((self.n, 2))
123
+ idx = 0
124
+ grid_points = np.linspace(0.1, 0.9, 5) # Use linspace for cleaner grid generation
125
+ for i in range(5):
126
+ for j in range(5):
127
+ if i == 2 and j == 2: continue # Skip center
128
+ centers[idx] = [grid_points[i], grid_points[j]]
129
+ idx += 1
130
+ # For n=26, the remaining two circles are placed centrally.
131
+ # This setup assumes n >= 26 for these specific placements.
132
+ if self.n >= 25: # At least 25 circles for the grid_split 5x5
133
+ if idx < self.n:
134
+ centers[idx] = [0.5, 0.45] # First central circle
135
+ idx += 1
136
+ if idx < self.n:
137
+ centers[idx] = [0.5, 0.55] # Second central circle
138
+ idx += 1
139
+
140
+ return centers[:self.n] # Return exactly N circles
141
+
142
+ def _get_uniform_grid_centers(self):
143
+ """
144
+ Generates centers in a more uniform grid pattern for N circles.
145
+ Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
146
+ """
147
+ side_len = int(np.ceil(np.sqrt(self.n)))
148
+ spacing = 1.0 / (side_len + 1)
149
+ centers = []
150
+ for i in range(side_len):
151
+ for j in range(side_len):
152
+ if len(centers) < self.n:
153
+ centers.append([spacing * (i + 1), spacing * (j + 1)])
154
+
155
+ # Fill remaining positions at or near the center to ensure N circles.
156
+ while len(centers) < self.n:
157
+ offset_x = np.random.uniform(-0.05, 0.05)
158
+ offset_y = np.random.uniform(-0.05, 0.05)
159
+ centers.append(np.clip([0.5 + offset_x, 0.5 + offset_y], 0.05, 0.95)) # Clip to avoid very edge
160
+
161
+ return np.array(centers[:self.n]) # Ensure exactly N circles
162
+
163
+ def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
164
+ """
165
+ Generates an initial optimization vector `x0` based on the specified strategy
166
+ and applies perturbation.
167
+ """
168
+ if strategy == 'grid_split_5x5':
169
+ base_centers = self._get_grid_split_5x5_centers()
170
+ elif strategy == 'uniform_grid':
171
+ base_centers = self._get_uniform_grid_centers()
172
+ else:
173
+ raise ValueError(f"Unknown initial guess strategy: {strategy}")
174
+
175
+ # Apply perturbation
176
+ perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
177
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
178
+
179
+ # Compute initial radii for the perturbed centers
180
+ perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
181
+
182
+ return self.problem._pack_vars(perturbed_centers, perturbed_radii)
183
+
184
+
185
+ class PackingOptimizer:
186
+ """
187
+ Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
188
+ """
189
+ def __init__(self, problem_instance, optimizer_config):
190
+ self.problem = problem_instance
191
+ self.config = optimizer_config
192
+ self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
193
+
194
+ def optimize(self):
195
+ """Runs the optimization process and returns the best result found."""
196
+ best_sum_radii = -np.inf
197
+ best_result_x = None
198
+
199
+ total_runs = sum(tier[0] for tier in self.config['perturb_schedule'])
200
+ run_strategies = self.config['run_strategies']
201
+
202
+ run_count = 0
203
+ for num_runs_in_tier, perturb_std_dev in self.config['perturb_schedule']:
204
+ for _ in range(num_runs_in_tier):
205
+ # Select initial guess strategy for this run. Cycle through strategies.
206
+ strategy_for_run = run_strategies[run_count % len(run_strategies)]
207
+ x0_run = self.initial_guesser.generate_initial_x0(
208
+ strategy=strategy_for_run, perturbation_std_dev=perturb_std_dev
209
+ )
210
+
211
+ # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
212
+ result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
213
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
214
+ options=self.config['options_stage1'])
215
+ x_after_s1 = result_stage1.x if result_stage1.success else x0_run
216
+
217
+ # Stage 2: Maximize sum of radii (r) with moderately tight options
218
+ result_stage2 = minimize(self.problem.objective_radii, x_after_s1, method='SLSQP',
219
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
220
+ options=self.config['options_stage2'])
221
+ x_after_s2 = result_stage2.x if result_stage2.success else x_after_s1
222
+
223
+ # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
224
+ result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
225
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
226
+ options=self.config['options_stage3'])
227
+
228
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
229
+ final_run_x = result_stage3.x if result_stage3.success else x_after_s2
230
+
231
+ _, current_radii = self.problem._unpack_vars(final_run_x)
232
+ current_sum_radii = np.sum(current_radii)
233
+
234
+ if current_sum_radii > best_sum_radii:
235
+ best_sum_radii = current_sum_radii
236
+ best_result_x = final_run_x
237
+
238
+ run_count += 1 # Increment run counter
239
+
240
+ # Fallback if no run was successful (highly unlikely), use a non-perturbed initial configuration.
241
+ if best_result_x is None:
242
+ best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
243
+ # Run one full optimization on this fallback to get a reasonable result
244
+ res_fallback = minimize(self.problem.objective_radii, best_result_x, method='SLSQP',
245
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
246
+ options=self.config['options_stage3'])
247
+ best_result_x = res_fallback.x if res_fallback.success else best_result_x
248
+
249
+
250
+ final_centers, final_radii = self.problem._unpack_vars(best_result_x)
251
+ final_radii = np.maximum(final_radii, 0) # Clean up potential floating point inaccuracies (e.g., small negative radii).
252
+
253
+ return final_centers, final_radii
254
+
255
+ def construct_packing():
256
+ """
257
+ Main function to construct an optimized packing of N=26 circles.
258
+ This uses a structurally redesigned program with clear separation of concerns
259
+ into Problem, InitialGuesser, and Optimizer classes. It incorporates
260
+ adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
261
+ with progressive solver tightness.
262
+ """
263
+ n_circles = 26
264
+
265
+ # 1. Initialize the problem definition
266
+ problem = CirclePackingProblem(n_circles)
267
+
268
+ # 2. Define optimizer configurations
269
+ optimizer_config = {
270
+ # Perturbation schedule: [(num_runs_in_tier, std_dev)]
271
+ 'perturb_schedule': [
272
+ (12, 0.030), # Broad exploration
273
+ (12, 0.010), # Medium refinement
274
+ (8, 0.004) # Fine-tuning
275
+ ],
276
+ 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
277
+ 'options_stage2': {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False},
278
+ 'options_stage3': {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False},
279
+ # Hybrid initial guess strategies distributed across runs
280
+ 'run_strategies': ['grid_split_5x5', 'uniform_grid']
281
+ }
282
+
283
+ # 3. Instantiate and run the optimizer
284
+ optimizer = PackingOptimizer(problem, optimizer_config)
285
+ final_centers, final_radii = optimizer.optimize()
286
+
287
+ return final_centers, final_radii
288
+ # EVOLVE-BLOCK-END
289
+
290
+
291
+ # This part remains fixed (not evolved)
292
+ def run_packing():
293
+ """Run the circle packing constructor for n=26"""
294
+ centers, radii = construct_packing()
295
+ # Calculate the sum of radii
296
+ sum_radii = np.sum(radii)
297
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/original.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ def construct_packing():
6
+ """
7
+ Constructs an optimized arrangement of 26 circles by synthesizing the most
8
+ successful strategies from previous generations. This implementation employs a
9
+ multi-start, three-stage optimization process designed for robust global
10
+ exploration and high-precision local refinement.
11
+
12
+ Key Features:
13
+ 1. **Multi-Start Strategy:** Instead of refining a single solution, this runs
14
+ many independent optimizations from different starting points, increasing
15
+ the probability of finding the global optimum. A total of 32 runs are performed.
16
+ 2. **Adaptive Perturbation:** A three-tier perturbation schedule is used. Initial
17
+ runs use a large perturbation to explore diverse configurations, while later
18
+ runs use smaller perturbations to refine promising regions of the solution space.
19
+ 3. **Three-Stage NLP:** Each run consists of a three-stage optimization:
20
+ - Stage 1: Maximize sum of areas (r^2) to achieve a dense initial packing.
21
+ - Stage 2: Maximize sum of radii (r) with high precision.
22
+ - Stage 3: Further maximize sum of radii (r) with extremely tight tolerances
23
+ for final polishing.
24
+ 4. **Numerical Stability:** Utilizes squared-distance constraints to avoid computationally
25
+ expensive and potentially unstable sqrt operations, and a robust initial radius
26
+ calculation to ensure a feasible starting point for the solver.
27
+ """
28
+ n = 26
29
+
30
+ # --- Parameters ---
31
+ # Perturbation schedule: [num_runs, std_dev]
32
+ PERTURB_SCHEDULE = [
33
+ (12, 0.030), # Broad exploration
34
+ (12, 0.010), # Medium refinement
35
+ (8, 0.004) # Fine-tuning
36
+ ]
37
+
38
+ # --- Helper Functions for Variable Packing/Unpacking ---
39
+ def pack_vars(centers, radii):
40
+ x = np.zeros(n * 3)
41
+ x[0::3] = centers[:, 0]
42
+ x[1::3] = centers[:, 1]
43
+ x[2::3] = radii
44
+ return x
45
+
46
+ def unpack_vars(x):
47
+ centers = np.vstack((x[0::3], x[1::3])).T
48
+ radii = x[2::3]
49
+ return centers, radii
50
+
51
+ # --- Initial Guess Generation ---
52
+ def _compute_initial_radii(centers, max_iter=200):
53
+ """
54
+ Computes maximum non-overlapping radii for a fixed set of centers,
55
+ providing a strong, feasible starting point. A small minimum gap is enforced.
56
+ """
57
+ num_circles = centers.shape[0]
58
+ radii = np.zeros(num_circles)
59
+ MIN_GAP = 1e-8 # Enforce a small gap for numerical robustness
60
+
61
+ # Initialize radii based on distance to walls
62
+ for i in range(num_circles):
63
+ x, y = centers[i]
64
+ radii[i] = min(x, 1 - x, y, 1 - y)
65
+
66
+ # Iteratively shrink radii to resolve overlaps
67
+ for _ in range(max_iter):
68
+ had_change = False
69
+ for i in range(num_circles):
70
+ for j in range(i + 1, num_circles):
71
+ dist = np.linalg.norm(centers[i] - centers[j])
72
+ sum_r = radii[i] + radii[j]
73
+ if sum_r > dist - MIN_GAP:
74
+ target_sum_r = max(0.0, dist - MIN_GAP)
75
+ if sum_r > 1e-12: # Avoid division by zero
76
+ scale = target_sum_r / sum_r
77
+ radii[i] *= scale
78
+ radii[j] *= scale
79
+ had_change = True
80
+ if not had_change:
81
+ break # Converged
82
+ return radii
83
+
84
+ # Base layout: A proven 5x5 grid with a split center.
85
+ base_initial_centers = np.zeros((n, 2))
86
+ idx = 0
87
+ grid_points = np.linspace(0.1, 0.9, 5)
88
+ for i in range(5):
89
+ for j in range(5):
90
+ if i == 2 and j == 2: continue # Skip center
91
+ base_initial_centers[idx] = [grid_points[i], grid_points[j]]
92
+ idx += 1
93
+ base_initial_centers[24] = [0.5, 0.45]
94
+ base_initial_centers[25] = [0.5, 0.55]
95
+
96
+ # --- Staged Objective Functions ---
97
+ def objective_area(x):
98
+ """Stage 1: Maximize sum of areas (minimize -sum(r^2))."""
99
+ _, radii = unpack_vars(x)
100
+ return -np.sum(radii**2)
101
+
102
+ def objective_radii(x):
103
+ """Stage 2 & 3: Maximize sum of radii (minimize -sum(r))."""
104
+ _, radii = unpack_vars(x)
105
+ return -np.sum(radii)
106
+
107
+ # --- Constraints (f(x) >= 0) ---
108
+ def non_overlap_constraint(x):
109
+ """Numerically stable non-overlap: dist_sq - (ri+rj)^2 >= 0."""
110
+ centers, radii = unpack_vars(x)
111
+ i, j = np.triu_indices(n, k=1)
112
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
113
+ sum_radii_sq = (radii[i] + radii[j])**2
114
+ return dist_sq - sum_radii_sq
115
+
116
+ def boundary_constraint(x):
117
+ """Circles must be within the [0,1]x[0,1] square."""
118
+ centers, radii = unpack_vars(x)
119
+ return np.concatenate([
120
+ centers[:, 0] - radii, 1 - centers[:, 0] - radii,
121
+ centers[:, 1] - radii, 1 - centers[:, 1] - radii
122
+ ])
123
+
124
+ cons = [
125
+ {'type': 'ineq', 'fun': non_overlap_constraint},
126
+ {'type': 'ineq', 'fun': boundary_constraint}
127
+ ]
128
+
129
+ # --- Variable Bounds ---
130
+ bounds = []
131
+ for _ in range(n):
132
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (0.0, 0.5)])
133
+
134
+ # --- Optimizer Settings for each Stage ---
135
+ options_stage1 = {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-7, 'disp': False}
136
+ options_stage2 = {'maxiter': 2500, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False}
137
+ options_stage3 = {'maxiter': 5000, 'ftol': 1e-13, 'gtol': 1e-9, 'disp': False}
138
+
139
+ # --- Main Optimization Loop ---
140
+ best_sum_radii = -np.inf
141
+ best_result_x = None
142
+
143
+ for num_runs_in_tier, perturb_std_dev in PERTURB_SCHEDULE:
144
+ for _ in range(num_runs_in_tier):
145
+ # 1. Create perturbed initial state
146
+ perturbed_centers = base_initial_centers + np.random.normal(0, perturb_std_dev, base_initial_centers.shape)
147
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0)
148
+
149
+ x0_run = pack_vars(perturbed_centers, _compute_initial_radii(perturbed_centers))
150
+
151
+ # 2. Run three-stage optimization
152
+ # Stage 1: Maximize sum of areas
153
+ res1 = minimize(objective_area, x0_run, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage1)
154
+ x1 = res1.x if res1.success else x0_run
155
+
156
+ # Stage 2: Maximize sum of radii
157
+ res2 = minimize(objective_radii, x1, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage2)
158
+ x2 = res2.x if res2.success else x1
159
+
160
+ # Stage 3: Final polish with tightest tolerances
161
+ res3 = minimize(objective_radii, x2, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
162
+
163
+ # 3. Check and update best result
164
+ final_run_x = res3.x if res3.success else x2
165
+ _, current_radii = unpack_vars(final_run_x)
166
+ current_sum_radii = np.sum(current_radii)
167
+
168
+ if current_sum_radii > best_sum_radii:
169
+ best_sum_radii = current_sum_radii
170
+ best_result_x = final_run_x
171
+
172
+ # --- Final Result Extraction ---
173
+ # Fallback if all runs fail (extremely unlikely)
174
+ if best_result_x is None:
175
+ x0_fallback = pack_vars(base_initial_centers, _compute_initial_radii(base_initial_centers))
176
+ res_fallback = minimize(objective_radii, x0_fallback, method='SLSQP', bounds=bounds, constraints=cons, options=options_stage3)
177
+ best_result_x = res_fallback.x
178
+
179
+ final_centers, final_radii = unpack_vars(best_result_x)
180
+
181
+ # Final cleanup to ensure no negative radii due to floating point inaccuracies
182
+ final_radii = np.maximum(final_radii, 0)
183
+
184
+ return final_centers, final_radii
185
+ # EVOLVE-BLOCK-END
186
+
187
+
188
+ # This part remains fixed (not evolved)
189
+ def run_packing():
190
+ """Run the circle packing constructor for n=26"""
191
+ centers, radii = construct_packing()
192
+ # Calculate the sum of radii
193
+ sum_radii = np.sum(radii)
194
+ return centers, radii, sum_radii
examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/rewrite.txt ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EVOLVE-BLOCK-START
2
+ import numpy as np
3
+ from scipy.optimize import minimize
4
+
5
+ class CirclePackingProblem:
6
+ """
7
+ Encapsulates the definition of the circle packing problem for N circles
8
+ within a unit square. Defines objectives, constraints, and variable packing/unpacking.
9
+ """
10
+ def __init__(self, n_circles):
11
+ self.n = n_circles
12
+ self.bounds = self._define_bounds()
13
+ self.constraints = self._define_constraints()
14
+
15
+ def _define_bounds(self):
16
+ """Define bounds for each variable: center_x, center_y, radius."""
17
+ bounds = []
18
+ MIN_RADIUS_BOUND = 1e-10 # Enforce a small positive minimum radius for numerical stability
19
+ for _ in range(self.n):
20
+ bounds.extend([(0.0, 1.0), (0.0, 1.0), (MIN_RADIUS_BOUND, 0.5)])
21
+ return bounds
22
+
23
+ def _define_constraints(self):
24
+ """Define non-overlap and boundary constraints."""
25
+ cons = []
26
+
27
+ # Constraint 1: Non-overlapping circles: dist_sq - (ri + rj)^2 >= 0
28
+ # Using squared distances for numerical stability and avoiding sqrt in derivatives.
29
+ def non_overlap_constraint(x_vars):
30
+ centers, radii = self._unpack_vars(x_vars)
31
+ i, j = np.triu_indices(self.n, k=1) # Get unique pairs of circles
32
+ dist_sq = np.sum((centers[i] - centers[j])**2, axis=1)
33
+ sum_radii_sq = (radii[i] + radii[j])**2
34
+ return dist_sq - sum_radii_sq
35
+
36
+ cons.append({'type': 'ineq', 'fun': non_overlap_constraint})
37
+
38
+ # Constraint 2: Circles must be within the [0,1] x [0,1] square.
39
+ def boundary_constraint(x_vars):
40
+ centers, radii = self._unpack_vars(x_vars)
41
+ return np.concatenate([
42
+ centers[:, 0] - radii, # x - r >= 0
43
+ 1 - centers[:, 0] - radii, # 1 - x - r >= 0
44
+ centers[:, 1] - radii, # y - r >= 0
45
+ 1 - centers[:, 1] - radii # 1 - y - r >= 0
46
+ ])
47
+
48
+ cons.append({'type': 'ineq', 'fun': boundary_constraint})
49
+ return cons
50
+
51
+ def objective_area(self, x_vars):
52
+ """Objective for Stage 1: Maximize sum of areas (r^2)."""
53
+ _, radii = self._unpack_vars(x_vars)
54
+ return -np.sum(radii**2)
55
+
56
+ def objective_radii(self, x_vars):
57
+ """Objective for Stages 2 & 3: Maximize sum of radii (r)."""
58
+ _, radii = self._unpack_vars(x_vars)
59
+ return -np.sum(radii)
60
+
61
+ def _pack_vars(self, centers, radii):
62
+ """Converts structured centers/radii into a flat optimization vector."""
63
+ x = np.zeros(self.n * 3)
64
+ x[0::3] = centers[:, 0]
65
+ x[1::3] = centers[:, 1]
66
+ x[2::3] = radii
67
+ return x
68
+
69
+ def _unpack_vars(self, x_vars):
70
+ """Converts a flat optimization vector into structured centers/radii."""
71
+ centers_x = x_vars[0::3]
72
+ centers_y = x_vars[1::3]
73
+ radii = x_vars[2::3]
74
+ centers = np.vstack((centers_x, centers_y)).T
75
+ return centers, radii
76
+
77
+ def compute_initial_radii(self, centers, max_iter=200):
78
+ """
79
+ Iteratively computes the maximum possible non-overlapping radii for a
80
+ given fixed set of centers, ensuring a small minimum gap.
81
+ """
82
+ radii = np.zeros(self.n)
83
+ # Dynamic MIN_GAP: scaled by N to allow for tighter packing with more circles,
84
+ # or more robust initial state for fewer circles.
85
+ MIN_GAP_THRESHOLD = 1e-7 / np.sqrt(self.n)
86
+ MIN_RADIUS_EPS = 1e-10 # Ensure radii are never exactly zero
87
+
88
+ # Initialize radii based on the minimum distance to the walls.
89
+ for i in range(self.n):
90
+ x, y = centers[i]
91
+ radii[i] = min(x, 1 - x, y, 1 - y)
92
+ radii = np.maximum(radii, MIN_RADIUS_EPS) # Ensure initial radii are positive
93
+
94
+ # Iteratively shrink radii to resolve overlaps.
95
+ for _ in range(max_iter):
96
+ had_change = False
97
+ for i in range(self.n):
98
+ for j in range(i + 1, self.n):
99
+ dist = np.linalg.norm(centers[i] - centers[j])
100
+ sum_r = radii[i] + radii[j]
101
+ if sum_r > dist - MIN_GAP_THRESHOLD:
102
+ target_sum_r = max(0.0, dist - MIN_GAP_THRESHOLD)
103
+ if sum_r > 1e-12: # Avoid division by zero
104
+ scale = target_sum_r / sum_r
105
+ radii[i] *= scale
106
+ radii[j] *= scale
107
+ had_change = True
108
+ if not had_change:
109
+ break # Converged
110
+ return np.maximum(radii, MIN_RADIUS_EPS) # Final check for minimum radius
111
+
112
+ class InitialGuesser:
113
+ """
114
+ Generates diverse initial configurations of circle centers for the NLP solver.
115
+ """
116
+ def __init__(self, n_circles, problem_instance):
117
+ self.n = n_circles
118
+ self.problem = problem_instance
119
+
120
+ def _get_grid_split_5x5_centers(self):
121
+ """Returns the proven 5x5 grid with a split center, a strong start for N=26."""
122
+ centers = np.zeros((self.n, 2))
123
+ idx = 0
124
+ grid_points = np.linspace(0.1, 0.9, 5) # Use linspace for cleaner grid generation
125
+ for i in range(5):
126
+ for j in range(5):
127
+ if i == 2 and j == 2: continue # Skip center
128
+ centers[idx] = [grid_points[i], grid_points[j]]
129
+ idx += 1
130
+ # For n=26, the remaining two circles are placed centrally.
131
+ # This setup assumes n >= 26 for these specific placements.
132
+ if self.n >= 25: # At least 25 circles for the grid_split 5x5
133
+ if idx < self.n:
134
+ centers[idx] = [0.5, 0.45] # First central circle
135
+ idx += 1
136
+ if idx < self.n:
137
+ centers[idx] = [0.5, 0.55] # Second central circle
138
+ idx += 1
139
+
140
+ return centers[:self.n] # Return exactly N circles
141
+
142
+ def _get_uniform_grid_centers(self):
143
+ """
144
+ Generates centers in a more uniform grid pattern for N circles.
145
+ Tries to fill a `sqrt(N) x sqrt(N)` grid and places remaining circles centrally.
146
+ """
147
+ side_len = int(np.ceil(np.sqrt(self.n)))
148
+ spacing = 1.0 / (side_len + 1)
149
+ centers = []
150
+ for i in range(side_len):
151
+ for j in range(side_len):
152
+ if len(centers) < self.n:
153
+ centers.append([spacing * (i + 1), spacing * (j + 1)])
154
+
155
+ # Fill remaining positions at or near the center to ensure N circles.
156
+ while len(centers) < self.n:
157
+ offset_x = np.random.uniform(-0.05, 0.05)
158
+ offset_y = np.random.uniform(-0.05, 0.05)
159
+ centers.append(np.clip([0.5 + offset_x, 0.5 + offset_y], 0.05, 0.95)) # Clip to avoid very edge
160
+
161
+ return np.array(centers[:self.n]) # Ensure exactly N circles
162
+
163
+ def generate_initial_x0(self, strategy='grid_split_5x5', perturbation_std_dev=0.01):
164
+ """
165
+ Generates an initial optimization vector `x0` based on the specified strategy
166
+ and applies perturbation.
167
+ """
168
+ if strategy == 'grid_split_5x5':
169
+ base_centers = self._get_grid_split_5x5_centers()
170
+ elif strategy == 'uniform_grid':
171
+ base_centers = self._get_uniform_grid_centers()
172
+ else:
173
+ raise ValueError(f"Unknown initial guess strategy: {strategy}")
174
+
175
+ # Apply perturbation
176
+ perturbed_centers = base_centers + np.random.normal(0, perturbation_std_dev, base_centers.shape)
177
+ perturbed_centers = np.clip(perturbed_centers, 0.0, 1.0) # Ensure centers stay within bounds
178
+
179
+ # Compute initial radii for the perturbed centers
180
+ perturbed_radii = self.problem.compute_initial_radii(perturbed_centers)
181
+
182
+ return self.problem._pack_vars(perturbed_centers, perturbed_radii)
183
+
184
+
185
+ class PackingOptimizer:
186
+ """
187
+ Manages the multi-run, multi-stage optimization process using scipy.optimize.minimize.
188
+ """
189
+ def __init__(self, problem_instance, optimizer_config):
190
+ self.problem = problem_instance
191
+ self.config = optimizer_config
192
+ self.initial_guesser = InitialGuesser(problem_instance.n, problem_instance)
193
+
194
+ def optimize(self):
195
+ """Runs the optimization process and returns the best result found."""
196
+ best_sum_radii = -np.inf
197
+ best_result_x = None
198
+
199
+ total_runs = sum(tier[0] for tier in self.config['perturb_schedule'])
200
+ run_strategies = self.config['run_strategies']
201
+
202
+ run_count = 0
203
+ for num_runs_in_tier, perturb_std_dev in self.config['perturb_schedule']:
204
+ for _ in range(num_runs_in_tier):
205
+ # Select initial guess strategy for this run. Cycle through strategies.
206
+ strategy_for_run = run_strategies[run_count % len(run_strategies)]
207
+ x0_run = self.initial_guesser.generate_initial_x0(
208
+ strategy=strategy_for_run, perturbation_std_dev=perturb_std_dev
209
+ )
210
+
211
+ # Stage 1: Maximize sum of areas (r^2) to find a globally dense configuration
212
+ result_stage1 = minimize(self.problem.objective_area, x0_run, method='SLSQP',
213
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
214
+ options=self.config['options_stage1'])
215
+ x_after_s1 = result_stage1.x if result_stage1.success else x0_run
216
+
217
+ # Stage 2: Maximize sum of radii (r) with moderately tight options
218
+ result_stage2 = minimize(self.problem.objective_radii, x_after_s1, method='SLSQP',
219
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
220
+ options=self.config['options_stage2'])
221
+ x_after_s2 = result_stage2.x if result_stage2.success else x_after_s1
222
+
223
+ # Stage 3: Further maximize sum of radii (r) with the tightest options for final refinement
224
+ result_stage3 = minimize(self.problem.objective_radii, x_after_s2, method='SLSQP',
225
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
226
+ options=self.config['options_stage3'])
227
+
228
+ # Use the result from the last successful stage. Prioritize Stage 3 if successful.
229
+ final_run_x = result_stage3.x if result_stage3.success else x_after_s2
230
+
231
+ _, current_radii = self.problem._unpack_vars(final_run_x)
232
+ current_sum_radii = np.sum(current_radii)
233
+
234
+ if current_sum_radii > best_sum_radii:
235
+ best_sum_radii = current_sum_radii
236
+ best_result_x = final_run_x
237
+
238
+ run_count += 1 # Increment run counter
239
+
240
+ # Fallback if no run was successful (highly unlikely), use a non-perturbed initial configuration.
241
+ if best_result_x is None:
242
+ best_result_x = self.initial_guesser.generate_initial_x0(strategy='grid_split_5x5', perturbation_std_dev=0.0)
243
+ # Run one full optimization on this fallback to get a reasonable result
244
+ res_fallback = minimize(self.problem.objective_radii, best_result_x, method='SLSQP',
245
+ bounds=self.problem.bounds, constraints=self.problem.constraints,
246
+ options=self.config['options_stage3'])
247
+ best_result_x = res_fallback.x if res_fallback.success else best_result_x
248
+
249
+
250
+ final_centers, final_radii = self.problem._unpack_vars(best_result_x)
251
+ final_radii = np.maximum(final_radii, 0) # Clean up potential floating point inaccuracies (e.g., small negative radii).
252
+
253
+ return final_centers, final_radii
254
+
255
+ def construct_packing():
256
+ """
257
+ Main function to construct an optimized packing of N=26 circles.
258
+ This uses a structurally redesigned program with clear separation of concerns
259
+ into Problem, InitialGuesser, and Optimizer classes. It incorporates
260
+ adaptive perturbation, hybrid initial guess strategies, and a three-stage NLP
261
+ with progressive solver tightness.
262
+ """
263
+ n_circles = 26
264
+
265
+ # 1. Initialize the problem definition
266
+ problem = CirclePackingProblem(n_circles)
267
+
268
+ # 2. Define optimizer configurations
269
+ optimizer_config = {
270
+ # Perturbation schedule: [(num_runs_in_tier, std_dev)]
271
+ 'perturb_schedule': [
272
+ (12, 0.030), # Broad exploration
273
+ (12, 0.010), # Medium refinement
274
+ (8, 0.004) # Fine-tuning
275
+ ],
276
+ 'options_stage1': {'maxiter': 1000, 'ftol': 1e-9, 'gtol': 1e-6, 'disp': False},
277
+ 'options_stage2': {'maxiter': 2500, 'ftol': 5e-10, 'gtol': 5e-7, 'disp': False},
278
+ 'options_stage3': {'maxiter': 5000, 'ftol': 1e-11, 'gtol': 1e-8, 'disp': False},
279
+ # Hybrid initial guess strategies distributed across runs
280
+ 'run_strategies': ['grid_split_5x5', 'uniform_grid']
281
+ }
282
+
283
+ # 3. Instantiate and run the optimizer
284
+ optimizer = PackingOptimizer(problem, optimizer_config)
285
+ final_centers, final_radii = optimizer.optimize()
286
+
287
+ return final_centers, final_radii
288
+ # EVOLVE-BLOCK-END