Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0 +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/0.0 +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/EVAL_AGENTS.md +635 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics.py +197 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/auxiliary_metrics_old.py +445 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/eval_agent_memory/service_state.json +608 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_0/main.py +94 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/edit.diff +121 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/main.py +108 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/original.py +94 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_1/search_replace.txt +60 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/edit.diff +172 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/original.py +138 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_10/search_replace.txt +75 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/edit.diff +191 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/main.py +179 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/original.py +170 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_100/search_replace.txt +120 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/edit.diff +202 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/main.py +174 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/original.py +170 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_101/search_replace.txt +93 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/edit.diff +248 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/main.py +233 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/original.py +180 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_102/search_replace.txt +326 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/edit.diff +336 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/main.py +264 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/original.py +168 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_103/rewrite.txt +255 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/edit.diff +207 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/main.py +188 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/original.py +173 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_104/search_replace.txt +195 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/edit.diff +343 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/main.py +206 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/original.py +205 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_105/rewrite.txt +197 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/edit.diff +307 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/main.py +188 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/original.py +195 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_106/rewrite.txt +179 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/edit.diff +447 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/main.py +310 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/original.py +173 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_107/search_replace.txt +469 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/edit.diff +458 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/main.py +297 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period10_20260206_062935/gen_108/original.py +194 -0
- 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
|