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__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/edit.diff +297 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/main.py +291 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/original.py +291 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/search_replace.txt +19 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/EVAL_AGENTS.md +46 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/aux_metrics.py +98 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/run_aux_metrics.py +23 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/service_state.json +212 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/EVAL_AGENTS.md +122 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/aux_metrics.py +149 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/auxiliary_metrics.py +87 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/run_aux_metrics_temp.py +14 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/service_state.json +128 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_0/main.py +94 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/edit.diff +140 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/main.py +105 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/original.py +94 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/search_replace.txt +201 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/edit.diff +150 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/main.py +96 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/original.py +101 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/search_replace.txt +186 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/edit.diff +364 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/main.py +356 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/original.py +327 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/search_replace.txt +171 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/edit.diff +300 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/main.py +284 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/original.py +237 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/search_replace.txt +130 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/edit.diff +417 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/main.py +280 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/original.py +356 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/rewrite.txt +271 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/edit.diff +270 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/main.py +266 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/original.py +265 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/search_replace.txt +32 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/edit.diff +295 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/main.py +266 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/original.py +252 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/rewrite.txt +257 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/edit.diff +419 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/main.py +405 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/original.py +402 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/rewrite.txt +396 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/edit.diff +400 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/main.py +376 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/original.py +356 -0
- examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/search_replace.txt +181 -0
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/edit.diff
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,291 +1,291 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 12 |
+
1. Initial 5x5 grid placement.
|
| 13 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 14 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 15 |
+
"""
|
| 16 |
+
def __init__(self, num_circles=26):
|
| 17 |
+
"""Initializes the packer for 26 circles."""
|
| 18 |
+
if num_circles != 26:
|
| 19 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 20 |
+
self.n = num_circles
|
| 21 |
+
self.centers = np.zeros((self.n, 2))
|
| 22 |
+
self.radii = np.zeros(self.n)
|
| 23 |
+
|
| 24 |
+
@staticmethod
|
| 25 |
+
def _compute_max_radii_static(centers: np.ndarray) -> np.ndarray:
|
| 26 |
+
"""
|
| 27 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 28 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 29 |
+
prior implementations for superior performance.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
np.array of shape (n) with the final radius of each circle.
|
| 36 |
+
"""
|
| 37 |
+
n = centers.shape[0]
|
| 38 |
+
radii = np.zeros(n)
|
| 39 |
+
|
| 40 |
+
# Parameters from the high-scoring prior program (score 2.5406)
|
| 41 |
+
growth_factor_start = 1.005
|
| 42 |
+
growth_factor_end = 1.002
|
| 43 |
+
outer_iterations = 400
|
| 44 |
+
tolerance_start = 1e-7
|
| 45 |
+
tolerance_end = 1e-11
|
| 46 |
+
# Increased inner iterations for more robust constraint satisfaction
|
| 47 |
+
inner_iterations = 20
|
| 48 |
+
|
| 49 |
+
# Initialize radii based on boundary distance
|
| 50 |
+
for i in range(n):
|
| 51 |
+
x, y = centers[i]
|
| 52 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 53 |
+
|
| 54 |
+
# Iteratively grow and resolve constraints
|
| 55 |
+
for k in range(outer_iterations):
|
| 56 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 57 |
+
# Exponential interpolation for smooth parameter transition
|
| 58 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 59 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 60 |
+
|
| 61 |
+
radii *= current_growth_factor
|
| 62 |
+
|
| 63 |
+
for _ in range(inner_iterations):
|
| 64 |
+
constraints_changed = False
|
| 65 |
+
# Boundary constraints
|
| 66 |
+
for i in range(n):
|
| 67 |
+
x, y = centers[i]
|
| 68 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 69 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 70 |
+
radii[i] = boundary_limit
|
| 71 |
+
constraints_changed = True
|
| 72 |
+
|
| 73 |
+
# Overlap constraints
|
| 74 |
+
for i in range(n):
|
| 75 |
+
for j in range(i + 1, n):
|
| 76 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 77 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 78 |
+
total_radius = radii[i] + radii[j]
|
| 79 |
+
if total_radius > tolerance_end: # Use tolerance_end for consistency
|
| 80 |
+
scale = dist / total_radius
|
| 81 |
+
radii[i] *= scale
|
| 82 |
+
radii[j] *= scale
|
| 83 |
+
constraints_changed = True
|
| 84 |
+
if not constraints_changed:
|
| 85 |
+
break
|
| 86 |
+
return radii
|
| 87 |
+
|
| 88 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 89 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 90 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 91 |
+
return np.array(list(product(coords, coords)))
|
| 92 |
+
|
| 93 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 94 |
+
"""
|
| 95 |
+
Performs a multi-resolution grid search for the 26th circle, identifying
|
| 96 |
+
promising regions in a coarse pass and then refining the search in a fine pass.
|
| 97 |
+
"""
|
| 98 |
+
best_sum_radii = -1.0
|
| 99 |
+
best_centers_config = None
|
| 100 |
+
|
| 101 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 102 |
+
- # Expanded initial range and moderate perturbations
|
| 103 |
+
- coarse_interstitial_coords = np.linspace(0.1, 0.9, 8) # e.g., 0.1 to 0.9 in 8 steps
|
| 104 |
+
- coarse_delta = 0.05
|
| 105 |
+
+ # Search around core interstitial points, focused on the central region.
|
| 106 |
+
+ coarse_interstitial_coords = np.linspace(0.2, 0.8, 4) # 4x4 grid of base points
|
| 107 |
+
+ coarse_delta = 0.025
|
| 108 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta]) # 3 offsets
|
| 109 |
+
|
| 110 |
+
coarse_candidate_points_and_sums = []
|
| 111 |
+
|
| 112 |
+
for base_x, base_y in product(coarse_interstitial_coords, coarse_interstitial_coords):
|
| 113 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 114 |
+
candidate_pos = np.array([base_x + offset_x, base_y + offset_y])
|
| 115 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 116 |
+
|
| 117 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 118 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 119 |
+
current_sum_radii = np.sum(trial_radii)
|
| 120 |
+
|
| 121 |
+
coarse_candidate_points_and_sums.append((current_sum_radii, clipped_candidate_pos))
|
| 122 |
+
|
| 123 |
+
if current_sum_radii > best_sum_radii:
|
| 124 |
+
best_sum_radii = current_sum_radii
|
| 125 |
+
best_centers_config = trial_centers
|
| 126 |
+
|
| 127 |
+
# --- Phase 2: Fine Grid Search around Top N Coarse Candidates ---
|
| 128 |
+
N_TOP_CANDIDATES = 5
|
| 129 |
+
# Sort by sum_radii in descending order and get the top N positions
|
| 130 |
+
coarse_candidate_points_and_sums.sort(key=lambda x: x[0], reverse=True)
|
| 131 |
+
top_coarse_positions = [item[1] for item in coarse_candidate_points_and_sums[:N_TOP_CANDIDATES]]
|
| 132 |
+
|
| 133 |
+
fine_delta = 0.01
|
| 134 |
+
fine_perturbation_offsets = np.linspace(-fine_delta, fine_delta, 5) # 5 finer offsets
|
| 135 |
+
|
| 136 |
+
for top_pos in top_coarse_positions:
|
| 137 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 138 |
+
candidate_pos = np.array([top_pos[0] + offset_x, top_pos[1] + offset_y])
|
| 139 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 140 |
+
|
| 141 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 142 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 143 |
+
current_sum_radii = np.sum(trial_radii)
|
| 144 |
+
|
| 145 |
+
if current_sum_radii > best_sum_radii:
|
| 146 |
+
best_sum_radii = current_sum_radii
|
| 147 |
+
best_centers_config = trial_centers
|
| 148 |
+
|
| 149 |
+
if best_centers_config is None:
|
| 150 |
+
# Fallback if no valid positions yielded positive sum_radii (should not happen with good initial base_centers)
|
| 151 |
+
best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 152 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config))
|
| 153 |
+
|
| 154 |
+
return best_centers_config, best_sum_radii
|
| 155 |
+
|
| 156 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 157 |
+
"""
|
| 158 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 159 |
+
"""
|
| 160 |
+
# Tuned SA parameters for local cluster refinement
|
| 161 |
+
sa_iterations = 300
|
| 162 |
+
sa_initial_temp = 0.0002
|
| 163 |
+
sa_cooling_rate = 0.99
|
| 164 |
+
sa_initial_step_size = 0.005
|
| 165 |
+
|
| 166 |
+
current_centers = np.copy(initial_centers)
|
| 167 |
+
current_sum_radii = initial_sum_radii
|
| 168 |
+
|
| 169 |
+
best_centers = np.copy(current_centers)
|
| 170 |
+
best_sum_radii = current_sum_radii
|
| 171 |
+
|
| 172 |
+
temp = sa_initial_temp
|
| 173 |
+
step_size = sa_initial_step_size
|
| 174 |
+
|
| 175 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 176 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 177 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 178 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 179 |
+
|
| 180 |
+
for _ in range(sa_iterations):
|
| 181 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 182 |
+
|
| 183 |
+
trial_centers = np.copy(current_centers)
|
| 184 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 185 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 186 |
+
|
| 187 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 188 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 189 |
+
|
| 190 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 191 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 192 |
+
current_centers = trial_centers
|
| 193 |
+
current_sum_radii = trial_sum_radii
|
| 194 |
+
|
| 195 |
+
if current_sum_radii > best_sum_radii:
|
| 196 |
+
best_sum_radii = current_sum_radii
|
| 197 |
+
best_centers = np.copy(current_centers)
|
| 198 |
+
|
| 199 |
+
temp *= sa_cooling_rate
|
| 200 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 201 |
+
|
| 202 |
+
return best_centers, best_sum_radii
|
| 203 |
+
|
| 204 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 205 |
+
"""
|
| 206 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a better global optimum.
|
| 207 |
+
This version prioritizes perturbing "stressed" circles (those with smaller radii).
|
| 208 |
+
"""
|
| 209 |
+
# SA parameters for a final, gentle, global refinement
|
| 210 |
+
sa_iterations = 2000 # Increased iterations for more thorough search
|
| 211 |
+
sa_initial_temp = 1e-5
|
| 212 |
+
sa_cooling_rate = 0.995
|
| 213 |
+
sa_initial_step_size = 0.004
|
| 214 |
+
|
| 215 |
+
current_centers = np.copy(initial_centers)
|
| 216 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers) # Calculate initial radii for stress
|
| 217 |
+
current_sum_radii = initial_sum_radii
|
| 218 |
+
|
| 219 |
+
best_centers = np.copy(current_centers)
|
| 220 |
+
best_sum_radii = current_sum_radii
|
| 221 |
+
|
| 222 |
+
temp = sa_initial_temp
|
| 223 |
+
step_size = sa_initial_step_size
|
| 224 |
+
|
| 225 |
+
for _ in range(sa_iterations):
|
| 226 |
+
# Perturb a random circle from the entire set, prioritizing "stressed" circles (smaller radii)
|
| 227 |
+
# This implements Recommendation 4.
|
| 228 |
+
inverse_radii = 1.0 / (current_radii + 1e-9) # Add small epsilon to avoid division by zero
|
| 229 |
+
selection_probs = inverse_radii / np.sum(inverse_radii)
|
| 230 |
+
idx_to_move = np.random.choice(self.n, p=selection_probs)
|
| 231 |
+
|
| 232 |
+
trial_centers = np.copy(current_centers)
|
| 233 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 234 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 235 |
+
|
| 236 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 237 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 238 |
+
|
| 239 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 240 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 241 |
+
current_centers = trial_centers
|
| 242 |
+
current_sum_radii = trial_sum_radii
|
| 243 |
+
current_radii = trial_radii # Update current_radii after successful move
|
| 244 |
+
|
| 245 |
+
if current_sum_radii > best_sum_radii:
|
| 246 |
+
best_sum_radii = current_sum_radii
|
| 247 |
+
best_centers = np.copy(current_centers)
|
| 248 |
+
|
| 249 |
+
temp *= sa_cooling_rate
|
| 250 |
+
step_size = max(step_size * sa_cooling_rate, 5e-8)
|
| 251 |
+
|
| 252 |
+
return best_centers, best_sum_radii
|
| 253 |
+
|
| 254 |
+
def construct_packing(self):
|
| 255 |
+
"""
|
| 256 |
+
Orchestrates the multi-stage packing process:
|
| 257 |
+
1. Grid search for 26th circle.
|
| 258 |
+
2. Local SA refinement of the resulting cluster.
|
| 259 |
+
3. Global SA refinement of the entire packing.
|
| 260 |
+
"""
|
| 261 |
+
base_centers = self._initial_grid_placement()
|
| 262 |
+
|
| 263 |
+
# Stage 1: Exhaustive search for a strong starting point
|
| 264 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 265 |
+
|
| 266 |
+
# Stage 2: Local refinement on the cluster to fine-tune
|
| 267 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 268 |
+
|
| 269 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles
|
| 270 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 271 |
+
|
| 272 |
+
self.centers = centers3
|
| 273 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 274 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers)
|
| 275 |
+
|
| 276 |
+
return self.centers, self.radii
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def construct_packing():
|
| 280 |
+
"""
|
| 281 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 282 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 283 |
+
a refined grid search followed by Simulated Annealing for local refinement.
|
| 284 |
+
"""
|
| 285 |
+
packer = CirclePacker(num_circles=26)
|
| 286 |
+
centers, radii = packer.construct_packing()
|
| 287 |
+
return centers, 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__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/main.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 9 |
+
1. Initial 5x5 grid placement.
|
| 10 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 11 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(self, num_circles=26):
|
| 14 |
+
"""Initializes the packer for 26 circles."""
|
| 15 |
+
if num_circles != 26:
|
| 16 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 17 |
+
self.n = num_circles
|
| 18 |
+
self.centers = np.zeros((self.n, 2))
|
| 19 |
+
self.radii = np.zeros(self.n)
|
| 20 |
+
|
| 21 |
+
@staticmethod
|
| 22 |
+
def _compute_max_radii_static(centers: np.ndarray) -> np.ndarray:
|
| 23 |
+
"""
|
| 24 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 25 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 26 |
+
prior implementations for superior performance.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
np.array of shape (n) with the final radius of each circle.
|
| 33 |
+
"""
|
| 34 |
+
n = centers.shape[0]
|
| 35 |
+
radii = np.zeros(n)
|
| 36 |
+
|
| 37 |
+
# Parameters from the high-scoring prior program (score 2.5406)
|
| 38 |
+
growth_factor_start = 1.005
|
| 39 |
+
growth_factor_end = 1.002
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
tolerance_start = 1e-7
|
| 42 |
+
tolerance_end = 1e-11
|
| 43 |
+
# Increased inner iterations for more robust constraint satisfaction
|
| 44 |
+
inner_iterations = 20
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on boundary distance
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
# Iteratively grow and resolve constraints
|
| 52 |
+
for k in range(outer_iterations):
|
| 53 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 54 |
+
# Exponential interpolation for smooth parameter transition
|
| 55 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 56 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 57 |
+
|
| 58 |
+
radii *= current_growth_factor
|
| 59 |
+
|
| 60 |
+
for _ in range(inner_iterations):
|
| 61 |
+
constraints_changed = False
|
| 62 |
+
# Boundary constraints
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Overlap constraints
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > tolerance_end: # Use tolerance_end for consistency
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
if not constraints_changed:
|
| 82 |
+
break
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 86 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
return np.array(list(product(coords, coords)))
|
| 89 |
+
|
| 90 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 91 |
+
"""
|
| 92 |
+
Performs a multi-resolution grid search for the 26th circle, identifying
|
| 93 |
+
promising regions in a coarse pass and then refining the search in a fine pass.
|
| 94 |
+
"""
|
| 95 |
+
best_sum_radii = -1.0
|
| 96 |
+
best_centers_config = None
|
| 97 |
+
|
| 98 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 99 |
+
# Search around core interstitial points, focused on the central region.
|
| 100 |
+
coarse_interstitial_coords = np.linspace(0.2, 0.8, 4) # 4x4 grid of base points
|
| 101 |
+
coarse_delta = 0.025
|
| 102 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta]) # 3 offsets
|
| 103 |
+
|
| 104 |
+
coarse_candidate_points_and_sums = []
|
| 105 |
+
|
| 106 |
+
for base_x, base_y in product(coarse_interstitial_coords, coarse_interstitial_coords):
|
| 107 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 108 |
+
candidate_pos = np.array([base_x + offset_x, base_y + offset_y])
|
| 109 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 110 |
+
|
| 111 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 112 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 113 |
+
current_sum_radii = np.sum(trial_radii)
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points_and_sums.append((current_sum_radii, clipped_candidate_pos))
|
| 116 |
+
|
| 117 |
+
if current_sum_radii > best_sum_radii:
|
| 118 |
+
best_sum_radii = current_sum_radii
|
| 119 |
+
best_centers_config = trial_centers
|
| 120 |
+
|
| 121 |
+
# --- Phase 2: Fine Grid Search around Top N Coarse Candidates ---
|
| 122 |
+
N_TOP_CANDIDATES = 5
|
| 123 |
+
# Sort by sum_radii in descending order and get the top N positions
|
| 124 |
+
coarse_candidate_points_and_sums.sort(key=lambda x: x[0], reverse=True)
|
| 125 |
+
top_coarse_positions = [item[1] for item in coarse_candidate_points_and_sums[:N_TOP_CANDIDATES]]
|
| 126 |
+
|
| 127 |
+
fine_delta = 0.01
|
| 128 |
+
fine_perturbation_offsets = np.linspace(-fine_delta, fine_delta, 5) # 5 finer offsets
|
| 129 |
+
|
| 130 |
+
for top_pos in top_coarse_positions:
|
| 131 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 132 |
+
candidate_pos = np.array([top_pos[0] + offset_x, top_pos[1] + offset_y])
|
| 133 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 134 |
+
|
| 135 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 136 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 137 |
+
current_sum_radii = np.sum(trial_radii)
|
| 138 |
+
|
| 139 |
+
if current_sum_radii > best_sum_radii:
|
| 140 |
+
best_sum_radii = current_sum_radii
|
| 141 |
+
best_centers_config = trial_centers
|
| 142 |
+
|
| 143 |
+
if best_centers_config is None:
|
| 144 |
+
# Fallback if no valid positions yielded positive sum_radii (should not happen with good initial base_centers)
|
| 145 |
+
best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 146 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config))
|
| 147 |
+
|
| 148 |
+
return best_centers_config, best_sum_radii
|
| 149 |
+
|
| 150 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 151 |
+
"""
|
| 152 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 153 |
+
"""
|
| 154 |
+
# Tuned SA parameters for local cluster refinement
|
| 155 |
+
sa_iterations = 300
|
| 156 |
+
sa_initial_temp = 0.0002
|
| 157 |
+
sa_cooling_rate = 0.99
|
| 158 |
+
sa_initial_step_size = 0.005
|
| 159 |
+
|
| 160 |
+
current_centers = np.copy(initial_centers)
|
| 161 |
+
current_sum_radii = initial_sum_radii
|
| 162 |
+
|
| 163 |
+
best_centers = np.copy(current_centers)
|
| 164 |
+
best_sum_radii = current_sum_radii
|
| 165 |
+
|
| 166 |
+
temp = sa_initial_temp
|
| 167 |
+
step_size = sa_initial_step_size
|
| 168 |
+
|
| 169 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 170 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 171 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 172 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 173 |
+
|
| 174 |
+
for _ in range(sa_iterations):
|
| 175 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 176 |
+
|
| 177 |
+
trial_centers = np.copy(current_centers)
|
| 178 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 179 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 180 |
+
|
| 181 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 182 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 183 |
+
|
| 184 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 185 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 186 |
+
current_centers = trial_centers
|
| 187 |
+
current_sum_radii = trial_sum_radii
|
| 188 |
+
|
| 189 |
+
if current_sum_radii > best_sum_radii:
|
| 190 |
+
best_sum_radii = current_sum_radii
|
| 191 |
+
best_centers = np.copy(current_centers)
|
| 192 |
+
|
| 193 |
+
temp *= sa_cooling_rate
|
| 194 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 195 |
+
|
| 196 |
+
return best_centers, best_sum_radii
|
| 197 |
+
|
| 198 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 199 |
+
"""
|
| 200 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a better global optimum.
|
| 201 |
+
This version prioritizes perturbing "stressed" circles (those with smaller radii).
|
| 202 |
+
"""
|
| 203 |
+
# SA parameters for a final, gentle, global refinement
|
| 204 |
+
sa_iterations = 2000 # Increased iterations for more thorough search
|
| 205 |
+
sa_initial_temp = 1e-5
|
| 206 |
+
sa_cooling_rate = 0.995
|
| 207 |
+
sa_initial_step_size = 0.004
|
| 208 |
+
|
| 209 |
+
current_centers = np.copy(initial_centers)
|
| 210 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers) # Calculate initial radii for stress
|
| 211 |
+
current_sum_radii = initial_sum_radii
|
| 212 |
+
|
| 213 |
+
best_centers = np.copy(current_centers)
|
| 214 |
+
best_sum_radii = current_sum_radii
|
| 215 |
+
|
| 216 |
+
temp = sa_initial_temp
|
| 217 |
+
step_size = sa_initial_step_size
|
| 218 |
+
|
| 219 |
+
for _ in range(sa_iterations):
|
| 220 |
+
# Perturb a random circle from the entire set, prioritizing "stressed" circles (smaller radii)
|
| 221 |
+
# This implements Recommendation 4.
|
| 222 |
+
inverse_radii = 1.0 / (current_radii + 1e-9) # Add small epsilon to avoid division by zero
|
| 223 |
+
selection_probs = inverse_radii / np.sum(inverse_radii)
|
| 224 |
+
idx_to_move = np.random.choice(self.n, p=selection_probs)
|
| 225 |
+
|
| 226 |
+
trial_centers = np.copy(current_centers)
|
| 227 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 228 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 229 |
+
|
| 230 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 231 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 232 |
+
|
| 233 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 234 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 235 |
+
current_centers = trial_centers
|
| 236 |
+
current_sum_radii = trial_sum_radii
|
| 237 |
+
current_radii = trial_radii # Update current_radii after successful move
|
| 238 |
+
|
| 239 |
+
if current_sum_radii > best_sum_radii:
|
| 240 |
+
best_sum_radii = current_sum_radii
|
| 241 |
+
best_centers = np.copy(current_centers)
|
| 242 |
+
|
| 243 |
+
temp *= sa_cooling_rate
|
| 244 |
+
step_size = max(step_size * sa_cooling_rate, 5e-8)
|
| 245 |
+
|
| 246 |
+
return best_centers, best_sum_radii
|
| 247 |
+
|
| 248 |
+
def construct_packing(self):
|
| 249 |
+
"""
|
| 250 |
+
Orchestrates the multi-stage packing process:
|
| 251 |
+
1. Grid search for 26th circle.
|
| 252 |
+
2. Local SA refinement of the resulting cluster.
|
| 253 |
+
3. Global SA refinement of the entire packing.
|
| 254 |
+
"""
|
| 255 |
+
base_centers = self._initial_grid_placement()
|
| 256 |
+
|
| 257 |
+
# Stage 1: Exhaustive search for a strong starting point
|
| 258 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 259 |
+
|
| 260 |
+
# Stage 2: Local refinement on the cluster to fine-tune
|
| 261 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 262 |
+
|
| 263 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles
|
| 264 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 265 |
+
|
| 266 |
+
self.centers = centers3
|
| 267 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 268 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers)
|
| 269 |
+
|
| 270 |
+
return self.centers, self.radii
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def construct_packing():
|
| 274 |
+
"""
|
| 275 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 276 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 277 |
+
a refined grid search followed by Simulated Annealing for local refinement.
|
| 278 |
+
"""
|
| 279 |
+
packer = CirclePacker(num_circles=26)
|
| 280 |
+
centers, radii = packer.construct_packing()
|
| 281 |
+
return centers, radii
|
| 282 |
+
# EVOLVE-BLOCK-END
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# This part remains fixed (not evolved)
|
| 286 |
+
def run_packing():
|
| 287 |
+
"""Run the circle packing constructor for n=26"""
|
| 288 |
+
centers, radii = construct_packing()
|
| 289 |
+
# Calculate the sum of radii
|
| 290 |
+
sum_radii = np.sum(radii)
|
| 291 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/original.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 9 |
+
1. Initial 5x5 grid placement.
|
| 10 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 11 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(self, num_circles=26):
|
| 14 |
+
"""Initializes the packer for 26 circles."""
|
| 15 |
+
if num_circles != 26:
|
| 16 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 17 |
+
self.n = num_circles
|
| 18 |
+
self.centers = np.zeros((self.n, 2))
|
| 19 |
+
self.radii = np.zeros(self.n)
|
| 20 |
+
|
| 21 |
+
@staticmethod
|
| 22 |
+
def _compute_max_radii_static(centers: np.ndarray) -> np.ndarray:
|
| 23 |
+
"""
|
| 24 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 25 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 26 |
+
prior implementations for superior performance.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
np.array of shape (n) with the final radius of each circle.
|
| 33 |
+
"""
|
| 34 |
+
n = centers.shape[0]
|
| 35 |
+
radii = np.zeros(n)
|
| 36 |
+
|
| 37 |
+
# Parameters from the high-scoring prior program (score 2.5406)
|
| 38 |
+
growth_factor_start = 1.005
|
| 39 |
+
growth_factor_end = 1.002
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
tolerance_start = 1e-7
|
| 42 |
+
tolerance_end = 1e-11
|
| 43 |
+
# Increased inner iterations for more robust constraint satisfaction
|
| 44 |
+
inner_iterations = 20
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on boundary distance
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
# Iteratively grow and resolve constraints
|
| 52 |
+
for k in range(outer_iterations):
|
| 53 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 54 |
+
# Exponential interpolation for smooth parameter transition
|
| 55 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 56 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 57 |
+
|
| 58 |
+
radii *= current_growth_factor
|
| 59 |
+
|
| 60 |
+
for _ in range(inner_iterations):
|
| 61 |
+
constraints_changed = False
|
| 62 |
+
# Boundary constraints
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Overlap constraints
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > tolerance_end: # Use tolerance_end for consistency
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
if not constraints_changed:
|
| 82 |
+
break
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 86 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
return np.array(list(product(coords, coords)))
|
| 89 |
+
|
| 90 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 91 |
+
"""
|
| 92 |
+
Performs a multi-resolution grid search for the 26th circle, identifying
|
| 93 |
+
promising regions in a coarse pass and then refining the search in a fine pass.
|
| 94 |
+
"""
|
| 95 |
+
best_sum_radii = -1.0
|
| 96 |
+
best_centers_config = None
|
| 97 |
+
|
| 98 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 99 |
+
# Expanded initial range and moderate perturbations
|
| 100 |
+
coarse_interstitial_coords = np.linspace(0.1, 0.9, 8) # e.g., 0.1 to 0.9 in 8 steps
|
| 101 |
+
coarse_delta = 0.05
|
| 102 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta]) # 3 offsets
|
| 103 |
+
|
| 104 |
+
coarse_candidate_points_and_sums = []
|
| 105 |
+
|
| 106 |
+
for base_x, base_y in product(coarse_interstitial_coords, coarse_interstitial_coords):
|
| 107 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 108 |
+
candidate_pos = np.array([base_x + offset_x, base_y + offset_y])
|
| 109 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 110 |
+
|
| 111 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 112 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 113 |
+
current_sum_radii = np.sum(trial_radii)
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points_and_sums.append((current_sum_radii, clipped_candidate_pos))
|
| 116 |
+
|
| 117 |
+
if current_sum_radii > best_sum_radii:
|
| 118 |
+
best_sum_radii = current_sum_radii
|
| 119 |
+
best_centers_config = trial_centers
|
| 120 |
+
|
| 121 |
+
# --- Phase 2: Fine Grid Search around Top N Coarse Candidates ---
|
| 122 |
+
N_TOP_CANDIDATES = 5
|
| 123 |
+
# Sort by sum_radii in descending order and get the top N positions
|
| 124 |
+
coarse_candidate_points_and_sums.sort(key=lambda x: x[0], reverse=True)
|
| 125 |
+
top_coarse_positions = [item[1] for item in coarse_candidate_points_and_sums[:N_TOP_CANDIDATES]]
|
| 126 |
+
|
| 127 |
+
fine_delta = 0.01
|
| 128 |
+
fine_perturbation_offsets = np.linspace(-fine_delta, fine_delta, 5) # 5 finer offsets
|
| 129 |
+
|
| 130 |
+
for top_pos in top_coarse_positions:
|
| 131 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 132 |
+
candidate_pos = np.array([top_pos[0] + offset_x, top_pos[1] + offset_y])
|
| 133 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 134 |
+
|
| 135 |
+
trial_centers = np.vstack([base_centers, clipped_candidate_pos])
|
| 136 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 137 |
+
current_sum_radii = np.sum(trial_radii)
|
| 138 |
+
|
| 139 |
+
if current_sum_radii > best_sum_radii:
|
| 140 |
+
best_sum_radii = current_sum_radii
|
| 141 |
+
best_centers_config = trial_centers
|
| 142 |
+
|
| 143 |
+
if best_centers_config is None:
|
| 144 |
+
# Fallback if no valid positions yielded positive sum_radii (should not happen with good initial base_centers)
|
| 145 |
+
best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 146 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config))
|
| 147 |
+
|
| 148 |
+
return best_centers_config, best_sum_radii
|
| 149 |
+
|
| 150 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 151 |
+
"""
|
| 152 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 153 |
+
"""
|
| 154 |
+
# Tuned SA parameters for local cluster refinement
|
| 155 |
+
sa_iterations = 300
|
| 156 |
+
sa_initial_temp = 0.0002
|
| 157 |
+
sa_cooling_rate = 0.99
|
| 158 |
+
sa_initial_step_size = 0.005
|
| 159 |
+
|
| 160 |
+
current_centers = np.copy(initial_centers)
|
| 161 |
+
current_sum_radii = initial_sum_radii
|
| 162 |
+
|
| 163 |
+
best_centers = np.copy(current_centers)
|
| 164 |
+
best_sum_radii = current_sum_radii
|
| 165 |
+
|
| 166 |
+
temp = sa_initial_temp
|
| 167 |
+
step_size = sa_initial_step_size
|
| 168 |
+
|
| 169 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 170 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 171 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 172 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 173 |
+
|
| 174 |
+
for _ in range(sa_iterations):
|
| 175 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 176 |
+
|
| 177 |
+
trial_centers = np.copy(current_centers)
|
| 178 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 179 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 180 |
+
|
| 181 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 182 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 183 |
+
|
| 184 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 185 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 186 |
+
current_centers = trial_centers
|
| 187 |
+
current_sum_radii = trial_sum_radii
|
| 188 |
+
|
| 189 |
+
if current_sum_radii > best_sum_radii:
|
| 190 |
+
best_sum_radii = current_sum_radii
|
| 191 |
+
best_centers = np.copy(current_centers)
|
| 192 |
+
|
| 193 |
+
temp *= sa_cooling_rate
|
| 194 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 195 |
+
|
| 196 |
+
return best_centers, best_sum_radii
|
| 197 |
+
|
| 198 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 199 |
+
"""
|
| 200 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a better global optimum.
|
| 201 |
+
This version prioritizes perturbing "stressed" circles (those with smaller radii).
|
| 202 |
+
"""
|
| 203 |
+
# SA parameters for a final, gentle, global refinement
|
| 204 |
+
sa_iterations = 2000 # Increased iterations for more thorough search
|
| 205 |
+
sa_initial_temp = 1e-5
|
| 206 |
+
sa_cooling_rate = 0.995
|
| 207 |
+
sa_initial_step_size = 0.004
|
| 208 |
+
|
| 209 |
+
current_centers = np.copy(initial_centers)
|
| 210 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers) # Calculate initial radii for stress
|
| 211 |
+
current_sum_radii = initial_sum_radii
|
| 212 |
+
|
| 213 |
+
best_centers = np.copy(current_centers)
|
| 214 |
+
best_sum_radii = current_sum_radii
|
| 215 |
+
|
| 216 |
+
temp = sa_initial_temp
|
| 217 |
+
step_size = sa_initial_step_size
|
| 218 |
+
|
| 219 |
+
for _ in range(sa_iterations):
|
| 220 |
+
# Perturb a random circle from the entire set, prioritizing "stressed" circles (smaller radii)
|
| 221 |
+
# This implements Recommendation 4.
|
| 222 |
+
inverse_radii = 1.0 / (current_radii + 1e-9) # Add small epsilon to avoid division by zero
|
| 223 |
+
selection_probs = inverse_radii / np.sum(inverse_radii)
|
| 224 |
+
idx_to_move = np.random.choice(self.n, p=selection_probs)
|
| 225 |
+
|
| 226 |
+
trial_centers = np.copy(current_centers)
|
| 227 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 228 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 229 |
+
|
| 230 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 231 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 232 |
+
|
| 233 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 234 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 235 |
+
current_centers = trial_centers
|
| 236 |
+
current_sum_radii = trial_sum_radii
|
| 237 |
+
current_radii = trial_radii # Update current_radii after successful move
|
| 238 |
+
|
| 239 |
+
if current_sum_radii > best_sum_radii:
|
| 240 |
+
best_sum_radii = current_sum_radii
|
| 241 |
+
best_centers = np.copy(current_centers)
|
| 242 |
+
|
| 243 |
+
temp *= sa_cooling_rate
|
| 244 |
+
step_size = max(step_size * sa_cooling_rate, 5e-8)
|
| 245 |
+
|
| 246 |
+
return best_centers, best_sum_radii
|
| 247 |
+
|
| 248 |
+
def construct_packing(self):
|
| 249 |
+
"""
|
| 250 |
+
Orchestrates the multi-stage packing process:
|
| 251 |
+
1. Grid search for 26th circle.
|
| 252 |
+
2. Local SA refinement of the resulting cluster.
|
| 253 |
+
3. Global SA refinement of the entire packing.
|
| 254 |
+
"""
|
| 255 |
+
base_centers = self._initial_grid_placement()
|
| 256 |
+
|
| 257 |
+
# Stage 1: Exhaustive search for a strong starting point
|
| 258 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 259 |
+
|
| 260 |
+
# Stage 2: Local refinement on the cluster to fine-tune
|
| 261 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 262 |
+
|
| 263 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles
|
| 264 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 265 |
+
|
| 266 |
+
self.centers = centers3
|
| 267 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 268 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers)
|
| 269 |
+
|
| 270 |
+
return self.centers, self.radii
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def construct_packing():
|
| 274 |
+
"""
|
| 275 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 276 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 277 |
+
a refined grid search followed by Simulated Annealing for local refinement.
|
| 278 |
+
"""
|
| 279 |
+
packer = CirclePacker(num_circles=26)
|
| 280 |
+
centers, radii = packer.construct_packing()
|
| 281 |
+
return centers, radii
|
| 282 |
+
# EVOLVE-BLOCK-END
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# This part remains fixed (not evolved)
|
| 286 |
+
def run_packing():
|
| 287 |
+
"""Run the circle packing constructor for n=26"""
|
| 288 |
+
centers, radii = construct_packing()
|
| 289 |
+
# Calculate the sum of radii
|
| 290 |
+
sum_radii = np.sum(radii)
|
| 291 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/best/search_replace.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<refine_26th_search>
|
| 2 |
+
A refinement of the 26th circle's initial placement strategy. This edit reverts the coarse search grid for the 26th circle to a more focused `4x4` pattern within the central `[0.2, 0.8]` region, instead of the broader `8x8` search over `[0.1, 0.9]`. The perturbation delta is also reduced accordingly. This change is based on prior successful implementations, where a more targeted initial search for the interstitial circle often leads to a better starting point for subsequent SA stages without over-exploring less promising areas near boundaries. The aim is to increase the efficiency of finding a high-quality initial placement, which can positively impact the final sum of radii.
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DIFF>
|
| 6 |
+
<<<<<<< SEARCH
|
| 7 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 8 |
+
# Expanded initial range and moderate perturbations
|
| 9 |
+
coarse_interstitial_coords = np.linspace(0.1, 0.9, 8) # e.g., 0.1 to 0.9 in 8 steps
|
| 10 |
+
coarse_delta = 0.05
|
| 11 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta]) # 3 offsets
|
| 12 |
+
=======
|
| 13 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 14 |
+
# Search around core interstitial points, focused on the central region.
|
| 15 |
+
coarse_interstitial_coords = np.linspace(0.2, 0.8, 4) # 4x4 grid of base points
|
| 16 |
+
coarse_delta = 0.025
|
| 17 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta]) # 3 offsets
|
| 18 |
+
>>>>>>> REPLACE
|
| 19 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/EVAL_AGENTS.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVAL_AGENTS.md - Auxiliary Evaluation Metrics
|
| 2 |
+
|
| 3 |
+
This file serves as a memory for the evaluation agent, documenting insights, successful auxiliary metrics, and patterns observed across generations during the code evolution process.
|
| 4 |
+
|
| 5 |
+
## Generation 19 Evaluation Summary
|
| 6 |
+
|
| 7 |
+
**Primary Score (Generation 19):** 1.9201
|
| 8 |
+
|
| 9 |
+
### Auxiliary Metrics Introduced (with Python implementation `aux_metrics.py`):
|
| 10 |
+
|
| 11 |
+
1. **Total Overlap Severity (`total_overlap_severity`)**
|
| 12 |
+
* **Purpose**: Quantifies the extent of overlap between circles. A value greater than 0 indicates overlapping circles. This provides a gradient of 'badness' beyond a simple boolean validation failure.
|
| 13 |
+
* **Calculation**: Sum of `(radii[i] + radii[j] - dist)` for all overlapping pairs `(i, j)`. Only positive overlap values contribute.
|
| 14 |
+
* **Generation 19 Result**: `0.000000`
|
| 15 |
+
* **Insight**: The current solution successfully avoids circle overlaps, meeting a critical validity constraint. This metric confirms that the solution is well within acceptable bounds for overlap.
|
| 16 |
+
|
| 17 |
+
2. **Total Out-of-Bounds Severity (`total_out_of_bounds_severity`)**
|
| 18 |
+
* **Purpose**: Quantifies how far circles extend beyond the [0,1]x[0,1] unit square boundaries. A value greater than 0 indicates circles extending outside the unit square.
|
| 19 |
+
* **Calculation**: Sum of `max(0, r - x)`, `max(0, x + r - 1)`, `max(0, r - y)`, `max(0, y + r - 1)` for all circles.
|
| 20 |
+
* **Generation 19 Result**: `0.0`
|
| 21 |
+
* **Insight**: The current solution keeps all circles strictly within the unit square, fulfilling another key validity constraint. This confirms robust handling of boundary conditions.
|
| 22 |
+
|
| 23 |
+
3. **Average Distance to Unit Center (`average_distance_to_unit_center`)**
|
| 24 |
+
* **Purpose**: Measures the average Euclidean distance of each circle's center from the center of the unit square (0.5, 0.5). A lower value indicates a more centralized and potentially compact packing.
|
| 25 |
+
* **Calculation**: `np.mean(np.linalg.norm(centers - [0.5, 0.5], axis=1))`.
|
| 26 |
+
* **Generation 19 Result**: `0.365894`
|
| 27 |
+
* **Insight**: This value suggests that circles are not highly clustered towards the absolute center of the unit square. For maximizing total radii, a more centralized packing might be beneficial, especially if it allows for larger circles. This metric identifies an area for potential optimization, suggesting that strategies for tighter central packing could lead to higher scores.
|
| 28 |
+
|
| 29 |
+
4. **Radii Standard Deviation (`radii_std_dev`)**
|
| 30 |
+
* **Purpose**: Measures the dispersion (variety) of the circle radii. A low standard deviation indicates similar-sized circles, while a high standard deviation suggests a mix of large and small circles.
|
| 31 |
+
* **Calculation**: `np.std(radii)`.
|
| 32 |
+
* **Generation 19 Result**: `0.025924`
|
| 33 |
+
* **Insight**: A relatively low standard deviation indicates that the current solution favors circles of somewhat uniform size. In some packing problems, a more diverse distribution of radii (e.g., a few large circles filling primary voids, surrounded by many smaller circles) can lead to higher total area. This metric suggests an opportunity to explore solutions with greater variation in circle sizes to potentially discover better packing configurations.
|
| 34 |
+
|
| 35 |
+
### Evaluation Stage Assessment:
|
| 36 |
+
* **Stage**: Optimization / Convergence
|
| 37 |
+
* **Rationale**: The solution is valid (near-zero overlap and out-of-bounds severity), indicating that basic correctness has been largely achieved. The current challenge is to further increase the primary score (sum of radii), suggesting a focus on refining the packing strategy.
|
| 38 |
+
|
| 39 |
+
### Recommendations for Future Evolution:
|
| 40 |
+
|
| 41 |
+
* **Track and Analyze Trends:** Continuously monitor `average_distance_to_unit_center` and `radii_std_dev` over subsequent generations.
|
| 42 |
+
* If `primary_score` plateaus, analyze the trend of `radii_std_dev`. If it's consistently low, consider introducing a temporary reward or exploration pressure towards higher `radii_std_dev` to encourage more diverse circle sizes, potentially escaping local optima.
|
| 43 |
+
* Observe if `average_distance_to_unit_center` correlates positively or negatively with `primary_score` to understand the optimal spatial arrangement.
|
| 44 |
+
* **Dynamic Weighting:** These auxiliary metrics could be integrated into a multi-objective evaluation framework, with their weights adjusted based on the current evolution stage:
|
| 45 |
+
* **EXPLORATION:** Prioritize penalizing `total_overlap_severity` and `total_out_of_bounds_severity` to guide towards feasible solutions.
|
| 46 |
+
* **OPTIMIZATION/CONVERGENCE:** Focus on `primary_score`, but use `average_distance_to_unit_center` to fine-tune spatial arrangement. If stagnation occurs, use `radii_std_dev` as a lever for renewed exploration.
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/aux_metrics.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import os
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
|
| 6 |
+
def calculate_auxiliary_metrics(results_dir: str) -> Dict[str, Any]:
|
| 7 |
+
"""
|
| 8 |
+
Calculates auxiliary metrics for circle packing solutions.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
results_dir: The directory where evaluation results (including extra.npz) are stored.
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
A dictionary containing auxiliary metrics.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
extra_file = os.path.join(results_dir, "extra.npz")
|
| 18 |
+
metrics = {}
|
| 19 |
+
|
| 20 |
+
if not os.path.exists(extra_file):
|
| 21 |
+
metrics["aux_metrics_error"] = "extra.npz not found."
|
| 22 |
+
return metrics
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
with np.load(extra_file) as data:
|
| 26 |
+
centers = data["centers"]
|
| 27 |
+
radii = data["radii"]
|
| 28 |
+
reported_sum = data["reported_sum"]
|
| 29 |
+
except Exception as e:
|
| 30 |
+
metrics["aux_metrics_error"] = f"Error loading extra.npz: {e}"
|
| 31 |
+
return metrics
|
| 32 |
+
|
| 33 |
+
n_expected = 26
|
| 34 |
+
|
| 35 |
+
# 1. Overlap Severity and Out-of-Bounds Severity
|
| 36 |
+
total_overlap_severity = 0.0
|
| 37 |
+
total_out_of_bounds_severity = 0.0
|
| 38 |
+
|
| 39 |
+
if centers.shape[0] == n_expected and radii.shape[0] == n_expected: # Only calculate if shapes are correct
|
| 40 |
+
# Out-of-bounds severity
|
| 41 |
+
for i in range(n_expected):
|
| 42 |
+
x, y = centers[i]
|
| 43 |
+
r = radii[i]
|
| 44 |
+
total_out_of_bounds_severity += max(0, r - x) # left bound (x-r < 0)
|
| 45 |
+
total_out_of_bounds_severity += max(0, x + r - 1) # right bound (x+r > 1)
|
| 46 |
+
total_out_of_bounds_severity += max(0, r - y) # bottom bound (y-r < 0)
|
| 47 |
+
total_out_of_bounds_severity += max(0, y + r - 1) # top bound (y+r > 1)
|
| 48 |
+
|
| 49 |
+
# Overlap severity
|
| 50 |
+
for i in range(n_expected):
|
| 51 |
+
for j in range(i + 1, n_expected):
|
| 52 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 53 |
+
overlap = (radii[i] + radii[j]) - dist
|
| 54 |
+
total_overlap_severity += max(0, overlap) # Only add if overlap is positive
|
| 55 |
+
|
| 56 |
+
metrics["total_overlap_severity"] = total_overlap_severity
|
| 57 |
+
metrics["total_out_of_bounds_severity"] = total_out_of_bounds_severity
|
| 58 |
+
|
| 59 |
+
# 2. Average Distance to Center of Unit Square (0.5, 0.5)
|
| 60 |
+
center_point = np.array([0.5, 0.5])
|
| 61 |
+
distances_to_center = np.linalg.norm(centers - center_point, axis=1)
|
| 62 |
+
metrics["average_distance_to_unit_center"] = np.mean(distances_to_center)
|
| 63 |
+
|
| 64 |
+
# 3. Radii Standard Deviation
|
| 65 |
+
if len(radii) > 0:
|
| 66 |
+
metrics["radii_std_dev"] = np.std(radii)
|
| 67 |
+
else:
|
| 68 |
+
metrics["radii_std_dev"] = 0.0 # Or np.nan, depending on desired behavior for empty radii
|
| 69 |
+
|
| 70 |
+
return metrics
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
# Example usage (for testing purposes)
|
| 74 |
+
# This part won't be run by the agent during evaluation, but useful for local testing
|
| 75 |
+
|
| 76 |
+
# Create dummy extra.npz for testing
|
| 77 |
+
dummy_results_dir = "./temp_results_dir"
|
| 78 |
+
os.makedirs(dummy_results_dir, exist_ok=True)
|
| 79 |
+
|
| 80 |
+
dummy_centers = np.array([[0.2, 0.2], [0.8, 0.8], [0.5, 0.5], [0.1, 0.1], [0.9, 0.9]])
|
| 81 |
+
dummy_radii = np.array([0.1, 0.1, 0.2, 0.05, 0.05])
|
| 82 |
+
dummy_reported_sum = np.sum(dummy_radii)
|
| 83 |
+
|
| 84 |
+
# Ensure 26 circles for testing validation logic
|
| 85 |
+
n_fill = 26 - len(dummy_centers)
|
| 86 |
+
if n_fill > 0:
|
| 87 |
+
dummy_centers = np.vstack([dummy_centers, np.random.rand(n_fill, 2)])
|
| 88 |
+
dummy_radii = np.hstack([dummy_radii, np.random.rand(n_fill) * 0.01]) # Small random radii
|
| 89 |
+
|
| 90 |
+
np.savez(os.path.join(dummy_results_dir, "extra.npz"),
|
| 91 |
+
centers=dummy_centers, radii=dummy_radii, reported_sum=dummy_reported_sum)
|
| 92 |
+
|
| 93 |
+
aux_metrics = calculate_auxiliary_metrics(dummy_results_dir)
|
| 94 |
+
print("Auxiliary Metrics:", aux_metrics)
|
| 95 |
+
|
| 96 |
+
# Clean up dummy files
|
| 97 |
+
os.remove(os.path.join(dummy_results_dir, "extra.npz"))
|
| 98 |
+
os.rmdir(dummy_results_dir)
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/run_aux_metrics.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# Add the current directory to sys.path to import aux_metrics
|
| 6 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 7 |
+
from aux_metrics import calculate_auxiliary_metrics
|
| 8 |
+
|
| 9 |
+
def main():
|
| 10 |
+
gen_path = sys.argv[1] # Path to the current generation directory
|
| 11 |
+
results_dir = os.path.join(gen_path, 'results')
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
metrics = calculate_auxiliary_metrics(results_dir)
|
| 15 |
+
|
| 16 |
+
for key, value in metrics.items():
|
| 17 |
+
print(f"Auxiliary Metric: {key} = {value:.6f}")
|
| 18 |
+
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"An error occurred: {e}")
|
| 21 |
+
|
| 22 |
+
if __name__ == "__main__":
|
| 23 |
+
main()
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/service_state.json
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generation_history": [
|
| 3 |
+
{
|
| 4 |
+
"generation": 0,
|
| 5 |
+
"primary_score": 2.4,
|
| 6 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 7 |
+
"timestamp": 1770062820.8740644
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"generation": 1,
|
| 11 |
+
"primary_score": 2.4051,
|
| 12 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 13 |
+
"timestamp": 1770062820.979731
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"generation": 2,
|
| 17 |
+
"primary_score": 2.4101999999999997,
|
| 18 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 19 |
+
"timestamp": 1770062821.0850384
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"generation": 3,
|
| 23 |
+
"primary_score": 2.4153000000000002,
|
| 24 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 25 |
+
"timestamp": 1770062821.1902685
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"generation": 4,
|
| 29 |
+
"primary_score": 2.4204,
|
| 30 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 31 |
+
"timestamp": 1770062821.295889
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"generation": 5,
|
| 35 |
+
"primary_score": 2.4255,
|
| 36 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 37 |
+
"timestamp": 1770062821.4016712
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"generation": 6,
|
| 41 |
+
"primary_score": 2.4305999999999996,
|
| 42 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 43 |
+
"timestamp": 1770062821.5073073
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"generation": 7,
|
| 47 |
+
"primary_score": 2.4357,
|
| 48 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 49 |
+
"timestamp": 1770062821.6131945
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"generation": 8,
|
| 53 |
+
"primary_score": 2.4408,
|
| 54 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 55 |
+
"timestamp": 1770062821.7192042
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"generation": 9,
|
| 59 |
+
"primary_score": 2.4459,
|
| 60 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 61 |
+
"timestamp": 1770062821.8252268
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"generation": 10,
|
| 65 |
+
"primary_score": 2.4509999999999996,
|
| 66 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 67 |
+
"timestamp": 1770062912.9271977
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"generation": 11,
|
| 71 |
+
"primary_score": 2.4561,
|
| 72 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 73 |
+
"timestamp": 1770062913.032786
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"generation": 12,
|
| 77 |
+
"primary_score": 2.4612,
|
| 78 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 79 |
+
"timestamp": 1770062913.1386952
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"generation": 13,
|
| 83 |
+
"primary_score": 2.4663,
|
| 84 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 85 |
+
"timestamp": 1770062913.2447174
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"generation": 14,
|
| 89 |
+
"primary_score": 2.4713999999999996,
|
| 90 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 91 |
+
"timestamp": 1770062913.350542
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"generation": 1,
|
| 95 |
+
"primary_score": 1.2957387034310313,
|
| 96 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_1/results",
|
| 97 |
+
"timestamp": 1770077545.7928894
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"generation": 2,
|
| 101 |
+
"primary_score": 0.0,
|
| 102 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_2/results",
|
| 103 |
+
"timestamp": 1770077645.315356
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"generation": 3,
|
| 107 |
+
"primary_score": 1.3779523687973616,
|
| 108 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_3/results",
|
| 109 |
+
"timestamp": 1770077714.4659176
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"generation": 4,
|
| 113 |
+
"primary_score": 1.2957192365106578,
|
| 114 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_4/results",
|
| 115 |
+
"timestamp": 1770077769.6756663
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"generation": 5,
|
| 119 |
+
"primary_score": 0.0,
|
| 120 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_5/results",
|
| 121 |
+
"timestamp": 1770077885.8956773
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"generation": 6,
|
| 125 |
+
"primary_score": 1.613114191172248,
|
| 126 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_6/results",
|
| 127 |
+
"timestamp": 1770077993.3654912
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"generation": 7,
|
| 131 |
+
"primary_score": 0.0,
|
| 132 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_7/results",
|
| 133 |
+
"timestamp": 1770078061.7032235
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"generation": 8,
|
| 137 |
+
"primary_score": 1.4247905652887678,
|
| 138 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_8/results",
|
| 139 |
+
"timestamp": 1770078229.7272182
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"generation": 9,
|
| 143 |
+
"primary_score": 1.6560915267313265,
|
| 144 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_9/results",
|
| 145 |
+
"timestamp": 1770078342.0215967
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"generation": 10,
|
| 149 |
+
"primary_score": 1.8683988559993825,
|
| 150 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_10/results",
|
| 151 |
+
"timestamp": 1770078503.12913
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"generation": 11,
|
| 155 |
+
"primary_score": 1.6664656324609348,
|
| 156 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_11/results",
|
| 157 |
+
"timestamp": 1770078548.1208487
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"generation": 12,
|
| 161 |
+
"primary_score": 1.8683988559993825,
|
| 162 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_12/results",
|
| 163 |
+
"timestamp": 1770078572.1637895
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"generation": 13,
|
| 167 |
+
"primary_score": 1.743544972870135,
|
| 168 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_13/results",
|
| 169 |
+
"timestamp": 1770078642.4562724
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"generation": 14,
|
| 173 |
+
"primary_score": 1.973952713938345,
|
| 174 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_14/results",
|
| 175 |
+
"timestamp": 1770078688.8499267
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"generation": 15,
|
| 179 |
+
"primary_score": 0.0,
|
| 180 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_15/results",
|
| 181 |
+
"timestamp": 1770078714.6741428
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"generation": 16,
|
| 185 |
+
"primary_score": 1.743544972870135,
|
| 186 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_16/results",
|
| 187 |
+
"timestamp": 1770078795.7139282
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"generation": 17,
|
| 191 |
+
"primary_score": 2.3333333333333326,
|
| 192 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_17/results",
|
| 193 |
+
"timestamp": 1770078884.1817498
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"generation": 18,
|
| 197 |
+
"primary_score": 1.8726437139594643,
|
| 198 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_18/results",
|
| 199 |
+
"timestamp": 1770078984.2956784
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"generation": 19,
|
| 203 |
+
"primary_score": 0.0,
|
| 204 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_integration_test_with_service_passive_20260203_000916/gen_19/results",
|
| 205 |
+
"timestamp": 1770079037.285556
|
| 206 |
+
}
|
| 207 |
+
],
|
| 208 |
+
"last_agent_trigger_gen": 19,
|
| 209 |
+
"total_notifications": 34,
|
| 210 |
+
"total_agent_runs": 2,
|
| 211 |
+
"last_update": 1770080831.396193
|
| 212 |
+
}
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/EVAL_AGENTS.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Evaluation Agent Memory
|
| 2 |
+
|
| 3 |
+
This document tracks insights, successful auxiliary metrics, and patterns observed across generations during the code evolution process.
|
| 4 |
+
|
| 5 |
+
## Auxiliary Metrics Design
|
| 6 |
+
|
| 7 |
+
### Metric 1: Radii Distribution Uniformity (Coefficient of Variation)
|
| 8 |
+
* **Description**: Measures the spread of radii relative to their mean. A lower value indicates more uniform circle sizes, while a higher value suggests a greater disparity (e.g., a few very large circles and many small ones). This metric helps to understand the balancing act between few large circles versus many smaller, more evenly sized circles to achieve a high total radius sum.
|
| 9 |
+
* **Implementation**: `np.std(radii) / np.mean(radii)`
|
| 10 |
+
* **Insight**: Solutions aiming for a high total radius might sometimes achieve it by making one or two circles very large, at the expense of others. This metric provides insight into whether the packing strategy prioritizes uniform sizing or allows for a wide range of sizes.
|
| 11 |
+
|
| 12 |
+
### Metric 2: Average Minimum Distance to Boundary
|
| 13 |
+
* **Description**: Calculates the average of the minimum distances from each circle's edge to the closest boundary of the unit square. This serves as an indicator of the 'robustness' or 'breathing room' of the packing.
|
| 14 |
+
* **Implementation**: For each circle (x, y, r), calculate `min(x-r, 1-(x+r), y-r, 1-(y+r))` and then average these minimums.
|
| 15 |
+
* **Insight**: A higher average minimum distance might suggest a more stable packing that is less sensitive to slight perturbations or numerical inaccuracies. Solutions with circles "kissing" the boundaries might be more fragile.
|
| 16 |
+
|
| 17 |
+
### Metric 3: Packing Area Efficiency
|
| 18 |
+
* **Description**: The sum of the areas of all packed circles, divided by the total area of the unit square (which is 1.0). This provides a measure of how much of the available area is physically covered by the circles.
|
| 19 |
+
* **Implementation**: `sum(math.pi * r^2 for r in radii) / 1.0`
|
| 20 |
+
* **Insight**: While the primary metric focuses on the sum of radii, this metric gives a direct sense of the spatial utilization. A high sum of radii doesn't always translate to the highest area coverage, especially if many small circles are present. This helps differentiate between solutions that achieve high radius sum through few large circles versus many small circles.
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## Generation 100 Evaluation Summary
|
| 25 |
+
|
| 26 |
+
**Primary Score**: 2.5405
|
| 27 |
+
|
| 28 |
+
**Calculated Auxiliary Metrics**:
|
| 29 |
+
* `radii_cv`: 0.1151 (Indicates a relatively uniform distribution of radii, not heavily skewed towards very large or very small circles.)
|
| 30 |
+
* `avg_min_dist_to_boundary`: 0.0831 (Suggests circles generally maintain a small but positive distance from the square boundaries, indicating a valid and somewhat robust packing.)
|
| 31 |
+
* `packing_area_efficiency`: 0.7902 (Approximately 79% of the unit square's area is covered by the circles, which is a good efficiency for this problem.)
|
| 32 |
+
|
| 33 |
+
**Analysis & Recommendations**:
|
| 34 |
+
|
| 35 |
+
* **Observation**: Generation 100 has a `combined_score` (sum of radii) of 2.5405. The auxiliary metrics provide further insights into the *nature* of this packing.
|
| 36 |
+
* **Radii CV (0.1151)**: This low coefficient of variation suggests the solution found a way to pack circles with relatively similar sizes, avoiding extreme size differences. This could be beneficial for stability or aesthetic reasons, though not directly optimized by the primary metric.
|
| 37 |
+
* **Avg Min Dist to Boundary (0.0831)**: The positive average minimum distance indicates that circles are not right up against the boundaries. This suggests the solution is not "cheating" by placing circles infinitesimally close to the edge, making it potentially more robust.
|
| 38 |
+
* **Packing Area Efficiency (0.7902)**: This value shows a significant portion of the square's area is covered. Comparing this to the primary score could highlight whether increasing radius sum comes at the cost of area efficiency (e.g., if many tiny circles contribute little to area but some to radius sum).
|
| 39 |
+
|
| 40 |
+
**Next Steps**:
|
| 41 |
+
Continue monitoring these auxiliary metrics across generations. If the primary score plateaus, these metrics can help identify new directions for exploration:
|
| 42 |
+
* Could increasing `packing_area_efficiency` lead to higher primary scores, or are there trade-offs?
|
| 43 |
+
* Can we achieve higher `avg_min_dist_to_boundary` while maintaining the primary score, indicating more robust solutions?
|
| 44 |
+
* How does `radii_cv` correlate with `combined_score` over evolution? Does a more uniform distribution lead to better packing in the long run, or do optimal solutions require diverse radii?
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
### Generation 9 Evaluation
|
| 48 |
+
|
| 49 |
+
**Primary Score**: 0.4256
|
| 50 |
+
|
| 51 |
+
**Auxiliary Metrics**:
|
| 52 |
+
* **Code Lines**: 67
|
| 53 |
+
* *Definition*: Number of non-empty, non-comment lines in `main.py`.
|
| 54 |
+
* *Purpose*: A proxy for code complexity and conciseness. Lower values might indicate simpler, potentially more robust or efficient solutions, or conversely, less comprehensive solutions.
|
| 55 |
+
* *Value for Gen 9*: 67 lines. This suggests a relatively concise solution.
|
| 56 |
+
|
| 57 |
+
* **Max Overlap Magnitude**: 0.0
|
| 58 |
+
* *Definition*: The largest positive difference between the sum of radii of any two circles and the distance between their centers. Returns 0.0 if no overlap.
|
| 59 |
+
* *Purpose*: Provides a "nearness to validity" score for solutions that fail due to overlaps. A value close to 0 but > 0 means it's "almost valid".
|
| 60 |
+
* *Value for Gen 9*: 0.0. This indicates that the solution successfully avoids overlaps (as confirmed by the primary evaluator likely passing this check, leading to a non-zero primary score).
|
| 61 |
+
|
| 62 |
+
* **Radii Standard Deviation**: 0.0558
|
| 63 |
+
* *Definition*: The standard deviation of the radii of all circles.
|
| 64 |
+
* *Purpose*: Measures the diversity in circle sizes. A higher standard deviation indicates a wider range of circle sizes, which might be beneficial for packing efficiency in certain configurations or for exploring different packing strategies. A very low standard deviation might suggest uniform circle sizes.
|
| 65 |
+
* *Value for Gen 9*: 0.0558. This indicates some variation in radii, but not extremely diverse.
|
| 66 |
+
|
| 67 |
+
**Overall Insights & Recommendations for Gen 9**:
|
| 68 |
+
* The solution for Generation 9 achieves a primary score of 0.4256, and crucially, has no overlaps (`max_overlap_magnitude: 0.0`). This means it's a valid packing in terms of non-overlap.
|
| 69 |
+
* The code complexity (67 lines) is moderate, which is a good baseline.
|
| 70 |
+
* The `radii_std_dev` of 0.0558 suggests that the solution is not using circles of uniform size, indicating some exploration of different circle distributions.
|
| 71 |
+
|
| 72 |
+
**Next Steps / Recommendations for Evolution**:
|
| 73 |
+
* Since overlap is 0.0, the current evolution is finding valid packings. The focus should now be on **optimization**: increasing the `reported_sum_of_radii` (primary score).
|
| 74 |
+
* Encourage solutions that might lead to a higher `radii_std_dev` if it correlates with higher primary scores in future generations, as this could indicate more effective utilization of space by varying circle sizes more dramatically.
|
| 75 |
+
* Monitor `code_lines` to ensure that increasing performance doesn't lead to excessively complex or unmanageable code. If `code_lines` starts increasing significantly without a proportional increase in primary score, it might be a sign of over-complication or inefficient strategies.
|
| 76 |
+
|
| 77 |
+
## Generation 19 Evaluation Summary
|
| 78 |
+
|
| 79 |
+
**Primary Metric Status:**
|
| 80 |
+
* The primary evaluation metric for the circle packing problem aims to **minimize the sum of radii** for 26 non-overlapping circles within a unit square. A lower score is better.
|
| 81 |
+
* Current Primary Score (Gen 19): `1.9201`
|
| 82 |
+
|
| 83 |
+
**Auxiliary Metrics Introduced:**
|
| 84 |
+
|
| 85 |
+
Based on the analysis of `evaluate_ori.py`, the primary metric provides a pass/fail for validity (no overlaps, no boundary violations) and then scores based on the sum of radii. To provide richer feedback, especially for solutions that fail validation, and to understand solution characteristics, the following auxiliary metrics were developed:
|
| 86 |
+
|
| 87 |
+
1. **`overlap_score`**
|
| 88 |
+
* **Purpose**: Quantifies the total "depth" of overlap between all pairs of circles. For valid solutions, this should be 0. For invalid solutions, a smaller positive value is better, indicating less severe overlaps. This helps differentiate between severely overlapping solutions and slightly overlapping ones, providing a gradient for optimization.
|
| 89 |
+
* **Calculation**: For each pair of circles `(i, j)`, calculate `radii[i] + radii[j] - dist(centers[i], centers[j])`. Sum all positive values (where overlap occurs).
|
| 90 |
+
* **Gen 19 Result**: `0.0` (indicates no overlaps)
|
| 91 |
+
|
| 92 |
+
2. **`boundary_violation_score`**
|
| 93 |
+
* **Purpose**: Quantifies how much circles extend beyond the unit square boundaries `[0, 1] x [0, 1]`. For valid solutions, this should be 0. For invalid solutions, a smaller positive value is better. This provides a gradient for solutions that are "almost" in bounds versus significantly out of bounds.
|
| 94 |
+
* **Calculation**: For each circle and each of its four sides, calculate `max(0, boundary_edge - limit)`. Sum these values for all violations.
|
| 95 |
+
* **Gen 19 Result**: `0.0` (indicates no boundary violations)
|
| 96 |
+
|
| 97 |
+
3. **`radius_statistics` (mean, std_dev, min, max)**
|
| 98 |
+
* **Purpose**: Provides insights into the distribution and diversity of circle radii used in the packing solution.
|
| 99 |
+
* `mean_radius`: Average size of circles.
|
| 100 |
+
* `std_dev_radius`: Standard deviation of radii, indicating the spread of circle sizes. A higher standard deviation suggests a more diverse set of circle sizes, which might be beneficial for complex packing.
|
| 101 |
+
* `min_radius`, `max_radius`: Smallest and largest radii used.
|
| 102 |
+
* **Calculation**: Standard statistical measures on the array of radii.
|
| 103 |
+
* **Gen 19 Result**:
|
| 104 |
+
* `mean_radius`: ~0.0738
|
| 105 |
+
* `std_dev_radius`: ~0.0259
|
| 106 |
+
* `min_radius`: ~0.0187
|
| 107 |
+
* `max_radius`: ~0.1026
|
| 108 |
+
(Indicates a variety of circle sizes are being used)
|
| 109 |
+
|
| 110 |
+
**Current Generation (Gen 19) Analysis & Recommendations:**
|
| 111 |
+
|
| 112 |
+
* **Status**: The solution for Generation 19 is `valid` according to the `overlap_score` and `boundary_violation_score` (both are 0.0). This means it adheres to the strict rules of non-overlapping and in-bounds circles.
|
| 113 |
+
* **Evolution Stage**: Given the primary score of 1.9201 (presumably we are trying to minimize it), and the validity of the solution, the evolution process is likely in an **OPTIMIZATION** or **CONVERGENCE** stage. The early **EXPLORATION** phase of finding *any* valid packing seems to have passed.
|
| 114 |
+
* **Insights**: The non-zero `std_dev_radius` suggests the current solution is not using uniform circles, which is a common strategy for denser packing.
|
| 115 |
+
* **Recommendations**:
|
| 116 |
+
* **For Optimization/Refinement**: Continue to focus on reducing the `primary_score` (sum of radii).
|
| 117 |
+
* **For Stagnation/Plateau**: If the `primary_score` stagnates, these auxiliary metrics can help diagnose.
|
| 118 |
+
* If `overlap_score` or `boundary_violation_score` start to increase (even slightly above 0 for new solutions), it means the evolver is drifting towards invalid solutions. This might indicate that the penalty for invalid solutions is not strong enough, or the search space is pushing towards locally optimal but invalid configurations. The *degree* of violation can be used as a soft penalty.
|
| 119 |
+
* Monitor `radius_statistics` for changes in diversity. If `std_dev_radius` approaches 0, it might indicate a loss of solution diversity, potentially leading to local optima. Encouraging a wider range of radii could lead to new packing arrangements.
|
| 120 |
+
* **Future Metric Idea**: Consider a "packing efficiency" metric that calculates the total area covered by the circles (`sum(pi * r^2)`) relative to the unit square area. This provides a direct measure of how "full" the square is. This can help distinguish between solutions that achieve a low `sum_of_radii` by using many tiny circles versus fewer, larger circles. This would be a good metric to add if we are trying to maximize the actual filled area.
|
| 121 |
+
* The code for these auxiliary metrics is located at `/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory/aux_metrics.py`.
|
| 122 |
+
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/aux_metrics.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import os
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
def calculate_overlap_score(centers: np.ndarray, radii: np.ndarray, atol: float = 1e-6) -> float:
|
| 7 |
+
"""
|
| 8 |
+
Calculates a score representing the total "depth" of overlap between circles.
|
| 9 |
+
A score of 0 means no overlap (within tolerance). Higher scores indicate more overlap.
|
| 10 |
+
"""
|
| 11 |
+
num_circles = centers.shape[0]
|
| 12 |
+
overlap_score = 0.0
|
| 13 |
+
for i in range(num_circles):
|
| 14 |
+
for j in range(i + 1, num_circles):
|
| 15 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 16 |
+
potential_overlap = radii[i] + radii[j] - dist
|
| 17 |
+
if potential_overlap > atol: # Only count positive overlap
|
| 18 |
+
overlap_score += potential_overlap
|
| 19 |
+
return overlap_score
|
| 20 |
+
|
| 21 |
+
def calculate_boundary_violation_score(centers: np.ndarray, radii: np.ndarray, atol: float = 1e-6) -> float:
|
| 22 |
+
"""
|
| 23 |
+
Calculates a score representing how much circles extend beyond the unit square [0, 1] x [0, 1].
|
| 24 |
+
A score of 0 means no boundary violation (within tolerance). Higher scores indicate more violation.
|
| 25 |
+
"""
|
| 26 |
+
boundary_violation_score = 0.0
|
| 27 |
+
for i in range(centers.shape[0]):
|
| 28 |
+
x, y = centers[i]
|
| 29 |
+
r = radii[i]
|
| 30 |
+
|
| 31 |
+
# Check left boundary (x - r < 0)
|
| 32 |
+
if x - r < -atol:
|
| 33 |
+
boundary_violation_score += abs(x - r)
|
| 34 |
+
|
| 35 |
+
# Check right boundary (x + r > 1)
|
| 36 |
+
if x + r > 1 + atol:
|
| 37 |
+
boundary_violation_score += (x + r - 1)
|
| 38 |
+
|
| 39 |
+
# Check bottom boundary (y - r < 0)
|
| 40 |
+
if y - r < -atol:
|
| 41 |
+
boundary_violation_score += abs(y - r)
|
| 42 |
+
|
| 43 |
+
# Check top boundary (y + r > 1)
|
| 44 |
+
if y + r > 1 + atol:
|
| 45 |
+
boundary_violation_score += (y + r - 1)
|
| 46 |
+
|
| 47 |
+
return boundary_violation_score
|
| 48 |
+
|
| 49 |
+
def calculate_radius_statistics(radii: np.ndarray) -> dict:
|
| 50 |
+
"""
|
| 51 |
+
Calculates statistics about the radii of the circles.
|
| 52 |
+
"""
|
| 53 |
+
if len(radii) == 0:
|
| 54 |
+
return {"mean_radius": 0.0, "std_dev_radius": 0.0, "min_radius": 0.0, "max_radius": 0.0}
|
| 55 |
+
return {
|
| 56 |
+
"mean_radius": float(np.mean(radii)),
|
| 57 |
+
"std_dev_radius": float(np.std(radii)),
|
| 58 |
+
"min_radius": float(np.min(radii)),
|
| 59 |
+
"max_radius": float(np.max(radii)),
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
def evaluate_auxiliary_metrics(generation_path: str) -> dict:
|
| 63 |
+
"""
|
| 64 |
+
Loads data from a generation's extra.npz and calculates auxiliary metrics.
|
| 65 |
+
"""
|
| 66 |
+
extra_npz_path = os.path.join(generation_path, "results", "extra.npz")
|
| 67 |
+
|
| 68 |
+
# Also get primary score for context
|
| 69 |
+
metrics_json_path = os.path.join(generation_path, "results", "metrics.json")
|
| 70 |
+
primary_score = None
|
| 71 |
+
try:
|
| 72 |
+
with open(metrics_json_path, 'r') as f:
|
| 73 |
+
metrics_data = json.load(f)
|
| 74 |
+
primary_score = metrics_data.get("combined_score")
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f"Error reading metrics.json: {e}")
|
| 77 |
+
|
| 78 |
+
if not os.path.exists(extra_npz_path):
|
| 79 |
+
print(f"Warning: extra.npz not found at {extra_npz_path}. Cannot calculate auxiliary metrics.")
|
| 80 |
+
return {
|
| 81 |
+
"primary_score": primary_score,
|
| 82 |
+
"aux_metrics": {
|
| 83 |
+
"overlap_score": -1, # Indicates missing data
|
| 84 |
+
"boundary_violation_score": -1,
|
| 85 |
+
"radius_statistics": {},
|
| 86 |
+
},
|
| 87 |
+
"diagnostics": {"status": "missing_extra_npz"},
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
data = np.load(extra_npz_path)
|
| 92 |
+
centers = data["centers"]
|
| 93 |
+
radii = data["radii"]
|
| 94 |
+
except Exception as e:
|
| 95 |
+
print(f"Error loading extra.npz: {e}")
|
| 96 |
+
return {
|
| 97 |
+
"primary_score": primary_score,
|
| 98 |
+
"aux_metrics": {
|
| 99 |
+
"overlap_score": -1,
|
| 100 |
+
"boundary_violation_score": -1,
|
| 101 |
+
"radius_statistics": {},
|
| 102 |
+
},
|
| 103 |
+
"diagnostics": {"status": f"error_loading_extra_npz: {e}"},
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
overlap = calculate_overlap_score(centers, radii)
|
| 107 |
+
boundary_violation = calculate_boundary_violation_score(centers, radii)
|
| 108 |
+
radius_stats = calculate_radius_statistics(radii)
|
| 109 |
+
|
| 110 |
+
return {
|
| 111 |
+
"primary_score": primary_score,
|
| 112 |
+
"aux_metrics": {
|
| 113 |
+
"overlap_score": overlap,
|
| 114 |
+
"boundary_violation_score": boundary_violation,
|
| 115 |
+
"radius_statistics": radius_stats,
|
| 116 |
+
},
|
| 117 |
+
"diagnostics": {"status": "success"},
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
if __name__ == "__main__":
|
| 121 |
+
# Example usage (for testing purposes)
|
| 122 |
+
# Replace with actual gen path or mock data for testing
|
| 123 |
+
current_gen_path = "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_19"
|
| 124 |
+
|
| 125 |
+
# Create dummy extra.npz and metrics.json for testing if they don't exist
|
| 126 |
+
if not os.path.exists(os.path.join(current_gen_path, "results", "extra.npz")):
|
| 127 |
+
print("Creating dummy extra.npz for testing...")
|
| 128 |
+
dummy_centers = np.array([[0.2, 0.2], [0.8, 0.8]])
|
| 129 |
+
dummy_radii = np.array([0.15, 0.15])
|
| 130 |
+
# Make them overlap
|
| 131 |
+
dummy_centers_overlap = np.array([[0.2, 0.2], [0.3, 0.2]])
|
| 132 |
+
dummy_radii_overlap = np.array([0.1, 0.1])
|
| 133 |
+
|
| 134 |
+
# Make one go out of bounds
|
| 135 |
+
dummy_centers_oob = np.array([[0.05, 0.05], [0.95, 0.95]])
|
| 136 |
+
dummy_radii_oob = np.array([0.1, 0.1])
|
| 137 |
+
|
| 138 |
+
os.makedirs(os.path.join(current_gen_path, "results"), exist_ok=True)
|
| 139 |
+
np.savez(os.path.join(current_gen_path, "results", "extra.npz"),
|
| 140 |
+
centers=dummy_centers_oob, radii=dummy_radii_oob, reported_sum=np.sum(dummy_radii_oob))
|
| 141 |
+
|
| 142 |
+
if not os.path.exists(os.path.join(current_gen_path, "results", "metrics.json")):
|
| 143 |
+
print("Creating dummy metrics.json for testing...")
|
| 144 |
+
dummy_metrics = {"combined_score": 0.3}
|
| 145 |
+
with open(os.path.join(current_gen_path, "results", "metrics.json"), 'w') as f:
|
| 146 |
+
json.dump(dummy_metrics, f)
|
| 147 |
+
|
| 148 |
+
results = evaluate_auxiliary_metrics(current_gen_path)
|
| 149 |
+
print(json.dumps(results, indent=2))
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/auxiliary_metrics.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
def calculate_code_complexity(file_path: str) -> int:
|
| 6 |
+
"""Calculates the number of non-empty, non-comment lines of code."""
|
| 7 |
+
line_count = 0
|
| 8 |
+
try:
|
| 9 |
+
with open(file_path, 'r') as f:
|
| 10 |
+
for line in f:
|
| 11 |
+
stripped_line = line.strip()
|
| 12 |
+
if stripped_line and not stripped_line.startswith('#'):
|
| 13 |
+
line_count += 1
|
| 14 |
+
except FileNotFoundError:
|
| 15 |
+
return -1 # Indicate error
|
| 16 |
+
return line_count
|
| 17 |
+
|
| 18 |
+
def calculate_overlap_magnitude(centers: np.ndarray, radii: np.ndarray, atol: float = 1e-6) -> float:
|
| 19 |
+
"""
|
| 20 |
+
Calculates the maximum overlap magnitude between any two circles.
|
| 21 |
+
Returns 0.0 if no overlap, or the maximum overlap distance if overlaps exist.
|
| 22 |
+
"""
|
| 23 |
+
n_circles = centers.shape[0]
|
| 24 |
+
max_overlap = 0.0
|
| 25 |
+
|
| 26 |
+
for i in range(n_circles):
|
| 27 |
+
for j in range(i + 1, n_circles):
|
| 28 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 29 |
+
# Overlap occurs if distance is less than sum of radii
|
| 30 |
+
overlap = (radii[i] + radii[j]) - dist
|
| 31 |
+
if overlap > max_overlap:
|
| 32 |
+
max_overlap = overlap
|
| 33 |
+
|
| 34 |
+
# Only return positive overlap, otherwise return 0.0 if no overlap or tiny negative due to precision
|
| 35 |
+
return max_overlap if max_overlap > atol else 0.0
|
| 36 |
+
|
| 37 |
+
def calculate_radii_std_dev(radii: np.ndarray) -> float:
|
| 38 |
+
"""Calculates the standard deviation of the radii."""
|
| 39 |
+
if len(radii) == 0:
|
| 40 |
+
return 0.0
|
| 41 |
+
return float(np.std(radii))
|
| 42 |
+
|
| 43 |
+
def load_packing_data(results_dir: str):
|
| 44 |
+
"""Loads centers and radii from extra.npz."""
|
| 45 |
+
extra_file = os.path.join(results_dir, "extra.npz")
|
| 46 |
+
try:
|
| 47 |
+
data = np.load(extra_file)
|
| 48 |
+
return data['centers'], data['radii']
|
| 49 |
+
except FileNotFoundError:
|
| 50 |
+
# print(f"Error: {extra_file} not found.")
|
| 51 |
+
return None, None
|
| 52 |
+
except Exception as e:
|
| 53 |
+
# print(f"Error loading {extra_file}: {e}")
|
| 54 |
+
return None, None
|
| 55 |
+
|
| 56 |
+
def run_auxiliary_metrics(gen_path: str, results_dir: str):
|
| 57 |
+
"""
|
| 58 |
+
Runs all auxiliary metrics for a given generation.
|
| 59 |
+
gen_path: Path to the generation directory (e.g., /.../gen_9/)
|
| 60 |
+
results_dir: Path to the results directory within the generation (e.g., /.../gen_9/results/)
|
| 61 |
+
"""
|
| 62 |
+
metrics = {}
|
| 63 |
+
|
| 64 |
+
# Metric 1: Code Complexity
|
| 65 |
+
main_py_path = os.path.join(gen_path, "main.py")
|
| 66 |
+
metrics["code_lines"] = calculate_code_complexity(main_py_path)
|
| 67 |
+
|
| 68 |
+
# Metric 2 & 3: Overlap Magnitude and Radii Std Dev
|
| 69 |
+
centers, radii = load_packing_data(results_dir)
|
| 70 |
+
if centers is not None and radii is not None:
|
| 71 |
+
metrics["max_overlap_magnitude"] = calculate_overlap_magnitude(centers, radii)
|
| 72 |
+
metrics["radii_std_dev"] = calculate_radii_std_dev(radii)
|
| 73 |
+
else:
|
| 74 |
+
metrics["max_overlap_magnitude"] = -1.0 # Indicate error
|
| 75 |
+
metrics["radii_std_dev"] = -1.0 # Indicate error
|
| 76 |
+
|
| 77 |
+
return metrics
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
# Example usage (replace with actual paths for testing)
|
| 81 |
+
# This block will not be executed by the agent, but is useful for local testing if needed.
|
| 82 |
+
current_gen_path = "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_9"
|
| 83 |
+
current_results_dir = "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_9/results"
|
| 84 |
+
|
| 85 |
+
aux_metrics = run_auxiliary_metrics(current_gen_path, current_results_dir)
|
| 86 |
+
print("Auxiliary Metrics:", aux_metrics)
|
| 87 |
+
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/run_aux_metrics_temp.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# Add the directory containing auxiliary_metrics.py to the Python path
|
| 6 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 7 |
+
|
| 8 |
+
import auxiliary_metrics
|
| 9 |
+
|
| 10 |
+
current_gen_path = "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_9"
|
| 11 |
+
current_results_dir = "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_9/results"
|
| 12 |
+
|
| 13 |
+
aux_metrics = auxiliary_metrics.run_auxiliary_metrics(current_gen_path, current_results_dir)
|
| 14 |
+
print("Auxiliary Metrics:", aux_metrics)
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/eval_agent_memory_backup_20260202_200614/service_state.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generation_history": [
|
| 3 |
+
{
|
| 4 |
+
"generation": 0,
|
| 5 |
+
"primary_score": 2.4,
|
| 6 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 7 |
+
"timestamp": 1770061173.8938816
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"generation": 1,
|
| 11 |
+
"primary_score": 2.4051,
|
| 12 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 13 |
+
"timestamp": 1770061173.9999762
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"generation": 2,
|
| 17 |
+
"primary_score": 2.4101999999999997,
|
| 18 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 19 |
+
"timestamp": 1770061174.1054802
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"generation": 3,
|
| 23 |
+
"primary_score": 2.4153000000000002,
|
| 24 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 25 |
+
"timestamp": 1770061174.2107964
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"generation": 4,
|
| 29 |
+
"primary_score": 2.4204,
|
| 30 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 31 |
+
"timestamp": 1770061174.3160756
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"generation": 5,
|
| 35 |
+
"primary_score": 2.4255,
|
| 36 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 37 |
+
"timestamp": 1770061174.4213583
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"generation": 6,
|
| 41 |
+
"primary_score": 2.4305999999999996,
|
| 42 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 43 |
+
"timestamp": 1770061174.5267565
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"generation": 7,
|
| 47 |
+
"primary_score": 2.4357,
|
| 48 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 49 |
+
"timestamp": 1770061174.6322458
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"generation": 8,
|
| 53 |
+
"primary_score": 2.4408,
|
| 54 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 55 |
+
"timestamp": 1770061174.7379289
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"generation": 9,
|
| 59 |
+
"primary_score": 2.4459,
|
| 60 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 61 |
+
"timestamp": 1770061174.843402
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"generation": 10,
|
| 65 |
+
"primary_score": 2.4509999999999996,
|
| 66 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 67 |
+
"timestamp": 1770061411.445956
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"generation": 11,
|
| 71 |
+
"primary_score": 2.4561,
|
| 72 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 73 |
+
"timestamp": 1770061411.5518658
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"generation": 12,
|
| 77 |
+
"primary_score": 2.4612,
|
| 78 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 79 |
+
"timestamp": 1770061411.6626825
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"generation": 13,
|
| 83 |
+
"primary_score": 2.4663,
|
| 84 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 85 |
+
"timestamp": 1770061411.7682931
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"generation": 14,
|
| 89 |
+
"primary_score": 2.4713999999999996,
|
| 90 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 91 |
+
"timestamp": 1770061411.8733294
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"generation": 15,
|
| 95 |
+
"primary_score": 2.4765,
|
| 96 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 97 |
+
"timestamp": 1770061411.978649
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"generation": 16,
|
| 101 |
+
"primary_score": 2.4816,
|
| 102 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 103 |
+
"timestamp": 1770061412.0836413
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"generation": 17,
|
| 107 |
+
"primary_score": 2.4867,
|
| 108 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 109 |
+
"timestamp": 1770061412.1885157
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"generation": 18,
|
| 113 |
+
"primary_score": 2.4917999999999996,
|
| 114 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 115 |
+
"timestamp": 1770061412.2932863
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"generation": 19,
|
| 119 |
+
"primary_score": 2.4969,
|
| 120 |
+
"results_dir": "examples/circle_packing/results/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215",
|
| 121 |
+
"timestamp": 1770061412.3980374
|
| 122 |
+
}
|
| 123 |
+
],
|
| 124 |
+
"last_agent_trigger_gen": 19,
|
| 125 |
+
"total_notifications": 20,
|
| 126 |
+
"total_agent_runs": 2,
|
| 127 |
+
"last_update": 1770062750.14768
|
| 128 |
+
}
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/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__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/edit.diff
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,94 +1,105 @@
|
| 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 |
+
+ This version uses a grid-like packing with 5 rows of circles,
|
| 16 |
+
+ with counts [5, 5, 6, 5, 5] to accommodate 26 circles. This
|
| 17 |
+
+ improves packing density and uniformity over the previous concentric
|
| 18 |
+
+ ring design.
|
| 19 |
+
+
|
| 20 |
+
Returns:
|
| 21 |
+
- Tuple of (centers, radii, sum_of_radii)
|
| 22 |
+
+ Tuple of (centers, radii)
|
| 23 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 24 |
+
radii: np.array of shape (26) with radius of each circle
|
| 25 |
+
- sum_of_radii: Sum of all radii
|
| 26 |
+
"""
|
| 27 |
+
- # Initialize arrays for 26 circles
|
| 28 |
+
n = 26
|
| 29 |
+
centers = np.zeros((n, 2))
|
| 30 |
+
+ count = 0
|
| 31 |
+
|
| 32 |
+
- # Place circles in a structured pattern
|
| 33 |
+
- # This is a simple pattern - evolution will improve this
|
| 34 |
+
+ # Based on a 6-circle wide row, the diameter is 1/6. Radius is 1/12.
|
| 35 |
+
+ r = 1.0 / 12.0
|
| 36 |
+
+ diameter = 2.0 * r
|
| 37 |
+
|
| 38 |
+
- # First, place a large circle in the center
|
| 39 |
+
- centers[0] = [0.5, 0.5]
|
| 40 |
+
+ # Define y-coordinates for 5 rows, centered in the square
|
| 41 |
+
+ y_total_height = 5 * diameter
|
| 42 |
+
+ y_margin = (1.0 - y_total_height) / 2.0
|
| 43 |
+
+ y_coords = [y_margin + r + i * diameter for i in range(5)]
|
| 44 |
+
|
| 45 |
+
- # Place 8 circles around it in a ring
|
| 46 |
+
- for i in range(8):
|
| 47 |
+
- angle = 2 * np.pi * i / 8
|
| 48 |
+
- centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 49 |
+
+ # Define x-coordinates for rows with 5 and 6 circles
|
| 50 |
+
+ x_coords_6 = [r + i * diameter for i in range(6)]
|
| 51 |
+
+ x_total_width_5 = 5 * diameter
|
| 52 |
+
+ x_margin_5 = (1.0 - x_total_width_5) / 2.0
|
| 53 |
+
+ x_coords_5 = [x_margin_5 + r + i * diameter for i in range(5)]
|
| 54 |
+
|
| 55 |
+
- # Place 16 more circles in an outer ring
|
| 56 |
+
- for i in range(16):
|
| 57 |
+
- angle = 2 * np.pi * i / 16
|
| 58 |
+
- centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 59 |
+
+ row_configs = [x_coords_5, x_coords_5, x_coords_6, x_coords_5, x_coords_5]
|
| 60 |
+
|
| 61 |
+
- # Additional positioning adjustment to make sure all circles
|
| 62 |
+
- # are inside the square and don't overlap
|
| 63 |
+
- # Clip to ensure everything is inside the unit square
|
| 64 |
+
- centers = np.clip(centers, 0.01, 0.99)
|
| 65 |
+
+ for i, x_coords in enumerate(row_configs):
|
| 66 |
+
+ y = y_coords[i]
|
| 67 |
+
+ for x in x_coords:
|
| 68 |
+
+ if count < n:
|
| 69 |
+
+ centers[count] = [x, y]
|
| 70 |
+
+ count += 1
|
| 71 |
+
|
| 72 |
+
- # Compute maximum valid radii for this configuration
|
| 73 |
+
radii = compute_max_radii(centers)
|
| 74 |
+
return centers, radii
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def compute_max_radii(centers):
|
| 78 |
+
"""
|
| 79 |
+
Compute the maximum possible radii for each circle position
|
| 80 |
+
such that they don't overlap and stay within the unit square.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
np.array of shape (n) with radius of each circle
|
| 87 |
+
"""
|
| 88 |
+
n = centers.shape[0]
|
| 89 |
+
radii = np.ones(n)
|
| 90 |
+
|
| 91 |
+
# First, limit by distance to square borders
|
| 92 |
+
for i in range(n):
|
| 93 |
+
x, y = centers[i]
|
| 94 |
+
# Distance to borders
|
| 95 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 96 |
+
|
| 97 |
+
- # Then, limit by distance to other circles
|
| 98 |
+
- # Each pair of circles with centers at distance d can have
|
| 99 |
+
- # sum of radii at most d to avoid overlap
|
| 100 |
+
- for i in range(n):
|
| 101 |
+
- for j in range(i + 1, n):
|
| 102 |
+
- dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 103 |
+
+ # Iteratively adjust radii to resolve overlaps until convergence
|
| 104 |
+
+ for _ in range(100): # Max 100 iterations to prevent infinite loops
|
| 105 |
+
+ changed = False
|
| 106 |
+
+ for i in range(n):
|
| 107 |
+
+ for j in range(i + 1, n):
|
| 108 |
+
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 109 |
+
|
| 110 |
+
- # If current radii would cause overlap
|
| 111 |
+
- if radii[i] + radii[j] > dist:
|
| 112 |
+
- # Scale both radii proportionally
|
| 113 |
+
- scale = dist / (radii[i] + radii[j])
|
| 114 |
+
- radii[i] *= scale
|
| 115 |
+
- radii[j] *= scale
|
| 116 |
+
+ # If current radii would cause overlap (with a small tolerance)
|
| 117 |
+
+ if radii[i] + radii[j] > dist + 1e-9:
|
| 118 |
+
+ # Scale both radii proportionally
|
| 119 |
+
+ if radii[i] + radii[j] > 0:
|
| 120 |
+
+ scale = dist / (radii[i] + radii[j])
|
| 121 |
+
+ radii[i] *= scale
|
| 122 |
+
+ radii[j] *= scale
|
| 123 |
+
+ changed = True
|
| 124 |
+
+
|
| 125 |
+
+ if not changed:
|
| 126 |
+
+ break # Exit if no changes were made in a full pass
|
| 127 |
+
|
| 128 |
+
return radii
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# EVOLVE-BLOCK-END
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# This part remains fixed (not evolved)
|
| 135 |
+
def run_packing():
|
| 136 |
+
"""Run the circle packing constructor for n=26"""
|
| 137 |
+
centers, radii = construct_packing()
|
| 138 |
+
# Calculate the sum of radii
|
| 139 |
+
sum_radii = np.sum(radii)
|
| 140 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/main.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
This version uses a grid-like packing with 5 rows of circles,
|
| 13 |
+
with counts [5, 5, 6, 5, 5] to accommodate 26 circles. This
|
| 14 |
+
improves packing density and uniformity over the previous concentric
|
| 15 |
+
ring design.
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
Tuple of (centers, radii)
|
| 19 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 20 |
+
radii: np.array of shape (26) with radius of each circle
|
| 21 |
+
"""
|
| 22 |
+
n = 26
|
| 23 |
+
centers = np.zeros((n, 2))
|
| 24 |
+
count = 0
|
| 25 |
+
|
| 26 |
+
# Based on a 6-circle wide row, the diameter is 1/6. Radius is 1/12.
|
| 27 |
+
r = 1.0 / 12.0
|
| 28 |
+
diameter = 2.0 * r
|
| 29 |
+
|
| 30 |
+
# Define y-coordinates for 5 rows, centered in the square
|
| 31 |
+
y_total_height = 5 * diameter
|
| 32 |
+
y_margin = (1.0 - y_total_height) / 2.0
|
| 33 |
+
y_coords = [y_margin + r + i * diameter for i in range(5)]
|
| 34 |
+
|
| 35 |
+
# Define x-coordinates for rows with 5 and 6 circles
|
| 36 |
+
x_coords_6 = [r + i * diameter for i in range(6)]
|
| 37 |
+
x_total_width_5 = 5 * diameter
|
| 38 |
+
x_margin_5 = (1.0 - x_total_width_5) / 2.0
|
| 39 |
+
x_coords_5 = [x_margin_5 + r + i * diameter for i in range(5)]
|
| 40 |
+
|
| 41 |
+
row_configs = [x_coords_5, x_coords_5, x_coords_6, x_coords_5, x_coords_5]
|
| 42 |
+
|
| 43 |
+
for i, x_coords in enumerate(row_configs):
|
| 44 |
+
y = y_coords[i]
|
| 45 |
+
for x in x_coords:
|
| 46 |
+
if count < n:
|
| 47 |
+
centers[count] = [x, y]
|
| 48 |
+
count += 1
|
| 49 |
+
|
| 50 |
+
radii = compute_max_radii(centers)
|
| 51 |
+
return centers, radii
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def compute_max_radii(centers):
|
| 55 |
+
"""
|
| 56 |
+
Compute the maximum possible radii for each circle position
|
| 57 |
+
such that they don't overlap and stay within the unit square.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
np.array of shape (n) with radius of each circle
|
| 64 |
+
"""
|
| 65 |
+
n = centers.shape[0]
|
| 66 |
+
radii = np.ones(n)
|
| 67 |
+
|
| 68 |
+
# First, limit by distance to square borders
|
| 69 |
+
for i in range(n):
|
| 70 |
+
x, y = centers[i]
|
| 71 |
+
# Distance to borders
|
| 72 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 73 |
+
|
| 74 |
+
# Iteratively adjust radii to resolve overlaps until convergence
|
| 75 |
+
for _ in range(100): # Max 100 iterations to prevent infinite loops
|
| 76 |
+
changed = False
|
| 77 |
+
for i in range(n):
|
| 78 |
+
for j in range(i + 1, n):
|
| 79 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 80 |
+
|
| 81 |
+
# If current radii would cause overlap (with a small tolerance)
|
| 82 |
+
if radii[i] + radii[j] > dist + 1e-9:
|
| 83 |
+
# Scale both radii proportionally
|
| 84 |
+
if radii[i] + radii[j] > 0:
|
| 85 |
+
scale = dist / (radii[i] + radii[j])
|
| 86 |
+
radii[i] *= scale
|
| 87 |
+
radii[j] *= scale
|
| 88 |
+
changed = True
|
| 89 |
+
|
| 90 |
+
if not changed:
|
| 91 |
+
break # Exit if no changes were made in a full pass
|
| 92 |
+
|
| 93 |
+
return radii
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# EVOLVE-BLOCK-END
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# This part remains fixed (not evolved)
|
| 100 |
+
def run_packing():
|
| 101 |
+
"""Run the circle packing constructor for n=26"""
|
| 102 |
+
centers, radii = construct_packing()
|
| 103 |
+
# Calculate the sum of radii
|
| 104 |
+
sum_radii = np.sum(radii)
|
| 105 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/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__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_1/search_replace.txt
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
I have analyzed the current circle packing implementation, its performance metrics, and the accompanying visualization. The feedback clearly indicates that the primary weaknesses are extremely low space utilization (high `gap_analysis` and low `packing_efficiency`) and an unbalanced packing density (`density_variance`). This is caused by a flawed constructor that places circles in concentric rings, with the outermost ring being pushed to the edges of the square by an aggressive `clip` operation, resulting in tiny, ineffective circles.
|
| 2 |
+
|
| 3 |
+
My improvement strategy consists of two independent but complementary changes:
|
| 4 |
+
|
| 5 |
+
1. **Replace the Constructor**: I will replace the inefficient ring-based constructor with a much more uniform grid-based approach. The new constructor arranges the 26 circles into 5 rows with a `[5, 5, 6, 5, 5]` configuration. This structure is designed to fill the unit square evenly, directly addressing the core issues of unused space and density variance. This change alone is expected to more than double the primary score (sum of radii).
|
| 6 |
+
|
| 7 |
+
2. **Improve Radii Calculation**: The existing `compute_max_radii` function uses a single-pass approach that is not guaranteed to find the optimal radii for a given set of centers. I will replace it with an iterative relaxation algorithm. This new method repeatedly adjusts radii to resolve overlaps until the configuration stabilizes, ensuring a better local optimum and maximizing the radii for the provided center positions.
|
| 8 |
+
|
| 9 |
+
These two changes work together to first create a vastly superior initial layout and then to fine-tune the radii within that layout for a maximal sum.
|
| 10 |
+
|
| 11 |
+
<NAME>
|
| 12 |
+
grid_based_constructor
|
| 13 |
+
</NAME>
|
| 14 |
+
|
| 15 |
+
<DESCRIPTION>
|
| 16 |
+
The original concentric ring constructor is highly inefficient. It creates a very non-uniform packing with large empty spaces, as confirmed by the low packing_efficiency (0.25) and gap_analysis (0.25) scores. The outer ring of circles is placed such that the `np.clip` operation forces them to the edges, resulting in minuscule radii and a low overall sum.
|
| 17 |
+
|
| 18 |
+
To address this, I am replacing the flawed constructor with a new one that arranges the 26 circles in a more uniform grid-like pattern. Specifically, it uses 5 rows of circles with a [5, 5, 6, 5, 5] configuration. This layout is designed to fill the unit square much more evenly.
|
| 19 |
+
|
| 20 |
+
The theoretical sum of radii for this configuration is approximately 2.167 (26 * 1/12), which is more than double the current score of 0.96. This change directly targets the "unused space" and "density variance" issues highlighted in the feedback. I've also corrected the docstring to accurately reflect the function's return values.
|
| 21 |
+
</DESCRIPTION>
|
| 22 |
+
|
| 23 |
+
<DIFF>
|
| 24 |
+
<<<<<<< SEARCH
|
| 25 |
+
def construct_packing():
|
| 26 |
+
"""
|
| 27 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 28 |
+
that attempts to maximize the sum of their radii.
|
| 29 |
+
|
| 30 |
+
Returns:
|
| 31 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 32 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 33 |
+
radii: np.array of shape (26) with radius of each circle
|
| 34 |
+
sum_of_radii: Sum of all radii
|
| 35 |
+
"""
|
| 36 |
+
# Initialize arrays for 26 circles
|
| 37 |
+
n = 26
|
| 38 |
+
centers = np.zeros((n, 2))
|
| 39 |
+
|
| 40 |
+
# Place circles in a structured pattern
|
| 41 |
+
# This is a simple pattern - evolution will improve this
|
| 42 |
+
|
| 43 |
+
# First, place a large circle in the center
|
| 44 |
+
centers[0] = [0.5, 0.5]
|
| 45 |
+
|
| 46 |
+
# Place 8 circles around it in a ring
|
| 47 |
+
for i in range(8):
|
| 48 |
+
angle = 2 * np.pi * i / 8
|
| 49 |
+
centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 50 |
+
|
| 51 |
+
# Place 16 more circles in an outer ring
|
| 52 |
+
for i in range(16):
|
| 53 |
+
angle = 2 * np.pi * i / 16
|
| 54 |
+
centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 55 |
+
|
| 56 |
+
# Additional positioning adjustment to make sure all circles
|
| 57 |
+
# are inside the square and don't overlap
|
| 58 |
+
# Clip to ensure everything is inside the unit square
|
| 59 |
+
centers = np.clip(centers, 0.01, 0.99)
|
| 60 |
+
|
| 61 |
+
# Compute maximum valid radii for this configuration
|
| 62 |
+
radii = compute_max_radii(centers)
|
| 63 |
+
return centers, radii
|
| 64 |
+
=======
|
| 65 |
+
def construct_packing():
|
| 66 |
+
"""
|
| 67 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 68 |
+
that attempts to maximize the sum of their radii.
|
| 69 |
+
|
| 70 |
+
This version uses a grid-like packing with 5 rows of circles,
|
| 71 |
+
with counts [5, 5, 6, 5, 5] to accommodate 26 circles. This
|
| 72 |
+
improves packing density and uniformity over the previous concentric
|
| 73 |
+
ring design.
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
Tuple of (centers, radii)
|
| 77 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 78 |
+
radii: np.array of shape (26) with radius of each circle
|
| 79 |
+
"""
|
| 80 |
+
n = 26
|
| 81 |
+
centers = np.zeros((n, 2))
|
| 82 |
+
count = 0
|
| 83 |
+
|
| 84 |
+
# Based on a 6-circle wide row, the diameter is 1/6. Radius is 1/12.
|
| 85 |
+
r = 1.0 / 12.0
|
| 86 |
+
diameter = 2.0 * r
|
| 87 |
+
|
| 88 |
+
# Define y-coordinates for 5 rows, centered in the square
|
| 89 |
+
y_total_height = 5 * diameter
|
| 90 |
+
y_margin = (1.0 - y_total_height) / 2.0
|
| 91 |
+
y_coords = [y_margin + r + i * diameter for i in range(5)]
|
| 92 |
+
|
| 93 |
+
# Define x-coordinates for rows with 5 and 6 circles
|
| 94 |
+
x_coords_6 = [r + i * diameter for i in range(6)]
|
| 95 |
+
x_total_width_5 = 5 * diameter
|
| 96 |
+
x_margin_5 = (1.0 - x_total_width_5) / 2.0
|
| 97 |
+
x_coords_5 = [x_margin_5 + r + i * diameter for i in range(5)]
|
| 98 |
+
|
| 99 |
+
row_configs = [x_coords_5, x_coords_5, x_coords_6, x_coords_5, x_coords_5]
|
| 100 |
+
|
| 101 |
+
for i, x_coords in enumerate(row_configs):
|
| 102 |
+
y = y_coords[i]
|
| 103 |
+
for x in x_coords:
|
| 104 |
+
if count < n:
|
| 105 |
+
centers[count] = [x, y]
|
| 106 |
+
count += 1
|
| 107 |
+
|
| 108 |
+
radii = compute_max_radii(centers)
|
| 109 |
+
return centers, radii
|
| 110 |
+
>>>>>>> REPLACE
|
| 111 |
+
</DIFF>
|
| 112 |
+
<NAME>
|
| 113 |
+
iterative_radii_computation
|
| 114 |
+
</NAME>
|
| 115 |
+
|
| 116 |
+
<DESCRIPTION>
|
| 117 |
+
The current `compute_max_radii` function performs a single pass over all pairs of circles to resolve overlaps. This is not guaranteed to find the maximal radii for a given set of centers. The final radii can depend on the order of comparisons, and shrinking one pair can open up space for another pair that was checked earlier, but this space is never reclaimed.
|
| 118 |
+
|
| 119 |
+
I am replacing this with an iterative algorithm. The new implementation repeatedly scans for and resolves overlaps until no more changes occur. This relaxation method allows the radii to settle into a more optimal, stable configuration for the given center positions, ensuring a better local maximum for the sum of radii. I've also added a small tolerance to handle floating point inaccuracies. This will improve the sum of radii for any given center configuration.
|
| 120 |
+
</DESCRIPTION>
|
| 121 |
+
|
| 122 |
+
<DIFF>
|
| 123 |
+
<<<<<<< SEARCH
|
| 124 |
+
def compute_max_radii(centers):
|
| 125 |
+
"""
|
| 126 |
+
Compute the maximum possible radii for each circle position
|
| 127 |
+
such that they don't overlap and stay within the unit square.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 131 |
+
|
| 132 |
+
Returns:
|
| 133 |
+
np.array of shape (n) with radius of each circle
|
| 134 |
+
"""
|
| 135 |
+
n = centers.shape[0]
|
| 136 |
+
radii = np.ones(n)
|
| 137 |
+
|
| 138 |
+
# First, limit by distance to square borders
|
| 139 |
+
for i in range(n):
|
| 140 |
+
x, y = centers[i]
|
| 141 |
+
# Distance to borders
|
| 142 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 143 |
+
|
| 144 |
+
# Then, limit by distance to other circles
|
| 145 |
+
# Each pair of circles with centers at distance d can have
|
| 146 |
+
# sum of radii at most d to avoid overlap
|
| 147 |
+
for i in range(n):
|
| 148 |
+
for j in range(i + 1, n):
|
| 149 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 150 |
+
|
| 151 |
+
# If current radii would cause overlap
|
| 152 |
+
if radii[i] + radii[j] > dist:
|
| 153 |
+
# Scale both radii proportionally
|
| 154 |
+
scale = dist / (radii[i] + radii[j])
|
| 155 |
+
radii[i] *= scale
|
| 156 |
+
radii[j] *= scale
|
| 157 |
+
|
| 158 |
+
return radii
|
| 159 |
+
=======
|
| 160 |
+
def compute_max_radii(centers):
|
| 161 |
+
"""
|
| 162 |
+
Compute the maximum possible radii for each circle position
|
| 163 |
+
such that they don't overlap and stay within the unit square.
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 167 |
+
|
| 168 |
+
Returns:
|
| 169 |
+
np.array of shape (n) with radius of each circle
|
| 170 |
+
"""
|
| 171 |
+
n = centers.shape[0]
|
| 172 |
+
radii = np.ones(n)
|
| 173 |
+
|
| 174 |
+
# First, limit by distance to square borders
|
| 175 |
+
for i in range(n):
|
| 176 |
+
x, y = centers[i]
|
| 177 |
+
# Distance to borders
|
| 178 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 179 |
+
|
| 180 |
+
# Iteratively adjust radii to resolve overlaps until convergence
|
| 181 |
+
for _ in range(100): # Max 100 iterations to prevent infinite loops
|
| 182 |
+
changed = False
|
| 183 |
+
for i in range(n):
|
| 184 |
+
for j in range(i + 1, n):
|
| 185 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 186 |
+
|
| 187 |
+
# If current radii would cause overlap (with a small tolerance)
|
| 188 |
+
if radii[i] + radii[j] > dist + 1e-9:
|
| 189 |
+
# Scale both radii proportionally
|
| 190 |
+
if radii[i] + radii[j] > 0:
|
| 191 |
+
scale = dist / (radii[i] + radii[j])
|
| 192 |
+
radii[i] *= scale
|
| 193 |
+
radii[j] *= scale
|
| 194 |
+
changed = True
|
| 195 |
+
|
| 196 |
+
if not changed:
|
| 197 |
+
break # Exit if no changes were made in a full pass
|
| 198 |
+
|
| 199 |
+
return radii
|
| 200 |
+
>>>>>>> REPLACE
|
| 201 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/edit.diff
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,101 +1,96 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
+from itertools import product
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def construct_packing():
|
| 12 |
+
"""
|
| 13 |
+
- Construct a specific arrangement of 26 circles in a unit square
|
| 14 |
+
- that attempts to maximize the sum of their radii.
|
| 15 |
+
+ Constructs an arrangement of 26 circles based on the highly effective
|
| 16 |
+
+ 5x5 grid structure, which is optimal for 25 circles, and adds a 26th
|
| 17 |
+
+ circle into a central interstitial space. This approach directly
|
| 18 |
+
+ addresses feedback on poor corner and edge utilization from previous
|
| 19 |
+
+ concentric models.
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
- Tuple of (centers, radii, sum_of_radii)
|
| 23 |
+
+ Tuple of (centers, radii)
|
| 24 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 25 |
+
radii: np.array of shape (26) with radius of each circle
|
| 26 |
+
- sum_of_radii: Sum of all radii
|
| 27 |
+
"""
|
| 28 |
+
- # Initialize arrays for 26 circles
|
| 29 |
+
n = 26
|
| 30 |
+
centers = np.zeros((n, 2))
|
| 31 |
+
|
| 32 |
+
- # Place circles in a structured pattern
|
| 33 |
+
- # This is a simple pattern - evolution will improve this
|
| 34 |
+
+ # Create 25 centers on a 5x5 grid. This is the known optimal packing for n=25.
|
| 35 |
+
+ # The grid coordinates are spaced to fill the unit square perfectly with
|
| 36 |
+
+ # circles of radius 0.1, centered at [0.1, 0.3, 0.5, 0.7, 0.9].
|
| 37 |
+
+ coords = np.linspace(0.1, 0.9, 5)
|
| 38 |
+
+ grid_centers = np.array(list(product(coords, coords)))
|
| 39 |
+
+ centers[:25] = grid_centers
|
| 40 |
+
|
| 41 |
+
- # Place central circle
|
| 42 |
+
- centers[0] = [0.5, 0.5]
|
| 43 |
+
+ # Place the 26th circle in one of the four central interstitial gaps.
|
| 44 |
+
+ # These gaps are the most spacious. We choose the one at (0.4, 0.4).
|
| 45 |
+
+ centers[25] = [0.4, 0.4]
|
| 46 |
+
|
| 47 |
+
- # Place 9 circles in an inner ring (total 1 + 9 = 10)
|
| 48 |
+
- num_inner_ring = 9
|
| 49 |
+
- inner_ring_radius_multiplier = 0.25
|
| 50 |
+
- for i in range(num_inner_ring):
|
| 51 |
+
- angle = 2 * np.pi * i / num_inner_ring
|
| 52 |
+
- centers[i + 1] = [0.5 + inner_ring_radius_multiplier * np.cos(angle),
|
| 53 |
+
- 0.5 + inner_ring_radius_multiplier * np.sin(angle)]
|
| 54 |
+
-
|
| 55 |
+
- # Place 16 circles in an outer ring (total 1 + 9 + 16 = 26)
|
| 56 |
+
- num_outer_ring = 16
|
| 57 |
+
- outer_ring_radius_multiplier = 0.47 # Increased to push circles closer to boundary
|
| 58 |
+
- for i in range(num_outer_ring):
|
| 59 |
+
- angle = 2 * np.pi * i / num_outer_ring
|
| 60 |
+
- centers[i + 1 + num_inner_ring] = [0.5 + outer_ring_radius_multiplier * np.cos(angle),
|
| 61 |
+
- 0.5 + outer_ring_radius_multiplier * np.sin(angle)]
|
| 62 |
+
-
|
| 63 |
+
- # Additional positioning adjustment to make sure all circles
|
| 64 |
+
- # are inside the square and don't overlap
|
| 65 |
+
- # The compute_max_radii function already handles boundary constraints.
|
| 66 |
+
- # Removing clipping allows circles to properly utilize the square's edges.
|
| 67 |
+
- # centers = np.clip(centers, 0.01, 0.99)
|
| 68 |
+
-
|
| 69 |
+
- # Compute maximum valid radii for this configuration
|
| 70 |
+
+ # Compute the maximum radii for this optimized initial 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 |
+
+ Compute the maximum possible radii for a given set of circle centers.
|
| 80 |
+
+ This function uses an iterative relaxation method to ensure no circles
|
| 81 |
+
+ overlap and all circles remain within the unit square. The iterative
|
| 82 |
+
+ loop is essential for convergence to a valid, locally optimal solution.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 86 |
+
|
| 87 |
+
Returns:
|
| 88 |
+
- np.array of shape (n) with radius of each circle
|
| 89 |
+
+ np.array of shape (n) with the radius of each circle.
|
| 90 |
+
"""
|
| 91 |
+
n = centers.shape[0]
|
| 92 |
+
- radii = np.ones(n)
|
| 93 |
+
+ radii = np.zeros(n)
|
| 94 |
+
|
| 95 |
+
- # First, limit by distance to square borders
|
| 96 |
+
+ # Initialize radii based on the distance to the square's boundaries.
|
| 97 |
+
for i in range(n):
|
| 98 |
+
x, y = centers[i]
|
| 99 |
+
- # Distance to borders
|
| 100 |
+
- radii[i] = min(x, y, 1 - x, 1 - y)
|
| 101 |
+
+ radii[i] = min(x, 1 - x, y, 1 - y)
|
| 102 |
+
|
| 103 |
+
- # Then, limit by distance to other circles
|
| 104 |
+
- # Each pair of circles with centers at distance d can have
|
| 105 |
+
- # sum of radii at most d to avoid overlap
|
| 106 |
+
- for i in range(n):
|
| 107 |
+
- for j in range(i + 1, n):
|
| 108 |
+
- dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 109 |
+
+ # Iteratively adjust radii to resolve all overlaps until the packing is stable.
|
| 110 |
+
+ # This loop is crucial for finding a valid solution.
|
| 111 |
+
+ for _ in range(200): # Increased iterations for better convergence
|
| 112 |
+
+ changed = False
|
| 113 |
+
+ for i in range(n):
|
| 114 |
+
+ for j in range(i + 1, n):
|
| 115 |
+
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 116 |
+
|
| 117 |
+
- # If current radii would cause overlap
|
| 118 |
+
- if radii[i] + radii[j] > dist:
|
| 119 |
+
- # Scale both radii proportionally
|
| 120 |
+
- scale = dist / (radii[i] + radii[j])
|
| 121 |
+
- radii[i] *= scale
|
| 122 |
+
- radii[j] *= scale
|
| 123 |
+
+ # If circles overlap (with a small tolerance for floating point errors)
|
| 124 |
+
+ if radii[i] + radii[j] > dist + 1e-12:
|
| 125 |
+
+ # Scale both radii down proportionally to resolve the overlap.
|
| 126 |
+
+ # This method is stable and converges well.
|
| 127 |
+
+ total_radius = radii[i] + radii[j]
|
| 128 |
+
+ if total_radius > 0:
|
| 129 |
+
+ scale = dist / total_radius
|
| 130 |
+
+ radii[i] *= scale
|
| 131 |
+
+ radii[j] *= scale
|
| 132 |
+
+ changed = True
|
| 133 |
+
+
|
| 134 |
+
+ if not changed:
|
| 135 |
+
+ # If a full pass is made with no changes, the configuration is stable.
|
| 136 |
+
+ break
|
| 137 |
+
|
| 138 |
+
return radii
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# EVOLVE-BLOCK-END
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# This part remains fixed (not evolved)
|
| 145 |
+
def run_packing():
|
| 146 |
+
"""Run the circle packing constructor for n=26"""
|
| 147 |
+
centers, radii = construct_packing()
|
| 148 |
+
# Calculate the sum of radii
|
| 149 |
+
sum_radii = np.sum(radii)
|
| 150 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/main.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
from itertools import product
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def construct_packing():
|
| 9 |
+
"""
|
| 10 |
+
Constructs an arrangement of 26 circles based on the highly effective
|
| 11 |
+
5x5 grid structure, which is optimal for 25 circles, and adds a 26th
|
| 12 |
+
circle into a central interstitial space. This approach directly
|
| 13 |
+
addresses feedback on poor corner and edge utilization from previous
|
| 14 |
+
concentric models.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
Tuple of (centers, radii)
|
| 18 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 19 |
+
radii: np.array of shape (26) with radius of each circle
|
| 20 |
+
"""
|
| 21 |
+
n = 26
|
| 22 |
+
centers = np.zeros((n, 2))
|
| 23 |
+
|
| 24 |
+
# Create 25 centers on a 5x5 grid. This is the known optimal packing for n=25.
|
| 25 |
+
# The grid coordinates are spaced to fill the unit square perfectly with
|
| 26 |
+
# circles of radius 0.1, centered at [0.1, 0.3, 0.5, 0.7, 0.9].
|
| 27 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 28 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 29 |
+
centers[:25] = grid_centers
|
| 30 |
+
|
| 31 |
+
# Place the 26th circle in one of the four central interstitial gaps.
|
| 32 |
+
# These gaps are the most spacious. We choose the one at (0.4, 0.4).
|
| 33 |
+
centers[25] = [0.4, 0.4]
|
| 34 |
+
|
| 35 |
+
# Compute the maximum radii for this optimized initial configuration.
|
| 36 |
+
radii = compute_max_radii(centers)
|
| 37 |
+
return centers, radii
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def compute_max_radii(centers):
|
| 41 |
+
"""
|
| 42 |
+
Compute the maximum possible radii for a given set of circle centers.
|
| 43 |
+
This function uses an iterative relaxation method to ensure no circles
|
| 44 |
+
overlap and all circles remain within the unit square. The iterative
|
| 45 |
+
loop is essential for convergence to a valid, locally optimal solution.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
np.array of shape (n) with the radius of each circle.
|
| 52 |
+
"""
|
| 53 |
+
n = centers.shape[0]
|
| 54 |
+
radii = np.zeros(n)
|
| 55 |
+
|
| 56 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 57 |
+
for i in range(n):
|
| 58 |
+
x, y = centers[i]
|
| 59 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 60 |
+
|
| 61 |
+
# Iteratively adjust radii to resolve all overlaps until the packing is stable.
|
| 62 |
+
# This loop is crucial for finding a valid solution.
|
| 63 |
+
for _ in range(200): # Increased iterations for better convergence
|
| 64 |
+
changed = False
|
| 65 |
+
for i in range(n):
|
| 66 |
+
for j in range(i + 1, n):
|
| 67 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 68 |
+
|
| 69 |
+
# If circles overlap (with a small tolerance for floating point errors)
|
| 70 |
+
if radii[i] + radii[j] > dist + 1e-12:
|
| 71 |
+
# Scale both radii down proportionally to resolve the overlap.
|
| 72 |
+
# This method is stable and converges well.
|
| 73 |
+
total_radius = radii[i] + radii[j]
|
| 74 |
+
if total_radius > 0:
|
| 75 |
+
scale = dist / total_radius
|
| 76 |
+
radii[i] *= scale
|
| 77 |
+
radii[j] *= scale
|
| 78 |
+
changed = True
|
| 79 |
+
|
| 80 |
+
if not changed:
|
| 81 |
+
# If a full pass is made with no changes, the configuration is stable.
|
| 82 |
+
break
|
| 83 |
+
|
| 84 |
+
return radii
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# EVOLVE-BLOCK-END
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
# This part remains fixed (not evolved)
|
| 91 |
+
def run_packing():
|
| 92 |
+
"""Run the circle packing constructor for n=26"""
|
| 93 |
+
centers, radii = construct_packing()
|
| 94 |
+
# Calculate the sum of radii
|
| 95 |
+
sum_radii = np.sum(radii)
|
| 96 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/original.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
# Place central circle
|
| 26 |
+
centers[0] = [0.5, 0.5]
|
| 27 |
+
|
| 28 |
+
# Place 9 circles in an inner ring (total 1 + 9 = 10)
|
| 29 |
+
num_inner_ring = 9
|
| 30 |
+
inner_ring_radius_multiplier = 0.25
|
| 31 |
+
for i in range(num_inner_ring):
|
| 32 |
+
angle = 2 * np.pi * i / num_inner_ring
|
| 33 |
+
centers[i + 1] = [0.5 + inner_ring_radius_multiplier * np.cos(angle),
|
| 34 |
+
0.5 + inner_ring_radius_multiplier * np.sin(angle)]
|
| 35 |
+
|
| 36 |
+
# Place 16 circles in an outer ring (total 1 + 9 + 16 = 26)
|
| 37 |
+
num_outer_ring = 16
|
| 38 |
+
outer_ring_radius_multiplier = 0.47 # Increased to push circles closer to boundary
|
| 39 |
+
for i in range(num_outer_ring):
|
| 40 |
+
angle = 2 * np.pi * i / num_outer_ring
|
| 41 |
+
centers[i + 1 + num_inner_ring] = [0.5 + outer_ring_radius_multiplier * np.cos(angle),
|
| 42 |
+
0.5 + outer_ring_radius_multiplier * np.sin(angle)]
|
| 43 |
+
|
| 44 |
+
# Additional positioning adjustment to make sure all circles
|
| 45 |
+
# are inside the square and don't overlap
|
| 46 |
+
# The compute_max_radii function already handles boundary constraints.
|
| 47 |
+
# Removing clipping allows circles to properly utilize the square's edges.
|
| 48 |
+
# centers = np.clip(centers, 0.01, 0.99)
|
| 49 |
+
|
| 50 |
+
# Compute maximum valid radii for this configuration
|
| 51 |
+
radii = compute_max_radii(centers)
|
| 52 |
+
return centers, radii
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def compute_max_radii(centers):
|
| 56 |
+
"""
|
| 57 |
+
Compute the maximum possible radii for each circle position
|
| 58 |
+
such that they don't overlap and stay within the unit square.
|
| 59 |
+
|
| 60 |
+
Args:
|
| 61 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
np.array of shape (n) with radius of each circle
|
| 65 |
+
"""
|
| 66 |
+
n = centers.shape[0]
|
| 67 |
+
radii = np.ones(n)
|
| 68 |
+
|
| 69 |
+
# First, limit by distance to square borders
|
| 70 |
+
for i in range(n):
|
| 71 |
+
x, y = centers[i]
|
| 72 |
+
# Distance to borders
|
| 73 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 74 |
+
|
| 75 |
+
# Then, limit by distance to other circles
|
| 76 |
+
# Each pair of circles with centers at distance d can have
|
| 77 |
+
# sum of radii at most d to avoid overlap
|
| 78 |
+
for i in range(n):
|
| 79 |
+
for j in range(i + 1, n):
|
| 80 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 81 |
+
|
| 82 |
+
# If current radii would cause overlap
|
| 83 |
+
if radii[i] + radii[j] > dist:
|
| 84 |
+
# Scale both radii proportionally
|
| 85 |
+
scale = dist / (radii[i] + radii[j])
|
| 86 |
+
radii[i] *= scale
|
| 87 |
+
radii[j] *= scale
|
| 88 |
+
|
| 89 |
+
return radii
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# EVOLVE-BLOCK-END
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# This part remains fixed (not evolved)
|
| 96 |
+
def run_packing():
|
| 97 |
+
"""Run the circle packing constructor for n=26"""
|
| 98 |
+
centers, radii = construct_packing()
|
| 99 |
+
# Calculate the sum of radii
|
| 100 |
+
sum_radii = np.sum(radii)
|
| 101 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_10/search_replace.txt
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
revert_to_proven_grid_packing
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DESCRIPTION>
|
| 6 |
+
The current implementation uses a concentric ring pattern and a non-iterative radius calculation, resulting in a low score of 1.6273 and poor metrics (especially corner utilization: 0.185, and gap analysis: 0.388). This change reverts to the strategy from the best-performing prior solution (score 1.9201) to address these issues.
|
| 7 |
+
|
| 8 |
+
This involves two key improvements:
|
| 9 |
+
1. **Placement Strategy:** The concentric ring pattern is replaced with a 5x5 grid plus one interstitial circle. This structure inherently utilizes the entire square, including corners and edges, directly addressing the feedback about poor boundary utilization and large unused space.
|
| 10 |
+
2. **Radius Calculation:** The flawed, single-pass `compute_max_radii` is replaced with a correct iterative relaxation algorithm. The new function iterates until the radii converge, ensuring a valid and locally-optimal packing, which is critical for maximizing the sum of radii.
|
| 11 |
+
|
| 12 |
+
This combined change holistically targets the weaknesses identified in the feedback and restores a proven, high-performance architecture.
|
| 13 |
+
</DESCRIPTION>
|
| 14 |
+
|
| 15 |
+
<DIFF>
|
| 16 |
+
<<<<<<< SEARCH
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def construct_packing():
|
| 21 |
+
"""
|
| 22 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 23 |
+
that attempts to maximize the sum of their radii.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 27 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 28 |
+
radii: np.array of shape (26) with radius of each circle
|
| 29 |
+
sum_of_radii: Sum of all radii
|
| 30 |
+
"""
|
| 31 |
+
# Initialize arrays for 26 circles
|
| 32 |
+
n = 26
|
| 33 |
+
centers = np.zeros((n, 2))
|
| 34 |
+
|
| 35 |
+
# Place circles in a structured pattern
|
| 36 |
+
# This is a simple pattern - evolution will improve this
|
| 37 |
+
|
| 38 |
+
# Place central circle
|
| 39 |
+
centers[0] = [0.5, 0.5]
|
| 40 |
+
|
| 41 |
+
# Place 9 circles in an inner ring (total 1 + 9 = 10)
|
| 42 |
+
num_inner_ring = 9
|
| 43 |
+
inner_ring_radius_multiplier = 0.25
|
| 44 |
+
for i in range(num_inner_ring):
|
| 45 |
+
angle = 2 * np.pi * i / num_inner_ring
|
| 46 |
+
centers[i + 1] = [0.5 + inner_ring_radius_multiplier * np.cos(angle),
|
| 47 |
+
0.5 + inner_ring_radius_multiplier * np.sin(angle)]
|
| 48 |
+
|
| 49 |
+
# Place 16 circles in an outer ring (total 1 + 9 + 16 = 26)
|
| 50 |
+
num_outer_ring = 16
|
| 51 |
+
outer_ring_radius_multiplier = 0.47 # Increased to push circles closer to boundary
|
| 52 |
+
for i in range(num_outer_ring):
|
| 53 |
+
angle = 2 * np.pi * i / num_outer_ring
|
| 54 |
+
centers[i + 1 + num_inner_ring] = [0.5 + outer_ring_radius_multiplier * np.cos(angle),
|
| 55 |
+
0.5 + outer_ring_radius_multiplier * np.sin(angle)]
|
| 56 |
+
|
| 57 |
+
# Additional positioning adjustment to make sure all circles
|
| 58 |
+
# are inside the square and don't overlap
|
| 59 |
+
# The compute_max_radii function already handles boundary constraints.
|
| 60 |
+
# Removing clipping allows circles to properly utilize the square's edges.
|
| 61 |
+
# centers = np.clip(centers, 0.01, 0.99)
|
| 62 |
+
|
| 63 |
+
# Compute maximum valid radii for this configuration
|
| 64 |
+
radii = compute_max_radii(centers)
|
| 65 |
+
return centers, radii
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def compute_max_radii(centers):
|
| 69 |
+
"""
|
| 70 |
+
Compute the maximum possible radii for each circle position
|
| 71 |
+
such that they don't overlap and stay within the unit square.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 75 |
+
|
| 76 |
+
Returns:
|
| 77 |
+
np.array of shape (n) with radius of each circle
|
| 78 |
+
"""
|
| 79 |
+
n = centers.shape[0]
|
| 80 |
+
radii = np.ones(n)
|
| 81 |
+
|
| 82 |
+
# First, limit by distance to square borders
|
| 83 |
+
for i in range(n):
|
| 84 |
+
x, y = centers[i]
|
| 85 |
+
# Distance to borders
|
| 86 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 87 |
+
|
| 88 |
+
# Then, limit by distance to other circles
|
| 89 |
+
# Each pair of circles with centers at distance d can have
|
| 90 |
+
# sum of radii at most d to avoid overlap
|
| 91 |
+
for i in range(n):
|
| 92 |
+
for j in range(i + 1, n):
|
| 93 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 94 |
+
|
| 95 |
+
# If current radii would cause overlap
|
| 96 |
+
if radii[i] + radii[j] > dist:
|
| 97 |
+
# Scale both radii proportionally
|
| 98 |
+
scale = dist / (radii[i] + radii[j])
|
| 99 |
+
radii[i] *= scale
|
| 100 |
+
radii[j] *= scale
|
| 101 |
+
|
| 102 |
+
return radii
|
| 103 |
+
=======
|
| 104 |
+
import numpy as np
|
| 105 |
+
from itertools import product
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def construct_packing():
|
| 109 |
+
"""
|
| 110 |
+
Constructs an arrangement of 26 circles based on the highly effective
|
| 111 |
+
5x5 grid structure, which is optimal for 25 circles, and adds a 26th
|
| 112 |
+
circle into a central interstitial space. This approach directly
|
| 113 |
+
addresses feedback on poor corner and edge utilization from previous
|
| 114 |
+
concentric models.
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
Tuple of (centers, radii)
|
| 118 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 119 |
+
radii: np.array of shape (26) with radius of each circle
|
| 120 |
+
"""
|
| 121 |
+
n = 26
|
| 122 |
+
centers = np.zeros((n, 2))
|
| 123 |
+
|
| 124 |
+
# Create 25 centers on a 5x5 grid. This is the known optimal packing for n=25.
|
| 125 |
+
# The grid coordinates are spaced to fill the unit square perfectly with
|
| 126 |
+
# circles of radius 0.1, centered at [0.1, 0.3, 0.5, 0.7, 0.9].
|
| 127 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 128 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 129 |
+
centers[:25] = grid_centers
|
| 130 |
+
|
| 131 |
+
# Place the 26th circle in one of the four central interstitial gaps.
|
| 132 |
+
# These gaps are the most spacious. We choose the one at (0.4, 0.4).
|
| 133 |
+
centers[25] = [0.4, 0.4]
|
| 134 |
+
|
| 135 |
+
# Compute the maximum radii for this optimized initial configuration.
|
| 136 |
+
radii = compute_max_radii(centers)
|
| 137 |
+
return centers, radii
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def compute_max_radii(centers):
|
| 141 |
+
"""
|
| 142 |
+
Compute the maximum possible radii for a given set of circle centers.
|
| 143 |
+
This function uses an iterative relaxation method to ensure no circles
|
| 144 |
+
overlap and all circles remain within the unit square. The iterative
|
| 145 |
+
loop is essential for convergence to a valid, locally optimal solution.
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
np.array of shape (n) with the radius of each circle.
|
| 152 |
+
"""
|
| 153 |
+
n = centers.shape[0]
|
| 154 |
+
radii = np.zeros(n)
|
| 155 |
+
|
| 156 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 157 |
+
for i in range(n):
|
| 158 |
+
x, y = centers[i]
|
| 159 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 160 |
+
|
| 161 |
+
# Iteratively adjust radii to resolve all overlaps until the packing is stable.
|
| 162 |
+
# This loop is crucial for finding a valid solution.
|
| 163 |
+
for _ in range(200): # Increased iterations for better convergence
|
| 164 |
+
changed = False
|
| 165 |
+
for i in range(n):
|
| 166 |
+
for j in range(i + 1, n):
|
| 167 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 168 |
+
|
| 169 |
+
# If circles overlap (with a small tolerance for floating point errors)
|
| 170 |
+
if radii[i] + radii[j] > dist + 1e-12:
|
| 171 |
+
# Scale both radii down proportionally to resolve the overlap.
|
| 172 |
+
# This method is stable and converges well.
|
| 173 |
+
total_radius = radii[i] + radii[j]
|
| 174 |
+
if total_radius > 0:
|
| 175 |
+
scale = dist / total_radius
|
| 176 |
+
radii[i] *= scale
|
| 177 |
+
radii[j] *= scale
|
| 178 |
+
changed = True
|
| 179 |
+
|
| 180 |
+
if not changed:
|
| 181 |
+
# If a full pass is made with no changes, the configuration is stable.
|
| 182 |
+
break
|
| 183 |
+
|
| 184 |
+
return radii
|
| 185 |
+
>>>>>>> REPLACE
|
| 186 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/edit.diff
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,327 +1,356 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 12 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 13 |
+
with a localized refinement stage.
|
| 14 |
+
"""
|
| 15 |
+
def __init__(self, num_circles=26):
|
| 16 |
+
self.n = num_circles
|
| 17 |
+
self.centers = np.zeros((self.n, 2))
|
| 18 |
+
self.radii = np.zeros(self.n)
|
| 19 |
+
|
| 20 |
+
@staticmethod
|
| 21 |
+
def _compute_max_radii_static(centers, n):
|
| 22 |
+
"""
|
| 23 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 24 |
+
an iterative growth and constraint resolution method. This static version
|
| 25 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 26 |
+
performance and precision, based on the best prior implementation.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 30 |
+
n (int): Number of circles.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 34 |
+
"""
|
| 35 |
+
radii = np.zeros(n)
|
| 36 |
+
|
| 37 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 38 |
+
growth_factor_initial = 1.005
|
| 39 |
+
growth_factor_final = 1.002
|
| 40 |
+
tolerance_initial = 1e-7
|
| 41 |
+
tolerance_final = 1e-10
|
| 42 |
+
|
| 43 |
+
outer_iterations = 400
|
| 44 |
+
inner_iterations = 20
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
for outer_iter_idx in range(outer_iterations):
|
| 52 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 53 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 54 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 55 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 56 |
+
|
| 57 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 58 |
+
|
| 59 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 60 |
+
constraints_changed = False
|
| 61 |
+
|
| 62 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > tolerance_final:
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
|
| 82 |
+
if not constraints_changed:
|
| 83 |
+
break
|
| 84 |
+
return radii
|
| 85 |
+
|
| 86 |
+
def _initial_grid_placement(self):
|
| 87 |
+
"""
|
| 88 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 89 |
+
"""
|
| 90 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 91 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 92 |
+
self.centers[:25] = grid_centers
|
| 93 |
+
|
| 94 |
+
def _find_optimal_26th_circle_position(self):
|
| 95 |
+
"""
|
| 96 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 97 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 98 |
+
"""
|
| 99 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 100 |
+
|
| 101 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 102 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 103 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 104 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 105 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 106 |
+
|
| 107 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 108 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 109 |
+
|
| 110 |
+
- interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 111 |
+
+ # Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 112 |
+
+ interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 113 |
+
|
| 114 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 115 |
+
# Explore a broader region first to identify promising areas.
|
| 116 |
+
coarse_delta = 0.05
|
| 117 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 118 |
+
|
| 119 |
+
coarse_candidate_points = []
|
| 120 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 121 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 122 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 123 |
+
|
| 124 |
+
for candidate_pos in coarse_candidate_points:
|
| 125 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 126 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 127 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 128 |
+
current_sum_radii = np.sum(trial_radii)
|
| 129 |
+
|
| 130 |
+
if current_sum_radii > best_sum_radii:
|
| 131 |
+
best_sum_radii = current_sum_radii
|
| 132 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 133 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 134 |
+
|
| 135 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 136 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 137 |
+
fine_delta = 0.01
|
| 138 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 139 |
+
|
| 140 |
+
fine_candidate_points = []
|
| 141 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 142 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 143 |
+
|
| 144 |
+
for candidate_pos in fine_candidate_points:
|
| 145 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 146 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 147 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 148 |
+
current_sum_radii = np.sum(trial_radii)
|
| 149 |
+
|
| 150 |
+
if current_sum_radii > best_sum_radii:
|
| 151 |
+
best_sum_radii = current_sum_radii
|
| 152 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 153 |
+
|
| 154 |
+
self.centers[25] = optimal_26th_pos
|
| 155 |
+
|
| 156 |
+
return self.centers, best_sum_radii
|
| 157 |
+
|
| 158 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 159 |
+
"""
|
| 160 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 161 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 162 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 163 |
+
+ This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 164 |
+
"""
|
| 165 |
+
current_centers = np.copy(initial_centers)
|
| 166 |
+
- current_sum_radii = initial_sum_radii
|
| 167 |
+
+ # Compute radii for initial stress calculation and consistent sum.
|
| 168 |
+
+ current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 169 |
+
+ current_sum_radii = np.sum(current_radii)
|
| 170 |
+
|
| 171 |
+
best_centers_local = np.copy(current_centers)
|
| 172 |
+
best_sum_radii_local = current_sum_radii
|
| 173 |
+
|
| 174 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 175 |
+
num_iterations = 150
|
| 176 |
+
initial_step_size = 0.005
|
| 177 |
+
initial_temp = 0.0001
|
| 178 |
+
cooling_rate = 0.99
|
| 179 |
+
|
| 180 |
+
step_size = initial_step_size
|
| 181 |
+
temp = initial_temp
|
| 182 |
+
|
| 183 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 184 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 185 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 186 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 187 |
+
|
| 188 |
+
- for _ in range(num_iterations):
|
| 189 |
+
- # Select a random circle from the cluster to perturb
|
| 190 |
+
- idx_to_move = np.random.choice(cluster_indices)
|
| 191 |
+
+ # Parameters for dynamic step size adjustment
|
| 192 |
+
+ acceptance_window = 30
|
| 193 |
+
+ acceptance_count = 0
|
| 194 |
+
+ target_acceptance_rate = 0.44
|
| 195 |
+
+ adjustment_factor = 1.05
|
| 196 |
+
+
|
| 197 |
+
+ for i in range(num_iterations):
|
| 198 |
+
+ # Stress-based selection within the cluster
|
| 199 |
+
+ cluster_radii = current_radii[cluster_indices]
|
| 200 |
+
+ inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 201 |
+
+ weights = (inv_radii - np.min(inv_radii))**2
|
| 202 |
+
+ if np.sum(weights) > 1e-9:
|
| 203 |
+
+ probabilities = weights / np.sum(weights)
|
| 204 |
+
+ idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 205 |
+
+ else:
|
| 206 |
+
+ idx_to_move = np.random.choice(cluster_indices)
|
| 207 |
+
+
|
| 208 |
+
|
| 209 |
+
trial_centers = np.copy(current_centers)
|
| 210 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 211 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 212 |
+
|
| 213 |
+
# Evaluate the new configuration
|
| 214 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 215 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 216 |
+
|
| 217 |
+
# Metropolis-Hastings acceptance criterion
|
| 218 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 219 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 220 |
+
current_centers = trial_centers
|
| 221 |
+
+ current_radii = trial_radii # Update radii for stress calculation
|
| 222 |
+
current_sum_radii = trial_sum_radii
|
| 223 |
+
+ acceptance_count += 1
|
| 224 |
+
|
| 225 |
+
if current_sum_radii > best_sum_radii_local:
|
| 226 |
+
best_sum_radii_local = current_sum_radii
|
| 227 |
+
best_centers_local = np.copy(current_centers)
|
| 228 |
+
+
|
| 229 |
+
+ # Dynamic step size adjustment
|
| 230 |
+
+ if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 231 |
+
+ acceptance_rate = acceptance_count / acceptance_window
|
| 232 |
+
+ if acceptance_rate > target_acceptance_rate:
|
| 233 |
+
+ step_size *= adjustment_factor
|
| 234 |
+
+ else:
|
| 235 |
+
+ step_size /= adjustment_factor
|
| 236 |
+
+ acceptance_count = 0
|
| 237 |
+
|
| 238 |
+
temp *= cooling_rate
|
| 239 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 240 |
+
|
| 241 |
+
return best_centers_local, best_sum_radii_local
|
| 242 |
+
|
| 243 |
+
def _global_refinement_sa(self, initial_centers):
|
| 244 |
+
"""
|
| 245 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 246 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 247 |
+
settle the entire packing into a better local optimum. This technique was
|
| 248 |
+
present in prior high-scoring implementations.
|
| 249 |
+
"""
|
| 250 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 251 |
+
sa_iterations = 300
|
| 252 |
+
sa_initial_temp = 5e-6
|
| 253 |
+
sa_cooling_rate = 0.99
|
| 254 |
+
sa_initial_step_size = 0.001
|
| 255 |
+
|
| 256 |
+
current_centers = np.copy(initial_centers)
|
| 257 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 258 |
+
current_sum_radii = np.sum(current_radii)
|
| 259 |
+
|
| 260 |
+
best_centers = np.copy(current_centers)
|
| 261 |
+
best_sum_radii = current_sum_radii
|
| 262 |
+
|
| 263 |
+
temp = sa_initial_temp
|
| 264 |
+
step_size = sa_initial_step_size
|
| 265 |
+
|
| 266 |
+
all_indices = np.arange(self.n)
|
| 267 |
+
|
| 268 |
+
# Parameters for dynamic step size adjustment
|
| 269 |
+
acceptance_window = 30
|
| 270 |
+
acceptance_count = 0
|
| 271 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 272 |
+
adjustment_factor = 1.05
|
| 273 |
+
|
| 274 |
+
for i in range(sa_iterations):
|
| 275 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 276 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 277 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 278 |
+
if np.sum(weights) > 1e-9:
|
| 279 |
+
probabilities = weights / np.sum(weights)
|
| 280 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 281 |
+
else:
|
| 282 |
+
idx_to_move = np.random.choice(all_indices)
|
| 283 |
+
|
| 284 |
+
trial_centers = np.copy(current_centers)
|
| 285 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 286 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 287 |
+
|
| 288 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 289 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 290 |
+
|
| 291 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 292 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 293 |
+
current_centers = trial_centers
|
| 294 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 295 |
+
current_sum_radii = trial_sum_radii
|
| 296 |
+
acceptance_count += 1
|
| 297 |
+
|
| 298 |
+
if current_sum_radii > best_sum_radii:
|
| 299 |
+
best_sum_radii = current_sum_radii
|
| 300 |
+
best_centers = np.copy(current_centers)
|
| 301 |
+
|
| 302 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 303 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 304 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 305 |
+
if acceptance_rate > target_acceptance_rate:
|
| 306 |
+
step_size *= adjustment_factor
|
| 307 |
+
else:
|
| 308 |
+
step_size /= adjustment_factor
|
| 309 |
+
acceptance_count = 0
|
| 310 |
+
|
| 311 |
+
temp *= sa_cooling_rate
|
| 312 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 313 |
+
|
| 314 |
+
return best_centers
|
| 315 |
+
|
| 316 |
+
def construct_packing(self):
|
| 317 |
+
"""
|
| 318 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 319 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 320 |
+
"""
|
| 321 |
+
self._initial_grid_placement()
|
| 322 |
+
|
| 323 |
+
if self.n > 25:
|
| 324 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 325 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 326 |
+
|
| 327 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 328 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 329 |
+
centers_after_search,
|
| 330 |
+
sum_radii_after_search
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 334 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 335 |
+
self.centers = centers_after_global_sa
|
| 336 |
+
|
| 337 |
+
# Final radius calculation for the fully optimized center configuration
|
| 338 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 339 |
+
return self.centers, self.radii
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def construct_packing():
|
| 343 |
+
"""
|
| 344 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 345 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 346 |
+
|
| 347 |
+
Returns:
|
| 348 |
+
Tuple of (centers, radii)
|
| 349 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 350 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 351 |
+
"""
|
| 352 |
+
packer = CirclePacker(num_circles=26)
|
| 353 |
+
centers, radii = packer.construct_packing()
|
| 354 |
+
return centers, radii
|
| 355 |
+
# EVOLVE-BLOCK-END
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
# This part remains fixed (not evolved)
|
| 359 |
+
def run_packing():
|
| 360 |
+
"""Run the circle packing constructor for n=26"""
|
| 361 |
+
centers, radii = construct_packing()
|
| 362 |
+
# Calculate the sum of radii
|
| 363 |
+
sum_radii = np.sum(radii)
|
| 364 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/main.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with a localized refinement stage.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This static version
|
| 22 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 23 |
+
performance and precision, based on the best prior implementation.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 27 |
+
n (int): Number of circles.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
radii = np.zeros(n)
|
| 33 |
+
|
| 34 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 35 |
+
growth_factor_initial = 1.005
|
| 36 |
+
growth_factor_final = 1.002
|
| 37 |
+
tolerance_initial = 1e-7
|
| 38 |
+
tolerance_final = 1e-10
|
| 39 |
+
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
inner_iterations = 20
|
| 42 |
+
|
| 43 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 44 |
+
for i in range(n):
|
| 45 |
+
x, y = centers[i]
|
| 46 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 47 |
+
|
| 48 |
+
for outer_iter_idx in range(outer_iterations):
|
| 49 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 50 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 51 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 52 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 53 |
+
|
| 54 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 55 |
+
|
| 56 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 57 |
+
constraints_changed = False
|
| 58 |
+
|
| 59 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 60 |
+
for i in range(n):
|
| 61 |
+
x, y = centers[i]
|
| 62 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 63 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 64 |
+
radii[i] = boundary_limit
|
| 65 |
+
constraints_changed = True
|
| 66 |
+
|
| 67 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
for j in range(i + 1, n):
|
| 70 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 71 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 72 |
+
total_radius = radii[i] + radii[j]
|
| 73 |
+
if total_radius > tolerance_final:
|
| 74 |
+
scale = dist / total_radius
|
| 75 |
+
radii[i] *= scale
|
| 76 |
+
radii[j] *= scale
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
if not constraints_changed:
|
| 80 |
+
break
|
| 81 |
+
return radii
|
| 82 |
+
|
| 83 |
+
def _initial_grid_placement(self):
|
| 84 |
+
"""
|
| 85 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 86 |
+
"""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 89 |
+
self.centers[:25] = grid_centers
|
| 90 |
+
|
| 91 |
+
def _find_optimal_26th_circle_position(self):
|
| 92 |
+
"""
|
| 93 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 94 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 95 |
+
"""
|
| 96 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 97 |
+
|
| 98 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 99 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 100 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 101 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 102 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 103 |
+
|
| 104 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 105 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 106 |
+
|
| 107 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 108 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 109 |
+
|
| 110 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 111 |
+
# Explore a broader region first to identify promising areas.
|
| 112 |
+
coarse_delta = 0.05
|
| 113 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points = []
|
| 116 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 117 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 118 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 119 |
+
|
| 120 |
+
for candidate_pos in coarse_candidate_points:
|
| 121 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 122 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 123 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 124 |
+
current_sum_radii = np.sum(trial_radii)
|
| 125 |
+
|
| 126 |
+
if current_sum_radii > best_sum_radii:
|
| 127 |
+
best_sum_radii = current_sum_radii
|
| 128 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 129 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 130 |
+
|
| 131 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 132 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 133 |
+
fine_delta = 0.01
|
| 134 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 135 |
+
|
| 136 |
+
fine_candidate_points = []
|
| 137 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 138 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 139 |
+
|
| 140 |
+
for candidate_pos in fine_candidate_points:
|
| 141 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 142 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 143 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 144 |
+
current_sum_radii = np.sum(trial_radii)
|
| 145 |
+
|
| 146 |
+
if current_sum_radii > best_sum_radii:
|
| 147 |
+
best_sum_radii = current_sum_radii
|
| 148 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 149 |
+
|
| 150 |
+
self.centers[25] = optimal_26th_pos
|
| 151 |
+
|
| 152 |
+
return self.centers, best_sum_radii
|
| 153 |
+
|
| 154 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 155 |
+
"""
|
| 156 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 157 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 158 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 159 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 160 |
+
"""
|
| 161 |
+
current_centers = np.copy(initial_centers)
|
| 162 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 163 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 164 |
+
current_sum_radii = np.sum(current_radii)
|
| 165 |
+
|
| 166 |
+
best_centers_local = np.copy(current_centers)
|
| 167 |
+
best_sum_radii_local = current_sum_radii
|
| 168 |
+
|
| 169 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 170 |
+
num_iterations = 150
|
| 171 |
+
initial_step_size = 0.005
|
| 172 |
+
initial_temp = 0.0001
|
| 173 |
+
cooling_rate = 0.99
|
| 174 |
+
|
| 175 |
+
step_size = initial_step_size
|
| 176 |
+
temp = initial_temp
|
| 177 |
+
|
| 178 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 179 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 180 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 181 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 182 |
+
|
| 183 |
+
# Parameters for dynamic step size adjustment
|
| 184 |
+
acceptance_window = 30
|
| 185 |
+
acceptance_count = 0
|
| 186 |
+
target_acceptance_rate = 0.44
|
| 187 |
+
adjustment_factor = 1.05
|
| 188 |
+
|
| 189 |
+
for i in range(num_iterations):
|
| 190 |
+
# Stress-based selection within the cluster
|
| 191 |
+
cluster_radii = current_radii[cluster_indices]
|
| 192 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 193 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 194 |
+
if np.sum(weights) > 1e-9:
|
| 195 |
+
probabilities = weights / np.sum(weights)
|
| 196 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 197 |
+
else:
|
| 198 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
trial_centers = np.copy(current_centers)
|
| 202 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 203 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 204 |
+
|
| 205 |
+
# Evaluate the new configuration
|
| 206 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 207 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 208 |
+
|
| 209 |
+
# Metropolis-Hastings acceptance criterion
|
| 210 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 211 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 212 |
+
current_centers = trial_centers
|
| 213 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 214 |
+
current_sum_radii = trial_sum_radii
|
| 215 |
+
acceptance_count += 1
|
| 216 |
+
|
| 217 |
+
if current_sum_radii > best_sum_radii_local:
|
| 218 |
+
best_sum_radii_local = current_sum_radii
|
| 219 |
+
best_centers_local = np.copy(current_centers)
|
| 220 |
+
|
| 221 |
+
# Dynamic step size adjustment
|
| 222 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 223 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 224 |
+
if acceptance_rate > target_acceptance_rate:
|
| 225 |
+
step_size *= adjustment_factor
|
| 226 |
+
else:
|
| 227 |
+
step_size /= adjustment_factor
|
| 228 |
+
acceptance_count = 0
|
| 229 |
+
|
| 230 |
+
temp *= cooling_rate
|
| 231 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 232 |
+
|
| 233 |
+
return best_centers_local, best_sum_radii_local
|
| 234 |
+
|
| 235 |
+
def _global_refinement_sa(self, initial_centers):
|
| 236 |
+
"""
|
| 237 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 238 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 239 |
+
settle the entire packing into a better local optimum. This technique was
|
| 240 |
+
present in prior high-scoring implementations.
|
| 241 |
+
"""
|
| 242 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 243 |
+
sa_iterations = 300
|
| 244 |
+
sa_initial_temp = 5e-6
|
| 245 |
+
sa_cooling_rate = 0.99
|
| 246 |
+
sa_initial_step_size = 0.001
|
| 247 |
+
|
| 248 |
+
current_centers = np.copy(initial_centers)
|
| 249 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 250 |
+
current_sum_radii = np.sum(current_radii)
|
| 251 |
+
|
| 252 |
+
best_centers = np.copy(current_centers)
|
| 253 |
+
best_sum_radii = current_sum_radii
|
| 254 |
+
|
| 255 |
+
temp = sa_initial_temp
|
| 256 |
+
step_size = sa_initial_step_size
|
| 257 |
+
|
| 258 |
+
all_indices = np.arange(self.n)
|
| 259 |
+
|
| 260 |
+
# Parameters for dynamic step size adjustment
|
| 261 |
+
acceptance_window = 30
|
| 262 |
+
acceptance_count = 0
|
| 263 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 264 |
+
adjustment_factor = 1.05
|
| 265 |
+
|
| 266 |
+
for i in range(sa_iterations):
|
| 267 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 268 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 269 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 270 |
+
if np.sum(weights) > 1e-9:
|
| 271 |
+
probabilities = weights / np.sum(weights)
|
| 272 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 273 |
+
else:
|
| 274 |
+
idx_to_move = np.random.choice(all_indices)
|
| 275 |
+
|
| 276 |
+
trial_centers = np.copy(current_centers)
|
| 277 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 278 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 279 |
+
|
| 280 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 281 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 282 |
+
|
| 283 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 284 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 285 |
+
current_centers = trial_centers
|
| 286 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 287 |
+
current_sum_radii = trial_sum_radii
|
| 288 |
+
acceptance_count += 1
|
| 289 |
+
|
| 290 |
+
if current_sum_radii > best_sum_radii:
|
| 291 |
+
best_sum_radii = current_sum_radii
|
| 292 |
+
best_centers = np.copy(current_centers)
|
| 293 |
+
|
| 294 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 295 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 296 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 297 |
+
if acceptance_rate > target_acceptance_rate:
|
| 298 |
+
step_size *= adjustment_factor
|
| 299 |
+
else:
|
| 300 |
+
step_size /= adjustment_factor
|
| 301 |
+
acceptance_count = 0
|
| 302 |
+
|
| 303 |
+
temp *= sa_cooling_rate
|
| 304 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 305 |
+
|
| 306 |
+
return best_centers
|
| 307 |
+
|
| 308 |
+
def construct_packing(self):
|
| 309 |
+
"""
|
| 310 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 311 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 312 |
+
"""
|
| 313 |
+
self._initial_grid_placement()
|
| 314 |
+
|
| 315 |
+
if self.n > 25:
|
| 316 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 317 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 318 |
+
|
| 319 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 320 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 321 |
+
centers_after_search,
|
| 322 |
+
sum_radii_after_search
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 326 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 327 |
+
self.centers = centers_after_global_sa
|
| 328 |
+
|
| 329 |
+
# Final radius calculation for the fully optimized center configuration
|
| 330 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 331 |
+
return self.centers, self.radii
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
def construct_packing():
|
| 335 |
+
"""
|
| 336 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 337 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
Tuple of (centers, radii)
|
| 341 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 342 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 343 |
+
"""
|
| 344 |
+
packer = CirclePacker(num_circles=26)
|
| 345 |
+
centers, radii = packer.construct_packing()
|
| 346 |
+
return centers, radii
|
| 347 |
+
# EVOLVE-BLOCK-END
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
# This part remains fixed (not evolved)
|
| 351 |
+
def run_packing():
|
| 352 |
+
"""Run the circle packing constructor for n=26"""
|
| 353 |
+
centers, radii = construct_packing()
|
| 354 |
+
# Calculate the sum of radii
|
| 355 |
+
sum_radii = np.sum(radii)
|
| 356 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/original.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with a localized refinement stage.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This static version
|
| 22 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 23 |
+
performance and precision, based on the best prior implementation.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 27 |
+
n (int): Number of circles.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
radii = np.zeros(n)
|
| 33 |
+
|
| 34 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 35 |
+
growth_factor_initial = 1.005
|
| 36 |
+
growth_factor_final = 1.002
|
| 37 |
+
tolerance_initial = 1e-7
|
| 38 |
+
tolerance_final = 1e-10
|
| 39 |
+
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
inner_iterations = 20
|
| 42 |
+
|
| 43 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 44 |
+
for i in range(n):
|
| 45 |
+
x, y = centers[i]
|
| 46 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 47 |
+
|
| 48 |
+
for outer_iter_idx in range(outer_iterations):
|
| 49 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 50 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 51 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 52 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 53 |
+
|
| 54 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 55 |
+
|
| 56 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 57 |
+
constraints_changed = False
|
| 58 |
+
|
| 59 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 60 |
+
for i in range(n):
|
| 61 |
+
x, y = centers[i]
|
| 62 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 63 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 64 |
+
radii[i] = boundary_limit
|
| 65 |
+
constraints_changed = True
|
| 66 |
+
|
| 67 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
for j in range(i + 1, n):
|
| 70 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 71 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 72 |
+
total_radius = radii[i] + radii[j]
|
| 73 |
+
if total_radius > tolerance_final:
|
| 74 |
+
scale = dist / total_radius
|
| 75 |
+
radii[i] *= scale
|
| 76 |
+
radii[j] *= scale
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
if not constraints_changed:
|
| 80 |
+
break
|
| 81 |
+
return radii
|
| 82 |
+
|
| 83 |
+
def _initial_grid_placement(self):
|
| 84 |
+
"""
|
| 85 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 86 |
+
"""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 89 |
+
self.centers[:25] = grid_centers
|
| 90 |
+
|
| 91 |
+
def _find_optimal_26th_circle_position(self):
|
| 92 |
+
"""
|
| 93 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 94 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 95 |
+
"""
|
| 96 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 97 |
+
|
| 98 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 99 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 100 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 101 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 102 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 103 |
+
|
| 104 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 105 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 106 |
+
|
| 107 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 108 |
+
|
| 109 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 110 |
+
# Explore a broader region first to identify promising areas.
|
| 111 |
+
coarse_delta = 0.05
|
| 112 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 113 |
+
|
| 114 |
+
coarse_candidate_points = []
|
| 115 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 116 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 117 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 118 |
+
|
| 119 |
+
for candidate_pos in coarse_candidate_points:
|
| 120 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 121 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 122 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 123 |
+
current_sum_radii = np.sum(trial_radii)
|
| 124 |
+
|
| 125 |
+
if current_sum_radii > best_sum_radii:
|
| 126 |
+
best_sum_radii = current_sum_radii
|
| 127 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 128 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 129 |
+
|
| 130 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 131 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 132 |
+
fine_delta = 0.01
|
| 133 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 134 |
+
|
| 135 |
+
fine_candidate_points = []
|
| 136 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 137 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 138 |
+
|
| 139 |
+
for candidate_pos in fine_candidate_points:
|
| 140 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 141 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 142 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 143 |
+
current_sum_radii = np.sum(trial_radii)
|
| 144 |
+
|
| 145 |
+
if current_sum_radii > best_sum_radii:
|
| 146 |
+
best_sum_radii = current_sum_radii
|
| 147 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 148 |
+
|
| 149 |
+
self.centers[25] = optimal_26th_pos
|
| 150 |
+
|
| 151 |
+
return self.centers, best_sum_radii
|
| 152 |
+
|
| 153 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 154 |
+
"""
|
| 155 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 156 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 157 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 158 |
+
"""
|
| 159 |
+
current_centers = np.copy(initial_centers)
|
| 160 |
+
current_sum_radii = initial_sum_radii
|
| 161 |
+
|
| 162 |
+
best_centers_local = np.copy(current_centers)
|
| 163 |
+
best_sum_radii_local = current_sum_radii
|
| 164 |
+
|
| 165 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 166 |
+
num_iterations = 150
|
| 167 |
+
initial_step_size = 0.005
|
| 168 |
+
initial_temp = 0.0001
|
| 169 |
+
cooling_rate = 0.99
|
| 170 |
+
|
| 171 |
+
step_size = initial_step_size
|
| 172 |
+
temp = initial_temp
|
| 173 |
+
|
| 174 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 175 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 176 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 177 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 178 |
+
|
| 179 |
+
for _ in range(num_iterations):
|
| 180 |
+
# Select a random circle from the cluster to perturb
|
| 181 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 182 |
+
|
| 183 |
+
trial_centers = np.copy(current_centers)
|
| 184 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 185 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 186 |
+
|
| 187 |
+
# Evaluate the new configuration
|
| 188 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 189 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 190 |
+
|
| 191 |
+
# Metropolis-Hastings acceptance criterion
|
| 192 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 193 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 194 |
+
current_centers = trial_centers
|
| 195 |
+
current_sum_radii = trial_sum_radii
|
| 196 |
+
|
| 197 |
+
if current_sum_radii > best_sum_radii_local:
|
| 198 |
+
best_sum_radii_local = current_sum_radii
|
| 199 |
+
best_centers_local = np.copy(current_centers)
|
| 200 |
+
|
| 201 |
+
temp *= cooling_rate
|
| 202 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 203 |
+
|
| 204 |
+
return best_centers_local, best_sum_radii_local
|
| 205 |
+
|
| 206 |
+
def _global_refinement_sa(self, initial_centers):
|
| 207 |
+
"""
|
| 208 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 209 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 210 |
+
settle the entire packing into a better local optimum. This technique was
|
| 211 |
+
present in prior high-scoring implementations.
|
| 212 |
+
"""
|
| 213 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 214 |
+
sa_iterations = 300
|
| 215 |
+
sa_initial_temp = 5e-6
|
| 216 |
+
sa_cooling_rate = 0.99
|
| 217 |
+
sa_initial_step_size = 0.001
|
| 218 |
+
|
| 219 |
+
current_centers = np.copy(initial_centers)
|
| 220 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 221 |
+
current_sum_radii = np.sum(current_radii)
|
| 222 |
+
|
| 223 |
+
best_centers = np.copy(current_centers)
|
| 224 |
+
best_sum_radii = current_sum_radii
|
| 225 |
+
|
| 226 |
+
temp = sa_initial_temp
|
| 227 |
+
step_size = sa_initial_step_size
|
| 228 |
+
|
| 229 |
+
all_indices = np.arange(self.n)
|
| 230 |
+
|
| 231 |
+
# Parameters for dynamic step size adjustment
|
| 232 |
+
acceptance_window = 30
|
| 233 |
+
acceptance_count = 0
|
| 234 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 235 |
+
adjustment_factor = 1.05
|
| 236 |
+
|
| 237 |
+
for i in range(sa_iterations):
|
| 238 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 239 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 240 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 241 |
+
if np.sum(weights) > 1e-9:
|
| 242 |
+
probabilities = weights / np.sum(weights)
|
| 243 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 244 |
+
else:
|
| 245 |
+
idx_to_move = np.random.choice(all_indices)
|
| 246 |
+
|
| 247 |
+
trial_centers = np.copy(current_centers)
|
| 248 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 249 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 250 |
+
|
| 251 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 252 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 253 |
+
|
| 254 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 255 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 256 |
+
current_centers = trial_centers
|
| 257 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 258 |
+
current_sum_radii = trial_sum_radii
|
| 259 |
+
acceptance_count += 1
|
| 260 |
+
|
| 261 |
+
if current_sum_radii > best_sum_radii:
|
| 262 |
+
best_sum_radii = current_sum_radii
|
| 263 |
+
best_centers = np.copy(current_centers)
|
| 264 |
+
|
| 265 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 266 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 267 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 268 |
+
if acceptance_rate > target_acceptance_rate:
|
| 269 |
+
step_size *= adjustment_factor
|
| 270 |
+
else:
|
| 271 |
+
step_size /= adjustment_factor
|
| 272 |
+
acceptance_count = 0
|
| 273 |
+
|
| 274 |
+
temp *= sa_cooling_rate
|
| 275 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 276 |
+
|
| 277 |
+
return best_centers
|
| 278 |
+
|
| 279 |
+
def construct_packing(self):
|
| 280 |
+
"""
|
| 281 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 282 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 283 |
+
"""
|
| 284 |
+
self._initial_grid_placement()
|
| 285 |
+
|
| 286 |
+
if self.n > 25:
|
| 287 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 288 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 289 |
+
|
| 290 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 291 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 292 |
+
centers_after_search,
|
| 293 |
+
sum_radii_after_search
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 297 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 298 |
+
self.centers = centers_after_global_sa
|
| 299 |
+
|
| 300 |
+
# Final radius calculation for the fully optimized center configuration
|
| 301 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 302 |
+
return self.centers, self.radii
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def construct_packing():
|
| 306 |
+
"""
|
| 307 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 308 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 309 |
+
|
| 310 |
+
Returns:
|
| 311 |
+
Tuple of (centers, radii)
|
| 312 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 313 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 314 |
+
"""
|
| 315 |
+
packer = CirclePacker(num_circles=26)
|
| 316 |
+
centers, radii = packer.construct_packing()
|
| 317 |
+
return centers, radii
|
| 318 |
+
# EVOLVE-BLOCK-END
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
# This part remains fixed (not evolved)
|
| 322 |
+
def run_packing():
|
| 323 |
+
"""Run the circle packing constructor for n=26"""
|
| 324 |
+
centers, radii = construct_packing()
|
| 325 |
+
# Calculate the sum of radii
|
| 326 |
+
sum_radii = np.sum(radii)
|
| 327 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_100/search_replace.txt
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
adaptive_local_sa_and_wider_initial_search
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DESCRIPTION>
|
| 6 |
+
This set of edits introduces two key improvements based on the actionable recommendations and analysis of high-performing strategies.
|
| 7 |
+
|
| 8 |
+
1. **Expanded Initial Search Grid (`_find_optimal_26th_circle_position`)**: Following recommendation #1, the coarse search for the 26th circle's initial position has been significantly broadened. The grid of core interstitial points is expanded from 4x4 (`np.linspace(0.2, 0.8, 4)`) to 8x8 (`np.linspace(0.1, 0.9, 8)`). This increases the number of coarse candidate locations from 144 to 576, providing a much more thorough exploration of the search space. A better initial placement is crucial as it sets a higher baseline for all subsequent refinement stages, increasing the likelihood of finding a superior global optimum.
|
| 9 |
+
|
| 10 |
+
2. **Enhanced Local Simulated Annealing (`_local_refinement_cluster_sa`)**: Following recommendations #3 and #4, the local SA stage is upgraded to incorporate techniques already proven effective in the global SA stage.
|
| 11 |
+
* **Stress-Based Perturbation Selection**: Instead of choosing a circle to move from the local cluster at random, the selection is now biased towards circles with smaller radii ("stressed" circles). This focuses computational effort on the most constrained parts of the local configuration, where adjustments are most likely to yield improvements.
|
| 12 |
+
* **Dynamic Step Size Adjustment**: The SA `step_size` is now dynamically adjusted based on the acceptance rate over a sliding window. This optimizes the exploration/exploitation trade-off, allowing the search to take larger steps when it's finding many improvements and smaller, more precise steps as it converges.
|
| 13 |
+
|
| 14 |
+
Together, these changes create a more powerful optimization pipeline: a more exhaustive initial search finds a better starting basin, and a more intelligent local refinement algorithm is better equipped to exploit it.
|
| 15 |
+
</DESCRIPTION>
|
| 16 |
+
|
| 17 |
+
<DIFF>
|
| 18 |
+
<<<<<<< SEARCH
|
| 19 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 20 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 21 |
+
|
| 22 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 23 |
+
|
| 24 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 25 |
+
=======
|
| 26 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 27 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 28 |
+
|
| 29 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 30 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 31 |
+
|
| 32 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 33 |
+
>>>>>>> REPLACE
|
| 34 |
+
</DIFF>
|
| 35 |
+
<DIFF>
|
| 36 |
+
<<<<<<< SEARCH
|
| 37 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 38 |
+
"""
|
| 39 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 40 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 41 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 42 |
+
"""
|
| 43 |
+
current_centers = np.copy(initial_centers)
|
| 44 |
+
current_sum_radii = initial_sum_radii
|
| 45 |
+
|
| 46 |
+
best_centers_local = np.copy(current_centers)
|
| 47 |
+
best_sum_radii_local = current_sum_radii
|
| 48 |
+
|
| 49 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 50 |
+
num_iterations = 150
|
| 51 |
+
initial_step_size = 0.005
|
| 52 |
+
initial_temp = 0.0001
|
| 53 |
+
cooling_rate = 0.99
|
| 54 |
+
|
| 55 |
+
step_size = initial_step_size
|
| 56 |
+
temp = initial_temp
|
| 57 |
+
|
| 58 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 59 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 60 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 61 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 62 |
+
|
| 63 |
+
for _ in range(num_iterations):
|
| 64 |
+
# Select a random circle from the cluster to perturb
|
| 65 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 66 |
+
|
| 67 |
+
trial_centers = np.copy(current_centers)
|
| 68 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 69 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 70 |
+
|
| 71 |
+
# Evaluate the new configuration
|
| 72 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 73 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 74 |
+
|
| 75 |
+
# Metropolis-Hastings acceptance criterion
|
| 76 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 77 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 78 |
+
current_centers = trial_centers
|
| 79 |
+
current_sum_radii = trial_sum_radii
|
| 80 |
+
|
| 81 |
+
if current_sum_radii > best_sum_radii_local:
|
| 82 |
+
best_sum_radii_local = current_sum_radii
|
| 83 |
+
best_centers_local = np.copy(current_centers)
|
| 84 |
+
|
| 85 |
+
temp *= cooling_rate
|
| 86 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 87 |
+
|
| 88 |
+
return best_centers_local, best_sum_radii_local
|
| 89 |
+
=======
|
| 90 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 91 |
+
"""
|
| 92 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 93 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 94 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 95 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 96 |
+
"""
|
| 97 |
+
current_centers = np.copy(initial_centers)
|
| 98 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 99 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 100 |
+
current_sum_radii = np.sum(current_radii)
|
| 101 |
+
|
| 102 |
+
best_centers_local = np.copy(current_centers)
|
| 103 |
+
best_sum_radii_local = current_sum_radii
|
| 104 |
+
|
| 105 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 106 |
+
num_iterations = 150
|
| 107 |
+
initial_step_size = 0.005
|
| 108 |
+
initial_temp = 0.0001
|
| 109 |
+
cooling_rate = 0.99
|
| 110 |
+
|
| 111 |
+
step_size = initial_step_size
|
| 112 |
+
temp = initial_temp
|
| 113 |
+
|
| 114 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 115 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 116 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 117 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 118 |
+
|
| 119 |
+
# Parameters for dynamic step size adjustment
|
| 120 |
+
acceptance_window = 30
|
| 121 |
+
acceptance_count = 0
|
| 122 |
+
target_acceptance_rate = 0.44
|
| 123 |
+
adjustment_factor = 1.05
|
| 124 |
+
|
| 125 |
+
for i in range(num_iterations):
|
| 126 |
+
# Stress-based selection within the cluster
|
| 127 |
+
cluster_radii = current_radii[cluster_indices]
|
| 128 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 129 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 130 |
+
if np.sum(weights) > 1e-9:
|
| 131 |
+
probabilities = weights / np.sum(weights)
|
| 132 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 133 |
+
else:
|
| 134 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
trial_centers = np.copy(current_centers)
|
| 138 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 139 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 140 |
+
|
| 141 |
+
# Evaluate the new configuration
|
| 142 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 143 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 144 |
+
|
| 145 |
+
# Metropolis-Hastings acceptance criterion
|
| 146 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 147 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 148 |
+
current_centers = trial_centers
|
| 149 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 150 |
+
current_sum_radii = trial_sum_radii
|
| 151 |
+
acceptance_count += 1
|
| 152 |
+
|
| 153 |
+
if current_sum_radii > best_sum_radii_local:
|
| 154 |
+
best_sum_radii_local = current_sum_radii
|
| 155 |
+
best_centers_local = np.copy(current_centers)
|
| 156 |
+
|
| 157 |
+
# Dynamic step size adjustment
|
| 158 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 159 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 160 |
+
if acceptance_rate > target_acceptance_rate:
|
| 161 |
+
step_size *= adjustment_factor
|
| 162 |
+
else:
|
| 163 |
+
step_size /= adjustment_factor
|
| 164 |
+
acceptance_count = 0
|
| 165 |
+
|
| 166 |
+
temp *= cooling_rate
|
| 167 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 168 |
+
|
| 169 |
+
return best_centers_local, best_sum_radii_local
|
| 170 |
+
>>>>>>> REPLACE
|
| 171 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/edit.diff
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,237 +1,284 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings within a unit square.
|
| 12 |
+
It encapsulates the logic for initial placement and iterative radius adjustment.
|
| 13 |
+
"""
|
| 14 |
+
def __init__(self, num_circles=26):
|
| 15 |
+
self.n = num_circles
|
| 16 |
+
self.centers = np.zeros((self.n, 2))
|
| 17 |
+
self.radii = np.zeros(self.n)
|
| 18 |
+
|
| 19 |
+
@staticmethod
|
| 20 |
+
def _compute_max_radii_static(centers, n):
|
| 21 |
+
"""
|
| 22 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 23 |
+
an iterative growth and constraint resolution method. This static version
|
| 24 |
+
incorporates adaptive growth factor and dynamic tolerance for enhanced
|
| 25 |
+
performance and precision.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 29 |
+
n (int): Number of circles.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 33 |
+
"""
|
| 34 |
+
radii = np.zeros(n)
|
| 35 |
+
|
| 36 |
+
# Parameters for adaptive growth factor (Recommendation 1 from previous feedback)
|
| 37 |
+
growth_factor_initial = 1.005 # Start with a higher growth pressure
|
| 38 |
+
growth_factor_final = 1.002 # Gradually decrease to the original value
|
| 39 |
+
|
| 40 |
+
# Parameters for dynamic tolerance (Recommendation 5 from previous feedback)
|
| 41 |
+
tolerance_initial = 1e-7 # Start with a looser tolerance to quickly resolve major overlaps
|
| 42 |
+
tolerance_final = 1e-10 # End with a tighter tolerance for precision
|
| 43 |
+
|
| 44 |
+
outer_iterations = 400
|
| 45 |
+
inner_iterations = 20 # Using 20 inner iterations as in the high-scoring program
|
| 46 |
+
|
| 47 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 48 |
+
for i in range(n):
|
| 49 |
+
x, y = centers[i]
|
| 50 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 51 |
+
|
| 52 |
+
for outer_iter_idx in range(outer_iterations):
|
| 53 |
+
- # Calculate dynamic growth factor (linear decay)
|
| 54 |
+
- # Factor goes from 0 (at first iter) to 1 (at last iter)
|
| 55 |
+
- interp_factor = outer_iter_idx / (outer_iterations - 1.0)
|
| 56 |
+
- current_growth_factor = growth_factor_initial - interp_factor * \
|
| 57 |
+
- (growth_factor_initial - growth_factor_final)
|
| 58 |
+
-
|
| 59 |
+
- # Calculate dynamic tolerance (linear decay)
|
| 60 |
+
- current_tolerance = tolerance_initial - interp_factor * \
|
| 61 |
+
- (tolerance_initial - tolerance_final)
|
| 62 |
+
+ # Use exponential interpolation for smoother parameter transition
|
| 63 |
+
+ progress = outer_iter_idx / (outer_iterations - 1 + 1e-9)
|
| 64 |
+
+ current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**progress
|
| 65 |
+
+ current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**progress
|
| 66 |
+
|
| 67 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 68 |
+
|
| 69 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 70 |
+
constraints_changed = False
|
| 71 |
+
|
| 72 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 73 |
+
for i in range(n):
|
| 74 |
+
x, y = centers[i]
|
| 75 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 76 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 77 |
+
radii[i] = boundary_limit
|
| 78 |
+
constraints_changed = True
|
| 79 |
+
|
| 80 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 81 |
+
for i in range(n):
|
| 82 |
+
for j in range(i + 1, n):
|
| 83 |
+
# Use np.linalg.norm for cleaner distance calculation
|
| 84 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 85 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 86 |
+
total_radius = radii[i] + radii[j]
|
| 87 |
+
# Use final_tolerance for the small radius check to avoid division by zero
|
| 88 |
+
if total_radius > tolerance_final:
|
| 89 |
+
scale = dist / total_radius
|
| 90 |
+
radii[i] *= scale
|
| 91 |
+
radii[j] *= scale
|
| 92 |
+
constraints_changed = True
|
| 93 |
+
|
| 94 |
+
if not constraints_changed:
|
| 95 |
+
# Inner loop converged, all constraints resolved for this growth step
|
| 96 |
+
break
|
| 97 |
+
return radii
|
| 98 |
+
|
| 99 |
+
def _initial_grid_placement(self):
|
| 100 |
+
"""
|
| 101 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 102 |
+
"""
|
| 103 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 104 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 105 |
+
self.centers[:25] = grid_centers
|
| 106 |
+
|
| 107 |
+
def _find_optimal_26th_circle_position(self):
|
| 108 |
+
"""
|
| 109 |
+
Performs an exhaustive search to find the best initial position for the
|
| 110 |
+
26th circle among a set of interstitial candidates. This method runs
|
| 111 |
+
the full radius optimization for each candidate.
|
| 112 |
+
"""
|
| 113 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 114 |
+
|
| 115 |
+
# Candidate locations for the 26th circle (16 interstitial voids, as in the best prior program)
|
| 116 |
+
interstitial_coords = np.linspace(0.2, 0.8, 4)
|
| 117 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 118 |
+
|
| 119 |
+
best_sum_radii = -1.0
|
| 120 |
+
optimal_26th_pos = None
|
| 121 |
+
|
| 122 |
+
for candidate_pos in candidate_points:
|
| 123 |
+
trial_centers = np.vstack([base_25_centers, candidate_pos])
|
| 124 |
+
# Evaluate using the static compute_max_radii function
|
| 125 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 126 |
+
current_sum_radii = np.sum(trial_radii)
|
| 127 |
+
|
| 128 |
+
if current_sum_radii > best_sum_radii:
|
| 129 |
+
best_sum_radii = current_sum_radii
|
| 130 |
+
optimal_26th_pos = np.array(candidate_pos)
|
| 131 |
+
|
| 132 |
+
if optimal_26th_pos is not None:
|
| 133 |
+
self.centers[25] = optimal_26th_pos
|
| 134 |
+
else:
|
| 135 |
+
# Fallback: Should not be reached if candidate_points is non-empty
|
| 136 |
+
self.centers[25] = [0.5, 0.5]
|
| 137 |
+
|
| 138 |
+
return self.centers, best_sum_radii
|
| 139 |
+
|
| 140 |
+
def _local_refinement_26th_circle(self, initial_centers, initial_sum_radii, index_to_perturb=25):
|
| 141 |
+
"""
|
| 142 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 143 |
+
position of a single specified circle (the 26th circle in this case),
|
| 144 |
+
starting from an already good initial placement. (Recommendation 4 from previous feedback)
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
current_centers = np.copy(initial_centers)
|
| 148 |
+
current_sum_radii = initial_sum_radii
|
| 149 |
+
|
| 150 |
+
best_centers_local = np.copy(current_centers)
|
| 151 |
+
best_sum_radii_local = current_sum_radii
|
| 152 |
+
|
| 153 |
+
# SA parameters for a very short, localized search
|
| 154 |
+
num_iterations = 200 # Small number of iterations for targeted refinement
|
| 155 |
+
initial_step_size = 0.01 # Very small step size for local search
|
| 156 |
+
initial_temp = 0.01 # Low initial temperature for fast convergence
|
| 157 |
+
cooling_rate = 0.98 # Fast cooling rate
|
| 158 |
+
|
| 159 |
+
for k in range(num_iterations):
|
| 160 |
+
# Step size decreases linearly
|
| 161 |
+
progress = k / num_iterations
|
| 162 |
+
step_size = initial_step_size * (1.0 - progress)
|
| 163 |
+
# Temperature decreases exponentially
|
| 164 |
+
temp = initial_temp * (cooling_rate**k)
|
| 165 |
+
|
| 166 |
+
# Perturb ONLY the specified circle's center
|
| 167 |
+
trial_centers = np.copy(current_centers)
|
| 168 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 169 |
+
dx = step_size * np.cos(random_angle)
|
| 170 |
+
dy = step_size * np.sin(random_angle)
|
| 171 |
+
|
| 172 |
+
trial_centers[index_to_perturb, 0] += dx
|
| 173 |
+
trial_centers[index_to_perturb, 1] += dy
|
| 174 |
+
|
| 175 |
+
# Ensure the new center is within the unit square [0, 1]
|
| 176 |
+
trial_centers[index_to_perturb] = np.clip(trial_centers[index_to_perturb], 0.0, 1.0)
|
| 177 |
+
|
| 178 |
+
# Evaluate the new configuration using the static compute_max_radii
|
| 179 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 180 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 181 |
+
|
| 182 |
+
# Metropolis-Hastings acceptance criterion for maximization
|
| 183 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 184 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 185 |
+
current_centers = trial_centers
|
| 186 |
+
current_sum_radii = trial_sum_radii
|
| 187 |
+
|
| 188 |
+
if current_sum_radii > best_sum_radii_local:
|
| 189 |
+
best_sum_radii_local = current_sum_radii
|
| 190 |
+
best_centers_local = np.copy(current_centers)
|
| 191 |
+
|
| 192 |
+
return best_centers_local, best_sum_radii_local
|
| 193 |
+
|
| 194 |
+
+ def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 195 |
+
+ """
|
| 196 |
+
+ Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 197 |
+
+ better global optimum, using tuned parameters from high-scoring versions.
|
| 198 |
+
+ """
|
| 199 |
+
+ current_centers = np.copy(initial_centers)
|
| 200 |
+
+ current_sum_radii = initial_sum_radii
|
| 201 |
+
+
|
| 202 |
+
+ best_centers_global = np.copy(current_centers)
|
| 203 |
+
+ best_sum_radii_global = current_sum_radii
|
| 204 |
+
+
|
| 205 |
+
+ # SA parameters for a final, gentle, global refinement
|
| 206 |
+
+ num_iterations = 1500
|
| 207 |
+
+ initial_step_size = 0.004
|
| 208 |
+
+ initial_temp = 1e-5
|
| 209 |
+
+ cooling_rate = 0.995
|
| 210 |
+
+
|
| 211 |
+
+ step_size = initial_step_size
|
| 212 |
+
+ temp = initial_temp
|
| 213 |
+
+
|
| 214 |
+
+ for _ in range(num_iterations):
|
| 215 |
+
+ # Perturb a random circle from the entire set
|
| 216 |
+
+ idx_to_perturb = np.random.randint(self.n)
|
| 217 |
+
+
|
| 218 |
+
+ trial_centers = np.copy(current_centers)
|
| 219 |
+
+ move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 220 |
+
+ trial_centers[idx_to_perturb] += move
|
| 221 |
+
+ trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 222 |
+
+
|
| 223 |
+
+ trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 224 |
+
+ trial_sum_radii = np.sum(trial_radii)
|
| 225 |
+
+
|
| 226 |
+
+ delta_energy = trial_sum_radii - current_sum_radii
|
| 227 |
+
+ if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 228 |
+
+ current_centers = trial_centers
|
| 229 |
+
+ current_sum_radii = trial_sum_radii
|
| 230 |
+
+
|
| 231 |
+
+ if current_sum_radii > best_sum_radii_global:
|
| 232 |
+
+ best_sum_radii_global = current_sum_radii
|
| 233 |
+
+ best_centers_global = np.copy(current_centers)
|
| 234 |
+
+
|
| 235 |
+
+ temp *= cooling_rate
|
| 236 |
+
+ step_size = max(step_size * cooling_rate, 5e-8)
|
| 237 |
+
+
|
| 238 |
+
+ return best_centers_global, best_sum_radii_global
|
| 239 |
+
+
|
| 240 |
+
def construct_packing(self):
|
| 241 |
+
"""
|
| 242 |
+
Main method to construct the circle packing.
|
| 243 |
+
Orchestrates initial placement, exhaustive search, and local refinement.
|
| 244 |
+
"""
|
| 245 |
+
self._initial_grid_placement()
|
| 246 |
+
|
| 247 |
+
if self.n > 25:
|
| 248 |
+
# Stage 1: Exhaustive search for the optimal 26th circle position
|
| 249 |
+
# This identifies a strong starting point for the 26th circle.
|
| 250 |
+
initial_centers_for_refinement, sum_radii_after_exhaustive = self._find_optimal_26th_circle_position()
|
| 251 |
+
|
| 252 |
+
# Stage 2: Local refinement using SA for the 26th circle's position
|
| 253 |
+
# This fine-tunes the position to potentially eke out further gains.
|
| 254 |
+
- refined_centers, _ = self._local_refinement_26th_circle(
|
| 255 |
+
+ centers_after_local_refinement, sum_radii_after_local_refinement = self._local_refinement_26th_circle(
|
| 256 |
+
initial_centers_for_refinement,
|
| 257 |
+
sum_radii_after_exhaustive,
|
| 258 |
+
index_to_perturb=25
|
| 259 |
+
)
|
| 260 |
+
- self.centers = refined_centers # Update the instance's centers with the refined ones
|
| 261 |
+
-
|
| 262 |
+
- # Final radius calculation for the optimized center configuration
|
| 263 |
+
+
|
| 264 |
+
+ # Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 265 |
+
+ refined_centers, _ = self._global_refinement_sa(
|
| 266 |
+
+ centers_after_local_refinement,
|
| 267 |
+
+ sum_radii_after_local_refinement
|
| 268 |
+
+ )
|
| 269 |
+
+ self.centers = refined_centers # Update the instance's centers with the globally refined ones
|
| 270 |
+
+
|
| 271 |
+
+ # Final radius calculation for the fully optimized center configuration
|
| 272 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 273 |
+
return self.centers, self.radii
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def construct_packing():
|
| 277 |
+
"""
|
| 278 |
+
Constructs an arrangement of 26 circles by leveraging a modular
|
| 279 |
+
object-oriented approach. It initializes with a 5x5 grid and
|
| 280 |
+
dynamically places the 26th circle in the most optimal interstitial
|
| 281 |
+
void, then iteratively adjusts radii to maximize their sum.
|
| 282 |
+
|
| 283 |
+
Returns:
|
| 284 |
+
Tuple of (centers, radii)
|
| 285 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 286 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 287 |
+
"""
|
| 288 |
+
packer = CirclePacker(num_circles=26)
|
| 289 |
+
centers, radii = packer.construct_packing()
|
| 290 |
+
return centers, radii
|
| 291 |
+
# EVOLVE-BLOCK-END
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# This part remains fixed (not evolved)
|
| 295 |
+
def run_packing():
|
| 296 |
+
"""Run the circle packing constructor for n=26"""
|
| 297 |
+
centers, radii = construct_packing()
|
| 298 |
+
# Calculate the sum of radii
|
| 299 |
+
sum_radii = np.sum(radii)
|
| 300 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/main.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square.
|
| 9 |
+
It encapsulates the logic for initial placement and iterative radius adjustment.
|
| 10 |
+
"""
|
| 11 |
+
def __init__(self, num_circles=26):
|
| 12 |
+
self.n = num_circles
|
| 13 |
+
self.centers = np.zeros((self.n, 2))
|
| 14 |
+
self.radii = np.zeros(self.n)
|
| 15 |
+
|
| 16 |
+
@staticmethod
|
| 17 |
+
def _compute_max_radii_static(centers, n):
|
| 18 |
+
"""
|
| 19 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 20 |
+
an iterative growth and constraint resolution method. This static version
|
| 21 |
+
incorporates adaptive growth factor and dynamic tolerance for enhanced
|
| 22 |
+
performance and precision.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 26 |
+
n (int): Number of circles.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
radii = np.zeros(n)
|
| 32 |
+
|
| 33 |
+
# Parameters for adaptive growth factor (Recommendation 1 from previous feedback)
|
| 34 |
+
growth_factor_initial = 1.005 # Start with a higher growth pressure
|
| 35 |
+
growth_factor_final = 1.002 # Gradually decrease to the original value
|
| 36 |
+
|
| 37 |
+
# Parameters for dynamic tolerance (Recommendation 5 from previous feedback)
|
| 38 |
+
tolerance_initial = 1e-7 # Start with a looser tolerance to quickly resolve major overlaps
|
| 39 |
+
tolerance_final = 1e-10 # End with a tighter tolerance for precision
|
| 40 |
+
|
| 41 |
+
outer_iterations = 400
|
| 42 |
+
inner_iterations = 20 # Using 20 inner iterations as in the high-scoring program
|
| 43 |
+
|
| 44 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 45 |
+
for i in range(n):
|
| 46 |
+
x, y = centers[i]
|
| 47 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 48 |
+
|
| 49 |
+
for outer_iter_idx in range(outer_iterations):
|
| 50 |
+
# Use exponential interpolation for smoother parameter transition
|
| 51 |
+
progress = outer_iter_idx / (outer_iterations - 1 + 1e-9)
|
| 52 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**progress
|
| 53 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**progress
|
| 54 |
+
|
| 55 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 56 |
+
|
| 57 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 58 |
+
constraints_changed = False
|
| 59 |
+
|
| 60 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 61 |
+
for i in range(n):
|
| 62 |
+
x, y = centers[i]
|
| 63 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 64 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 65 |
+
radii[i] = boundary_limit
|
| 66 |
+
constraints_changed = True
|
| 67 |
+
|
| 68 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 69 |
+
for i in range(n):
|
| 70 |
+
for j in range(i + 1, n):
|
| 71 |
+
# Use np.linalg.norm for cleaner distance calculation
|
| 72 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 73 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 74 |
+
total_radius = radii[i] + radii[j]
|
| 75 |
+
# Use final_tolerance for the small radius check to avoid division by zero
|
| 76 |
+
if total_radius > tolerance_final:
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
|
| 82 |
+
if not constraints_changed:
|
| 83 |
+
# Inner loop converged, all constraints resolved for this growth step
|
| 84 |
+
break
|
| 85 |
+
return radii
|
| 86 |
+
|
| 87 |
+
def _initial_grid_placement(self):
|
| 88 |
+
"""
|
| 89 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 90 |
+
"""
|
| 91 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 92 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 93 |
+
self.centers[:25] = grid_centers
|
| 94 |
+
|
| 95 |
+
def _find_optimal_26th_circle_position(self):
|
| 96 |
+
"""
|
| 97 |
+
Performs an exhaustive search to find the best initial position for the
|
| 98 |
+
26th circle among a set of interstitial candidates. This method runs
|
| 99 |
+
the full radius optimization for each candidate.
|
| 100 |
+
"""
|
| 101 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 102 |
+
|
| 103 |
+
# Candidate locations for the 26th circle (16 interstitial voids, as in the best prior program)
|
| 104 |
+
interstitial_coords = np.linspace(0.2, 0.8, 4)
|
| 105 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 106 |
+
|
| 107 |
+
best_sum_radii = -1.0
|
| 108 |
+
optimal_26th_pos = None
|
| 109 |
+
|
| 110 |
+
for candidate_pos in candidate_points:
|
| 111 |
+
trial_centers = np.vstack([base_25_centers, candidate_pos])
|
| 112 |
+
# Evaluate using the static compute_max_radii function
|
| 113 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 114 |
+
current_sum_radii = np.sum(trial_radii)
|
| 115 |
+
|
| 116 |
+
if current_sum_radii > best_sum_radii:
|
| 117 |
+
best_sum_radii = current_sum_radii
|
| 118 |
+
optimal_26th_pos = np.array(candidate_pos)
|
| 119 |
+
|
| 120 |
+
if optimal_26th_pos is not None:
|
| 121 |
+
self.centers[25] = optimal_26th_pos
|
| 122 |
+
else:
|
| 123 |
+
# Fallback: Should not be reached if candidate_points is non-empty
|
| 124 |
+
self.centers[25] = [0.5, 0.5]
|
| 125 |
+
|
| 126 |
+
return self.centers, best_sum_radii
|
| 127 |
+
|
| 128 |
+
def _local_refinement_26th_circle(self, initial_centers, initial_sum_radii, index_to_perturb=25):
|
| 129 |
+
"""
|
| 130 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 131 |
+
position of a single specified circle (the 26th circle in this case),
|
| 132 |
+
starting from an already good initial placement. (Recommendation 4 from previous feedback)
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
current_centers = np.copy(initial_centers)
|
| 136 |
+
current_sum_radii = initial_sum_radii
|
| 137 |
+
|
| 138 |
+
best_centers_local = np.copy(current_centers)
|
| 139 |
+
best_sum_radii_local = current_sum_radii
|
| 140 |
+
|
| 141 |
+
# SA parameters for a very short, localized search
|
| 142 |
+
num_iterations = 200 # Small number of iterations for targeted refinement
|
| 143 |
+
initial_step_size = 0.01 # Very small step size for local search
|
| 144 |
+
initial_temp = 0.01 # Low initial temperature for fast convergence
|
| 145 |
+
cooling_rate = 0.98 # Fast cooling rate
|
| 146 |
+
|
| 147 |
+
for k in range(num_iterations):
|
| 148 |
+
# Step size decreases linearly
|
| 149 |
+
progress = k / num_iterations
|
| 150 |
+
step_size = initial_step_size * (1.0 - progress)
|
| 151 |
+
# Temperature decreases exponentially
|
| 152 |
+
temp = initial_temp * (cooling_rate**k)
|
| 153 |
+
|
| 154 |
+
# Perturb ONLY the specified circle's center
|
| 155 |
+
trial_centers = np.copy(current_centers)
|
| 156 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 157 |
+
dx = step_size * np.cos(random_angle)
|
| 158 |
+
dy = step_size * np.sin(random_angle)
|
| 159 |
+
|
| 160 |
+
trial_centers[index_to_perturb, 0] += dx
|
| 161 |
+
trial_centers[index_to_perturb, 1] += dy
|
| 162 |
+
|
| 163 |
+
# Ensure the new center is within the unit square [0, 1]
|
| 164 |
+
trial_centers[index_to_perturb] = np.clip(trial_centers[index_to_perturb], 0.0, 1.0)
|
| 165 |
+
|
| 166 |
+
# Evaluate the new configuration using the static compute_max_radii
|
| 167 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 168 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 169 |
+
|
| 170 |
+
# Metropolis-Hastings acceptance criterion for maximization
|
| 171 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 172 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 173 |
+
current_centers = trial_centers
|
| 174 |
+
current_sum_radii = trial_sum_radii
|
| 175 |
+
|
| 176 |
+
if current_sum_radii > best_sum_radii_local:
|
| 177 |
+
best_sum_radii_local = current_sum_radii
|
| 178 |
+
best_centers_local = np.copy(current_centers)
|
| 179 |
+
|
| 180 |
+
return best_centers_local, best_sum_radii_local
|
| 181 |
+
|
| 182 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 183 |
+
"""
|
| 184 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 185 |
+
better global optimum, using tuned parameters from high-scoring versions.
|
| 186 |
+
"""
|
| 187 |
+
current_centers = np.copy(initial_centers)
|
| 188 |
+
current_sum_radii = initial_sum_radii
|
| 189 |
+
|
| 190 |
+
best_centers_global = np.copy(current_centers)
|
| 191 |
+
best_sum_radii_global = current_sum_radii
|
| 192 |
+
|
| 193 |
+
# SA parameters for a final, gentle, global refinement
|
| 194 |
+
num_iterations = 1500
|
| 195 |
+
initial_step_size = 0.004
|
| 196 |
+
initial_temp = 1e-5
|
| 197 |
+
cooling_rate = 0.995
|
| 198 |
+
|
| 199 |
+
step_size = initial_step_size
|
| 200 |
+
temp = initial_temp
|
| 201 |
+
|
| 202 |
+
for _ in range(num_iterations):
|
| 203 |
+
# Perturb a random circle from the entire set
|
| 204 |
+
idx_to_perturb = np.random.randint(self.n)
|
| 205 |
+
|
| 206 |
+
trial_centers = np.copy(current_centers)
|
| 207 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 208 |
+
trial_centers[idx_to_perturb] += move
|
| 209 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 210 |
+
|
| 211 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 212 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 213 |
+
|
| 214 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 215 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 216 |
+
current_centers = trial_centers
|
| 217 |
+
current_sum_radii = trial_sum_radii
|
| 218 |
+
|
| 219 |
+
if current_sum_radii > best_sum_radii_global:
|
| 220 |
+
best_sum_radii_global = current_sum_radii
|
| 221 |
+
best_centers_global = np.copy(current_centers)
|
| 222 |
+
|
| 223 |
+
temp *= cooling_rate
|
| 224 |
+
step_size = max(step_size * cooling_rate, 5e-8)
|
| 225 |
+
|
| 226 |
+
return best_centers_global, best_sum_radii_global
|
| 227 |
+
|
| 228 |
+
def construct_packing(self):
|
| 229 |
+
"""
|
| 230 |
+
Main method to construct the circle packing.
|
| 231 |
+
Orchestrates initial placement, exhaustive search, and local refinement.
|
| 232 |
+
"""
|
| 233 |
+
self._initial_grid_placement()
|
| 234 |
+
|
| 235 |
+
if self.n > 25:
|
| 236 |
+
# Stage 1: Exhaustive search for the optimal 26th circle position
|
| 237 |
+
# This identifies a strong starting point for the 26th circle.
|
| 238 |
+
initial_centers_for_refinement, sum_radii_after_exhaustive = self._find_optimal_26th_circle_position()
|
| 239 |
+
|
| 240 |
+
# Stage 2: Local refinement using SA for the 26th circle's position
|
| 241 |
+
# This fine-tunes the position to potentially eke out further gains.
|
| 242 |
+
centers_after_local_refinement, sum_radii_after_local_refinement = self._local_refinement_26th_circle(
|
| 243 |
+
initial_centers_for_refinement,
|
| 244 |
+
sum_radii_after_exhaustive,
|
| 245 |
+
index_to_perturb=25
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
# Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 249 |
+
refined_centers, _ = self._global_refinement_sa(
|
| 250 |
+
centers_after_local_refinement,
|
| 251 |
+
sum_radii_after_local_refinement
|
| 252 |
+
)
|
| 253 |
+
self.centers = refined_centers # Update the instance's centers with the globally refined ones
|
| 254 |
+
|
| 255 |
+
# Final radius calculation for the fully optimized center configuration
|
| 256 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 257 |
+
return self.centers, self.radii
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def construct_packing():
|
| 261 |
+
"""
|
| 262 |
+
Constructs an arrangement of 26 circles by leveraging a modular
|
| 263 |
+
object-oriented approach. It initializes with a 5x5 grid and
|
| 264 |
+
dynamically places the 26th circle in the most optimal interstitial
|
| 265 |
+
void, then iteratively adjusts radii to maximize their sum.
|
| 266 |
+
|
| 267 |
+
Returns:
|
| 268 |
+
Tuple of (centers, radii)
|
| 269 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 270 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 271 |
+
"""
|
| 272 |
+
packer = CirclePacker(num_circles=26)
|
| 273 |
+
centers, radii = packer.construct_packing()
|
| 274 |
+
return centers, radii
|
| 275 |
+
# EVOLVE-BLOCK-END
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# This part remains fixed (not evolved)
|
| 279 |
+
def run_packing():
|
| 280 |
+
"""Run the circle packing constructor for n=26"""
|
| 281 |
+
centers, radii = construct_packing()
|
| 282 |
+
# Calculate the sum of radii
|
| 283 |
+
sum_radii = np.sum(radii)
|
| 284 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/original.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square.
|
| 9 |
+
It encapsulates the logic for initial placement and iterative radius adjustment.
|
| 10 |
+
"""
|
| 11 |
+
def __init__(self, num_circles=26):
|
| 12 |
+
self.n = num_circles
|
| 13 |
+
self.centers = np.zeros((self.n, 2))
|
| 14 |
+
self.radii = np.zeros(self.n)
|
| 15 |
+
|
| 16 |
+
@staticmethod
|
| 17 |
+
def _compute_max_radii_static(centers, n):
|
| 18 |
+
"""
|
| 19 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 20 |
+
an iterative growth and constraint resolution method. This static version
|
| 21 |
+
incorporates adaptive growth factor and dynamic tolerance for enhanced
|
| 22 |
+
performance and precision.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 26 |
+
n (int): Number of circles.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
radii = np.zeros(n)
|
| 32 |
+
|
| 33 |
+
# Parameters for adaptive growth factor (Recommendation 1 from previous feedback)
|
| 34 |
+
growth_factor_initial = 1.005 # Start with a higher growth pressure
|
| 35 |
+
growth_factor_final = 1.002 # Gradually decrease to the original value
|
| 36 |
+
|
| 37 |
+
# Parameters for dynamic tolerance (Recommendation 5 from previous feedback)
|
| 38 |
+
tolerance_initial = 1e-7 # Start with a looser tolerance to quickly resolve major overlaps
|
| 39 |
+
tolerance_final = 1e-10 # End with a tighter tolerance for precision
|
| 40 |
+
|
| 41 |
+
outer_iterations = 400
|
| 42 |
+
inner_iterations = 20 # Using 20 inner iterations as in the high-scoring program
|
| 43 |
+
|
| 44 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 45 |
+
for i in range(n):
|
| 46 |
+
x, y = centers[i]
|
| 47 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 48 |
+
|
| 49 |
+
for outer_iter_idx in range(outer_iterations):
|
| 50 |
+
# Calculate dynamic growth factor (linear decay)
|
| 51 |
+
# Factor goes from 0 (at first iter) to 1 (at last iter)
|
| 52 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0)
|
| 53 |
+
current_growth_factor = growth_factor_initial - interp_factor * \
|
| 54 |
+
(growth_factor_initial - growth_factor_final)
|
| 55 |
+
|
| 56 |
+
# Calculate dynamic tolerance (linear decay)
|
| 57 |
+
current_tolerance = tolerance_initial - interp_factor * \
|
| 58 |
+
(tolerance_initial - tolerance_final)
|
| 59 |
+
|
| 60 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 61 |
+
|
| 62 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 63 |
+
constraints_changed = False
|
| 64 |
+
|
| 65 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 66 |
+
for i in range(n):
|
| 67 |
+
x, y = centers[i]
|
| 68 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 69 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 70 |
+
radii[i] = boundary_limit
|
| 71 |
+
constraints_changed = True
|
| 72 |
+
|
| 73 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 74 |
+
for i in range(n):
|
| 75 |
+
for j in range(i + 1, n):
|
| 76 |
+
# Use np.linalg.norm for cleaner distance calculation
|
| 77 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 78 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 79 |
+
total_radius = radii[i] + radii[j]
|
| 80 |
+
# Use final_tolerance for the small radius check to avoid division by zero
|
| 81 |
+
if total_radius > tolerance_final:
|
| 82 |
+
scale = dist / total_radius
|
| 83 |
+
radii[i] *= scale
|
| 84 |
+
radii[j] *= scale
|
| 85 |
+
constraints_changed = True
|
| 86 |
+
|
| 87 |
+
if not constraints_changed:
|
| 88 |
+
# Inner loop converged, all constraints resolved for this growth step
|
| 89 |
+
break
|
| 90 |
+
return radii
|
| 91 |
+
|
| 92 |
+
def _initial_grid_placement(self):
|
| 93 |
+
"""
|
| 94 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 95 |
+
"""
|
| 96 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 97 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 98 |
+
self.centers[:25] = grid_centers
|
| 99 |
+
|
| 100 |
+
def _find_optimal_26th_circle_position(self):
|
| 101 |
+
"""
|
| 102 |
+
Performs an exhaustive search to find the best initial position for the
|
| 103 |
+
26th circle among a set of interstitial candidates. This method runs
|
| 104 |
+
the full radius optimization for each candidate.
|
| 105 |
+
"""
|
| 106 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 107 |
+
|
| 108 |
+
# Candidate locations for the 26th circle (16 interstitial voids, as in the best prior program)
|
| 109 |
+
interstitial_coords = np.linspace(0.2, 0.8, 4)
|
| 110 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 111 |
+
|
| 112 |
+
best_sum_radii = -1.0
|
| 113 |
+
optimal_26th_pos = None
|
| 114 |
+
|
| 115 |
+
for candidate_pos in candidate_points:
|
| 116 |
+
trial_centers = np.vstack([base_25_centers, candidate_pos])
|
| 117 |
+
# Evaluate using the static compute_max_radii function
|
| 118 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 119 |
+
current_sum_radii = np.sum(trial_radii)
|
| 120 |
+
|
| 121 |
+
if current_sum_radii > best_sum_radii:
|
| 122 |
+
best_sum_radii = current_sum_radii
|
| 123 |
+
optimal_26th_pos = np.array(candidate_pos)
|
| 124 |
+
|
| 125 |
+
if optimal_26th_pos is not None:
|
| 126 |
+
self.centers[25] = optimal_26th_pos
|
| 127 |
+
else:
|
| 128 |
+
# Fallback: Should not be reached if candidate_points is non-empty
|
| 129 |
+
self.centers[25] = [0.5, 0.5]
|
| 130 |
+
|
| 131 |
+
return self.centers, best_sum_radii
|
| 132 |
+
|
| 133 |
+
def _local_refinement_26th_circle(self, initial_centers, initial_sum_radii, index_to_perturb=25):
|
| 134 |
+
"""
|
| 135 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 136 |
+
position of a single specified circle (the 26th circle in this case),
|
| 137 |
+
starting from an already good initial placement. (Recommendation 4 from previous feedback)
|
| 138 |
+
"""
|
| 139 |
+
|
| 140 |
+
current_centers = np.copy(initial_centers)
|
| 141 |
+
current_sum_radii = initial_sum_radii
|
| 142 |
+
|
| 143 |
+
best_centers_local = np.copy(current_centers)
|
| 144 |
+
best_sum_radii_local = current_sum_radii
|
| 145 |
+
|
| 146 |
+
# SA parameters for a very short, localized search
|
| 147 |
+
num_iterations = 200 # Small number of iterations for targeted refinement
|
| 148 |
+
initial_step_size = 0.01 # Very small step size for local search
|
| 149 |
+
initial_temp = 0.01 # Low initial temperature for fast convergence
|
| 150 |
+
cooling_rate = 0.98 # Fast cooling rate
|
| 151 |
+
|
| 152 |
+
for k in range(num_iterations):
|
| 153 |
+
# Step size decreases linearly
|
| 154 |
+
progress = k / num_iterations
|
| 155 |
+
step_size = initial_step_size * (1.0 - progress)
|
| 156 |
+
# Temperature decreases exponentially
|
| 157 |
+
temp = initial_temp * (cooling_rate**k)
|
| 158 |
+
|
| 159 |
+
# Perturb ONLY the specified circle's center
|
| 160 |
+
trial_centers = np.copy(current_centers)
|
| 161 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 162 |
+
dx = step_size * np.cos(random_angle)
|
| 163 |
+
dy = step_size * np.sin(random_angle)
|
| 164 |
+
|
| 165 |
+
trial_centers[index_to_perturb, 0] += dx
|
| 166 |
+
trial_centers[index_to_perturb, 1] += dy
|
| 167 |
+
|
| 168 |
+
# Ensure the new center is within the unit square [0, 1]
|
| 169 |
+
trial_centers[index_to_perturb] = np.clip(trial_centers[index_to_perturb], 0.0, 1.0)
|
| 170 |
+
|
| 171 |
+
# Evaluate the new configuration using the static compute_max_radii
|
| 172 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 173 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 174 |
+
|
| 175 |
+
# Metropolis-Hastings acceptance criterion for maximization
|
| 176 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 177 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 178 |
+
current_centers = trial_centers
|
| 179 |
+
current_sum_radii = trial_sum_radii
|
| 180 |
+
|
| 181 |
+
if current_sum_radii > best_sum_radii_local:
|
| 182 |
+
best_sum_radii_local = current_sum_radii
|
| 183 |
+
best_centers_local = np.copy(current_centers)
|
| 184 |
+
|
| 185 |
+
return best_centers_local, best_sum_radii_local
|
| 186 |
+
|
| 187 |
+
def construct_packing(self):
|
| 188 |
+
"""
|
| 189 |
+
Main method to construct the circle packing.
|
| 190 |
+
Orchestrates initial placement, exhaustive search, and local refinement.
|
| 191 |
+
"""
|
| 192 |
+
self._initial_grid_placement()
|
| 193 |
+
|
| 194 |
+
if self.n > 25:
|
| 195 |
+
# Stage 1: Exhaustive search for the optimal 26th circle position
|
| 196 |
+
# This identifies a strong starting point for the 26th circle.
|
| 197 |
+
initial_centers_for_refinement, sum_radii_after_exhaustive = self._find_optimal_26th_circle_position()
|
| 198 |
+
|
| 199 |
+
# Stage 2: Local refinement using SA for the 26th circle's position
|
| 200 |
+
# This fine-tunes the position to potentially eke out further gains.
|
| 201 |
+
refined_centers, _ = self._local_refinement_26th_circle(
|
| 202 |
+
initial_centers_for_refinement,
|
| 203 |
+
sum_radii_after_exhaustive,
|
| 204 |
+
index_to_perturb=25
|
| 205 |
+
)
|
| 206 |
+
self.centers = refined_centers # Update the instance's centers with the refined ones
|
| 207 |
+
|
| 208 |
+
# Final radius calculation for the optimized center configuration
|
| 209 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 210 |
+
return self.centers, self.radii
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def construct_packing():
|
| 214 |
+
"""
|
| 215 |
+
Constructs an arrangement of 26 circles by leveraging a modular
|
| 216 |
+
object-oriented approach. It initializes with a 5x5 grid and
|
| 217 |
+
dynamically places the 26th circle in the most optimal interstitial
|
| 218 |
+
void, then iteratively adjusts radii to maximize their sum.
|
| 219 |
+
|
| 220 |
+
Returns:
|
| 221 |
+
Tuple of (centers, radii)
|
| 222 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 223 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 224 |
+
"""
|
| 225 |
+
packer = CirclePacker(num_circles=26)
|
| 226 |
+
centers, radii = packer.construct_packing()
|
| 227 |
+
return centers, radii
|
| 228 |
+
# EVOLVE-BLOCK-END
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
# This part remains fixed (not evolved)
|
| 232 |
+
def run_packing():
|
| 233 |
+
"""Run the circle packing constructor for n=26"""
|
| 234 |
+
centers, radii = construct_packing()
|
| 235 |
+
# Calculate the sum of radii
|
| 236 |
+
sum_radii = np.sum(radii)
|
| 237 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_101/search_replace.txt
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<exponential_decay_radii_calc>
|
| 2 |
+
A previous version description indicated an exponential decay for the growth factor and tolerance parameters within `_compute_max_radii_static`, but the actual implementation used a linear decay. This change corrects `_compute_max_radii_static` to use an exponential decay for both `current_growth_factor` and `current_tolerance`, aligning the code with the intended and often more effective annealing schedule for iterative radius determination. This smoother, non-linear transition can lead to more precise and robust radius calculations, which are critical for accurate evaluation of packing quality during the SA stages.
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DIFF>
|
| 6 |
+
<<<<<<< SEARCH
|
| 7 |
+
# Calculate dynamic growth factor (linear decay)
|
| 8 |
+
# Factor goes from 0 (at first iter) to 1 (at last iter)
|
| 9 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0)
|
| 10 |
+
current_growth_factor = growth_factor_initial - interp_factor * \
|
| 11 |
+
(growth_factor_initial - growth_factor_final)
|
| 12 |
+
|
| 13 |
+
# Calculate dynamic tolerance (linear decay)
|
| 14 |
+
current_tolerance = tolerance_initial - interp_factor * \
|
| 15 |
+
(tolerance_initial - tolerance_final)
|
| 16 |
+
=======
|
| 17 |
+
# Use exponential interpolation for smoother parameter transition
|
| 18 |
+
progress = outer_iter_idx / (outer_iterations - 1 + 1e-9)
|
| 19 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**progress
|
| 20 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**progress
|
| 21 |
+
>>>>>>> REPLACE
|
| 22 |
+
</DIFF>
|
| 23 |
+
|
| 24 |
+
<add_global_sa_refinement>
|
| 25 |
+
The current implementation lacks a global simulated annealing (SA) refinement stage that perturbs all circles. This omission prevents the packing from breaking free from the initial rigid 5x5 grid and exploring more optimal global arrangements. This edit introduces a `_global_refinement_sa` method, taking inspiration from highly-scoring previous versions. This new stage will apply SA to all 26 circles, using a carefully tuned schedule (more iterations, slightly lower step size and temperature) to gently "jiggle" the entire packing into a better global optimum after the initial placement and local refinement. This will allow the grid structure to deform and the circles to settle into more dense configurations.
|
| 26 |
+
</NAME>
|
| 27 |
+
|
| 28 |
+
<DIFF>
|
| 29 |
+
<<<<<<< SEARCH
|
| 30 |
+
return best_centers_local, best_sum_radii_local
|
| 31 |
+
|
| 32 |
+
def construct_packing(self):
|
| 33 |
+
"""
|
| 34 |
+
Main method to construct the circle packing.
|
| 35 |
+
Orchestrates initial placement, exhaustive search, and local refinement.
|
| 36 |
+
"""
|
| 37 |
+
=======
|
| 38 |
+
return best_centers_local, best_sum_radii_local
|
| 39 |
+
|
| 40 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 41 |
+
"""
|
| 42 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 43 |
+
better global optimum, using tuned parameters from high-scoring versions.
|
| 44 |
+
"""
|
| 45 |
+
current_centers = np.copy(initial_centers)
|
| 46 |
+
current_sum_radii = initial_sum_radii
|
| 47 |
+
|
| 48 |
+
best_centers_global = np.copy(current_centers)
|
| 49 |
+
best_sum_radii_global = current_sum_radii
|
| 50 |
+
|
| 51 |
+
# SA parameters for a final, gentle, global refinement
|
| 52 |
+
num_iterations = 1500
|
| 53 |
+
initial_step_size = 0.004
|
| 54 |
+
initial_temp = 1e-5
|
| 55 |
+
cooling_rate = 0.995
|
| 56 |
+
|
| 57 |
+
step_size = initial_step_size
|
| 58 |
+
temp = initial_temp
|
| 59 |
+
|
| 60 |
+
for _ in range(num_iterations):
|
| 61 |
+
# Perturb a random circle from the entire set
|
| 62 |
+
idx_to_perturb = np.random.randint(self.n)
|
| 63 |
+
|
| 64 |
+
trial_centers = np.copy(current_centers)
|
| 65 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 66 |
+
trial_centers[idx_to_perturb] += move
|
| 67 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 68 |
+
|
| 69 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 70 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 71 |
+
|
| 72 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 73 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 74 |
+
current_centers = trial_centers
|
| 75 |
+
current_sum_radii = trial_sum_radii
|
| 76 |
+
|
| 77 |
+
if current_sum_radii > best_sum_radii_global:
|
| 78 |
+
best_sum_radii_global = current_sum_radii
|
| 79 |
+
best_centers_global = np.copy(current_centers)
|
| 80 |
+
|
| 81 |
+
temp *= cooling_rate
|
| 82 |
+
step_size = max(step_size * cooling_rate, 5e-8)
|
| 83 |
+
|
| 84 |
+
return best_centers_global, best_sum_radii_global
|
| 85 |
+
|
| 86 |
+
def construct_packing(self):
|
| 87 |
+
"""
|
| 88 |
+
Main method to construct the circle packing.
|
| 89 |
+
Orchestrates initial placement, exhaustive search, and local refinement.
|
| 90 |
+
"""
|
| 91 |
+
>>>>>>> REPLACE
|
| 92 |
+
</DIFF>
|
| 93 |
+
|
| 94 |
+
<integrate_global_sa>
|
| 95 |
+
This edit integrates the newly added `_global_refinement_sa` into the `construct_packing` pipeline. After the initial grid placement, exhaustive search for the 26th circle, and local SA refinement of that circle, a final `_global_refinement_sa` stage is introduced. This crucial step will allow all 26 circles to adjust their positions simultaneously, breaking the rigidity of the initial grid and enabling the discovery of higher-scoring, globally optimized packing configurations. This funnel approach, from coarse to fine and local to global refinement, is a hallmark of successful circle packing algorithms.
|
| 96 |
+
</NAME>
|
| 97 |
+
|
| 98 |
+
<DIFF>
|
| 99 |
+
<<<<<<< SEARCH
|
| 100 |
+
# This fine-tunes the position to potentially eke out further gains.
|
| 101 |
+
refined_centers, _ = self._local_refinement_26th_circle(
|
| 102 |
+
initial_centers_for_refinement,
|
| 103 |
+
sum_radii_after_exhaustive,
|
| 104 |
+
index_to_perturb=25
|
| 105 |
+
)
|
| 106 |
+
self.centers = refined_centers # Update the instance's centers with the refined ones
|
| 107 |
+
|
| 108 |
+
# Final radius calculation for the optimized center configuration
|
| 109 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 110 |
+
return self.centers, self.radii
|
| 111 |
+
=======
|
| 112 |
+
# This fine-tunes the position to potentially eke out further gains.
|
| 113 |
+
centers_after_local_refinement, sum_radii_after_local_refinement = self._local_refinement_26th_circle(
|
| 114 |
+
initial_centers_for_refinement,
|
| 115 |
+
sum_radii_after_exhaustive,
|
| 116 |
+
index_to_perturb=25
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 120 |
+
refined_centers, _ = self._global_refinement_sa(
|
| 121 |
+
centers_after_local_refinement,
|
| 122 |
+
sum_radii_after_local_refinement
|
| 123 |
+
)
|
| 124 |
+
self.centers = refined_centers # Update the instance's centers with the globally refined ones
|
| 125 |
+
|
| 126 |
+
# Final radius calculation for the fully optimized center configuration
|
| 127 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 128 |
+
return self.centers, self.radii
|
| 129 |
+
>>>>>>> REPLACE
|
| 130 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/edit.diff
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,356 +1,280 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 12 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 13 |
+
- with a localized refinement stage.
|
| 14 |
+
+ with localized and global refinement stages.
|
| 15 |
+
"""
|
| 16 |
+
def __init__(self, num_circles=26):
|
| 17 |
+
self.n = num_circles
|
| 18 |
+
self.centers = np.zeros((self.n, 2))
|
| 19 |
+
self.radii = np.zeros(self.n)
|
| 20 |
+
|
| 21 |
+
@staticmethod
|
| 22 |
+
def _compute_max_radii_static(centers, n):
|
| 23 |
+
"""
|
| 24 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 25 |
+
- an iterative growth and constraint resolution method. This static version
|
| 26 |
+
- incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 27 |
+
- performance and precision, based on the best prior implementation.
|
| 28 |
+
+ an iterative growth and constraint resolution method. This version uses a
|
| 29 |
+
+ tighter final tolerance for enhanced precision.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 33 |
+
n (int): Number of circles.
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 37 |
+
"""
|
| 38 |
+
radii = np.zeros(n)
|
| 39 |
+
|
| 40 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 41 |
+
growth_factor_initial = 1.005
|
| 42 |
+
growth_factor_final = 1.002
|
| 43 |
+
tolerance_initial = 1e-7
|
| 44 |
+
- tolerance_final = 1e-10
|
| 45 |
+
+ tolerance_final = 1e-11 # Increased precision from 1e-10
|
| 46 |
+
|
| 47 |
+
outer_iterations = 400
|
| 48 |
+
inner_iterations = 20
|
| 49 |
+
|
| 50 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 51 |
+
for i in range(n):
|
| 52 |
+
x, y = centers[i]
|
| 53 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 54 |
+
|
| 55 |
+
for outer_iter_idx in range(outer_iterations):
|
| 56 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 57 |
+
- # Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 58 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 59 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 60 |
+
|
| 61 |
+
- radii *= current_growth_factor # Tentatively grow all radii
|
| 62 |
+
+ radii *= current_growth_factor
|
| 63 |
+
|
| 64 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 65 |
+
constraints_changed = False
|
| 66 |
+
|
| 67 |
+
- # Enforce boundary constraints with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
x, y = centers[i]
|
| 70 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 71 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 72 |
+
radii[i] = boundary_limit
|
| 73 |
+
constraints_changed = True
|
| 74 |
+
|
| 75 |
+
- # Resolve overlaps between circles with dynamic tolerance
|
| 76 |
+
for i in range(n):
|
| 77 |
+
for j in range(i + 1, n):
|
| 78 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 79 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 80 |
+
total_radius = radii[i] + radii[j]
|
| 81 |
+
if total_radius > tolerance_final:
|
| 82 |
+
scale = dist / total_radius
|
| 83 |
+
radii[i] *= scale
|
| 84 |
+
radii[j] *= scale
|
| 85 |
+
constraints_changed = True
|
| 86 |
+
|
| 87 |
+
if not constraints_changed:
|
| 88 |
+
break
|
| 89 |
+
return radii
|
| 90 |
+
|
| 91 |
+
def _initial_grid_placement(self):
|
| 92 |
+
"""
|
| 93 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 94 |
+
"""
|
| 95 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 96 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 97 |
+
self.centers[:25] = grid_centers
|
| 98 |
+
|
| 99 |
+
def _find_optimal_26th_circle_position(self):
|
| 100 |
+
"""
|
| 101 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 102 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 103 |
+
"""
|
| 104 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 105 |
+
-
|
| 106 |
+
- # Initialize with a default position (center of the square) and calculate its sum of radii
|
| 107 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 108 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 109 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 110 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 111 |
+
-
|
| 112 |
+
- # Keep track of the best position from the coarse search to center the fine search
|
| 113 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 114 |
+
-
|
| 115 |
+
- # Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 116 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 117 |
+
-
|
| 118 |
+
- # --- Phase 1: Coarse Grid Search ---
|
| 119 |
+
- # Explore a broader region first to identify promising areas.
|
| 120 |
+
coarse_delta = 0.05
|
| 121 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 122 |
+
-
|
| 123 |
+
- coarse_candidate_points = []
|
| 124 |
+
- for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 125 |
+
- for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 126 |
+
- coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 127 |
+
+ coarse_candidate_points = [
|
| 128 |
+
+ [base_x + offset_x, base_y + offset_y]
|
| 129 |
+
+ for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords)
|
| 130 |
+
+ for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets)
|
| 131 |
+
+ ]
|
| 132 |
+
|
| 133 |
+
for candidate_pos in coarse_candidate_points:
|
| 134 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 135 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 136 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 137 |
+
current_sum_radii = np.sum(trial_radii)
|
| 138 |
+
-
|
| 139 |
+
if current_sum_radii > best_sum_radii:
|
| 140 |
+
best_sum_radii = current_sum_radii
|
| 141 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 142 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 143 |
+
|
| 144 |
+
- # --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 145 |
+
- # Focus the search more precisely around the most promising area identified in Phase 1.
|
| 146 |
+
fine_delta = 0.01
|
| 147 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 148 |
+
-
|
| 149 |
+
- fine_candidate_points = []
|
| 150 |
+
- for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 151 |
+
- fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 152 |
+
+ fine_candidate_points = [
|
| 153 |
+
+ [best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y]
|
| 154 |
+
+ for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets)
|
| 155 |
+
+ ]
|
| 156 |
+
|
| 157 |
+
for candidate_pos in fine_candidate_points:
|
| 158 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 159 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 160 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 161 |
+
current_sum_radii = np.sum(trial_radii)
|
| 162 |
+
-
|
| 163 |
+
if current_sum_radii > best_sum_radii:
|
| 164 |
+
best_sum_radii = current_sum_radii
|
| 165 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 166 |
+
|
| 167 |
+
self.centers[25] = optimal_26th_pos
|
| 168 |
+
-
|
| 169 |
+
return self.centers, best_sum_radii
|
| 170 |
+
|
| 171 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 172 |
+
"""
|
| 173 |
+
- Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 174 |
+
- positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 175 |
+
- This allows the base grid to relax and better accommodate the interstitial circle.
|
| 176 |
+
- This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 177 |
+
+ Applies a localized SA search to fine-tune the positions of a cluster of circles.
|
| 178 |
+
"""
|
| 179 |
+
current_centers = np.copy(initial_centers)
|
| 180 |
+
- # Compute radii for initial stress calculation and consistent sum.
|
| 181 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 182 |
+
current_sum_radii = np.sum(current_radii)
|
| 183 |
+
-
|
| 184 |
+
best_centers_local = np.copy(current_centers)
|
| 185 |
+
best_sum_radii_local = current_sum_radii
|
| 186 |
+
-
|
| 187 |
+
- # SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 188 |
+
num_iterations = 150
|
| 189 |
+
initial_step_size = 0.005
|
| 190 |
+
initial_temp = 0.0001
|
| 191 |
+
cooling_rate = 0.99
|
| 192 |
+
-
|
| 193 |
+
step_size = initial_step_size
|
| 194 |
+
temp = initial_temp
|
| 195 |
+
-
|
| 196 |
+
- # Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 197 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 198 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 199 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 200 |
+
-
|
| 201 |
+
- # Parameters for dynamic step size adjustment
|
| 202 |
+
- acceptance_window = 30
|
| 203 |
+
- acceptance_count = 0
|
| 204 |
+
- target_acceptance_rate = 0.44
|
| 205 |
+
- adjustment_factor = 1.05
|
| 206 |
+
+ acceptance_window, acceptance_count = 30, 0
|
| 207 |
+
+ target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 208 |
+
|
| 209 |
+
for i in range(num_iterations):
|
| 210 |
+
- # Stress-based selection within the cluster
|
| 211 |
+
cluster_radii = current_radii[cluster_indices]
|
| 212 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 213 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 214 |
+
- if np.sum(weights) > 1e-9:
|
| 215 |
+
- probabilities = weights / np.sum(weights)
|
| 216 |
+
- idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 217 |
+
- else:
|
| 218 |
+
- idx_to_move = np.random.choice(cluster_indices)
|
| 219 |
+
-
|
| 220 |
+
+ idx_to_move = np.random.choice(cluster_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(cluster_indices)
|
| 221 |
+
|
| 222 |
+
trial_centers = np.copy(current_centers)
|
| 223 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 224 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 225 |
+
-
|
| 226 |
+
- # Evaluate the new configuration
|
| 227 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 228 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 229 |
+
-
|
| 230 |
+
- # Metropolis-Hastings acceptance criterion
|
| 231 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 232 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 233 |
+
- current_centers = trial_centers
|
| 234 |
+
- current_radii = trial_radii # Update radii for stress calculation
|
| 235 |
+
- current_sum_radii = trial_sum_radii
|
| 236 |
+
+ current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 237 |
+
acceptance_count += 1
|
| 238 |
+
-
|
| 239 |
+
if current_sum_radii > best_sum_radii_local:
|
| 240 |
+
best_sum_radii_local = current_sum_radii
|
| 241 |
+
best_centers_local = np.copy(current_centers)
|
| 242 |
+
|
| 243 |
+
- # Dynamic step size adjustment
|
| 244 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 245 |
+
- acceptance_rate = acceptance_count / acceptance_window
|
| 246 |
+
- if acceptance_rate > target_acceptance_rate:
|
| 247 |
+
- step_size *= adjustment_factor
|
| 248 |
+
- else:
|
| 249 |
+
- step_size /= adjustment_factor
|
| 250 |
+
+ step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1 / adjustment_factor
|
| 251 |
+
acceptance_count = 0
|
| 252 |
+
-
|
| 253 |
+
temp *= cooling_rate
|
| 254 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 255 |
+
-
|
| 256 |
+
return best_centers_local, best_sum_radii_local
|
| 257 |
+
|
| 258 |
+
def _global_refinement_sa(self, initial_centers):
|
| 259 |
+
"""
|
| 260 |
+
- Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 261 |
+
- the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 262 |
+
- settle the entire packing into a better local optimum. This technique was
|
| 263 |
+
- present in prior high-scoring implementations.
|
| 264 |
+
- """
|
| 265 |
+
- # SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 266 |
+
- sa_iterations = 300
|
| 267 |
+
+ Applies a global SA search with probabilistic cluster moves to refine all circles.
|
| 268 |
+
+ """
|
| 269 |
+
+ sa_iterations = 350 # Increased for more thorough search
|
| 270 |
+
sa_initial_temp = 5e-6
|
| 271 |
+
sa_cooling_rate = 0.99
|
| 272 |
+
sa_initial_step_size = 0.001
|
| 273 |
+
-
|
| 274 |
+
current_centers = np.copy(initial_centers)
|
| 275 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 276 |
+
current_sum_radii = np.sum(current_radii)
|
| 277 |
+
-
|
| 278 |
+
- best_centers = np.copy(current_centers)
|
| 279 |
+
- best_sum_radii = current_sum_radii
|
| 280 |
+
-
|
| 281 |
+
- temp = sa_initial_temp
|
| 282 |
+
- step_size = sa_initial_step_size
|
| 283 |
+
-
|
| 284 |
+
+ best_centers, best_sum_radii = np.copy(current_centers), current_sum_radii
|
| 285 |
+
+ temp, step_size = sa_initial_temp, sa_initial_step_size
|
| 286 |
+
all_indices = np.arange(self.n)
|
| 287 |
+
-
|
| 288 |
+
- # Parameters for dynamic step size adjustment
|
| 289 |
+
- acceptance_window = 30
|
| 290 |
+
- acceptance_count = 0
|
| 291 |
+
- target_acceptance_rate = 0.44 # Common target for SA
|
| 292 |
+
- adjustment_factor = 1.05
|
| 293 |
+
+ acceptance_window, acceptance_count = 30, 0
|
| 294 |
+
+ target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 295 |
+
+ cluster_move_prob = 0.15
|
| 296 |
+
+ num_cluster_neighbors = 2
|
| 297 |
+
|
| 298 |
+
for i in range(sa_iterations):
|
| 299 |
+
- # Stress-based selection: prioritize moving circles with smaller radii.
|
| 300 |
+
- inv_radii = 1.0 / (current_radii + 1e-9)
|
| 301 |
+
- weights = (inv_radii - np.min(inv_radii))**2
|
| 302 |
+
- if np.sum(weights) > 1e-9:
|
| 303 |
+
- probabilities = weights / np.sum(weights)
|
| 304 |
+
- idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 305 |
+
+ trial_centers = np.copy(current_centers)
|
| 306 |
+
+
|
| 307 |
+
+ # Probabilistically choose between a single move and a cluster move
|
| 308 |
+
+ if np.random.rand() < cluster_move_prob:
|
| 309 |
+
+ # --- Cluster Move ---
|
| 310 |
+
+ inv_radii = 1.0 / (current_radii + 1e-9)
|
| 311 |
+
+ weights = (inv_radii - np.min(inv_radii))**2
|
| 312 |
+
+ primary_idx = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 313 |
+
+ distances = np.linalg.norm(trial_centers - trial_centers[primary_idx], axis=1)
|
| 314 |
+
+ distances[primary_idx] = np.inf
|
| 315 |
+
+ neighbor_indices = np.argsort(distances)[:num_cluster_neighbors]
|
| 316 |
+
+ cluster_indices = np.append(neighbor_indices, primary_idx)
|
| 317 |
+
+ move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 318 |
+
+ trial_centers[cluster_indices] += move
|
| 319 |
+
+ trial_centers[cluster_indices] = np.clip(trial_centers[cluster_indices], 0.0, 1.0)
|
| 320 |
+
else:
|
| 321 |
+
- idx_to_move = np.random.choice(all_indices)
|
| 322 |
+
-
|
| 323 |
+
- trial_centers = np.copy(current_centers)
|
| 324 |
+
- move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 325 |
+
- trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 326 |
+
-
|
| 327 |
+
+ # --- Single Move ---
|
| 328 |
+
+ inv_radii = 1.0 / (current_radii + 1e-9)
|
| 329 |
+
+ weights = (inv_radii - np.min(inv_radii))**2
|
| 330 |
+
+ idx_to_move = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 331 |
+
+ move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 332 |
+
+ trial_centers[idx_to_move] += move
|
| 333 |
+
+ trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move], 0.0, 1.0)
|
| 334 |
+
+
|
| 335 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 336 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 337 |
+
-
|
| 338 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 339 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 340 |
+
- current_centers = trial_centers
|
| 341 |
+
- current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 342 |
+
- current_sum_radii = trial_sum_radii
|
| 343 |
+
+ current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 344 |
+
acceptance_count += 1
|
| 345 |
+
-
|
| 346 |
+
if current_sum_radii > best_sum_radii:
|
| 347 |
+
best_sum_radii = current_sum_radii
|
| 348 |
+
best_centers = np.copy(current_centers)
|
| 349 |
+
|
| 350 |
+
- # Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 351 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 352 |
+
- acceptance_rate = acceptance_count / acceptance_window
|
| 353 |
+
- if acceptance_rate > target_acceptance_rate:
|
| 354 |
+
- step_size *= adjustment_factor
|
| 355 |
+
- else:
|
| 356 |
+
- step_size /= adjustment_factor
|
| 357 |
+
+ step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1/adjustment_factor
|
| 358 |
+
acceptance_count = 0
|
| 359 |
+
|
| 360 |
+
temp *= sa_cooling_rate
|
| 361 |
+
- step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 362 |
+
-
|
| 363 |
+
+ step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 364 |
+
return best_centers
|
| 365 |
+
|
| 366 |
+
def construct_packing(self):
|
| 367 |
+
"""
|
| 368 |
+
- Main method to construct the circle packing, orchestrating a multi-stage
|
| 369 |
+
- optimization: initial placement, multi-res search, local SA, and global SA.
|
| 370 |
+
+ Orchestrates the multi-stage optimization process.
|
| 371 |
+
"""
|
| 372 |
+
self._initial_grid_placement()
|
| 373 |
+
-
|
| 374 |
+
if self.n > 25:
|
| 375 |
+
- # Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 376 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 377 |
+
-
|
| 378 |
+
- # Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 379 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 380 |
+
centers_after_search,
|
| 381 |
+
sum_radii_after_search
|
| 382 |
+
)
|
| 383 |
+
-
|
| 384 |
+
- # Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 385 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 386 |
+
self.centers = centers_after_global_sa
|
| 387 |
+
|
| 388 |
+
- # Final radius calculation for the fully optimized center configuration
|
| 389 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 390 |
+
return self.centers, self.radii
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def construct_packing():
|
| 394 |
+
"""
|
| 395 |
+
- Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 396 |
+
- optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 397 |
+
-
|
| 398 |
+
- Returns:
|
| 399 |
+
- Tuple of (centers, radii)
|
| 400 |
+
- centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 401 |
+
- radii: np.array of shape (26) with final radius of each circle
|
| 402 |
+
+ Constructs an arrangement of 26 circles by leveraging a superior multi-stage
|
| 403 |
+
+ optimization strategy including grid search, local SA, and global SA with cluster moves.
|
| 404 |
+
"""
|
| 405 |
+
packer = CirclePacker(num_circles=26)
|
| 406 |
+
centers, radii = packer.construct_packing()
|
| 407 |
+
return centers, radii
|
| 408 |
+
# EVOLVE-BLOCK-END
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
# This part remains fixed (not evolved)
|
| 412 |
+
def run_packing():
|
| 413 |
+
"""Run the circle packing constructor for n=26"""
|
| 414 |
+
centers, radii = construct_packing()
|
| 415 |
+
# Calculate the sum of radii
|
| 416 |
+
sum_radii = np.sum(radii)
|
| 417 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/main.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with localized and global refinement stages.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This version uses a
|
| 22 |
+
tighter final tolerance for enhanced precision.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 26 |
+
n (int): Number of circles.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
radii = np.zeros(n)
|
| 32 |
+
|
| 33 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 34 |
+
growth_factor_initial = 1.005
|
| 35 |
+
growth_factor_final = 1.002
|
| 36 |
+
tolerance_initial = 1e-7
|
| 37 |
+
tolerance_final = 1e-11 # Increased precision from 1e-10
|
| 38 |
+
|
| 39 |
+
outer_iterations = 400
|
| 40 |
+
inner_iterations = 20
|
| 41 |
+
|
| 42 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 43 |
+
for i in range(n):
|
| 44 |
+
x, y = centers[i]
|
| 45 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 46 |
+
|
| 47 |
+
for outer_iter_idx in range(outer_iterations):
|
| 48 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 49 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 50 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 51 |
+
|
| 52 |
+
radii *= current_growth_factor
|
| 53 |
+
|
| 54 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 55 |
+
constraints_changed = False
|
| 56 |
+
|
| 57 |
+
for i in range(n):
|
| 58 |
+
x, y = centers[i]
|
| 59 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 60 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 61 |
+
radii[i] = boundary_limit
|
| 62 |
+
constraints_changed = True
|
| 63 |
+
|
| 64 |
+
for i in range(n):
|
| 65 |
+
for j in range(i + 1, n):
|
| 66 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 67 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 68 |
+
total_radius = radii[i] + radii[j]
|
| 69 |
+
if total_radius > tolerance_final:
|
| 70 |
+
scale = dist / total_radius
|
| 71 |
+
radii[i] *= scale
|
| 72 |
+
radii[j] *= scale
|
| 73 |
+
constraints_changed = True
|
| 74 |
+
|
| 75 |
+
if not constraints_changed:
|
| 76 |
+
break
|
| 77 |
+
return radii
|
| 78 |
+
|
| 79 |
+
def _initial_grid_placement(self):
|
| 80 |
+
"""
|
| 81 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 82 |
+
"""
|
| 83 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 84 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 85 |
+
self.centers[:25] = grid_centers
|
| 86 |
+
|
| 87 |
+
def _find_optimal_26th_circle_position(self):
|
| 88 |
+
"""
|
| 89 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 90 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 91 |
+
"""
|
| 92 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 93 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 94 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 95 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 96 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 97 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 98 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 99 |
+
coarse_delta = 0.05
|
| 100 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 101 |
+
coarse_candidate_points = [
|
| 102 |
+
[base_x + offset_x, base_y + offset_y]
|
| 103 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords)
|
| 104 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets)
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
for candidate_pos in coarse_candidate_points:
|
| 108 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 109 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 110 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 111 |
+
current_sum_radii = np.sum(trial_radii)
|
| 112 |
+
if current_sum_radii > best_sum_radii:
|
| 113 |
+
best_sum_radii = current_sum_radii
|
| 114 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 115 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 116 |
+
|
| 117 |
+
fine_delta = 0.01
|
| 118 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 119 |
+
fine_candidate_points = [
|
| 120 |
+
[best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y]
|
| 121 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets)
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
for candidate_pos in fine_candidate_points:
|
| 125 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 126 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 127 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 128 |
+
current_sum_radii = np.sum(trial_radii)
|
| 129 |
+
if current_sum_radii > best_sum_radii:
|
| 130 |
+
best_sum_radii = current_sum_radii
|
| 131 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 132 |
+
|
| 133 |
+
self.centers[25] = optimal_26th_pos
|
| 134 |
+
return self.centers, best_sum_radii
|
| 135 |
+
|
| 136 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 137 |
+
"""
|
| 138 |
+
Applies a localized SA search to fine-tune the positions of a cluster of circles.
|
| 139 |
+
"""
|
| 140 |
+
current_centers = np.copy(initial_centers)
|
| 141 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 142 |
+
current_sum_radii = np.sum(current_radii)
|
| 143 |
+
best_centers_local = np.copy(current_centers)
|
| 144 |
+
best_sum_radii_local = current_sum_radii
|
| 145 |
+
num_iterations = 150
|
| 146 |
+
initial_step_size = 0.005
|
| 147 |
+
initial_temp = 0.0001
|
| 148 |
+
cooling_rate = 0.99
|
| 149 |
+
step_size = initial_step_size
|
| 150 |
+
temp = initial_temp
|
| 151 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 152 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 153 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 154 |
+
acceptance_window, acceptance_count = 30, 0
|
| 155 |
+
target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 156 |
+
|
| 157 |
+
for i in range(num_iterations):
|
| 158 |
+
cluster_radii = current_radii[cluster_indices]
|
| 159 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 160 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 161 |
+
idx_to_move = np.random.choice(cluster_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(cluster_indices)
|
| 162 |
+
|
| 163 |
+
trial_centers = np.copy(current_centers)
|
| 164 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 165 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 166 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 167 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 168 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 169 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 170 |
+
current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 171 |
+
acceptance_count += 1
|
| 172 |
+
if current_sum_radii > best_sum_radii_local:
|
| 173 |
+
best_sum_radii_local = current_sum_radii
|
| 174 |
+
best_centers_local = np.copy(current_centers)
|
| 175 |
+
|
| 176 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 177 |
+
step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1 / adjustment_factor
|
| 178 |
+
acceptance_count = 0
|
| 179 |
+
temp *= cooling_rate
|
| 180 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 181 |
+
return best_centers_local, best_sum_radii_local
|
| 182 |
+
|
| 183 |
+
def _global_refinement_sa(self, initial_centers):
|
| 184 |
+
"""
|
| 185 |
+
Applies a global SA search with probabilistic cluster moves to refine all circles.
|
| 186 |
+
"""
|
| 187 |
+
sa_iterations = 350 # Increased for more thorough search
|
| 188 |
+
sa_initial_temp = 5e-6
|
| 189 |
+
sa_cooling_rate = 0.99
|
| 190 |
+
sa_initial_step_size = 0.001
|
| 191 |
+
current_centers = np.copy(initial_centers)
|
| 192 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 193 |
+
current_sum_radii = np.sum(current_radii)
|
| 194 |
+
best_centers, best_sum_radii = np.copy(current_centers), current_sum_radii
|
| 195 |
+
temp, step_size = sa_initial_temp, sa_initial_step_size
|
| 196 |
+
all_indices = np.arange(self.n)
|
| 197 |
+
acceptance_window, acceptance_count = 30, 0
|
| 198 |
+
target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 199 |
+
cluster_move_prob = 0.15
|
| 200 |
+
num_cluster_neighbors = 2
|
| 201 |
+
|
| 202 |
+
for i in range(sa_iterations):
|
| 203 |
+
trial_centers = np.copy(current_centers)
|
| 204 |
+
|
| 205 |
+
# Probabilistically choose between a single move and a cluster move
|
| 206 |
+
if np.random.rand() < cluster_move_prob:
|
| 207 |
+
# --- Cluster Move ---
|
| 208 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 209 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 210 |
+
primary_idx = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 211 |
+
distances = np.linalg.norm(trial_centers - trial_centers[primary_idx], axis=1)
|
| 212 |
+
distances[primary_idx] = np.inf
|
| 213 |
+
neighbor_indices = np.argsort(distances)[:num_cluster_neighbors]
|
| 214 |
+
cluster_indices = np.append(neighbor_indices, primary_idx)
|
| 215 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 216 |
+
trial_centers[cluster_indices] += move
|
| 217 |
+
trial_centers[cluster_indices] = np.clip(trial_centers[cluster_indices], 0.0, 1.0)
|
| 218 |
+
else:
|
| 219 |
+
# --- Single Move ---
|
| 220 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 221 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 222 |
+
idx_to_move = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 223 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 224 |
+
trial_centers[idx_to_move] += move
|
| 225 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move], 0.0, 1.0)
|
| 226 |
+
|
| 227 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 228 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 229 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 230 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 231 |
+
current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 232 |
+
acceptance_count += 1
|
| 233 |
+
if current_sum_radii > best_sum_radii:
|
| 234 |
+
best_sum_radii = current_sum_radii
|
| 235 |
+
best_centers = np.copy(current_centers)
|
| 236 |
+
|
| 237 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 238 |
+
step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1/adjustment_factor
|
| 239 |
+
acceptance_count = 0
|
| 240 |
+
|
| 241 |
+
temp *= sa_cooling_rate
|
| 242 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 243 |
+
return best_centers
|
| 244 |
+
|
| 245 |
+
def construct_packing(self):
|
| 246 |
+
"""
|
| 247 |
+
Orchestrates the multi-stage optimization process.
|
| 248 |
+
"""
|
| 249 |
+
self._initial_grid_placement()
|
| 250 |
+
if self.n > 25:
|
| 251 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 252 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 253 |
+
centers_after_search,
|
| 254 |
+
sum_radii_after_search
|
| 255 |
+
)
|
| 256 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 257 |
+
self.centers = centers_after_global_sa
|
| 258 |
+
|
| 259 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 260 |
+
return self.centers, self.radii
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def construct_packing():
|
| 264 |
+
"""
|
| 265 |
+
Constructs an arrangement of 26 circles by leveraging a superior multi-stage
|
| 266 |
+
optimization strategy including grid search, local SA, and global SA with cluster moves.
|
| 267 |
+
"""
|
| 268 |
+
packer = CirclePacker(num_circles=26)
|
| 269 |
+
centers, radii = packer.construct_packing()
|
| 270 |
+
return centers, radii
|
| 271 |
+
# EVOLVE-BLOCK-END
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# This part remains fixed (not evolved)
|
| 275 |
+
def run_packing():
|
| 276 |
+
"""Run the circle packing constructor for n=26"""
|
| 277 |
+
centers, radii = construct_packing()
|
| 278 |
+
# Calculate the sum of radii
|
| 279 |
+
sum_radii = np.sum(radii)
|
| 280 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/original.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with a localized refinement stage.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This static version
|
| 22 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 23 |
+
performance and precision, based on the best prior implementation.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 27 |
+
n (int): Number of circles.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
radii = np.zeros(n)
|
| 33 |
+
|
| 34 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 35 |
+
growth_factor_initial = 1.005
|
| 36 |
+
growth_factor_final = 1.002
|
| 37 |
+
tolerance_initial = 1e-7
|
| 38 |
+
tolerance_final = 1e-10
|
| 39 |
+
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
inner_iterations = 20
|
| 42 |
+
|
| 43 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 44 |
+
for i in range(n):
|
| 45 |
+
x, y = centers[i]
|
| 46 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 47 |
+
|
| 48 |
+
for outer_iter_idx in range(outer_iterations):
|
| 49 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 50 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 51 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 52 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 53 |
+
|
| 54 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 55 |
+
|
| 56 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 57 |
+
constraints_changed = False
|
| 58 |
+
|
| 59 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 60 |
+
for i in range(n):
|
| 61 |
+
x, y = centers[i]
|
| 62 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 63 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 64 |
+
radii[i] = boundary_limit
|
| 65 |
+
constraints_changed = True
|
| 66 |
+
|
| 67 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
for j in range(i + 1, n):
|
| 70 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 71 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 72 |
+
total_radius = radii[i] + radii[j]
|
| 73 |
+
if total_radius > tolerance_final:
|
| 74 |
+
scale = dist / total_radius
|
| 75 |
+
radii[i] *= scale
|
| 76 |
+
radii[j] *= scale
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
if not constraints_changed:
|
| 80 |
+
break
|
| 81 |
+
return radii
|
| 82 |
+
|
| 83 |
+
def _initial_grid_placement(self):
|
| 84 |
+
"""
|
| 85 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 86 |
+
"""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 89 |
+
self.centers[:25] = grid_centers
|
| 90 |
+
|
| 91 |
+
def _find_optimal_26th_circle_position(self):
|
| 92 |
+
"""
|
| 93 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 94 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 95 |
+
"""
|
| 96 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 97 |
+
|
| 98 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 99 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 100 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 101 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 102 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 103 |
+
|
| 104 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 105 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 106 |
+
|
| 107 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 108 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 109 |
+
|
| 110 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 111 |
+
# Explore a broader region first to identify promising areas.
|
| 112 |
+
coarse_delta = 0.05
|
| 113 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points = []
|
| 116 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 117 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 118 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 119 |
+
|
| 120 |
+
for candidate_pos in coarse_candidate_points:
|
| 121 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 122 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 123 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 124 |
+
current_sum_radii = np.sum(trial_radii)
|
| 125 |
+
|
| 126 |
+
if current_sum_radii > best_sum_radii:
|
| 127 |
+
best_sum_radii = current_sum_radii
|
| 128 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 129 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 130 |
+
|
| 131 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 132 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 133 |
+
fine_delta = 0.01
|
| 134 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 135 |
+
|
| 136 |
+
fine_candidate_points = []
|
| 137 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 138 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 139 |
+
|
| 140 |
+
for candidate_pos in fine_candidate_points:
|
| 141 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 142 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 143 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 144 |
+
current_sum_radii = np.sum(trial_radii)
|
| 145 |
+
|
| 146 |
+
if current_sum_radii > best_sum_radii:
|
| 147 |
+
best_sum_radii = current_sum_radii
|
| 148 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 149 |
+
|
| 150 |
+
self.centers[25] = optimal_26th_pos
|
| 151 |
+
|
| 152 |
+
return self.centers, best_sum_radii
|
| 153 |
+
|
| 154 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 155 |
+
"""
|
| 156 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 157 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 158 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 159 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 160 |
+
"""
|
| 161 |
+
current_centers = np.copy(initial_centers)
|
| 162 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 163 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 164 |
+
current_sum_radii = np.sum(current_radii)
|
| 165 |
+
|
| 166 |
+
best_centers_local = np.copy(current_centers)
|
| 167 |
+
best_sum_radii_local = current_sum_radii
|
| 168 |
+
|
| 169 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 170 |
+
num_iterations = 150
|
| 171 |
+
initial_step_size = 0.005
|
| 172 |
+
initial_temp = 0.0001
|
| 173 |
+
cooling_rate = 0.99
|
| 174 |
+
|
| 175 |
+
step_size = initial_step_size
|
| 176 |
+
temp = initial_temp
|
| 177 |
+
|
| 178 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 179 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 180 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 181 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 182 |
+
|
| 183 |
+
# Parameters for dynamic step size adjustment
|
| 184 |
+
acceptance_window = 30
|
| 185 |
+
acceptance_count = 0
|
| 186 |
+
target_acceptance_rate = 0.44
|
| 187 |
+
adjustment_factor = 1.05
|
| 188 |
+
|
| 189 |
+
for i in range(num_iterations):
|
| 190 |
+
# Stress-based selection within the cluster
|
| 191 |
+
cluster_radii = current_radii[cluster_indices]
|
| 192 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 193 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 194 |
+
if np.sum(weights) > 1e-9:
|
| 195 |
+
probabilities = weights / np.sum(weights)
|
| 196 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 197 |
+
else:
|
| 198 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
trial_centers = np.copy(current_centers)
|
| 202 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 203 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 204 |
+
|
| 205 |
+
# Evaluate the new configuration
|
| 206 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 207 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 208 |
+
|
| 209 |
+
# Metropolis-Hastings acceptance criterion
|
| 210 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 211 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 212 |
+
current_centers = trial_centers
|
| 213 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 214 |
+
current_sum_radii = trial_sum_radii
|
| 215 |
+
acceptance_count += 1
|
| 216 |
+
|
| 217 |
+
if current_sum_radii > best_sum_radii_local:
|
| 218 |
+
best_sum_radii_local = current_sum_radii
|
| 219 |
+
best_centers_local = np.copy(current_centers)
|
| 220 |
+
|
| 221 |
+
# Dynamic step size adjustment
|
| 222 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 223 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 224 |
+
if acceptance_rate > target_acceptance_rate:
|
| 225 |
+
step_size *= adjustment_factor
|
| 226 |
+
else:
|
| 227 |
+
step_size /= adjustment_factor
|
| 228 |
+
acceptance_count = 0
|
| 229 |
+
|
| 230 |
+
temp *= cooling_rate
|
| 231 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 232 |
+
|
| 233 |
+
return best_centers_local, best_sum_radii_local
|
| 234 |
+
|
| 235 |
+
def _global_refinement_sa(self, initial_centers):
|
| 236 |
+
"""
|
| 237 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 238 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 239 |
+
settle the entire packing into a better local optimum. This technique was
|
| 240 |
+
present in prior high-scoring implementations.
|
| 241 |
+
"""
|
| 242 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 243 |
+
sa_iterations = 300
|
| 244 |
+
sa_initial_temp = 5e-6
|
| 245 |
+
sa_cooling_rate = 0.99
|
| 246 |
+
sa_initial_step_size = 0.001
|
| 247 |
+
|
| 248 |
+
current_centers = np.copy(initial_centers)
|
| 249 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 250 |
+
current_sum_radii = np.sum(current_radii)
|
| 251 |
+
|
| 252 |
+
best_centers = np.copy(current_centers)
|
| 253 |
+
best_sum_radii = current_sum_radii
|
| 254 |
+
|
| 255 |
+
temp = sa_initial_temp
|
| 256 |
+
step_size = sa_initial_step_size
|
| 257 |
+
|
| 258 |
+
all_indices = np.arange(self.n)
|
| 259 |
+
|
| 260 |
+
# Parameters for dynamic step size adjustment
|
| 261 |
+
acceptance_window = 30
|
| 262 |
+
acceptance_count = 0
|
| 263 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 264 |
+
adjustment_factor = 1.05
|
| 265 |
+
|
| 266 |
+
for i in range(sa_iterations):
|
| 267 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 268 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 269 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 270 |
+
if np.sum(weights) > 1e-9:
|
| 271 |
+
probabilities = weights / np.sum(weights)
|
| 272 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 273 |
+
else:
|
| 274 |
+
idx_to_move = np.random.choice(all_indices)
|
| 275 |
+
|
| 276 |
+
trial_centers = np.copy(current_centers)
|
| 277 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 278 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 279 |
+
|
| 280 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 281 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 282 |
+
|
| 283 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 284 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 285 |
+
current_centers = trial_centers
|
| 286 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 287 |
+
current_sum_radii = trial_sum_radii
|
| 288 |
+
acceptance_count += 1
|
| 289 |
+
|
| 290 |
+
if current_sum_radii > best_sum_radii:
|
| 291 |
+
best_sum_radii = current_sum_radii
|
| 292 |
+
best_centers = np.copy(current_centers)
|
| 293 |
+
|
| 294 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 295 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 296 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 297 |
+
if acceptance_rate > target_acceptance_rate:
|
| 298 |
+
step_size *= adjustment_factor
|
| 299 |
+
else:
|
| 300 |
+
step_size /= adjustment_factor
|
| 301 |
+
acceptance_count = 0
|
| 302 |
+
|
| 303 |
+
temp *= sa_cooling_rate
|
| 304 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 305 |
+
|
| 306 |
+
return best_centers
|
| 307 |
+
|
| 308 |
+
def construct_packing(self):
|
| 309 |
+
"""
|
| 310 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 311 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 312 |
+
"""
|
| 313 |
+
self._initial_grid_placement()
|
| 314 |
+
|
| 315 |
+
if self.n > 25:
|
| 316 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 317 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 318 |
+
|
| 319 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 320 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 321 |
+
centers_after_search,
|
| 322 |
+
sum_radii_after_search
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 326 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 327 |
+
self.centers = centers_after_global_sa
|
| 328 |
+
|
| 329 |
+
# Final radius calculation for the fully optimized center configuration
|
| 330 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 331 |
+
return self.centers, self.radii
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
def construct_packing():
|
| 335 |
+
"""
|
| 336 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 337 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
Tuple of (centers, radii)
|
| 341 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 342 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 343 |
+
"""
|
| 344 |
+
packer = CirclePacker(num_circles=26)
|
| 345 |
+
centers, radii = packer.construct_packing()
|
| 346 |
+
return centers, radii
|
| 347 |
+
# EVOLVE-BLOCK-END
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
# This part remains fixed (not evolved)
|
| 351 |
+
def run_packing():
|
| 352 |
+
"""Run the circle packing constructor for n=26"""
|
| 353 |
+
centers, radii = construct_packing()
|
| 354 |
+
# Calculate the sum of radii
|
| 355 |
+
sum_radii = np.sum(radii)
|
| 356 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_102/rewrite.txt
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with localized and global refinement stages.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This version uses a
|
| 22 |
+
tighter final tolerance for enhanced precision.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 26 |
+
n (int): Number of circles.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
radii = np.zeros(n)
|
| 32 |
+
|
| 33 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 34 |
+
growth_factor_initial = 1.005
|
| 35 |
+
growth_factor_final = 1.002
|
| 36 |
+
tolerance_initial = 1e-7
|
| 37 |
+
tolerance_final = 1e-11 # Increased precision from 1e-10
|
| 38 |
+
|
| 39 |
+
outer_iterations = 400
|
| 40 |
+
inner_iterations = 20
|
| 41 |
+
|
| 42 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 43 |
+
for i in range(n):
|
| 44 |
+
x, y = centers[i]
|
| 45 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 46 |
+
|
| 47 |
+
for outer_iter_idx in range(outer_iterations):
|
| 48 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 49 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 50 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 51 |
+
|
| 52 |
+
radii *= current_growth_factor
|
| 53 |
+
|
| 54 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 55 |
+
constraints_changed = False
|
| 56 |
+
|
| 57 |
+
for i in range(n):
|
| 58 |
+
x, y = centers[i]
|
| 59 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 60 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 61 |
+
radii[i] = boundary_limit
|
| 62 |
+
constraints_changed = True
|
| 63 |
+
|
| 64 |
+
for i in range(n):
|
| 65 |
+
for j in range(i + 1, n):
|
| 66 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 67 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 68 |
+
total_radius = radii[i] + radii[j]
|
| 69 |
+
if total_radius > tolerance_final:
|
| 70 |
+
scale = dist / total_radius
|
| 71 |
+
radii[i] *= scale
|
| 72 |
+
radii[j] *= scale
|
| 73 |
+
constraints_changed = True
|
| 74 |
+
|
| 75 |
+
if not constraints_changed:
|
| 76 |
+
break
|
| 77 |
+
return radii
|
| 78 |
+
|
| 79 |
+
def _initial_grid_placement(self):
|
| 80 |
+
"""
|
| 81 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 82 |
+
"""
|
| 83 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 84 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 85 |
+
self.centers[:25] = grid_centers
|
| 86 |
+
|
| 87 |
+
def _find_optimal_26th_circle_position(self):
|
| 88 |
+
"""
|
| 89 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 90 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 91 |
+
"""
|
| 92 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 93 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 94 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 95 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 96 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 97 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 98 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 99 |
+
coarse_delta = 0.05
|
| 100 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 101 |
+
coarse_candidate_points = [
|
| 102 |
+
[base_x + offset_x, base_y + offset_y]
|
| 103 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords)
|
| 104 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets)
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
for candidate_pos in coarse_candidate_points:
|
| 108 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 109 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 110 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 111 |
+
current_sum_radii = np.sum(trial_radii)
|
| 112 |
+
if current_sum_radii > best_sum_radii:
|
| 113 |
+
best_sum_radii = current_sum_radii
|
| 114 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 115 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 116 |
+
|
| 117 |
+
fine_delta = 0.01
|
| 118 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 119 |
+
fine_candidate_points = [
|
| 120 |
+
[best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y]
|
| 121 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets)
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
for candidate_pos in fine_candidate_points:
|
| 125 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 126 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 127 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 128 |
+
current_sum_radii = np.sum(trial_radii)
|
| 129 |
+
if current_sum_radii > best_sum_radii:
|
| 130 |
+
best_sum_radii = current_sum_radii
|
| 131 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 132 |
+
|
| 133 |
+
self.centers[25] = optimal_26th_pos
|
| 134 |
+
return self.centers, best_sum_radii
|
| 135 |
+
|
| 136 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 137 |
+
"""
|
| 138 |
+
Applies a localized SA search to fine-tune the positions of a cluster of circles.
|
| 139 |
+
"""
|
| 140 |
+
current_centers = np.copy(initial_centers)
|
| 141 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 142 |
+
current_sum_radii = np.sum(current_radii)
|
| 143 |
+
best_centers_local = np.copy(current_centers)
|
| 144 |
+
best_sum_radii_local = current_sum_radii
|
| 145 |
+
num_iterations = 150
|
| 146 |
+
initial_step_size = 0.005
|
| 147 |
+
initial_temp = 0.0001
|
| 148 |
+
cooling_rate = 0.99
|
| 149 |
+
step_size = initial_step_size
|
| 150 |
+
temp = initial_temp
|
| 151 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 152 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 153 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 154 |
+
acceptance_window, acceptance_count = 30, 0
|
| 155 |
+
target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 156 |
+
|
| 157 |
+
for i in range(num_iterations):
|
| 158 |
+
cluster_radii = current_radii[cluster_indices]
|
| 159 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 160 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 161 |
+
idx_to_move = np.random.choice(cluster_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(cluster_indices)
|
| 162 |
+
|
| 163 |
+
trial_centers = np.copy(current_centers)
|
| 164 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 165 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 166 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 167 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 168 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 169 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 170 |
+
current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 171 |
+
acceptance_count += 1
|
| 172 |
+
if current_sum_radii > best_sum_radii_local:
|
| 173 |
+
best_sum_radii_local = current_sum_radii
|
| 174 |
+
best_centers_local = np.copy(current_centers)
|
| 175 |
+
|
| 176 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 177 |
+
step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1 / adjustment_factor
|
| 178 |
+
acceptance_count = 0
|
| 179 |
+
temp *= cooling_rate
|
| 180 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 181 |
+
return best_centers_local, best_sum_radii_local
|
| 182 |
+
|
| 183 |
+
def _global_refinement_sa(self, initial_centers):
|
| 184 |
+
"""
|
| 185 |
+
Applies a global SA search with probabilistic cluster moves to refine all circles.
|
| 186 |
+
"""
|
| 187 |
+
sa_iterations = 350 # Increased for more thorough search
|
| 188 |
+
sa_initial_temp = 5e-6
|
| 189 |
+
sa_cooling_rate = 0.99
|
| 190 |
+
sa_initial_step_size = 0.001
|
| 191 |
+
current_centers = np.copy(initial_centers)
|
| 192 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 193 |
+
current_sum_radii = np.sum(current_radii)
|
| 194 |
+
best_centers, best_sum_radii = np.copy(current_centers), current_sum_radii
|
| 195 |
+
temp, step_size = sa_initial_temp, sa_initial_step_size
|
| 196 |
+
all_indices = np.arange(self.n)
|
| 197 |
+
acceptance_window, acceptance_count = 30, 0
|
| 198 |
+
target_acceptance_rate, adjustment_factor = 0.44, 1.05
|
| 199 |
+
cluster_move_prob = 0.15
|
| 200 |
+
num_cluster_neighbors = 2
|
| 201 |
+
|
| 202 |
+
for i in range(sa_iterations):
|
| 203 |
+
trial_centers = np.copy(current_centers)
|
| 204 |
+
|
| 205 |
+
# Probabilistically choose between a single move and a cluster move
|
| 206 |
+
if np.random.rand() < cluster_move_prob:
|
| 207 |
+
# --- Cluster Move ---
|
| 208 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 209 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 210 |
+
primary_idx = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 211 |
+
distances = np.linalg.norm(trial_centers - trial_centers[primary_idx], axis=1)
|
| 212 |
+
distances[primary_idx] = np.inf
|
| 213 |
+
neighbor_indices = np.argsort(distances)[:num_cluster_neighbors]
|
| 214 |
+
cluster_indices = np.append(neighbor_indices, primary_idx)
|
| 215 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 216 |
+
trial_centers[cluster_indices] += move
|
| 217 |
+
trial_centers[cluster_indices] = np.clip(trial_centers[cluster_indices], 0.0, 1.0)
|
| 218 |
+
else:
|
| 219 |
+
# --- Single Move ---
|
| 220 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 221 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 222 |
+
idx_to_move = np.random.choice(all_indices, p=weights / np.sum(weights)) if np.sum(weights) > 1e-9 else np.random.choice(all_indices)
|
| 223 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 224 |
+
trial_centers[idx_to_move] += move
|
| 225 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move], 0.0, 1.0)
|
| 226 |
+
|
| 227 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 228 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 229 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 230 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 231 |
+
current_centers, current_radii, current_sum_radii = trial_centers, trial_radii, trial_sum_radii
|
| 232 |
+
acceptance_count += 1
|
| 233 |
+
if current_sum_radii > best_sum_radii:
|
| 234 |
+
best_sum_radii = current_sum_radii
|
| 235 |
+
best_centers = np.copy(current_centers)
|
| 236 |
+
|
| 237 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 238 |
+
step_size *= adjustment_factor if acceptance_count / acceptance_window > target_acceptance_rate else 1/adjustment_factor
|
| 239 |
+
acceptance_count = 0
|
| 240 |
+
|
| 241 |
+
temp *= sa_cooling_rate
|
| 242 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 243 |
+
return best_centers
|
| 244 |
+
|
| 245 |
+
def construct_packing(self):
|
| 246 |
+
"""
|
| 247 |
+
Orchestrates the multi-stage optimization process.
|
| 248 |
+
"""
|
| 249 |
+
self._initial_grid_placement()
|
| 250 |
+
if self.n > 25:
|
| 251 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 252 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 253 |
+
centers_after_search,
|
| 254 |
+
sum_radii_after_search
|
| 255 |
+
)
|
| 256 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 257 |
+
self.centers = centers_after_global_sa
|
| 258 |
+
|
| 259 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 260 |
+
return self.centers, self.radii
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def construct_packing():
|
| 264 |
+
"""
|
| 265 |
+
Constructs an arrangement of 26 circles by leveraging a superior multi-stage
|
| 266 |
+
optimization strategy including grid search, local SA, and global SA with cluster moves.
|
| 267 |
+
"""
|
| 268 |
+
packer = CirclePacker(num_circles=26)
|
| 269 |
+
centers, radii = packer.construct_packing()
|
| 270 |
+
return centers, radii
|
| 271 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/edit.diff
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,265 +1,266 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 12 |
+
optimization approach. It orchestrates initial placement, exhaustive
|
| 13 |
+
search for a critical circle, local refinement, and iterative radius adjustment.
|
| 14 |
+
"""
|
| 15 |
+
def __init__(self, num_circles=26):
|
| 16 |
+
self.n = num_circles
|
| 17 |
+
self.centers = np.zeros((self.n, 2))
|
| 18 |
+
self.radii = np.zeros(self.n)
|
| 19 |
+
|
| 20 |
+
@staticmethod
|
| 21 |
+
def _compute_max_radii_static(centers, n):
|
| 22 |
+
"""
|
| 23 |
+
Computes maximum radii using an iterative method with an exponential decay
|
| 24 |
+
for growth factor and tolerance, providing a smoother convergence. The inner
|
| 25 |
+
iteration count is tuned for high precision based on successful priors.
|
| 26 |
+
"""
|
| 27 |
+
radii = np.zeros(n)
|
| 28 |
+
|
| 29 |
+
# Parameters using exponential decay for smoother convergence
|
| 30 |
+
growth_factor_start = 1.005
|
| 31 |
+
growth_factor_end = 1.002
|
| 32 |
+
tolerance_start = 1e-7
|
| 33 |
+
tolerance_end = 1e-11 # Tighter tolerance
|
| 34 |
+
|
| 35 |
+
outer_iterations = 400
|
| 36 |
+
inner_iterations = 20 # Tuned for precision/speed balance
|
| 37 |
+
|
| 38 |
+
# Initialize radii based on boundary distance
|
| 39 |
+
for i in range(n):
|
| 40 |
+
x, y = centers[i]
|
| 41 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 42 |
+
|
| 43 |
+
for k in range(outer_iterations):
|
| 44 |
+
# Use exponential interpolation for smoother parameter transition
|
| 45 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 46 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 47 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 48 |
+
|
| 49 |
+
radii *= current_growth_factor
|
| 50 |
+
|
| 51 |
+
for _ in range(inner_iterations):
|
| 52 |
+
constraints_changed = False
|
| 53 |
+
# Enforce boundary constraints
|
| 54 |
+
for i in range(n):
|
| 55 |
+
x, y = centers[i]
|
| 56 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 57 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 58 |
+
radii[i] = boundary_limit
|
| 59 |
+
constraints_changed = True
|
| 60 |
+
|
| 61 |
+
# Resolve overlaps between circles
|
| 62 |
+
for i in range(n):
|
| 63 |
+
for j in range(i + 1, n):
|
| 64 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 65 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 66 |
+
total_radius = radii[i] + radii[j]
|
| 67 |
+
if total_radius > 1e-12: # Avoid division by zero
|
| 68 |
+
scale = dist / total_radius
|
| 69 |
+
radii[i] *= scale
|
| 70 |
+
radii[j] *= scale
|
| 71 |
+
constraints_changed = True
|
| 72 |
+
if not constraints_changed:
|
| 73 |
+
break
|
| 74 |
+
return radii
|
| 75 |
+
|
| 76 |
+
def _initial_grid_placement(self):
|
| 77 |
+
"""
|
| 78 |
+
Places the first 25 circles in a 5x5 grid pattern.
|
| 79 |
+
"""
|
| 80 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 81 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 82 |
+
self.centers[:25] = grid_centers
|
| 83 |
+
|
| 84 |
+
def _find_optimal_26th_circle_position(self):
|
| 85 |
+
"""
|
| 86 |
+
Performs a hierarchical grid search for the 26th circle by perturbing
|
| 87 |
+
around core interstitial points for a denser, more effective search.
|
| 88 |
+
"""
|
| 89 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 90 |
+
|
| 91 |
+
# Hierarchical search: 4x4 coarse grid + 3x3 fine grid around each point
|
| 92 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 93 |
+
delta = 0.025
|
| 94 |
+
- perturbation_offsets = np.array([-delta, 0, delta])
|
| 95 |
+
+ # Increased density for perturbation offsets: 5 points instead of 3 for a 5x5 sub-grid
|
| 96 |
+
+ perturbation_offsets = np.linspace(-delta, delta, 5)
|
| 97 |
+
|
| 98 |
+
candidate_points = []
|
| 99 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 100 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 101 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 102 |
+
|
| 103 |
+
best_sum_radii = -1.0
|
| 104 |
+
optimal_centers = None
|
| 105 |
+
|
| 106 |
+
for candidate_pos in candidate_points:
|
| 107 |
+
# Clip to ensure validity before calculation
|
| 108 |
+
trial_centers = np.vstack([base_25_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 109 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 110 |
+
current_sum_radii = np.sum(trial_radii)
|
| 111 |
+
|
| 112 |
+
if current_sum_radii > best_sum_radii:
|
| 113 |
+
best_sum_radii = current_sum_radii
|
| 114 |
+
optimal_centers = trial_centers
|
| 115 |
+
|
| 116 |
+
if optimal_centers is None:
|
| 117 |
+
# Fallback
|
| 118 |
+
optimal_centers = np.vstack([base_25_centers, [[0.5, 0.5]]])
|
| 119 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(optimal_centers, self.n))
|
| 120 |
+
|
| 121 |
+
return optimal_centers, best_sum_radii
|
| 122 |
+
|
| 123 |
+
def _local_refinement_cluster(self, initial_centers, initial_sum_radii):
|
| 124 |
+
"""
|
| 125 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle
|
| 126 |
+
and its 4 closest neighbors, using more conservative parameters.
|
| 127 |
+
"""
|
| 128 |
+
current_centers = np.copy(initial_centers)
|
| 129 |
+
current_sum_radii = initial_sum_radii
|
| 130 |
+
|
| 131 |
+
best_centers_local = np.copy(current_centers)
|
| 132 |
+
best_sum_radii_local = current_sum_radii
|
| 133 |
+
|
| 134 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 135 |
+
index_to_refine = 25
|
| 136 |
+
num_neighbors = 4
|
| 137 |
+
distances_to_26th = np.linalg.norm(initial_centers[index_to_refine] - initial_centers[:index_to_refine], axis=1)
|
| 138 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:num_neighbors]
|
| 139 |
+
indices_to_perturb = np.append(closest_neighbor_indices, index_to_refine)
|
| 140 |
+
|
| 141 |
+
# Tuned SA parameters for local cluster refinement
|
| 142 |
+
num_iterations = 300
|
| 143 |
+
initial_step_size = 0.005
|
| 144 |
+
initial_temp = 0.0002
|
| 145 |
+
cooling_rate = 0.99
|
| 146 |
+
|
| 147 |
+
step_size = initial_step_size
|
| 148 |
+
temp = initial_temp
|
| 149 |
+
|
| 150 |
+
for _ in range(num_iterations):
|
| 151 |
+
trial_centers = np.copy(current_centers)
|
| 152 |
+
|
| 153 |
+
# Perturb a random circle from the cluster
|
| 154 |
+
idx = np.random.choice(indices_to_perturb)
|
| 155 |
+
|
| 156 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 157 |
+
trial_centers[idx] += move
|
| 158 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0)
|
| 159 |
+
|
| 160 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 161 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 162 |
+
|
| 163 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 164 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 165 |
+
current_centers = trial_centers
|
| 166 |
+
current_sum_radii = trial_sum_radii
|
| 167 |
+
|
| 168 |
+
if current_sum_radii > best_sum_radii_local:
|
| 169 |
+
best_sum_radii_local = current_sum_radii
|
| 170 |
+
best_centers_local = np.copy(current_centers)
|
| 171 |
+
|
| 172 |
+
temp *= cooling_rate
|
| 173 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 174 |
+
|
| 175 |
+
return best_centers_local, best_sum_radii_local
|
| 176 |
+
|
| 177 |
+
def _global_refinement_sa(self, initial_centers, initial_sum_radii):
|
| 178 |
+
"""
|
| 179 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 180 |
+
better global optimum, using tuned parameters from high-scoring versions.
|
| 181 |
+
"""
|
| 182 |
+
current_centers = np.copy(initial_centers)
|
| 183 |
+
current_sum_radii = initial_sum_radii
|
| 184 |
+
|
| 185 |
+
best_centers_global = np.copy(current_centers)
|
| 186 |
+
best_sum_radii_global = current_sum_radii
|
| 187 |
+
|
| 188 |
+
# SA parameters for a final, gentle, global refinement
|
| 189 |
+
num_iterations = 1500
|
| 190 |
+
initial_step_size = 0.004
|
| 191 |
+
initial_temp = 1e-5
|
| 192 |
+
cooling_rate = 0.995
|
| 193 |
+
|
| 194 |
+
step_size = initial_step_size
|
| 195 |
+
temp = initial_temp
|
| 196 |
+
|
| 197 |
+
for _ in range(num_iterations):
|
| 198 |
+
# Perturb a random circle from the entire set
|
| 199 |
+
idx_to_perturb = np.random.randint(self.n)
|
| 200 |
+
|
| 201 |
+
trial_centers = np.copy(current_centers)
|
| 202 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 203 |
+
trial_centers[idx_to_perturb] += move
|
| 204 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 205 |
+
|
| 206 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 207 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 208 |
+
|
| 209 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 210 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 211 |
+
current_centers = trial_centers
|
| 212 |
+
current_sum_radii = trial_sum_radii
|
| 213 |
+
|
| 214 |
+
if current_sum_radii > best_sum_radii_global:
|
| 215 |
+
best_sum_radii_global = current_sum_radii
|
| 216 |
+
best_centers_global = np.copy(current_centers)
|
| 217 |
+
|
| 218 |
+
temp *= cooling_rate
|
| 219 |
+
step_size = max(step_size * cooling_rate, 5e-8)
|
| 220 |
+
|
| 221 |
+
return best_centers_global, best_sum_radii_global
|
| 222 |
+
|
| 223 |
+
def construct_packing(self):
|
| 224 |
+
"""
|
| 225 |
+
Main method using a multi-stage funnel: exhaustive search -> local SA -> global SA.
|
| 226 |
+
"""
|
| 227 |
+
np.random.seed(42) # For reproducible SA results
|
| 228 |
+
self._initial_grid_placement()
|
| 229 |
+
|
| 230 |
+
if self.n > 25:
|
| 231 |
+
# Stage 1: Denser exhaustive search for the optimal 26th circle position.
|
| 232 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 233 |
+
|
| 234 |
+
# Stage 2: Local refinement on the 26th circle and its neighbors.
|
| 235 |
+
centers_after_local, sum_radii_after_local = self._local_refinement_cluster(
|
| 236 |
+
centers_after_search,
|
| 237 |
+
sum_radii_after_search
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
# Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 241 |
+
refined_centers, _ = self._global_refinement_sa(
|
| 242 |
+
centers_after_local,
|
| 243 |
+
sum_radii_after_local
|
| 244 |
+
)
|
| 245 |
+
self.centers = refined_centers
|
| 246 |
+
|
| 247 |
+
# Final radius calculation for the fully optimized center configuration.
|
| 248 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 249 |
+
return self.centers, self.radii
|
| 250 |
+
|
| 251 |
+
def construct_packing():
|
| 252 |
+
"""
|
| 253 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 254 |
+
multi-stage optimization strategy to maximize the sum of radii.
|
| 255 |
+
"""
|
| 256 |
+
packer = CirclePacker(num_circles=26)
|
| 257 |
+
centers, radii = packer.construct_packing()
|
| 258 |
+
return centers, radii
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
# EVOLVE-BLOCK-END
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
# This part remains fixed (not evolved)
|
| 265 |
+
def run_packing():
|
| 266 |
+
"""Run the circle packing constructor for n=26"""
|
| 267 |
+
centers, radii = construct_packing()
|
| 268 |
+
# Calculate the sum of radii
|
| 269 |
+
sum_radii = np.sum(radii)
|
| 270 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/main.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It orchestrates initial placement, exhaustive
|
| 10 |
+
search for a critical circle, local refinement, and iterative radius adjustment.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes maximum radii using an iterative method with an exponential decay
|
| 21 |
+
for growth factor and tolerance, providing a smoother convergence. The inner
|
| 22 |
+
iteration count is tuned for high precision based on successful priors.
|
| 23 |
+
"""
|
| 24 |
+
radii = np.zeros(n)
|
| 25 |
+
|
| 26 |
+
# Parameters using exponential decay for smoother convergence
|
| 27 |
+
growth_factor_start = 1.005
|
| 28 |
+
growth_factor_end = 1.002
|
| 29 |
+
tolerance_start = 1e-7
|
| 30 |
+
tolerance_end = 1e-11 # Tighter tolerance
|
| 31 |
+
|
| 32 |
+
outer_iterations = 400
|
| 33 |
+
inner_iterations = 20 # Tuned for precision/speed balance
|
| 34 |
+
|
| 35 |
+
# Initialize radii based on boundary distance
|
| 36 |
+
for i in range(n):
|
| 37 |
+
x, y = centers[i]
|
| 38 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 39 |
+
|
| 40 |
+
for k in range(outer_iterations):
|
| 41 |
+
# Use exponential interpolation for smoother parameter transition
|
| 42 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 43 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 44 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 45 |
+
|
| 46 |
+
radii *= current_growth_factor
|
| 47 |
+
|
| 48 |
+
for _ in range(inner_iterations):
|
| 49 |
+
constraints_changed = False
|
| 50 |
+
# Enforce boundary constraints
|
| 51 |
+
for i in range(n):
|
| 52 |
+
x, y = centers[i]
|
| 53 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 54 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 55 |
+
radii[i] = boundary_limit
|
| 56 |
+
constraints_changed = True
|
| 57 |
+
|
| 58 |
+
# Resolve overlaps between circles
|
| 59 |
+
for i in range(n):
|
| 60 |
+
for j in range(i + 1, n):
|
| 61 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 62 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 63 |
+
total_radius = radii[i] + radii[j]
|
| 64 |
+
if total_radius > 1e-12: # Avoid division by zero
|
| 65 |
+
scale = dist / total_radius
|
| 66 |
+
radii[i] *= scale
|
| 67 |
+
radii[j] *= scale
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
if not constraints_changed:
|
| 70 |
+
break
|
| 71 |
+
return radii
|
| 72 |
+
|
| 73 |
+
def _initial_grid_placement(self):
|
| 74 |
+
"""
|
| 75 |
+
Places the first 25 circles in a 5x5 grid pattern.
|
| 76 |
+
"""
|
| 77 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 78 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 79 |
+
self.centers[:25] = grid_centers
|
| 80 |
+
|
| 81 |
+
def _find_optimal_26th_circle_position(self):
|
| 82 |
+
"""
|
| 83 |
+
Performs a hierarchical grid search for the 26th circle by perturbing
|
| 84 |
+
around core interstitial points for a denser, more effective search.
|
| 85 |
+
"""
|
| 86 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 87 |
+
|
| 88 |
+
# Hierarchical search: 4x4 coarse grid + 3x3 fine grid around each point
|
| 89 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 90 |
+
delta = 0.025
|
| 91 |
+
# Increased density for perturbation offsets: 5 points instead of 3 for a 5x5 sub-grid
|
| 92 |
+
perturbation_offsets = np.linspace(-delta, delta, 5)
|
| 93 |
+
|
| 94 |
+
candidate_points = []
|
| 95 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 96 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 97 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 98 |
+
|
| 99 |
+
best_sum_radii = -1.0
|
| 100 |
+
optimal_centers = None
|
| 101 |
+
|
| 102 |
+
for candidate_pos in candidate_points:
|
| 103 |
+
# Clip to ensure validity before calculation
|
| 104 |
+
trial_centers = np.vstack([base_25_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 105 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 106 |
+
current_sum_radii = np.sum(trial_radii)
|
| 107 |
+
|
| 108 |
+
if current_sum_radii > best_sum_radii:
|
| 109 |
+
best_sum_radii = current_sum_radii
|
| 110 |
+
optimal_centers = trial_centers
|
| 111 |
+
|
| 112 |
+
if optimal_centers is None:
|
| 113 |
+
# Fallback
|
| 114 |
+
optimal_centers = np.vstack([base_25_centers, [[0.5, 0.5]]])
|
| 115 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(optimal_centers, self.n))
|
| 116 |
+
|
| 117 |
+
return optimal_centers, best_sum_radii
|
| 118 |
+
|
| 119 |
+
def _local_refinement_cluster(self, initial_centers, initial_sum_radii):
|
| 120 |
+
"""
|
| 121 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle
|
| 122 |
+
and its 4 closest neighbors, using more conservative parameters.
|
| 123 |
+
"""
|
| 124 |
+
current_centers = np.copy(initial_centers)
|
| 125 |
+
current_sum_radii = initial_sum_radii
|
| 126 |
+
|
| 127 |
+
best_centers_local = np.copy(current_centers)
|
| 128 |
+
best_sum_radii_local = current_sum_radii
|
| 129 |
+
|
| 130 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 131 |
+
index_to_refine = 25
|
| 132 |
+
num_neighbors = 4
|
| 133 |
+
distances_to_26th = np.linalg.norm(initial_centers[index_to_refine] - initial_centers[:index_to_refine], axis=1)
|
| 134 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:num_neighbors]
|
| 135 |
+
indices_to_perturb = np.append(closest_neighbor_indices, index_to_refine)
|
| 136 |
+
|
| 137 |
+
# Tuned SA parameters for local cluster refinement
|
| 138 |
+
num_iterations = 300
|
| 139 |
+
initial_step_size = 0.005
|
| 140 |
+
initial_temp = 0.0002
|
| 141 |
+
cooling_rate = 0.99
|
| 142 |
+
|
| 143 |
+
step_size = initial_step_size
|
| 144 |
+
temp = initial_temp
|
| 145 |
+
|
| 146 |
+
for _ in range(num_iterations):
|
| 147 |
+
trial_centers = np.copy(current_centers)
|
| 148 |
+
|
| 149 |
+
# Perturb a random circle from the cluster
|
| 150 |
+
idx = np.random.choice(indices_to_perturb)
|
| 151 |
+
|
| 152 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 153 |
+
trial_centers[idx] += move
|
| 154 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0)
|
| 155 |
+
|
| 156 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 157 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 158 |
+
|
| 159 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 160 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 161 |
+
current_centers = trial_centers
|
| 162 |
+
current_sum_radii = trial_sum_radii
|
| 163 |
+
|
| 164 |
+
if current_sum_radii > best_sum_radii_local:
|
| 165 |
+
best_sum_radii_local = current_sum_radii
|
| 166 |
+
best_centers_local = np.copy(current_centers)
|
| 167 |
+
|
| 168 |
+
temp *= cooling_rate
|
| 169 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 170 |
+
|
| 171 |
+
return best_centers_local, best_sum_radii_local
|
| 172 |
+
|
| 173 |
+
def _global_refinement_sa(self, initial_centers, initial_sum_radii):
|
| 174 |
+
"""
|
| 175 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 176 |
+
better global optimum, using tuned parameters from high-scoring versions.
|
| 177 |
+
"""
|
| 178 |
+
current_centers = np.copy(initial_centers)
|
| 179 |
+
current_sum_radii = initial_sum_radii
|
| 180 |
+
|
| 181 |
+
best_centers_global = np.copy(current_centers)
|
| 182 |
+
best_sum_radii_global = current_sum_radii
|
| 183 |
+
|
| 184 |
+
# SA parameters for a final, gentle, global refinement
|
| 185 |
+
num_iterations = 1500
|
| 186 |
+
initial_step_size = 0.004
|
| 187 |
+
initial_temp = 1e-5
|
| 188 |
+
cooling_rate = 0.995
|
| 189 |
+
|
| 190 |
+
step_size = initial_step_size
|
| 191 |
+
temp = initial_temp
|
| 192 |
+
|
| 193 |
+
for _ in range(num_iterations):
|
| 194 |
+
# Perturb a random circle from the entire set
|
| 195 |
+
idx_to_perturb = np.random.randint(self.n)
|
| 196 |
+
|
| 197 |
+
trial_centers = np.copy(current_centers)
|
| 198 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 199 |
+
trial_centers[idx_to_perturb] += move
|
| 200 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 201 |
+
|
| 202 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 203 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 204 |
+
|
| 205 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 206 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 207 |
+
current_centers = trial_centers
|
| 208 |
+
current_sum_radii = trial_sum_radii
|
| 209 |
+
|
| 210 |
+
if current_sum_radii > best_sum_radii_global:
|
| 211 |
+
best_sum_radii_global = current_sum_radii
|
| 212 |
+
best_centers_global = np.copy(current_centers)
|
| 213 |
+
|
| 214 |
+
temp *= cooling_rate
|
| 215 |
+
step_size = max(step_size * cooling_rate, 5e-8)
|
| 216 |
+
|
| 217 |
+
return best_centers_global, best_sum_radii_global
|
| 218 |
+
|
| 219 |
+
def construct_packing(self):
|
| 220 |
+
"""
|
| 221 |
+
Main method using a multi-stage funnel: exhaustive search -> local SA -> global SA.
|
| 222 |
+
"""
|
| 223 |
+
np.random.seed(42) # For reproducible SA results
|
| 224 |
+
self._initial_grid_placement()
|
| 225 |
+
|
| 226 |
+
if self.n > 25:
|
| 227 |
+
# Stage 1: Denser exhaustive search for the optimal 26th circle position.
|
| 228 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 229 |
+
|
| 230 |
+
# Stage 2: Local refinement on the 26th circle and its neighbors.
|
| 231 |
+
centers_after_local, sum_radii_after_local = self._local_refinement_cluster(
|
| 232 |
+
centers_after_search,
|
| 233 |
+
sum_radii_after_search
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
# Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 237 |
+
refined_centers, _ = self._global_refinement_sa(
|
| 238 |
+
centers_after_local,
|
| 239 |
+
sum_radii_after_local
|
| 240 |
+
)
|
| 241 |
+
self.centers = refined_centers
|
| 242 |
+
|
| 243 |
+
# Final radius calculation for the fully optimized center configuration.
|
| 244 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 245 |
+
return self.centers, self.radii
|
| 246 |
+
|
| 247 |
+
def construct_packing():
|
| 248 |
+
"""
|
| 249 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 250 |
+
multi-stage optimization strategy to maximize the sum of radii.
|
| 251 |
+
"""
|
| 252 |
+
packer = CirclePacker(num_circles=26)
|
| 253 |
+
centers, radii = packer.construct_packing()
|
| 254 |
+
return centers, radii
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# EVOLVE-BLOCK-END
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# This part remains fixed (not evolved)
|
| 261 |
+
def run_packing():
|
| 262 |
+
"""Run the circle packing constructor for n=26"""
|
| 263 |
+
centers, radii = construct_packing()
|
| 264 |
+
# Calculate the sum of radii
|
| 265 |
+
sum_radii = np.sum(radii)
|
| 266 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/original.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It orchestrates initial placement, exhaustive
|
| 10 |
+
search for a critical circle, local refinement, and iterative radius adjustment.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes maximum radii using an iterative method with an exponential decay
|
| 21 |
+
for growth factor and tolerance, providing a smoother convergence. The inner
|
| 22 |
+
iteration count is tuned for high precision based on successful priors.
|
| 23 |
+
"""
|
| 24 |
+
radii = np.zeros(n)
|
| 25 |
+
|
| 26 |
+
# Parameters using exponential decay for smoother convergence
|
| 27 |
+
growth_factor_start = 1.005
|
| 28 |
+
growth_factor_end = 1.002
|
| 29 |
+
tolerance_start = 1e-7
|
| 30 |
+
tolerance_end = 1e-11 # Tighter tolerance
|
| 31 |
+
|
| 32 |
+
outer_iterations = 400
|
| 33 |
+
inner_iterations = 20 # Tuned for precision/speed balance
|
| 34 |
+
|
| 35 |
+
# Initialize radii based on boundary distance
|
| 36 |
+
for i in range(n):
|
| 37 |
+
x, y = centers[i]
|
| 38 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 39 |
+
|
| 40 |
+
for k in range(outer_iterations):
|
| 41 |
+
# Use exponential interpolation for smoother parameter transition
|
| 42 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 43 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 44 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 45 |
+
|
| 46 |
+
radii *= current_growth_factor
|
| 47 |
+
|
| 48 |
+
for _ in range(inner_iterations):
|
| 49 |
+
constraints_changed = False
|
| 50 |
+
# Enforce boundary constraints
|
| 51 |
+
for i in range(n):
|
| 52 |
+
x, y = centers[i]
|
| 53 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 54 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 55 |
+
radii[i] = boundary_limit
|
| 56 |
+
constraints_changed = True
|
| 57 |
+
|
| 58 |
+
# Resolve overlaps between circles
|
| 59 |
+
for i in range(n):
|
| 60 |
+
for j in range(i + 1, n):
|
| 61 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 62 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 63 |
+
total_radius = radii[i] + radii[j]
|
| 64 |
+
if total_radius > 1e-12: # Avoid division by zero
|
| 65 |
+
scale = dist / total_radius
|
| 66 |
+
radii[i] *= scale
|
| 67 |
+
radii[j] *= scale
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
if not constraints_changed:
|
| 70 |
+
break
|
| 71 |
+
return radii
|
| 72 |
+
|
| 73 |
+
def _initial_grid_placement(self):
|
| 74 |
+
"""
|
| 75 |
+
Places the first 25 circles in a 5x5 grid pattern.
|
| 76 |
+
"""
|
| 77 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 78 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 79 |
+
self.centers[:25] = grid_centers
|
| 80 |
+
|
| 81 |
+
def _find_optimal_26th_circle_position(self):
|
| 82 |
+
"""
|
| 83 |
+
Performs a hierarchical grid search for the 26th circle by perturbing
|
| 84 |
+
around core interstitial points for a denser, more effective search.
|
| 85 |
+
"""
|
| 86 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 87 |
+
|
| 88 |
+
# Hierarchical search: 4x4 coarse grid + 3x3 fine grid around each point
|
| 89 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 90 |
+
delta = 0.025
|
| 91 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 92 |
+
|
| 93 |
+
candidate_points = []
|
| 94 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 95 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 96 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 97 |
+
|
| 98 |
+
best_sum_radii = -1.0
|
| 99 |
+
optimal_centers = None
|
| 100 |
+
|
| 101 |
+
for candidate_pos in candidate_points:
|
| 102 |
+
# Clip to ensure validity before calculation
|
| 103 |
+
trial_centers = np.vstack([base_25_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 104 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 105 |
+
current_sum_radii = np.sum(trial_radii)
|
| 106 |
+
|
| 107 |
+
if current_sum_radii > best_sum_radii:
|
| 108 |
+
best_sum_radii = current_sum_radii
|
| 109 |
+
optimal_centers = trial_centers
|
| 110 |
+
|
| 111 |
+
if optimal_centers is None:
|
| 112 |
+
# Fallback
|
| 113 |
+
optimal_centers = np.vstack([base_25_centers, [[0.5, 0.5]]])
|
| 114 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(optimal_centers, self.n))
|
| 115 |
+
|
| 116 |
+
return optimal_centers, best_sum_radii
|
| 117 |
+
|
| 118 |
+
def _local_refinement_cluster(self, initial_centers, initial_sum_radii):
|
| 119 |
+
"""
|
| 120 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle
|
| 121 |
+
and its 4 closest neighbors, using more conservative parameters.
|
| 122 |
+
"""
|
| 123 |
+
current_centers = np.copy(initial_centers)
|
| 124 |
+
current_sum_radii = initial_sum_radii
|
| 125 |
+
|
| 126 |
+
best_centers_local = np.copy(current_centers)
|
| 127 |
+
best_sum_radii_local = current_sum_radii
|
| 128 |
+
|
| 129 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 130 |
+
index_to_refine = 25
|
| 131 |
+
num_neighbors = 4
|
| 132 |
+
distances_to_26th = np.linalg.norm(initial_centers[index_to_refine] - initial_centers[:index_to_refine], axis=1)
|
| 133 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:num_neighbors]
|
| 134 |
+
indices_to_perturb = np.append(closest_neighbor_indices, index_to_refine)
|
| 135 |
+
|
| 136 |
+
# Tuned SA parameters for local cluster refinement
|
| 137 |
+
num_iterations = 300
|
| 138 |
+
initial_step_size = 0.005
|
| 139 |
+
initial_temp = 0.0002
|
| 140 |
+
cooling_rate = 0.99
|
| 141 |
+
|
| 142 |
+
step_size = initial_step_size
|
| 143 |
+
temp = initial_temp
|
| 144 |
+
|
| 145 |
+
for _ in range(num_iterations):
|
| 146 |
+
trial_centers = np.copy(current_centers)
|
| 147 |
+
|
| 148 |
+
# Perturb a random circle from the cluster
|
| 149 |
+
idx = np.random.choice(indices_to_perturb)
|
| 150 |
+
|
| 151 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 152 |
+
trial_centers[idx] += move
|
| 153 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0)
|
| 154 |
+
|
| 155 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 156 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 157 |
+
|
| 158 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 159 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 160 |
+
current_centers = trial_centers
|
| 161 |
+
current_sum_radii = trial_sum_radii
|
| 162 |
+
|
| 163 |
+
if current_sum_radii > best_sum_radii_local:
|
| 164 |
+
best_sum_radii_local = current_sum_radii
|
| 165 |
+
best_centers_local = np.copy(current_centers)
|
| 166 |
+
|
| 167 |
+
temp *= cooling_rate
|
| 168 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 169 |
+
|
| 170 |
+
return best_centers_local, best_sum_radii_local
|
| 171 |
+
|
| 172 |
+
def _global_refinement_sa(self, initial_centers, initial_sum_radii):
|
| 173 |
+
"""
|
| 174 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a
|
| 175 |
+
better global optimum, using tuned parameters from high-scoring versions.
|
| 176 |
+
"""
|
| 177 |
+
current_centers = np.copy(initial_centers)
|
| 178 |
+
current_sum_radii = initial_sum_radii
|
| 179 |
+
|
| 180 |
+
best_centers_global = np.copy(current_centers)
|
| 181 |
+
best_sum_radii_global = current_sum_radii
|
| 182 |
+
|
| 183 |
+
# SA parameters for a final, gentle, global refinement
|
| 184 |
+
num_iterations = 1500
|
| 185 |
+
initial_step_size = 0.004
|
| 186 |
+
initial_temp = 1e-5
|
| 187 |
+
cooling_rate = 0.995
|
| 188 |
+
|
| 189 |
+
step_size = initial_step_size
|
| 190 |
+
temp = initial_temp
|
| 191 |
+
|
| 192 |
+
for _ in range(num_iterations):
|
| 193 |
+
# Perturb a random circle from the entire set
|
| 194 |
+
idx_to_perturb = np.random.randint(self.n)
|
| 195 |
+
|
| 196 |
+
trial_centers = np.copy(current_centers)
|
| 197 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 198 |
+
trial_centers[idx_to_perturb] += move
|
| 199 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0)
|
| 200 |
+
|
| 201 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 202 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 203 |
+
|
| 204 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 205 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.random() < np.exp(delta_energy / temp)):
|
| 206 |
+
current_centers = trial_centers
|
| 207 |
+
current_sum_radii = trial_sum_radii
|
| 208 |
+
|
| 209 |
+
if current_sum_radii > best_sum_radii_global:
|
| 210 |
+
best_sum_radii_global = current_sum_radii
|
| 211 |
+
best_centers_global = np.copy(current_centers)
|
| 212 |
+
|
| 213 |
+
temp *= cooling_rate
|
| 214 |
+
step_size = max(step_size * cooling_rate, 5e-8)
|
| 215 |
+
|
| 216 |
+
return best_centers_global, best_sum_radii_global
|
| 217 |
+
|
| 218 |
+
def construct_packing(self):
|
| 219 |
+
"""
|
| 220 |
+
Main method using a multi-stage funnel: exhaustive search -> local SA -> global SA.
|
| 221 |
+
"""
|
| 222 |
+
np.random.seed(42) # For reproducible SA results
|
| 223 |
+
self._initial_grid_placement()
|
| 224 |
+
|
| 225 |
+
if self.n > 25:
|
| 226 |
+
# Stage 1: Denser exhaustive search for the optimal 26th circle position.
|
| 227 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 228 |
+
|
| 229 |
+
# Stage 2: Local refinement on the 26th circle and its neighbors.
|
| 230 |
+
centers_after_local, sum_radii_after_local = self._local_refinement_cluster(
|
| 231 |
+
centers_after_search,
|
| 232 |
+
sum_radii_after_search
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# Stage 3: Global "gentle jiggle" refinement using SA on all circles.
|
| 236 |
+
refined_centers, _ = self._global_refinement_sa(
|
| 237 |
+
centers_after_local,
|
| 238 |
+
sum_radii_after_local
|
| 239 |
+
)
|
| 240 |
+
self.centers = refined_centers
|
| 241 |
+
|
| 242 |
+
# Final radius calculation for the fully optimized center configuration.
|
| 243 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 244 |
+
return self.centers, self.radii
|
| 245 |
+
|
| 246 |
+
def construct_packing():
|
| 247 |
+
"""
|
| 248 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 249 |
+
multi-stage optimization strategy to maximize the sum of radii.
|
| 250 |
+
"""
|
| 251 |
+
packer = CirclePacker(num_circles=26)
|
| 252 |
+
centers, radii = packer.construct_packing()
|
| 253 |
+
return centers, radii
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
# EVOLVE-BLOCK-END
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# This part remains fixed (not evolved)
|
| 260 |
+
def run_packing():
|
| 261 |
+
"""Run the circle packing constructor for n=26"""
|
| 262 |
+
centers, radii = construct_packing()
|
| 263 |
+
# Calculate the sum of radii
|
| 264 |
+
sum_radii = np.sum(radii)
|
| 265 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_103/search_replace.txt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
denser_26th_circle_search
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DESCRIPTION>
|
| 6 |
+
Refine the hierarchical search for the 26th circle by increasing the density of perturbation offsets.
|
| 7 |
+
The original code used 3 perturbation offsets (`-delta`, `0`, `delta`), leading to a 3x3 perturbation grid (9 points) around each of the 16 interstitial core coordinates.
|
| 8 |
+
This modification changes the perturbation offsets to a `np.linspace(-delta, delta, 5)`, creating a 5x5 perturbation grid (25 points) around each core coordinate.
|
| 9 |
+
This increases the total candidate positions for the 26th circle from 144 to 400, allowing for a more granular and potentially more optimal initial placement, which can significantly benefit the subsequent SA stages.
|
| 10 |
+
This change directly addresses "Recommendation 1: Refine the Hierarchical Interstitial Search for the 26th Circle with Increased Density" from the provided actionable recommendations.
|
| 11 |
+
</DESCRIPTION>
|
| 12 |
+
|
| 13 |
+
<DIFF>
|
| 14 |
+
<<<<<<< SEARCH
|
| 15 |
+
delta = 0.025
|
| 16 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 17 |
+
|
| 18 |
+
candidate_points = []
|
| 19 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 20 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 21 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 22 |
+
=======
|
| 23 |
+
delta = 0.025
|
| 24 |
+
# Increased density for perturbation offsets: 5 points instead of 3 for a 5x5 sub-grid
|
| 25 |
+
perturbation_offsets = np.linspace(-delta, delta, 5)
|
| 26 |
+
|
| 27 |
+
candidate_points = []
|
| 28 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 29 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 30 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 31 |
+
>>>>>>> REPLACE
|
| 32 |
+
</DIFF>
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/edit.diff
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,252 +1,266 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 12 |
+
1. Initial 5x5 grid placement.
|
| 13 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 14 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 15 |
+
+ 4. An extended global SA refinement for the entire packing.
|
| 16 |
+
"""
|
| 17 |
+
def __init__(self, num_circles=26):
|
| 18 |
+
"""Initializes the packer for 26 circles."""
|
| 19 |
+
if num_circles != 26:
|
| 20 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 21 |
+
self.n = num_circles
|
| 22 |
+
self.centers = np.zeros((self.n, 2))
|
| 23 |
+
self.radii = np.zeros(self.n)
|
| 24 |
+
|
| 25 |
+
@staticmethod
|
| 26 |
+
- def _compute_max_radii_static(centers: np.ndarray) -> np.ndarray:
|
| 27 |
+
+ def _compute_max_radii_static(centers: np.ndarray, n: int) -> np.ndarray:
|
| 28 |
+
"""
|
| 29 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 30 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 31 |
+
prior implementations for superior performance.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 35 |
+
+ n (int): Number of circles (passed explicitly for consistency with some priors).
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
np.array of shape (n) with the final radius of each circle.
|
| 39 |
+
"""
|
| 40 |
+
- n = centers.shape[0]
|
| 41 |
+
radii = np.zeros(n)
|
| 42 |
+
|
| 43 |
+
- # Parameters from the high-scoring prior program (score 2.5406)
|
| 44 |
+
+ # Parameters from high-scoring prior programs (score 2.5407)
|
| 45 |
+
growth_factor_start = 1.005
|
| 46 |
+
growth_factor_end = 1.002
|
| 47 |
+
outer_iterations = 400
|
| 48 |
+
tolerance_start = 1e-7
|
| 49 |
+
- tolerance_end = 1e-11
|
| 50 |
+
- # Increased inner iterations for more robust constraint satisfaction
|
| 51 |
+
- inner_iterations = 20
|
| 52 |
+
+ tolerance_end = 1e-11 # Tighter tolerance for precision
|
| 53 |
+
+ inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 54 |
+
|
| 55 |
+
# Initialize radii based on boundary distance
|
| 56 |
+
for i in range(n):
|
| 57 |
+
x, y = centers[i]
|
| 58 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 59 |
+
|
| 60 |
+
# Iteratively grow and resolve constraints
|
| 61 |
+
for k in range(outer_iterations):
|
| 62 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 63 |
+
# Exponential interpolation for smooth parameter transition
|
| 64 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 65 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 66 |
+
|
| 67 |
+
radii *= current_growth_factor
|
| 68 |
+
|
| 69 |
+
for _ in range(inner_iterations):
|
| 70 |
+
constraints_changed = False
|
| 71 |
+
# Boundary constraints
|
| 72 |
+
for i in range(n):
|
| 73 |
+
x, y = centers[i]
|
| 74 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 75 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 76 |
+
radii[i] = boundary_limit
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
# Overlap constraints
|
| 80 |
+
for i in range(n):
|
| 81 |
+
for j in range(i + 1, n):
|
| 82 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 83 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 84 |
+
total_radius = radii[i] + radii[j]
|
| 85 |
+
- if total_radius > 1e-12: # Avoid division by zero
|
| 86 |
+
+ if total_radius > 1e-12: # Avoid division by zero with a robust threshold
|
| 87 |
+
scale = dist / total_radius
|
| 88 |
+
radii[i] *= scale
|
| 89 |
+
radii[j] *= scale
|
| 90 |
+
constraints_changed = True
|
| 91 |
+
if not constraints_changed:
|
| 92 |
+
break
|
| 93 |
+
return radii
|
| 94 |
+
|
| 95 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 96 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 97 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 98 |
+
return np.array(list(product(coords, coords)))
|
| 99 |
+
|
| 100 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 101 |
+
"""
|
| 102 |
+
Performs a refined grid search for the 26th circle by perturbing around core interstitial points.
|
| 103 |
+
"""
|
| 104 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 105 |
+
delta = 0.025
|
| 106 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 107 |
+
|
| 108 |
+
candidate_points = []
|
| 109 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 110 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 111 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 112 |
+
|
| 113 |
+
best_sum_radii = -1.0
|
| 114 |
+
best_centers_config = None
|
| 115 |
+
|
| 116 |
+
for candidate_pos in candidate_points:
|
| 117 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 118 |
+
- trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 119 |
+
+ trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 120 |
+
current_sum_radii = np.sum(trial_radii)
|
| 121 |
+
|
| 122 |
+
if current_sum_radii > best_sum_radii:
|
| 123 |
+
best_sum_radii = current_sum_radii
|
| 124 |
+
best_centers_config = trial_centers
|
| 125 |
+
|
| 126 |
+
+ # Fallback if no valid candidate found (shouldn't happen with proper candidate generation)
|
| 127 |
+
+ if best_centers_config is None:
|
| 128 |
+
+ best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 129 |
+
+ best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config, self.n))
|
| 130 |
+
+
|
| 131 |
+
+
|
| 132 |
+
return best_centers_config, best_sum_radii
|
| 133 |
+
|
| 134 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 135 |
+
"""
|
| 136 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 137 |
+
"""
|
| 138 |
+
- # Tuned SA parameters for local cluster refinement
|
| 139 |
+
+ # Tuned SA parameters for local cluster refinement (from current high-scoring program)
|
| 140 |
+
sa_iterations = 300
|
| 141 |
+
sa_initial_temp = 0.0002
|
| 142 |
+
sa_cooling_rate = 0.99
|
| 143 |
+
sa_initial_step_size = 0.005
|
| 144 |
+
|
| 145 |
+
current_centers = np.copy(initial_centers)
|
| 146 |
+
current_sum_radii = initial_sum_radii
|
| 147 |
+
|
| 148 |
+
best_centers = np.copy(current_centers)
|
| 149 |
+
best_sum_radii = current_sum_radii
|
| 150 |
+
|
| 151 |
+
temp = sa_initial_temp
|
| 152 |
+
step_size = sa_initial_step_size
|
| 153 |
+
|
| 154 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 155 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 156 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 157 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 158 |
+
|
| 159 |
+
for _ in range(sa_iterations):
|
| 160 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 161 |
+
|
| 162 |
+
trial_centers = np.copy(current_centers)
|
| 163 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 164 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 165 |
+
|
| 166 |
+
- trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 167 |
+
+ trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 168 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 169 |
+
|
| 170 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 171 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 172 |
+
current_centers = trial_centers
|
| 173 |
+
current_sum_radii = trial_sum_radii
|
| 174 |
+
|
| 175 |
+
if current_sum_radii > best_sum_radii:
|
| 176 |
+
best_sum_radii = current_sum_radii
|
| 177 |
+
best_centers = np.copy(current_centers)
|
| 178 |
+
|
| 179 |
+
temp *= sa_cooling_rate
|
| 180 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 181 |
+
|
| 182 |
+
return best_centers, best_sum_radii
|
| 183 |
+
|
| 184 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 185 |
+
"""
|
| 186 |
+
- Applies a global, low-temperature SA to "jiggle" all circles into a better global optimum.
|
| 187 |
+
- """
|
| 188 |
+
- # SA parameters for a final, gentle, global refinement
|
| 189 |
+
- sa_iterations = 1500
|
| 190 |
+
- sa_initial_temp = 1e-5
|
| 191 |
+
- sa_cooling_rate = 0.995
|
| 192 |
+
- sa_initial_step_size = 0.004
|
| 193 |
+
+ Applies a global SA to "jiggle" all circles into a better global optimum,
|
| 194 |
+
+ using parameters from the crossover inspiration for broader exploration.
|
| 195 |
+
+ """
|
| 196 |
+
+ # SA parameters for a final, gentle, global refinement (from crossover inspiration)
|
| 197 |
+
+ sa_iterations = 2000 # Increased iterations
|
| 198 |
+
+ sa_initial_temp = 0.0005 # Higher initial temperature
|
| 199 |
+
+ sa_cooling_rate = 0.997 # Slower cooling rate
|
| 200 |
+
+ sa_initial_step_size = 0.01 # Larger initial step size
|
| 201 |
+
|
| 202 |
+
current_centers = np.copy(initial_centers)
|
| 203 |
+
current_sum_radii = initial_sum_radii
|
| 204 |
+
|
| 205 |
+
best_centers = np.copy(current_centers)
|
| 206 |
+
best_sum_radii = current_sum_radii
|
| 207 |
+
|
| 208 |
+
temp = sa_initial_temp
|
| 209 |
+
step_size = sa_initial_step_size
|
| 210 |
+
|
| 211 |
+
- for _ in range(sa_iterations):
|
| 212 |
+
+ for k in range(sa_iterations):
|
| 213 |
+
+ # Dynamic temperature and step size schedules
|
| 214 |
+
+ temp = sa_initial_temp * (sa_cooling_rate ** k)
|
| 215 |
+
+ # Linearly decreasing step size for a more controlled search towards the end
|
| 216 |
+
+ step_size = sa_initial_step_size * (1.0 - k / sa_iterations)
|
| 217 |
+
+
|
| 218 |
+
# Perturb a random circle from the entire set
|
| 219 |
+
idx_to_move = np.random.randint(self.n)
|
| 220 |
+
|
| 221 |
+
trial_centers = np.copy(current_centers)
|
| 222 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 223 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 224 |
+
|
| 225 |
+
- trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 226 |
+
+ trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 227 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 228 |
+
|
| 229 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 230 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 231 |
+
current_centers = trial_centers
|
| 232 |
+
current_sum_radii = trial_sum_radii
|
| 233 |
+
|
| 234 |
+
if current_sum_radii > best_sum_radii:
|
| 235 |
+
best_sum_radii = current_sum_radii
|
| 236 |
+
best_centers = np.copy(current_centers)
|
| 237 |
+
|
| 238 |
+
- temp *= sa_cooling_rate
|
| 239 |
+
- step_size = max(step_size * sa_cooling_rate, 5e-8)
|
| 240 |
+
+ # Ensure step_size doesn't go below a useful minimum
|
| 241 |
+
+ step_size = max(step_size, 5e-8)
|
| 242 |
+
|
| 243 |
+
return best_centers, best_sum_radii
|
| 244 |
+
|
| 245 |
+
def construct_packing(self):
|
| 246 |
+
"""
|
| 247 |
+
Orchestrates the multi-stage packing process:
|
| 248 |
+
1. Grid search for 26th circle.
|
| 249 |
+
2. Local SA refinement of the resulting cluster.
|
| 250 |
+
3. Global SA refinement of the entire packing.
|
| 251 |
+
"""
|
| 252 |
+
+ np.random.seed(42) # For reproducible SA results
|
| 253 |
+
+
|
| 254 |
+
base_centers = self._initial_grid_placement()
|
| 255 |
+
|
| 256 |
+
- # Stage 1: Exhaustive search for a strong starting point
|
| 257 |
+
+ # Stage 1: Exhaustive search for a strong starting point for the 26th circle
|
| 258 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 259 |
+
|
| 260 |
+
- # Stage 2: Local refinement on the cluster to fine-tune
|
| 261 |
+
+ # Stage 2: Local refinement on the cluster (26th circle + neighbors) to fine-tune
|
| 262 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 263 |
+
|
| 264 |
+
- # Stage 3: Global "gentle jiggle" refinement on all circles
|
| 265 |
+
+ # Stage 3: Global "gentle jiggle" refinement on all circles with enhanced parameters
|
| 266 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 267 |
+
|
| 268 |
+
self.centers = centers3
|
| 269 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 270 |
+
- self.radii = CirclePacker._compute_max_radii_static(self.centers)
|
| 271 |
+
+ self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 272 |
+
|
| 273 |
+
return self.centers, self.radii
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def construct_packing():
|
| 277 |
+
"""
|
| 278 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 279 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 280 |
+
- a refined grid search followed by Simulated Annealing for local refinement.
|
| 281 |
+
+ a refined grid search followed by localized and then global Simulated Annealing.
|
| 282 |
+
"""
|
| 283 |
+
packer = CirclePacker(num_circles=26)
|
| 284 |
+
centers, radii = packer.construct_packing()
|
| 285 |
+
return centers, radii
|
| 286 |
+
# EVOLVE-BLOCK-END
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
# This part remains fixed (not evolved)
|
| 290 |
+
def run_packing():
|
| 291 |
+
"""Run the circle packing constructor for n=26"""
|
| 292 |
+
centers, radii = construct_packing()
|
| 293 |
+
# Calculate the sum of radii
|
| 294 |
+
sum_radii = np.sum(radii)
|
| 295 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/main.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 9 |
+
1. Initial 5x5 grid placement.
|
| 10 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 11 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 12 |
+
4. An extended global SA refinement for the entire packing.
|
| 13 |
+
"""
|
| 14 |
+
def __init__(self, num_circles=26):
|
| 15 |
+
"""Initializes the packer for 26 circles."""
|
| 16 |
+
if num_circles != 26:
|
| 17 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 18 |
+
self.n = num_circles
|
| 19 |
+
self.centers = np.zeros((self.n, 2))
|
| 20 |
+
self.radii = np.zeros(self.n)
|
| 21 |
+
|
| 22 |
+
@staticmethod
|
| 23 |
+
def _compute_max_radii_static(centers: np.ndarray, n: int) -> np.ndarray:
|
| 24 |
+
"""
|
| 25 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 26 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 27 |
+
prior implementations for superior performance.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 31 |
+
n (int): Number of circles (passed explicitly for consistency with some priors).
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
np.array of shape (n) with the final radius of each circle.
|
| 35 |
+
"""
|
| 36 |
+
radii = np.zeros(n)
|
| 37 |
+
|
| 38 |
+
# Parameters from high-scoring prior programs (score 2.5407)
|
| 39 |
+
growth_factor_start = 1.005
|
| 40 |
+
growth_factor_end = 1.002
|
| 41 |
+
outer_iterations = 400
|
| 42 |
+
tolerance_start = 1e-7
|
| 43 |
+
tolerance_end = 1e-11 # Tighter tolerance for precision
|
| 44 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on boundary distance
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
# Iteratively grow and resolve constraints
|
| 52 |
+
for k in range(outer_iterations):
|
| 53 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 54 |
+
# Exponential interpolation for smooth parameter transition
|
| 55 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 56 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 57 |
+
|
| 58 |
+
radii *= current_growth_factor
|
| 59 |
+
|
| 60 |
+
for _ in range(inner_iterations):
|
| 61 |
+
constraints_changed = False
|
| 62 |
+
# Boundary constraints
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Overlap constraints
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > 1e-12: # Avoid division by zero with a robust threshold
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
if not constraints_changed:
|
| 82 |
+
break
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 86 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
return np.array(list(product(coords, coords)))
|
| 89 |
+
|
| 90 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 91 |
+
"""
|
| 92 |
+
Performs a refined grid search for the 26th circle by perturbing around core interstitial points.
|
| 93 |
+
"""
|
| 94 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 95 |
+
delta = 0.025
|
| 96 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 97 |
+
|
| 98 |
+
candidate_points = []
|
| 99 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 100 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 101 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 102 |
+
|
| 103 |
+
best_sum_radii = -1.0
|
| 104 |
+
best_centers_config = None
|
| 105 |
+
|
| 106 |
+
for candidate_pos in candidate_points:
|
| 107 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 108 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 109 |
+
current_sum_radii = np.sum(trial_radii)
|
| 110 |
+
|
| 111 |
+
if current_sum_radii > best_sum_radii:
|
| 112 |
+
best_sum_radii = current_sum_radii
|
| 113 |
+
best_centers_config = trial_centers
|
| 114 |
+
|
| 115 |
+
# Fallback if no valid candidate found (shouldn't happen with proper candidate generation)
|
| 116 |
+
if best_centers_config is None:
|
| 117 |
+
best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 118 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config, self.n))
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
return best_centers_config, best_sum_radii
|
| 122 |
+
|
| 123 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 124 |
+
"""
|
| 125 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 126 |
+
"""
|
| 127 |
+
# Tuned SA parameters for local cluster refinement (from current high-scoring program)
|
| 128 |
+
sa_iterations = 300
|
| 129 |
+
sa_initial_temp = 0.0002
|
| 130 |
+
sa_cooling_rate = 0.99
|
| 131 |
+
sa_initial_step_size = 0.005
|
| 132 |
+
|
| 133 |
+
current_centers = np.copy(initial_centers)
|
| 134 |
+
current_sum_radii = initial_sum_radii
|
| 135 |
+
|
| 136 |
+
best_centers = np.copy(current_centers)
|
| 137 |
+
best_sum_radii = current_sum_radii
|
| 138 |
+
|
| 139 |
+
temp = sa_initial_temp
|
| 140 |
+
step_size = sa_initial_step_size
|
| 141 |
+
|
| 142 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 143 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 144 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 145 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 146 |
+
|
| 147 |
+
for _ in range(sa_iterations):
|
| 148 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 149 |
+
|
| 150 |
+
trial_centers = np.copy(current_centers)
|
| 151 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 152 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 153 |
+
|
| 154 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 155 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 156 |
+
|
| 157 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 158 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 159 |
+
current_centers = trial_centers
|
| 160 |
+
current_sum_radii = trial_sum_radii
|
| 161 |
+
|
| 162 |
+
if current_sum_radii > best_sum_radii:
|
| 163 |
+
best_sum_radii = current_sum_radii
|
| 164 |
+
best_centers = np.copy(current_centers)
|
| 165 |
+
|
| 166 |
+
temp *= sa_cooling_rate
|
| 167 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 168 |
+
|
| 169 |
+
return best_centers, best_sum_radii
|
| 170 |
+
|
| 171 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 172 |
+
"""
|
| 173 |
+
Applies a global SA to "jiggle" all circles into a better global optimum,
|
| 174 |
+
using parameters from the crossover inspiration for broader exploration.
|
| 175 |
+
"""
|
| 176 |
+
# SA parameters for a final, gentle, global refinement (from crossover inspiration)
|
| 177 |
+
sa_iterations = 2000 # Increased iterations
|
| 178 |
+
sa_initial_temp = 0.0005 # Higher initial temperature
|
| 179 |
+
sa_cooling_rate = 0.997 # Slower cooling rate
|
| 180 |
+
sa_initial_step_size = 0.01 # Larger initial step size
|
| 181 |
+
|
| 182 |
+
current_centers = np.copy(initial_centers)
|
| 183 |
+
current_sum_radii = initial_sum_radii
|
| 184 |
+
|
| 185 |
+
best_centers = np.copy(current_centers)
|
| 186 |
+
best_sum_radii = current_sum_radii
|
| 187 |
+
|
| 188 |
+
temp = sa_initial_temp
|
| 189 |
+
step_size = sa_initial_step_size
|
| 190 |
+
|
| 191 |
+
for k in range(sa_iterations):
|
| 192 |
+
# Dynamic temperature and step size schedules
|
| 193 |
+
temp = sa_initial_temp * (sa_cooling_rate ** k)
|
| 194 |
+
# Linearly decreasing step size for a more controlled search towards the end
|
| 195 |
+
step_size = sa_initial_step_size * (1.0 - k / sa_iterations)
|
| 196 |
+
|
| 197 |
+
# Perturb a random circle from the entire set
|
| 198 |
+
idx_to_move = np.random.randint(self.n)
|
| 199 |
+
|
| 200 |
+
trial_centers = np.copy(current_centers)
|
| 201 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 202 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 203 |
+
|
| 204 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 205 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 206 |
+
|
| 207 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 208 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 209 |
+
current_centers = trial_centers
|
| 210 |
+
current_sum_radii = trial_sum_radii
|
| 211 |
+
|
| 212 |
+
if current_sum_radii > best_sum_radii:
|
| 213 |
+
best_sum_radii = current_sum_radii
|
| 214 |
+
best_centers = np.copy(current_centers)
|
| 215 |
+
|
| 216 |
+
# Ensure step_size doesn't go below a useful minimum
|
| 217 |
+
step_size = max(step_size, 5e-8)
|
| 218 |
+
|
| 219 |
+
return best_centers, best_sum_radii
|
| 220 |
+
|
| 221 |
+
def construct_packing(self):
|
| 222 |
+
"""
|
| 223 |
+
Orchestrates the multi-stage packing process:
|
| 224 |
+
1. Grid search for 26th circle.
|
| 225 |
+
2. Local SA refinement of the resulting cluster.
|
| 226 |
+
3. Global SA refinement of the entire packing.
|
| 227 |
+
"""
|
| 228 |
+
np.random.seed(42) # For reproducible SA results
|
| 229 |
+
|
| 230 |
+
base_centers = self._initial_grid_placement()
|
| 231 |
+
|
| 232 |
+
# Stage 1: Exhaustive search for a strong starting point for the 26th circle
|
| 233 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 234 |
+
|
| 235 |
+
# Stage 2: Local refinement on the cluster (26th circle + neighbors) to fine-tune
|
| 236 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 237 |
+
|
| 238 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles with enhanced parameters
|
| 239 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 240 |
+
|
| 241 |
+
self.centers = centers3
|
| 242 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 243 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 244 |
+
|
| 245 |
+
return self.centers, self.radii
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def construct_packing():
|
| 249 |
+
"""
|
| 250 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 251 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 252 |
+
a refined grid search followed by localized and then global Simulated Annealing.
|
| 253 |
+
"""
|
| 254 |
+
packer = CirclePacker(num_circles=26)
|
| 255 |
+
centers, radii = packer.construct_packing()
|
| 256 |
+
return centers, radii
|
| 257 |
+
# EVOLVE-BLOCK-END
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# This part remains fixed (not evolved)
|
| 261 |
+
def run_packing():
|
| 262 |
+
"""Run the circle packing constructor for n=26"""
|
| 263 |
+
centers, radii = construct_packing()
|
| 264 |
+
# Calculate the sum of radii
|
| 265 |
+
sum_radii = np.sum(radii)
|
| 266 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/original.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 9 |
+
1. Initial 5x5 grid placement.
|
| 10 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 11 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(self, num_circles=26):
|
| 14 |
+
"""Initializes the packer for 26 circles."""
|
| 15 |
+
if num_circles != 26:
|
| 16 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 17 |
+
self.n = num_circles
|
| 18 |
+
self.centers = np.zeros((self.n, 2))
|
| 19 |
+
self.radii = np.zeros(self.n)
|
| 20 |
+
|
| 21 |
+
@staticmethod
|
| 22 |
+
def _compute_max_radii_static(centers: np.ndarray) -> np.ndarray:
|
| 23 |
+
"""
|
| 24 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 25 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 26 |
+
prior implementations for superior performance.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
np.array of shape (n) with the final radius of each circle.
|
| 33 |
+
"""
|
| 34 |
+
n = centers.shape[0]
|
| 35 |
+
radii = np.zeros(n)
|
| 36 |
+
|
| 37 |
+
# Parameters from the high-scoring prior program (score 2.5406)
|
| 38 |
+
growth_factor_start = 1.005
|
| 39 |
+
growth_factor_end = 1.002
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
tolerance_start = 1e-7
|
| 42 |
+
tolerance_end = 1e-11
|
| 43 |
+
# Increased inner iterations for more robust constraint satisfaction
|
| 44 |
+
inner_iterations = 20
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on boundary distance
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
# Iteratively grow and resolve constraints
|
| 52 |
+
for k in range(outer_iterations):
|
| 53 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 54 |
+
# Exponential interpolation for smooth parameter transition
|
| 55 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 56 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 57 |
+
|
| 58 |
+
radii *= current_growth_factor
|
| 59 |
+
|
| 60 |
+
for _ in range(inner_iterations):
|
| 61 |
+
constraints_changed = False
|
| 62 |
+
# Boundary constraints
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Overlap constraints
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > 1e-12: # Avoid division by zero
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
if not constraints_changed:
|
| 82 |
+
break
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 86 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
return np.array(list(product(coords, coords)))
|
| 89 |
+
|
| 90 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 91 |
+
"""
|
| 92 |
+
Performs a refined grid search for the 26th circle by perturbing around core interstitial points.
|
| 93 |
+
"""
|
| 94 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 95 |
+
delta = 0.025
|
| 96 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 97 |
+
|
| 98 |
+
candidate_points = []
|
| 99 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 100 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 101 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 102 |
+
|
| 103 |
+
best_sum_radii = -1.0
|
| 104 |
+
best_centers_config = None
|
| 105 |
+
|
| 106 |
+
for candidate_pos in candidate_points:
|
| 107 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 108 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 109 |
+
current_sum_radii = np.sum(trial_radii)
|
| 110 |
+
|
| 111 |
+
if current_sum_radii > best_sum_radii:
|
| 112 |
+
best_sum_radii = current_sum_radii
|
| 113 |
+
best_centers_config = trial_centers
|
| 114 |
+
|
| 115 |
+
return best_centers_config, best_sum_radii
|
| 116 |
+
|
| 117 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 118 |
+
"""
|
| 119 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 120 |
+
"""
|
| 121 |
+
# Tuned SA parameters for local cluster refinement
|
| 122 |
+
sa_iterations = 300
|
| 123 |
+
sa_initial_temp = 0.0002
|
| 124 |
+
sa_cooling_rate = 0.99
|
| 125 |
+
sa_initial_step_size = 0.005
|
| 126 |
+
|
| 127 |
+
current_centers = np.copy(initial_centers)
|
| 128 |
+
current_sum_radii = initial_sum_radii
|
| 129 |
+
|
| 130 |
+
best_centers = np.copy(current_centers)
|
| 131 |
+
best_sum_radii = current_sum_radii
|
| 132 |
+
|
| 133 |
+
temp = sa_initial_temp
|
| 134 |
+
step_size = sa_initial_step_size
|
| 135 |
+
|
| 136 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 137 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 138 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 139 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 140 |
+
|
| 141 |
+
for _ in range(sa_iterations):
|
| 142 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 143 |
+
|
| 144 |
+
trial_centers = np.copy(current_centers)
|
| 145 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 146 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 147 |
+
|
| 148 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 149 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 150 |
+
|
| 151 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 152 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 153 |
+
current_centers = trial_centers
|
| 154 |
+
current_sum_radii = trial_sum_radii
|
| 155 |
+
|
| 156 |
+
if current_sum_radii > best_sum_radii:
|
| 157 |
+
best_sum_radii = current_sum_radii
|
| 158 |
+
best_centers = np.copy(current_centers)
|
| 159 |
+
|
| 160 |
+
temp *= sa_cooling_rate
|
| 161 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 162 |
+
|
| 163 |
+
return best_centers, best_sum_radii
|
| 164 |
+
|
| 165 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 166 |
+
"""
|
| 167 |
+
Applies a global, low-temperature SA to "jiggle" all circles into a better global optimum.
|
| 168 |
+
"""
|
| 169 |
+
# SA parameters for a final, gentle, global refinement
|
| 170 |
+
sa_iterations = 1500
|
| 171 |
+
sa_initial_temp = 1e-5
|
| 172 |
+
sa_cooling_rate = 0.995
|
| 173 |
+
sa_initial_step_size = 0.004
|
| 174 |
+
|
| 175 |
+
current_centers = np.copy(initial_centers)
|
| 176 |
+
current_sum_radii = initial_sum_radii
|
| 177 |
+
|
| 178 |
+
best_centers = np.copy(current_centers)
|
| 179 |
+
best_sum_radii = current_sum_radii
|
| 180 |
+
|
| 181 |
+
temp = sa_initial_temp
|
| 182 |
+
step_size = sa_initial_step_size
|
| 183 |
+
|
| 184 |
+
for _ in range(sa_iterations):
|
| 185 |
+
# Perturb a random circle from the entire set
|
| 186 |
+
idx_to_move = np.random.randint(self.n)
|
| 187 |
+
|
| 188 |
+
trial_centers = np.copy(current_centers)
|
| 189 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 190 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 191 |
+
|
| 192 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers)
|
| 193 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 194 |
+
|
| 195 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 196 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 197 |
+
current_centers = trial_centers
|
| 198 |
+
current_sum_radii = trial_sum_radii
|
| 199 |
+
|
| 200 |
+
if current_sum_radii > best_sum_radii:
|
| 201 |
+
best_sum_radii = current_sum_radii
|
| 202 |
+
best_centers = np.copy(current_centers)
|
| 203 |
+
|
| 204 |
+
temp *= sa_cooling_rate
|
| 205 |
+
step_size = max(step_size * sa_cooling_rate, 5e-8)
|
| 206 |
+
|
| 207 |
+
return best_centers, best_sum_radii
|
| 208 |
+
|
| 209 |
+
def construct_packing(self):
|
| 210 |
+
"""
|
| 211 |
+
Orchestrates the multi-stage packing process:
|
| 212 |
+
1. Grid search for 26th circle.
|
| 213 |
+
2. Local SA refinement of the resulting cluster.
|
| 214 |
+
3. Global SA refinement of the entire packing.
|
| 215 |
+
"""
|
| 216 |
+
base_centers = self._initial_grid_placement()
|
| 217 |
+
|
| 218 |
+
# Stage 1: Exhaustive search for a strong starting point
|
| 219 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 220 |
+
|
| 221 |
+
# Stage 2: Local refinement on the cluster to fine-tune
|
| 222 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 223 |
+
|
| 224 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles
|
| 225 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 226 |
+
|
| 227 |
+
self.centers = centers3
|
| 228 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 229 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers)
|
| 230 |
+
|
| 231 |
+
return self.centers, self.radii
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def construct_packing():
|
| 235 |
+
"""
|
| 236 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 237 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 238 |
+
a refined grid search followed by Simulated Annealing for local refinement.
|
| 239 |
+
"""
|
| 240 |
+
packer = CirclePacker(num_circles=26)
|
| 241 |
+
centers, radii = packer.construct_packing()
|
| 242 |
+
return centers, radii
|
| 243 |
+
# EVOLVE-BLOCK-END
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
# This part remains fixed (not evolved)
|
| 247 |
+
def run_packing():
|
| 248 |
+
"""Run the circle packing constructor for n=26"""
|
| 249 |
+
centers, radii = construct_packing()
|
| 250 |
+
# Calculate the sum of radii
|
| 251 |
+
sum_radii = np.sum(radii)
|
| 252 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_104/rewrite.txt
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings using a multi-stage optimization process:
|
| 9 |
+
1. Initial 5x5 grid placement.
|
| 10 |
+
2. A refined interstitial grid search for the 26th circle.
|
| 11 |
+
3. A localized Simulated Annealing (SA) refinement of the resulting cluster.
|
| 12 |
+
4. An extended global SA refinement for the entire packing.
|
| 13 |
+
"""
|
| 14 |
+
def __init__(self, num_circles=26):
|
| 15 |
+
"""Initializes the packer for 26 circles."""
|
| 16 |
+
if num_circles != 26:
|
| 17 |
+
raise ValueError("This CirclePacker is specialized for exactly 26 circles.")
|
| 18 |
+
self.n = num_circles
|
| 19 |
+
self.centers = np.zeros((self.n, 2))
|
| 20 |
+
self.radii = np.zeros(self.n)
|
| 21 |
+
|
| 22 |
+
@staticmethod
|
| 23 |
+
def _compute_max_radii_static(centers: np.ndarray, n: int) -> np.ndarray:
|
| 24 |
+
"""
|
| 25 |
+
Computes maximum radii using an iterative method with an adaptive growth
|
| 26 |
+
factor and tolerance with exponential decay, adopted from the highest-scoring
|
| 27 |
+
prior implementations for superior performance.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 31 |
+
n (int): Number of circles (passed explicitly for consistency with some priors).
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
np.array of shape (n) with the final radius of each circle.
|
| 35 |
+
"""
|
| 36 |
+
radii = np.zeros(n)
|
| 37 |
+
|
| 38 |
+
# Parameters from high-scoring prior programs (score 2.5407)
|
| 39 |
+
growth_factor_start = 1.005
|
| 40 |
+
growth_factor_end = 1.002
|
| 41 |
+
outer_iterations = 400
|
| 42 |
+
tolerance_start = 1e-7
|
| 43 |
+
tolerance_end = 1e-11 # Tighter tolerance for precision
|
| 44 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on boundary distance
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
# Iteratively grow and resolve constraints
|
| 52 |
+
for k in range(outer_iterations):
|
| 53 |
+
progress = k / (outer_iterations - 1 + 1e-9)
|
| 54 |
+
# Exponential interpolation for smooth parameter transition
|
| 55 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**progress
|
| 56 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**progress
|
| 57 |
+
|
| 58 |
+
radii *= current_growth_factor
|
| 59 |
+
|
| 60 |
+
for _ in range(inner_iterations):
|
| 61 |
+
constraints_changed = False
|
| 62 |
+
# Boundary constraints
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Overlap constraints
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > 1e-12: # Avoid division by zero with a robust threshold
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
if not constraints_changed:
|
| 82 |
+
break
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
def _initial_grid_placement(self) -> np.ndarray:
|
| 86 |
+
"""Places the first 25 circles in a perfect 5x5 grid."""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
return np.array(list(product(coords, coords)))
|
| 89 |
+
|
| 90 |
+
def _grid_search_for_26th_circle(self, base_centers: np.ndarray) -> tuple[np.ndarray, float]:
|
| 91 |
+
"""
|
| 92 |
+
Performs a refined grid search for the 26th circle by perturbing around core interstitial points.
|
| 93 |
+
"""
|
| 94 |
+
interstitial_core_coords = np.linspace(0.2, 0.8, 4)
|
| 95 |
+
delta = 0.025
|
| 96 |
+
perturbation_offsets = np.array([-delta, 0, delta])
|
| 97 |
+
|
| 98 |
+
candidate_points = []
|
| 99 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 100 |
+
for offset_x, offset_y in product(perturbation_offsets, perturbation_offsets):
|
| 101 |
+
candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 102 |
+
|
| 103 |
+
best_sum_radii = -1.0
|
| 104 |
+
best_centers_config = None
|
| 105 |
+
|
| 106 |
+
for candidate_pos in candidate_points:
|
| 107 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 108 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 109 |
+
current_sum_radii = np.sum(trial_radii)
|
| 110 |
+
|
| 111 |
+
if current_sum_radii > best_sum_radii:
|
| 112 |
+
best_sum_radii = current_sum_radii
|
| 113 |
+
best_centers_config = trial_centers
|
| 114 |
+
|
| 115 |
+
# Fallback if no valid candidate found (shouldn't happen with proper candidate generation)
|
| 116 |
+
if best_centers_config is None:
|
| 117 |
+
best_centers_config = np.vstack([base_centers, [[0.5, 0.5]]])
|
| 118 |
+
best_sum_radii = np.sum(CirclePacker._compute_max_radii_static(best_centers_config, self.n))
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
return best_centers_config, best_sum_radii
|
| 122 |
+
|
| 123 |
+
def _local_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 124 |
+
"""
|
| 125 |
+
Applies a localized SA search to fine-tune the positions of the 26th circle and its 4 nearest neighbors.
|
| 126 |
+
"""
|
| 127 |
+
# Tuned SA parameters for local cluster refinement (from current high-scoring program)
|
| 128 |
+
sa_iterations = 300
|
| 129 |
+
sa_initial_temp = 0.0002
|
| 130 |
+
sa_cooling_rate = 0.99
|
| 131 |
+
sa_initial_step_size = 0.005
|
| 132 |
+
|
| 133 |
+
current_centers = np.copy(initial_centers)
|
| 134 |
+
current_sum_radii = initial_sum_radii
|
| 135 |
+
|
| 136 |
+
best_centers = np.copy(current_centers)
|
| 137 |
+
best_sum_radii = current_sum_radii
|
| 138 |
+
|
| 139 |
+
temp = sa_initial_temp
|
| 140 |
+
step_size = sa_initial_step_size
|
| 141 |
+
|
| 142 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 143 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 144 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 145 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 146 |
+
|
| 147 |
+
for _ in range(sa_iterations):
|
| 148 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 149 |
+
|
| 150 |
+
trial_centers = np.copy(current_centers)
|
| 151 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 152 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 153 |
+
|
| 154 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 155 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 156 |
+
|
| 157 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 158 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 159 |
+
current_centers = trial_centers
|
| 160 |
+
current_sum_radii = trial_sum_radii
|
| 161 |
+
|
| 162 |
+
if current_sum_radii > best_sum_radii:
|
| 163 |
+
best_sum_radii = current_sum_radii
|
| 164 |
+
best_centers = np.copy(current_centers)
|
| 165 |
+
|
| 166 |
+
temp *= sa_cooling_rate
|
| 167 |
+
step_size = max(step_size * sa_cooling_rate, 1e-7)
|
| 168 |
+
|
| 169 |
+
return best_centers, best_sum_radii
|
| 170 |
+
|
| 171 |
+
def _global_refinement_sa(self, initial_centers: np.ndarray, initial_sum_radii: float) -> tuple[np.ndarray, float]:
|
| 172 |
+
"""
|
| 173 |
+
Applies a global SA to "jiggle" all circles into a better global optimum,
|
| 174 |
+
using parameters from the crossover inspiration for broader exploration.
|
| 175 |
+
"""
|
| 176 |
+
# SA parameters for a final, gentle, global refinement (from crossover inspiration)
|
| 177 |
+
sa_iterations = 2000 # Increased iterations
|
| 178 |
+
sa_initial_temp = 0.0005 # Higher initial temperature
|
| 179 |
+
sa_cooling_rate = 0.997 # Slower cooling rate
|
| 180 |
+
sa_initial_step_size = 0.01 # Larger initial step size
|
| 181 |
+
|
| 182 |
+
current_centers = np.copy(initial_centers)
|
| 183 |
+
current_sum_radii = initial_sum_radii
|
| 184 |
+
|
| 185 |
+
best_centers = np.copy(current_centers)
|
| 186 |
+
best_sum_radii = current_sum_radii
|
| 187 |
+
|
| 188 |
+
temp = sa_initial_temp
|
| 189 |
+
step_size = sa_initial_step_size
|
| 190 |
+
|
| 191 |
+
for k in range(sa_iterations):
|
| 192 |
+
# Dynamic temperature and step size schedules
|
| 193 |
+
temp = sa_initial_temp * (sa_cooling_rate ** k)
|
| 194 |
+
# Linearly decreasing step size for a more controlled search towards the end
|
| 195 |
+
step_size = sa_initial_step_size * (1.0 - k / sa_iterations)
|
| 196 |
+
|
| 197 |
+
# Perturb a random circle from the entire set
|
| 198 |
+
idx_to_move = np.random.randint(self.n)
|
| 199 |
+
|
| 200 |
+
trial_centers = np.copy(current_centers)
|
| 201 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 202 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 203 |
+
|
| 204 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 205 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 206 |
+
|
| 207 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 208 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 209 |
+
current_centers = trial_centers
|
| 210 |
+
current_sum_radii = trial_sum_radii
|
| 211 |
+
|
| 212 |
+
if current_sum_radii > best_sum_radii:
|
| 213 |
+
best_sum_radii = current_sum_radii
|
| 214 |
+
best_centers = np.copy(current_centers)
|
| 215 |
+
|
| 216 |
+
# Ensure step_size doesn't go below a useful minimum
|
| 217 |
+
step_size = max(step_size, 5e-8)
|
| 218 |
+
|
| 219 |
+
return best_centers, best_sum_radii
|
| 220 |
+
|
| 221 |
+
def construct_packing(self):
|
| 222 |
+
"""
|
| 223 |
+
Orchestrates the multi-stage packing process:
|
| 224 |
+
1. Grid search for 26th circle.
|
| 225 |
+
2. Local SA refinement of the resulting cluster.
|
| 226 |
+
3. Global SA refinement of the entire packing.
|
| 227 |
+
"""
|
| 228 |
+
np.random.seed(42) # For reproducible SA results
|
| 229 |
+
|
| 230 |
+
base_centers = self._initial_grid_placement()
|
| 231 |
+
|
| 232 |
+
# Stage 1: Exhaustive search for a strong starting point for the 26th circle
|
| 233 |
+
centers1, sum_radii1 = self._grid_search_for_26th_circle(base_centers)
|
| 234 |
+
|
| 235 |
+
# Stage 2: Local refinement on the cluster (26th circle + neighbors) to fine-tune
|
| 236 |
+
centers2, sum_radii2 = self._local_refinement_sa(centers1, sum_radii1)
|
| 237 |
+
|
| 238 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles with enhanced parameters
|
| 239 |
+
centers3, _ = self._global_refinement_sa(centers2, sum_radii2)
|
| 240 |
+
|
| 241 |
+
self.centers = centers3
|
| 242 |
+
# Final radius calculation for maximum precision on the best-found centers
|
| 243 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 244 |
+
|
| 245 |
+
return self.centers, self.radii
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def construct_packing():
|
| 249 |
+
"""
|
| 250 |
+
Constructs an arrangement of 26 circles by leveraging a multi-stage
|
| 251 |
+
optimization strategy managed by the CirclePacker class. This involves
|
| 252 |
+
a refined grid search followed by localized and then global Simulated Annealing.
|
| 253 |
+
"""
|
| 254 |
+
packer = CirclePacker(num_circles=26)
|
| 255 |
+
centers, radii = packer.construct_packing()
|
| 256 |
+
return centers, radii
|
| 257 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/edit.diff
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,402 +1,405 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# --- Data Structure ---
|
| 10 |
+
class PackingState:
|
| 11 |
+
"""Holds the current state of the circle packing (centers, radii, sum_radii)."""
|
| 12 |
+
def __init__(self, n, centers=None, radii=None):
|
| 13 |
+
self.n = n
|
| 14 |
+
self.centers = np.zeros((n, 2)) if centers is None else centers
|
| 15 |
+
self.radii = np.zeros(n) if radii is None else radii
|
| 16 |
+
self.sum_radii = 0.0 # Will be updated after radii optimization
|
| 17 |
+
|
| 18 |
+
def update_radii(self, new_radii):
|
| 19 |
+
"""Updates radii and recalculates the sum of radii."""
|
| 20 |
+
self.radii = new_radii
|
| 21 |
+
self.sum_radii = np.sum(new_radii)
|
| 22 |
+
|
| 23 |
+
def update_centers(self, new_centers):
|
| 24 |
+
"""Updates circle centers."""
|
| 25 |
+
self.centers = new_centers
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# --- Core Optimization Logic (Radii) ---
|
| 29 |
+
class RadiiOptimizer:
|
| 30 |
+
"""
|
| 31 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 32 |
+
an iterative growth and constraint resolution method.
|
| 33 |
+
Incorporates adaptive growth factor and dynamic tolerance with non-linear (exponential) decay.
|
| 34 |
+
"""
|
| 35 |
+
@staticmethod
|
| 36 |
+
def optimize_radii(centers: np.ndarray, n: int) -> np.ndarray:
|
| 37 |
+
"""
|
| 38 |
+
Calculates the maximum radii for `n` circles with given `centers`.
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 42 |
+
n (int): Number of circles.
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 46 |
+
"""
|
| 47 |
+
radii = np.zeros(n)
|
| 48 |
+
|
| 49 |
+
# Proven parameters from high-scoring implementations
|
| 50 |
+
growth_factor_start = 1.005
|
| 51 |
+
growth_factor_end = 1.002
|
| 52 |
+
outer_iterations = 400
|
| 53 |
+
tolerance_start = 1e-7
|
| 54 |
+
tolerance_end = 1e-11 # Tighter precision for the final stages
|
| 55 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 56 |
+
|
| 57 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 58 |
+
for i in range(n):
|
| 59 |
+
x, y = centers[i]
|
| 60 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 61 |
+
|
| 62 |
+
for k in range(outer_iterations):
|
| 63 |
+
interp_factor = k / (outer_iterations - 1.0 + 1e-9) # Avoid division by zero
|
| 64 |
+
|
| 65 |
+
# Non-linear (exponential) interpolation for smooth parameter transition
|
| 66 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**interp_factor
|
| 67 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**interp_factor
|
| 68 |
+
|
| 69 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 70 |
+
|
| 71 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 72 |
+
constraints_changed = False
|
| 73 |
+
|
| 74 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 75 |
+
for i in range(n):
|
| 76 |
+
x, y = centers[i]
|
| 77 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 78 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 79 |
+
radii[i] = boundary_limit
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
|
| 82 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 83 |
+
for i in range(n):
|
| 84 |
+
for j in range(i + 1, n):
|
| 85 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 86 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 87 |
+
total_radius = radii[i] + radii[j]
|
| 88 |
+
if total_radius > 1e-12: # Avoid division by zero for extremely small radii
|
| 89 |
+
scale = dist / total_radius
|
| 90 |
+
radii[i] *= scale
|
| 91 |
+
radii[j] *= scale
|
| 92 |
+
constraints_changed = True
|
| 93 |
+
|
| 94 |
+
if not constraints_changed:
|
| 95 |
+
break # Inner loop converged
|
| 96 |
+
|
| 97 |
+
return radii
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# --- Placement Strategies (Components of the pipeline) ---
|
| 101 |
+
class GridInitializer:
|
| 102 |
+
"""Places the first N_GRID_CIRCLES in a symmetric grid pattern."""
|
| 103 |
+
def __init__(self, n_grid_circles=25):
|
| 104 |
+
self.n_grid_circles = n_grid_circles
|
| 105 |
+
|
| 106 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 107 |
+
"""Applies the initial grid placement to the packing state."""
|
| 108 |
+
# Calculate grid size (e.g., 5 for 25 circles)
|
| 109 |
+
grid_dim = int(np.sqrt(self.n_grid_circles))
|
| 110 |
+
coords = np.linspace(0.1, 0.9, grid_dim)
|
| 111 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 112 |
+
packing_state.centers[:self.n_grid_circles] = grid_centers
|
| 113 |
+
return packing_state
|
| 114 |
+
|
| 115 |
+
class InitialGridRefiner:
|
| 116 |
+
"""
|
| 117 |
+
Applies a gentle, local SA refinement to the initial N_GRID_CIRCLES
|
| 118 |
+
to break the initial grid rigidity and find a better base packing.
|
| 119 |
+
"""
|
| 120 |
+
- def __init__(self, n_grid_circles=25, num_iterations=500, initial_step_size=0.005,
|
| 121 |
+
- initial_temp=0.005, cooling_rate=0.995):
|
| 122 |
+
+ def __init__(self, n_grid_circles=25, num_iterations=750, # Increased iterations
|
| 123 |
+
+ initial_step_size=0.005, initial_temp=0.005, cooling_rate=0.995):
|
| 124 |
+
self.n_grid_circles = n_grid_circles
|
| 125 |
+
self.num_iterations = num_iterations
|
| 126 |
+
self.initial_step_size = initial_step_size
|
| 127 |
+
self.initial_temp = initial_temp
|
| 128 |
+
self.cooling_rate = cooling_rate
|
| 129 |
+
|
| 130 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 131 |
+
"""Applies SA-based perturbation and refinement to the base circles."""
|
| 132 |
+
if self.n_grid_circles > packing_state.n:
|
| 133 |
+
return packing_state
|
| 134 |
+
|
| 135 |
+
current_centers_subset = np.copy(packing_state.centers[:self.n_grid_circles])
|
| 136 |
+
current_radii_subset = RadiiOptimizer.optimize_radii(current_centers_subset, self.n_grid_circles)
|
| 137 |
+
current_sum_radii_subset = np.sum(current_radii_subset)
|
| 138 |
+
|
| 139 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 140 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 141 |
+
|
| 142 |
+
temp = self.initial_temp
|
| 143 |
+
|
| 144 |
+
for k in range(self.num_iterations):
|
| 145 |
+
progress = k / self.num_iterations
|
| 146 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 147 |
+
|
| 148 |
+
# Perturb ONE random circle from the subset
|
| 149 |
+
idx_to_perturb = np.random.randint(self.n_grid_circles)
|
| 150 |
+
trial_centers_subset = np.copy(current_centers_subset)
|
| 151 |
+
|
| 152 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 153 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 154 |
+
trial_centers_subset[idx_to_perturb] += [dx, dy]
|
| 155 |
+
trial_centers_subset[idx_to_perturb] = np.clip(trial_centers_subset[idx_to_perturb], 0.0, 1.0)
|
| 156 |
+
|
| 157 |
+
# Evaluate the new configuration
|
| 158 |
+
trial_radii_subset = RadiiOptimizer.optimize_radii(trial_centers_subset, self.n_grid_circles)
|
| 159 |
+
trial_sum_radii_subset = np.sum(trial_radii_subset)
|
| 160 |
+
|
| 161 |
+
# Metropolis-Hastings acceptance criterion
|
| 162 |
+
delta_energy = trial_sum_radii_subset - current_sum_radii_subset
|
| 163 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 164 |
+
current_centers_subset = trial_centers_subset
|
| 165 |
+
current_sum_radii_subset = trial_sum_radii_subset
|
| 166 |
+
|
| 167 |
+
if current_sum_radii_subset > best_sum_radii_subset_local:
|
| 168 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 169 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 170 |
+
|
| 171 |
+
temp *= self.cooling_rate
|
| 172 |
+
|
| 173 |
+
packing_state.centers[:self.n_grid_circles] = best_centers_subset_local
|
| 174 |
+
return packing_state
|
| 175 |
+
|
| 176 |
+
class InterstitialSearcher:
|
| 177 |
+
"""
|
| 178 |
+
Finds the best initial position for an additional circle (e.g., the 26th)
|
| 179 |
+
by exhaustive search over candidate points.
|
| 180 |
+
"""
|
| 181 |
+
- def __init__(self, index_to_place: int, base_circles_count: int = 25, grid_resolution: int = 9):
|
| 182 |
+
+ def __init__(self, index_to_place: int, base_circles_count: int = 25, grid_resolution: int = 12): # Increased grid_resolution
|
| 183 |
+
self.index_to_place = index_to_place
|
| 184 |
+
self.base_circles_count = base_circles_count
|
| 185 |
+
- self.grid_resolution = grid_resolution # e.g., 9x9 for 81 candidates
|
| 186 |
+
+ self.grid_resolution = grid_resolution # e.g., 12x12 for 144 candidates
|
| 187 |
+
|
| 188 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 189 |
+
"""
|
| 190 |
+
Searches for the optimal position for the circle at `index_to_place`.
|
| 191 |
+
"""
|
| 192 |
+
if self.index_to_place >= packing_state.n:
|
| 193 |
+
return packing_state
|
| 194 |
+
|
| 195 |
+
base_centers = np.copy(packing_state.centers[:self.base_circles_count])
|
| 196 |
+
|
| 197 |
+
# Increased granularity and broader range for interstitial search (Recommendation 1)
|
| 198 |
+
- interstitial_coords = np.linspace(0.1, 0.9, self.grid_resolution) # e.g., 9 points from 0.1 to 0.9
|
| 199 |
+
+ interstitial_coords = np.linspace(0.1, 0.9, self.grid_resolution)
|
| 200 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 201 |
+
|
| 202 |
+
best_sum_radii = -1.0
|
| 203 |
+
optimal_pos = None
|
| 204 |
+
|
| 205 |
+
for candidate_pos in candidate_points:
|
| 206 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 207 |
+
# Use RadiiOptimizer for evaluation
|
| 208 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, self.base_circles_count + 1)
|
| 209 |
+
current_sum_radii = np.sum(trial_radii)
|
| 210 |
+
|
| 211 |
+
if current_sum_radii > best_sum_radii:
|
| 212 |
+
best_sum_radii = current_sum_radii
|
| 213 |
+
optimal_pos = np.array(candidate_pos)
|
| 214 |
+
|
| 215 |
+
if optimal_pos is not None:
|
| 216 |
+
packing_state.centers[self.index_to_place] = optimal_pos
|
| 217 |
+
else:
|
| 218 |
+
packing_state.centers[self.index_to_place] = [0.5, 0.5] # Fallback
|
| 219 |
+
|
| 220 |
+
return packing_state
|
| 221 |
+
|
| 222 |
+
class LocalSARefiner:
|
| 223 |
+
"""
|
| 224 |
+
Applies localized Simulated Annealing to fine-tune positions
|
| 225 |
+
of a target circle and its closest neighbors.
|
| 226 |
+
"""
|
| 227 |
+
- def __init__(self, index_to_perturb: int, num_neighbors: int = 3, num_iterations: int = 500,
|
| 228 |
+
+ def __init__(self, index_to_perturb: int, num_neighbors: int = 4, # Increased num_neighbors
|
| 229 |
+
+ num_iterations: int = 750, # Increased iterations
|
| 230 |
+
initial_step_size: float = 0.01, initial_temp: float = 0.01, cooling_rate: float = 0.98):
|
| 231 |
+
self.index_to_perturb = index_to_perturb
|
| 232 |
+
self.num_neighbors = num_neighbors
|
| 233 |
+
self.num_iterations = num_iterations
|
| 234 |
+
self.initial_step_size = initial_step_size
|
| 235 |
+
self.initial_temp = initial_temp
|
| 236 |
+
self.cooling_rate = cooling_rate
|
| 237 |
+
|
| 238 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 239 |
+
"""
|
| 240 |
+
Refines the position of the target circle and its neighbors using SA.
|
| 241 |
+
"""
|
| 242 |
+
current_centers = np.copy(packing_state.centers)
|
| 243 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 244 |
+
current_sum_radii = np.sum(current_radii)
|
| 245 |
+
|
| 246 |
+
best_centers_local = np.copy(current_centers)
|
| 247 |
+
best_sum_radii_local = current_sum_radii
|
| 248 |
+
|
| 249 |
+
temp = self.initial_temp
|
| 250 |
+
|
| 251 |
+
# Determine which circles to perturb: target and its closest neighbors
|
| 252 |
+
distances = np.linalg.norm(current_centers - current_centers[self.index_to_perturb], axis=1)
|
| 253 |
+
neighbor_indices = np.argsort(distances)[1:1 + self.num_neighbors] # Exclude self
|
| 254 |
+
indices_to_perturb = np.append([self.index_to_perturb], neighbor_indices)
|
| 255 |
+
|
| 256 |
+
for k in range(self.num_iterations):
|
| 257 |
+
progress = k / self.num_iterations
|
| 258 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 259 |
+
|
| 260 |
+
trial_centers = np.copy(current_centers)
|
| 261 |
+
|
| 262 |
+
for idx in indices_to_perturb:
|
| 263 |
+
# Use a slightly smaller step for neighbors to maintain overall structure integrity
|
| 264 |
+
perturb_step_size = step_size if idx == self.index_to_perturb else step_size / 2.0
|
| 265 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 266 |
+
dx, dy = perturb_step_size * np.cos(random_angle), perturb_step_size * np.sin(random_angle)
|
| 267 |
+
trial_centers[idx] += [dx, dy]
|
| 268 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0) # Enforce boundary constraints
|
| 269 |
+
|
| 270 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 271 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 272 |
+
|
| 273 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 274 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 275 |
+
current_centers = trial_centers
|
| 276 |
+
current_sum_radii = trial_sum_radii
|
| 277 |
+
|
| 278 |
+
if current_sum_radii > best_sum_radii_local:
|
| 279 |
+
best_sum_radii_local = current_sum_radii
|
| 280 |
+
best_centers_local = np.copy(current_centers)
|
| 281 |
+
|
| 282 |
+
temp *= self.cooling_rate
|
| 283 |
+
|
| 284 |
+
packing_state.update_centers(best_centers_local)
|
| 285 |
+
return packing_state
|
| 286 |
+
|
| 287 |
+
class GlobalSARefiner:
|
| 288 |
+
"""
|
| 289 |
+
Applies a global Simulated Annealing (SA) refinement phase to all circles
|
| 290 |
+
to help the entire packing relax into a potentially better overall configuration.
|
| 291 |
+
This serves as a 'gentle jiggle' to escape subtle local optima.
|
| 292 |
+
"""
|
| 293 |
+
- def __init__(self, num_iterations: int = 2000, initial_step_size: float = 0.005,
|
| 294 |
+
- initial_temp: float = 0.0005, cooling_rate: float = 0.997):
|
| 295 |
+
+ def __init__(self, num_iterations: int = 2500, # Increased iterations
|
| 296 |
+
+ initial_step_size: float = 0.005,
|
| 297 |
+
+ initial_temp: float = 0.0007, # Slightly increased initial temp
|
| 298 |
+
+ cooling_rate: float = 0.997):
|
| 299 |
+
self.num_iterations = num_iterations
|
| 300 |
+
self.initial_step_size = initial_step_size
|
| 301 |
+
self.initial_temp = initial_temp
|
| 302 |
+
self.cooling_rate = cooling_rate
|
| 303 |
+
|
| 304 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 305 |
+
"""
|
| 306 |
+
Refines the positions of all circles using SA.
|
| 307 |
+
"""
|
| 308 |
+
current_centers = np.copy(packing_state.centers)
|
| 309 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 310 |
+
current_sum_radii = np.sum(current_radii)
|
| 311 |
+
|
| 312 |
+
best_centers_global = np.copy(current_centers)
|
| 313 |
+
best_sum_radii_global = current_sum_radii
|
| 314 |
+
|
| 315 |
+
temp = self.initial_temp
|
| 316 |
+
|
| 317 |
+
for k in range(self.num_iterations):
|
| 318 |
+
progress = k / self.num_iterations
|
| 319 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 320 |
+
|
| 321 |
+
trial_centers = np.copy(current_centers)
|
| 322 |
+
|
| 323 |
+
# Perturb one random circle from the entire set of 'n' circles
|
| 324 |
+
idx_to_perturb = np.random.randint(packing_state.n)
|
| 325 |
+
|
| 326 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 327 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 328 |
+
trial_centers[idx_to_perturb] += [dx, dy]
|
| 329 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0) # Enforce boundary constraints
|
| 330 |
+
|
| 331 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 332 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 333 |
+
|
| 334 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 335 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 336 |
+
current_centers = trial_centers
|
| 337 |
+
current_sum_radii = trial_sum_radii
|
| 338 |
+
|
| 339 |
+
if current_sum_radii > best_sum_radii_global:
|
| 340 |
+
best_sum_radii_global = current_sum_radii
|
| 341 |
+
best_centers_global = np.copy(current_centers)
|
| 342 |
+
|
| 343 |
+
temp *= self.cooling_rate
|
| 344 |
+
|
| 345 |
+
packing_state.update_centers(best_centers_global)
|
| 346 |
+
return packing_state
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
# --- Orchestrator (Main control flow) ---
|
| 350 |
+
class PackingOrchestrator:
|
| 351 |
+
"""
|
| 352 |
+
Orchestrates the circle packing process using a pipeline of strategies.
|
| 353 |
+
This provides a clear structural separation of concerns.
|
| 354 |
+
"""
|
| 355 |
+
def __init__(self, num_circles: int = 26):
|
| 356 |
+
if num_circles != 26:
|
| 357 |
+
raise ValueError("This orchestrator is specialized for exactly 26 circles.")
|
| 358 |
+
|
| 359 |
+
self.n = num_circles
|
| 360 |
+
self.packing_state = PackingState(num_circles)
|
| 361 |
+
self.pipeline = []
|
| 362 |
+
|
| 363 |
+
# Define the packing pipeline stages
|
| 364 |
+
self.pipeline.append(GridInitializer(n_grid_circles=25))
|
| 365 |
+
|
| 366 |
+
# New stage: pre-refine the initial 25-circle grid
|
| 367 |
+
self.pipeline.append(InitialGridRefiner(n_grid_circles=25))
|
| 368 |
+
|
| 369 |
+
if self.n > 25:
|
| 370 |
+
# Stage for placing the 26th circle with an enhanced search
|
| 371 |
+
- self.pipeline.append(InterstitialSearcher(index_to_place=25, base_circles_count=25, grid_resolution=9))
|
| 372 |
+
+ self.pipeline.append(InterstitialSearcher(index_to_place=25, base_circles_count=25))
|
| 373 |
+
|
| 374 |
+
# Local refinement on the newly placed circle and its neighbors
|
| 375 |
+
- self.pipeline.append(LocalSARefiner(index_to_perturb=25, num_iterations=500))
|
| 376 |
+
+ self.pipeline.append(LocalSARefiner(index_to_perturb=25))
|
| 377 |
+
|
| 378 |
+
# Final global SA refinement for the entire packing
|
| 379 |
+
- self.pipeline.append(GlobalSARefiner(num_iterations=2000))
|
| 380 |
+
+ self.pipeline.append(GlobalSARefiner())
|
| 381 |
+
|
| 382 |
+
def construct_packing(self) -> tuple[np.ndarray, np.ndarray]:
|
| 383 |
+
"""
|
| 384 |
+
Executes the predefined packing pipeline, applying each strategy sequentially.
|
| 385 |
+
"""
|
| 386 |
+
np.random.seed(42) # For reproducible SA results
|
| 387 |
+
for step in self.pipeline:
|
| 388 |
+
self.packing_state = step.apply(self.packing_state)
|
| 389 |
+
|
| 390 |
+
# Final global radius calculation after all center placements/refinements are complete
|
| 391 |
+
final_radii = RadiiOptimizer.optimize_radii(self.packing_state.centers, self.n)
|
| 392 |
+
self.packing_state.update_radii(final_radii)
|
| 393 |
+
|
| 394 |
+
return self.packing_state.centers, self.packing_state.radii
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def construct_packing() -> tuple[np.ndarray, np.ndarray]:
|
| 398 |
+
"""
|
| 399 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 400 |
+
pipeline-based optimization strategy to maximize the sum of radii.
|
| 401 |
+
|
| 402 |
+
Returns:
|
| 403 |
+
Tuple of (centers, radii)
|
| 404 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 405 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 406 |
+
"""
|
| 407 |
+
orchestrator = PackingOrchestrator(num_circles=26)
|
| 408 |
+
centers, radii = orchestrator.construct_packing()
|
| 409 |
+
return centers, radii
|
| 410 |
+
# EVOLVE-BLOCK-END
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
# This part remains fixed (not evolved)
|
| 414 |
+
def run_packing():
|
| 415 |
+
"""Run the circle packing constructor for n=26"""
|
| 416 |
+
centers, radii = construct_packing()
|
| 417 |
+
# Calculate the sum of radii
|
| 418 |
+
sum_radii = np.sum(radii)
|
| 419 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/main.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# --- Data Structure ---
|
| 7 |
+
class PackingState:
|
| 8 |
+
"""Holds the current state of the circle packing (centers, radii, sum_radii)."""
|
| 9 |
+
def __init__(self, n, centers=None, radii=None):
|
| 10 |
+
self.n = n
|
| 11 |
+
self.centers = np.zeros((n, 2)) if centers is None else centers
|
| 12 |
+
self.radii = np.zeros(n) if radii is None else radii
|
| 13 |
+
self.sum_radii = 0.0 # Will be updated after radii optimization
|
| 14 |
+
|
| 15 |
+
def update_radii(self, new_radii):
|
| 16 |
+
"""Updates radii and recalculates the sum of radii."""
|
| 17 |
+
self.radii = new_radii
|
| 18 |
+
self.sum_radii = np.sum(new_radii)
|
| 19 |
+
|
| 20 |
+
def update_centers(self, new_centers):
|
| 21 |
+
"""Updates circle centers."""
|
| 22 |
+
self.centers = new_centers
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# --- Core Optimization Logic (Radii) ---
|
| 26 |
+
class RadiiOptimizer:
|
| 27 |
+
"""
|
| 28 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 29 |
+
an iterative growth and constraint resolution method.
|
| 30 |
+
Incorporates adaptive growth factor and dynamic tolerance with non-linear (exponential) decay.
|
| 31 |
+
"""
|
| 32 |
+
@staticmethod
|
| 33 |
+
def optimize_radii(centers: np.ndarray, n: int) -> np.ndarray:
|
| 34 |
+
"""
|
| 35 |
+
Calculates the maximum radii for `n` circles with given `centers`.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 39 |
+
n (int): Number of circles.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 43 |
+
"""
|
| 44 |
+
radii = np.zeros(n)
|
| 45 |
+
|
| 46 |
+
# Proven parameters from high-scoring implementations
|
| 47 |
+
growth_factor_start = 1.005
|
| 48 |
+
growth_factor_end = 1.002
|
| 49 |
+
outer_iterations = 400
|
| 50 |
+
tolerance_start = 1e-7
|
| 51 |
+
tolerance_end = 1e-11 # Tighter precision for the final stages
|
| 52 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 53 |
+
|
| 54 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 55 |
+
for i in range(n):
|
| 56 |
+
x, y = centers[i]
|
| 57 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 58 |
+
|
| 59 |
+
for k in range(outer_iterations):
|
| 60 |
+
interp_factor = k / (outer_iterations - 1.0 + 1e-9) # Avoid division by zero
|
| 61 |
+
|
| 62 |
+
# Non-linear (exponential) interpolation for smooth parameter transition
|
| 63 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**interp_factor
|
| 64 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**interp_factor
|
| 65 |
+
|
| 66 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 67 |
+
|
| 68 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 69 |
+
constraints_changed = False
|
| 70 |
+
|
| 71 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 72 |
+
for i in range(n):
|
| 73 |
+
x, y = centers[i]
|
| 74 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 75 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 76 |
+
radii[i] = boundary_limit
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 80 |
+
for i in range(n):
|
| 81 |
+
for j in range(i + 1, n):
|
| 82 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 83 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 84 |
+
total_radius = radii[i] + radii[j]
|
| 85 |
+
if total_radius > 1e-12: # Avoid division by zero for extremely small radii
|
| 86 |
+
scale = dist / total_radius
|
| 87 |
+
radii[i] *= scale
|
| 88 |
+
radii[j] *= scale
|
| 89 |
+
constraints_changed = True
|
| 90 |
+
|
| 91 |
+
if not constraints_changed:
|
| 92 |
+
break # Inner loop converged
|
| 93 |
+
|
| 94 |
+
return radii
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# --- Placement Strategies (Components of the pipeline) ---
|
| 98 |
+
class GridInitializer:
|
| 99 |
+
"""Places the first N_GRID_CIRCLES in a symmetric grid pattern."""
|
| 100 |
+
def __init__(self, n_grid_circles=25):
|
| 101 |
+
self.n_grid_circles = n_grid_circles
|
| 102 |
+
|
| 103 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 104 |
+
"""Applies the initial grid placement to the packing state."""
|
| 105 |
+
# Calculate grid size (e.g., 5 for 25 circles)
|
| 106 |
+
grid_dim = int(np.sqrt(self.n_grid_circles))
|
| 107 |
+
coords = np.linspace(0.1, 0.9, grid_dim)
|
| 108 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 109 |
+
packing_state.centers[:self.n_grid_circles] = grid_centers
|
| 110 |
+
return packing_state
|
| 111 |
+
|
| 112 |
+
class InitialGridRefiner:
|
| 113 |
+
"""
|
| 114 |
+
Applies a gentle, local SA refinement to the initial N_GRID_CIRCLES
|
| 115 |
+
to break the initial grid rigidity and find a better base packing.
|
| 116 |
+
"""
|
| 117 |
+
def __init__(self, n_grid_circles=25, num_iterations=750, # Increased iterations
|
| 118 |
+
initial_step_size=0.005, initial_temp=0.005, cooling_rate=0.995):
|
| 119 |
+
self.n_grid_circles = n_grid_circles
|
| 120 |
+
self.num_iterations = num_iterations
|
| 121 |
+
self.initial_step_size = initial_step_size
|
| 122 |
+
self.initial_temp = initial_temp
|
| 123 |
+
self.cooling_rate = cooling_rate
|
| 124 |
+
|
| 125 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 126 |
+
"""Applies SA-based perturbation and refinement to the base circles."""
|
| 127 |
+
if self.n_grid_circles > packing_state.n:
|
| 128 |
+
return packing_state
|
| 129 |
+
|
| 130 |
+
current_centers_subset = np.copy(packing_state.centers[:self.n_grid_circles])
|
| 131 |
+
current_radii_subset = RadiiOptimizer.optimize_radii(current_centers_subset, self.n_grid_circles)
|
| 132 |
+
current_sum_radii_subset = np.sum(current_radii_subset)
|
| 133 |
+
|
| 134 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 135 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 136 |
+
|
| 137 |
+
temp = self.initial_temp
|
| 138 |
+
|
| 139 |
+
for k in range(self.num_iterations):
|
| 140 |
+
progress = k / self.num_iterations
|
| 141 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 142 |
+
|
| 143 |
+
# Perturb ONE random circle from the subset
|
| 144 |
+
idx_to_perturb = np.random.randint(self.n_grid_circles)
|
| 145 |
+
trial_centers_subset = np.copy(current_centers_subset)
|
| 146 |
+
|
| 147 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 148 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 149 |
+
trial_centers_subset[idx_to_perturb] += [dx, dy]
|
| 150 |
+
trial_centers_subset[idx_to_perturb] = np.clip(trial_centers_subset[idx_to_perturb], 0.0, 1.0)
|
| 151 |
+
|
| 152 |
+
# Evaluate the new configuration
|
| 153 |
+
trial_radii_subset = RadiiOptimizer.optimize_radii(trial_centers_subset, self.n_grid_circles)
|
| 154 |
+
trial_sum_radii_subset = np.sum(trial_radii_subset)
|
| 155 |
+
|
| 156 |
+
# Metropolis-Hastings acceptance criterion
|
| 157 |
+
delta_energy = trial_sum_radii_subset - current_sum_radii_subset
|
| 158 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 159 |
+
current_centers_subset = trial_centers_subset
|
| 160 |
+
current_sum_radii_subset = trial_sum_radii_subset
|
| 161 |
+
|
| 162 |
+
if current_sum_radii_subset > best_sum_radii_subset_local:
|
| 163 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 164 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 165 |
+
|
| 166 |
+
temp *= self.cooling_rate
|
| 167 |
+
|
| 168 |
+
packing_state.centers[:self.n_grid_circles] = best_centers_subset_local
|
| 169 |
+
return packing_state
|
| 170 |
+
|
| 171 |
+
class InterstitialSearcher:
|
| 172 |
+
"""
|
| 173 |
+
Finds the best initial position for an additional circle (e.g., the 26th)
|
| 174 |
+
by exhaustive search over candidate points.
|
| 175 |
+
"""
|
| 176 |
+
def __init__(self, index_to_place: int, base_circles_count: int = 25, grid_resolution: int = 12): # Increased grid_resolution
|
| 177 |
+
self.index_to_place = index_to_place
|
| 178 |
+
self.base_circles_count = base_circles_count
|
| 179 |
+
self.grid_resolution = grid_resolution # e.g., 12x12 for 144 candidates
|
| 180 |
+
|
| 181 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 182 |
+
"""
|
| 183 |
+
Searches for the optimal position for the circle at `index_to_place`.
|
| 184 |
+
"""
|
| 185 |
+
if self.index_to_place >= packing_state.n:
|
| 186 |
+
return packing_state
|
| 187 |
+
|
| 188 |
+
base_centers = np.copy(packing_state.centers[:self.base_circles_count])
|
| 189 |
+
|
| 190 |
+
# Increased granularity and broader range for interstitial search (Recommendation 1)
|
| 191 |
+
interstitial_coords = np.linspace(0.1, 0.9, self.grid_resolution)
|
| 192 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 193 |
+
|
| 194 |
+
best_sum_radii = -1.0
|
| 195 |
+
optimal_pos = None
|
| 196 |
+
|
| 197 |
+
for candidate_pos in candidate_points:
|
| 198 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 199 |
+
# Use RadiiOptimizer for evaluation
|
| 200 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, self.base_circles_count + 1)
|
| 201 |
+
current_sum_radii = np.sum(trial_radii)
|
| 202 |
+
|
| 203 |
+
if current_sum_radii > best_sum_radii:
|
| 204 |
+
best_sum_radii = current_sum_radii
|
| 205 |
+
optimal_pos = np.array(candidate_pos)
|
| 206 |
+
|
| 207 |
+
if optimal_pos is not None:
|
| 208 |
+
packing_state.centers[self.index_to_place] = optimal_pos
|
| 209 |
+
else:
|
| 210 |
+
packing_state.centers[self.index_to_place] = [0.5, 0.5] # Fallback
|
| 211 |
+
|
| 212 |
+
return packing_state
|
| 213 |
+
|
| 214 |
+
class LocalSARefiner:
|
| 215 |
+
"""
|
| 216 |
+
Applies localized Simulated Annealing to fine-tune positions
|
| 217 |
+
of a target circle and its closest neighbors.
|
| 218 |
+
"""
|
| 219 |
+
def __init__(self, index_to_perturb: int, num_neighbors: int = 4, # Increased num_neighbors
|
| 220 |
+
num_iterations: int = 750, # Increased iterations
|
| 221 |
+
initial_step_size: float = 0.01, initial_temp: float = 0.01, cooling_rate: float = 0.98):
|
| 222 |
+
self.index_to_perturb = index_to_perturb
|
| 223 |
+
self.num_neighbors = num_neighbors
|
| 224 |
+
self.num_iterations = num_iterations
|
| 225 |
+
self.initial_step_size = initial_step_size
|
| 226 |
+
self.initial_temp = initial_temp
|
| 227 |
+
self.cooling_rate = cooling_rate
|
| 228 |
+
|
| 229 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 230 |
+
"""
|
| 231 |
+
Refines the position of the target circle and its neighbors using SA.
|
| 232 |
+
"""
|
| 233 |
+
current_centers = np.copy(packing_state.centers)
|
| 234 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 235 |
+
current_sum_radii = np.sum(current_radii)
|
| 236 |
+
|
| 237 |
+
best_centers_local = np.copy(current_centers)
|
| 238 |
+
best_sum_radii_local = current_sum_radii
|
| 239 |
+
|
| 240 |
+
temp = self.initial_temp
|
| 241 |
+
|
| 242 |
+
# Determine which circles to perturb: target and its closest neighbors
|
| 243 |
+
distances = np.linalg.norm(current_centers - current_centers[self.index_to_perturb], axis=1)
|
| 244 |
+
neighbor_indices = np.argsort(distances)[1:1 + self.num_neighbors] # Exclude self
|
| 245 |
+
indices_to_perturb = np.append([self.index_to_perturb], neighbor_indices)
|
| 246 |
+
|
| 247 |
+
for k in range(self.num_iterations):
|
| 248 |
+
progress = k / self.num_iterations
|
| 249 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 250 |
+
|
| 251 |
+
trial_centers = np.copy(current_centers)
|
| 252 |
+
|
| 253 |
+
for idx in indices_to_perturb:
|
| 254 |
+
# Use a slightly smaller step for neighbors to maintain overall structure integrity
|
| 255 |
+
perturb_step_size = step_size if idx == self.index_to_perturb else step_size / 2.0
|
| 256 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 257 |
+
dx, dy = perturb_step_size * np.cos(random_angle), perturb_step_size * np.sin(random_angle)
|
| 258 |
+
trial_centers[idx] += [dx, dy]
|
| 259 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0) # Enforce boundary constraints
|
| 260 |
+
|
| 261 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 262 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 263 |
+
|
| 264 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 265 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 266 |
+
current_centers = trial_centers
|
| 267 |
+
current_sum_radii = trial_sum_radii
|
| 268 |
+
|
| 269 |
+
if current_sum_radii > best_sum_radii_local:
|
| 270 |
+
best_sum_radii_local = current_sum_radii
|
| 271 |
+
best_centers_local = np.copy(current_centers)
|
| 272 |
+
|
| 273 |
+
temp *= self.cooling_rate
|
| 274 |
+
|
| 275 |
+
packing_state.update_centers(best_centers_local)
|
| 276 |
+
return packing_state
|
| 277 |
+
|
| 278 |
+
class GlobalSARefiner:
|
| 279 |
+
"""
|
| 280 |
+
Applies a global Simulated Annealing (SA) refinement phase to all circles
|
| 281 |
+
to help the entire packing relax into a potentially better overall configuration.
|
| 282 |
+
This serves as a 'gentle jiggle' to escape subtle local optima.
|
| 283 |
+
"""
|
| 284 |
+
def __init__(self, num_iterations: int = 2500, # Increased iterations
|
| 285 |
+
initial_step_size: float = 0.005,
|
| 286 |
+
initial_temp: float = 0.0007, # Slightly increased initial temp
|
| 287 |
+
cooling_rate: float = 0.997):
|
| 288 |
+
self.num_iterations = num_iterations
|
| 289 |
+
self.initial_step_size = initial_step_size
|
| 290 |
+
self.initial_temp = initial_temp
|
| 291 |
+
self.cooling_rate = cooling_rate
|
| 292 |
+
|
| 293 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 294 |
+
"""
|
| 295 |
+
Refines the positions of all circles using SA.
|
| 296 |
+
"""
|
| 297 |
+
current_centers = np.copy(packing_state.centers)
|
| 298 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 299 |
+
current_sum_radii = np.sum(current_radii)
|
| 300 |
+
|
| 301 |
+
best_centers_global = np.copy(current_centers)
|
| 302 |
+
best_sum_radii_global = current_sum_radii
|
| 303 |
+
|
| 304 |
+
temp = self.initial_temp
|
| 305 |
+
|
| 306 |
+
for k in range(self.num_iterations):
|
| 307 |
+
progress = k / self.num_iterations
|
| 308 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 309 |
+
|
| 310 |
+
trial_centers = np.copy(current_centers)
|
| 311 |
+
|
| 312 |
+
# Perturb one random circle from the entire set of 'n' circles
|
| 313 |
+
idx_to_perturb = np.random.randint(packing_state.n)
|
| 314 |
+
|
| 315 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 316 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 317 |
+
trial_centers[idx_to_perturb] += [dx, dy]
|
| 318 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0) # Enforce boundary constraints
|
| 319 |
+
|
| 320 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 321 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 322 |
+
|
| 323 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 324 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 325 |
+
current_centers = trial_centers
|
| 326 |
+
current_sum_radii = trial_sum_radii
|
| 327 |
+
|
| 328 |
+
if current_sum_radii > best_sum_radii_global:
|
| 329 |
+
best_sum_radii_global = current_sum_radii
|
| 330 |
+
best_centers_global = np.copy(current_centers)
|
| 331 |
+
|
| 332 |
+
temp *= self.cooling_rate
|
| 333 |
+
|
| 334 |
+
packing_state.update_centers(best_centers_global)
|
| 335 |
+
return packing_state
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
# --- Orchestrator (Main control flow) ---
|
| 339 |
+
class PackingOrchestrator:
|
| 340 |
+
"""
|
| 341 |
+
Orchestrates the circle packing process using a pipeline of strategies.
|
| 342 |
+
This provides a clear structural separation of concerns.
|
| 343 |
+
"""
|
| 344 |
+
def __init__(self, num_circles: int = 26):
|
| 345 |
+
if num_circles != 26:
|
| 346 |
+
raise ValueError("This orchestrator is specialized for exactly 26 circles.")
|
| 347 |
+
|
| 348 |
+
self.n = num_circles
|
| 349 |
+
self.packing_state = PackingState(num_circles)
|
| 350 |
+
self.pipeline = []
|
| 351 |
+
|
| 352 |
+
# Define the packing pipeline stages
|
| 353 |
+
self.pipeline.append(GridInitializer(n_grid_circles=25))
|
| 354 |
+
|
| 355 |
+
# New stage: pre-refine the initial 25-circle grid
|
| 356 |
+
self.pipeline.append(InitialGridRefiner(n_grid_circles=25))
|
| 357 |
+
|
| 358 |
+
if self.n > 25:
|
| 359 |
+
# Stage for placing the 26th circle with an enhanced search
|
| 360 |
+
self.pipeline.append(InterstitialSearcher(index_to_place=25, base_circles_count=25))
|
| 361 |
+
|
| 362 |
+
# Local refinement on the newly placed circle and its neighbors
|
| 363 |
+
self.pipeline.append(LocalSARefiner(index_to_perturb=25))
|
| 364 |
+
|
| 365 |
+
# Final global SA refinement for the entire packing
|
| 366 |
+
self.pipeline.append(GlobalSARefiner())
|
| 367 |
+
|
| 368 |
+
def construct_packing(self) -> tuple[np.ndarray, np.ndarray]:
|
| 369 |
+
"""
|
| 370 |
+
Executes the predefined packing pipeline, applying each strategy sequentially.
|
| 371 |
+
"""
|
| 372 |
+
np.random.seed(42) # For reproducible SA results
|
| 373 |
+
for step in self.pipeline:
|
| 374 |
+
self.packing_state = step.apply(self.packing_state)
|
| 375 |
+
|
| 376 |
+
# Final global radius calculation after all center placements/refinements are complete
|
| 377 |
+
final_radii = RadiiOptimizer.optimize_radii(self.packing_state.centers, self.n)
|
| 378 |
+
self.packing_state.update_radii(final_radii)
|
| 379 |
+
|
| 380 |
+
return self.packing_state.centers, self.packing_state.radii
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def construct_packing() -> tuple[np.ndarray, np.ndarray]:
|
| 384 |
+
"""
|
| 385 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 386 |
+
pipeline-based optimization strategy to maximize the sum of radii.
|
| 387 |
+
|
| 388 |
+
Returns:
|
| 389 |
+
Tuple of (centers, radii)
|
| 390 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 391 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 392 |
+
"""
|
| 393 |
+
orchestrator = PackingOrchestrator(num_circles=26)
|
| 394 |
+
centers, radii = orchestrator.construct_packing()
|
| 395 |
+
return centers, radii
|
| 396 |
+
# EVOLVE-BLOCK-END
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
# This part remains fixed (not evolved)
|
| 400 |
+
def run_packing():
|
| 401 |
+
"""Run the circle packing constructor for n=26"""
|
| 402 |
+
centers, radii = construct_packing()
|
| 403 |
+
# Calculate the sum of radii
|
| 404 |
+
sum_radii = np.sum(radii)
|
| 405 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/original.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# --- Data Structure ---
|
| 7 |
+
class PackingState:
|
| 8 |
+
"""Holds the current state of the circle packing (centers, radii, sum_radii)."""
|
| 9 |
+
def __init__(self, n, centers=None, radii=None):
|
| 10 |
+
self.n = n
|
| 11 |
+
self.centers = np.zeros((n, 2)) if centers is None else centers
|
| 12 |
+
self.radii = np.zeros(n) if radii is None else radii
|
| 13 |
+
self.sum_radii = 0.0 # Will be updated after radii optimization
|
| 14 |
+
|
| 15 |
+
def update_radii(self, new_radii):
|
| 16 |
+
"""Updates radii and recalculates the sum of radii."""
|
| 17 |
+
self.radii = new_radii
|
| 18 |
+
self.sum_radii = np.sum(new_radii)
|
| 19 |
+
|
| 20 |
+
def update_centers(self, new_centers):
|
| 21 |
+
"""Updates circle centers."""
|
| 22 |
+
self.centers = new_centers
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# --- Core Optimization Logic (Radii) ---
|
| 26 |
+
class RadiiOptimizer:
|
| 27 |
+
"""
|
| 28 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 29 |
+
an iterative growth and constraint resolution method.
|
| 30 |
+
Incorporates adaptive growth factor and dynamic tolerance with non-linear (exponential) decay.
|
| 31 |
+
"""
|
| 32 |
+
@staticmethod
|
| 33 |
+
def optimize_radii(centers: np.ndarray, n: int) -> np.ndarray:
|
| 34 |
+
"""
|
| 35 |
+
Calculates the maximum radii for `n` circles with given `centers`.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 39 |
+
n (int): Number of circles.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 43 |
+
"""
|
| 44 |
+
radii = np.zeros(n)
|
| 45 |
+
|
| 46 |
+
# Proven parameters from high-scoring implementations
|
| 47 |
+
growth_factor_start = 1.005
|
| 48 |
+
growth_factor_end = 1.002
|
| 49 |
+
outer_iterations = 400
|
| 50 |
+
tolerance_start = 1e-7
|
| 51 |
+
tolerance_end = 1e-11 # Tighter precision for the final stages
|
| 52 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 53 |
+
|
| 54 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 55 |
+
for i in range(n):
|
| 56 |
+
x, y = centers[i]
|
| 57 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 58 |
+
|
| 59 |
+
for k in range(outer_iterations):
|
| 60 |
+
interp_factor = k / (outer_iterations - 1.0 + 1e-9) # Avoid division by zero
|
| 61 |
+
|
| 62 |
+
# Non-linear (exponential) interpolation for smooth parameter transition
|
| 63 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**interp_factor
|
| 64 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**interp_factor
|
| 65 |
+
|
| 66 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 67 |
+
|
| 68 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 69 |
+
constraints_changed = False
|
| 70 |
+
|
| 71 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 72 |
+
for i in range(n):
|
| 73 |
+
x, y = centers[i]
|
| 74 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 75 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 76 |
+
radii[i] = boundary_limit
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 80 |
+
for i in range(n):
|
| 81 |
+
for j in range(i + 1, n):
|
| 82 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 83 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 84 |
+
total_radius = radii[i] + radii[j]
|
| 85 |
+
if total_radius > 1e-12: # Avoid division by zero for extremely small radii
|
| 86 |
+
scale = dist / total_radius
|
| 87 |
+
radii[i] *= scale
|
| 88 |
+
radii[j] *= scale
|
| 89 |
+
constraints_changed = True
|
| 90 |
+
|
| 91 |
+
if not constraints_changed:
|
| 92 |
+
break # Inner loop converged
|
| 93 |
+
|
| 94 |
+
return radii
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# --- Placement Strategies (Components of the pipeline) ---
|
| 98 |
+
class GridInitializer:
|
| 99 |
+
"""Places the first N_GRID_CIRCLES in a symmetric grid pattern."""
|
| 100 |
+
def __init__(self, n_grid_circles=25):
|
| 101 |
+
self.n_grid_circles = n_grid_circles
|
| 102 |
+
|
| 103 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 104 |
+
"""Applies the initial grid placement to the packing state."""
|
| 105 |
+
# Calculate grid size (e.g., 5 for 25 circles)
|
| 106 |
+
grid_dim = int(np.sqrt(self.n_grid_circles))
|
| 107 |
+
coords = np.linspace(0.1, 0.9, grid_dim)
|
| 108 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 109 |
+
packing_state.centers[:self.n_grid_circles] = grid_centers
|
| 110 |
+
return packing_state
|
| 111 |
+
|
| 112 |
+
class InitialGridRefiner:
|
| 113 |
+
"""
|
| 114 |
+
Applies a gentle, local SA refinement to the initial N_GRID_CIRCLES
|
| 115 |
+
to break the initial grid rigidity and find a better base packing.
|
| 116 |
+
"""
|
| 117 |
+
def __init__(self, n_grid_circles=25, num_iterations=500, initial_step_size=0.005,
|
| 118 |
+
initial_temp=0.005, cooling_rate=0.995):
|
| 119 |
+
self.n_grid_circles = n_grid_circles
|
| 120 |
+
self.num_iterations = num_iterations
|
| 121 |
+
self.initial_step_size = initial_step_size
|
| 122 |
+
self.initial_temp = initial_temp
|
| 123 |
+
self.cooling_rate = cooling_rate
|
| 124 |
+
|
| 125 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 126 |
+
"""Applies SA-based perturbation and refinement to the base circles."""
|
| 127 |
+
if self.n_grid_circles > packing_state.n:
|
| 128 |
+
return packing_state
|
| 129 |
+
|
| 130 |
+
current_centers_subset = np.copy(packing_state.centers[:self.n_grid_circles])
|
| 131 |
+
current_radii_subset = RadiiOptimizer.optimize_radii(current_centers_subset, self.n_grid_circles)
|
| 132 |
+
current_sum_radii_subset = np.sum(current_radii_subset)
|
| 133 |
+
|
| 134 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 135 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 136 |
+
|
| 137 |
+
temp = self.initial_temp
|
| 138 |
+
|
| 139 |
+
for k in range(self.num_iterations):
|
| 140 |
+
progress = k / self.num_iterations
|
| 141 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 142 |
+
|
| 143 |
+
# Perturb ONE random circle from the subset
|
| 144 |
+
idx_to_perturb = np.random.randint(self.n_grid_circles)
|
| 145 |
+
trial_centers_subset = np.copy(current_centers_subset)
|
| 146 |
+
|
| 147 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 148 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 149 |
+
trial_centers_subset[idx_to_perturb] += [dx, dy]
|
| 150 |
+
trial_centers_subset[idx_to_perturb] = np.clip(trial_centers_subset[idx_to_perturb], 0.0, 1.0)
|
| 151 |
+
|
| 152 |
+
# Evaluate the new configuration
|
| 153 |
+
trial_radii_subset = RadiiOptimizer.optimize_radii(trial_centers_subset, self.n_grid_circles)
|
| 154 |
+
trial_sum_radii_subset = np.sum(trial_radii_subset)
|
| 155 |
+
|
| 156 |
+
# Metropolis-Hastings acceptance criterion
|
| 157 |
+
delta_energy = trial_sum_radii_subset - current_sum_radii_subset
|
| 158 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 159 |
+
current_centers_subset = trial_centers_subset
|
| 160 |
+
current_sum_radii_subset = trial_sum_radii_subset
|
| 161 |
+
|
| 162 |
+
if current_sum_radii_subset > best_sum_radii_subset_local:
|
| 163 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 164 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 165 |
+
|
| 166 |
+
temp *= self.cooling_rate
|
| 167 |
+
|
| 168 |
+
packing_state.centers[:self.n_grid_circles] = best_centers_subset_local
|
| 169 |
+
return packing_state
|
| 170 |
+
|
| 171 |
+
class InterstitialSearcher:
|
| 172 |
+
"""
|
| 173 |
+
Finds the best initial position for an additional circle (e.g., the 26th)
|
| 174 |
+
by exhaustive search over candidate points.
|
| 175 |
+
"""
|
| 176 |
+
def __init__(self, index_to_place: int, base_circles_count: int = 25, grid_resolution: int = 9):
|
| 177 |
+
self.index_to_place = index_to_place
|
| 178 |
+
self.base_circles_count = base_circles_count
|
| 179 |
+
self.grid_resolution = grid_resolution # e.g., 9x9 for 81 candidates
|
| 180 |
+
|
| 181 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 182 |
+
"""
|
| 183 |
+
Searches for the optimal position for the circle at `index_to_place`.
|
| 184 |
+
"""
|
| 185 |
+
if self.index_to_place >= packing_state.n:
|
| 186 |
+
return packing_state
|
| 187 |
+
|
| 188 |
+
base_centers = np.copy(packing_state.centers[:self.base_circles_count])
|
| 189 |
+
|
| 190 |
+
# Increased granularity and broader range for interstitial search (Recommendation 1)
|
| 191 |
+
interstitial_coords = np.linspace(0.1, 0.9, self.grid_resolution) # e.g., 9 points from 0.1 to 0.9
|
| 192 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 193 |
+
|
| 194 |
+
best_sum_radii = -1.0
|
| 195 |
+
optimal_pos = None
|
| 196 |
+
|
| 197 |
+
for candidate_pos in candidate_points:
|
| 198 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 199 |
+
# Use RadiiOptimizer for evaluation
|
| 200 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, self.base_circles_count + 1)
|
| 201 |
+
current_sum_radii = np.sum(trial_radii)
|
| 202 |
+
|
| 203 |
+
if current_sum_radii > best_sum_radii:
|
| 204 |
+
best_sum_radii = current_sum_radii
|
| 205 |
+
optimal_pos = np.array(candidate_pos)
|
| 206 |
+
|
| 207 |
+
if optimal_pos is not None:
|
| 208 |
+
packing_state.centers[self.index_to_place] = optimal_pos
|
| 209 |
+
else:
|
| 210 |
+
packing_state.centers[self.index_to_place] = [0.5, 0.5] # Fallback
|
| 211 |
+
|
| 212 |
+
return packing_state
|
| 213 |
+
|
| 214 |
+
class LocalSARefiner:
|
| 215 |
+
"""
|
| 216 |
+
Applies localized Simulated Annealing to fine-tune positions
|
| 217 |
+
of a target circle and its closest neighbors.
|
| 218 |
+
"""
|
| 219 |
+
def __init__(self, index_to_perturb: int, num_neighbors: int = 3, num_iterations: int = 500,
|
| 220 |
+
initial_step_size: float = 0.01, initial_temp: float = 0.01, cooling_rate: float = 0.98):
|
| 221 |
+
self.index_to_perturb = index_to_perturb
|
| 222 |
+
self.num_neighbors = num_neighbors
|
| 223 |
+
self.num_iterations = num_iterations
|
| 224 |
+
self.initial_step_size = initial_step_size
|
| 225 |
+
self.initial_temp = initial_temp
|
| 226 |
+
self.cooling_rate = cooling_rate
|
| 227 |
+
|
| 228 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 229 |
+
"""
|
| 230 |
+
Refines the position of the target circle and its neighbors using SA.
|
| 231 |
+
"""
|
| 232 |
+
current_centers = np.copy(packing_state.centers)
|
| 233 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 234 |
+
current_sum_radii = np.sum(current_radii)
|
| 235 |
+
|
| 236 |
+
best_centers_local = np.copy(current_centers)
|
| 237 |
+
best_sum_radii_local = current_sum_radii
|
| 238 |
+
|
| 239 |
+
temp = self.initial_temp
|
| 240 |
+
|
| 241 |
+
# Determine which circles to perturb: target and its closest neighbors
|
| 242 |
+
distances = np.linalg.norm(current_centers - current_centers[self.index_to_perturb], axis=1)
|
| 243 |
+
neighbor_indices = np.argsort(distances)[1:1 + self.num_neighbors] # Exclude self
|
| 244 |
+
indices_to_perturb = np.append([self.index_to_perturb], neighbor_indices)
|
| 245 |
+
|
| 246 |
+
for k in range(self.num_iterations):
|
| 247 |
+
progress = k / self.num_iterations
|
| 248 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 249 |
+
|
| 250 |
+
trial_centers = np.copy(current_centers)
|
| 251 |
+
|
| 252 |
+
for idx in indices_to_perturb:
|
| 253 |
+
# Use a slightly smaller step for neighbors to maintain overall structure integrity
|
| 254 |
+
perturb_step_size = step_size if idx == self.index_to_perturb else step_size / 2.0
|
| 255 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 256 |
+
dx, dy = perturb_step_size * np.cos(random_angle), perturb_step_size * np.sin(random_angle)
|
| 257 |
+
trial_centers[idx] += [dx, dy]
|
| 258 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0) # Enforce boundary constraints
|
| 259 |
+
|
| 260 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 261 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 262 |
+
|
| 263 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 264 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 265 |
+
current_centers = trial_centers
|
| 266 |
+
current_sum_radii = trial_sum_radii
|
| 267 |
+
|
| 268 |
+
if current_sum_radii > best_sum_radii_local:
|
| 269 |
+
best_sum_radii_local = current_sum_radii
|
| 270 |
+
best_centers_local = np.copy(current_centers)
|
| 271 |
+
|
| 272 |
+
temp *= self.cooling_rate
|
| 273 |
+
|
| 274 |
+
packing_state.update_centers(best_centers_local)
|
| 275 |
+
return packing_state
|
| 276 |
+
|
| 277 |
+
class GlobalSARefiner:
|
| 278 |
+
"""
|
| 279 |
+
Applies a global Simulated Annealing (SA) refinement phase to all circles
|
| 280 |
+
to help the entire packing relax into a potentially better overall configuration.
|
| 281 |
+
This serves as a 'gentle jiggle' to escape subtle local optima.
|
| 282 |
+
"""
|
| 283 |
+
def __init__(self, num_iterations: int = 2000, initial_step_size: float = 0.005,
|
| 284 |
+
initial_temp: float = 0.0005, cooling_rate: float = 0.997):
|
| 285 |
+
self.num_iterations = num_iterations
|
| 286 |
+
self.initial_step_size = initial_step_size
|
| 287 |
+
self.initial_temp = initial_temp
|
| 288 |
+
self.cooling_rate = cooling_rate
|
| 289 |
+
|
| 290 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 291 |
+
"""
|
| 292 |
+
Refines the positions of all circles using SA.
|
| 293 |
+
"""
|
| 294 |
+
current_centers = np.copy(packing_state.centers)
|
| 295 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 296 |
+
current_sum_radii = np.sum(current_radii)
|
| 297 |
+
|
| 298 |
+
best_centers_global = np.copy(current_centers)
|
| 299 |
+
best_sum_radii_global = current_sum_radii
|
| 300 |
+
|
| 301 |
+
temp = self.initial_temp
|
| 302 |
+
|
| 303 |
+
for k in range(self.num_iterations):
|
| 304 |
+
progress = k / self.num_iterations
|
| 305 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 306 |
+
|
| 307 |
+
trial_centers = np.copy(current_centers)
|
| 308 |
+
|
| 309 |
+
# Perturb one random circle from the entire set of 'n' circles
|
| 310 |
+
idx_to_perturb = np.random.randint(packing_state.n)
|
| 311 |
+
|
| 312 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 313 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 314 |
+
trial_centers[idx_to_perturb] += [dx, dy]
|
| 315 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0) # Enforce boundary constraints
|
| 316 |
+
|
| 317 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 318 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 319 |
+
|
| 320 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 321 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 322 |
+
current_centers = trial_centers
|
| 323 |
+
current_sum_radii = trial_sum_radii
|
| 324 |
+
|
| 325 |
+
if current_sum_radii > best_sum_radii_global:
|
| 326 |
+
best_sum_radii_global = current_sum_radii
|
| 327 |
+
best_centers_global = np.copy(current_centers)
|
| 328 |
+
|
| 329 |
+
temp *= self.cooling_rate
|
| 330 |
+
|
| 331 |
+
packing_state.update_centers(best_centers_global)
|
| 332 |
+
return packing_state
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
# --- Orchestrator (Main control flow) ---
|
| 336 |
+
class PackingOrchestrator:
|
| 337 |
+
"""
|
| 338 |
+
Orchestrates the circle packing process using a pipeline of strategies.
|
| 339 |
+
This provides a clear structural separation of concerns.
|
| 340 |
+
"""
|
| 341 |
+
def __init__(self, num_circles: int = 26):
|
| 342 |
+
if num_circles != 26:
|
| 343 |
+
raise ValueError("This orchestrator is specialized for exactly 26 circles.")
|
| 344 |
+
|
| 345 |
+
self.n = num_circles
|
| 346 |
+
self.packing_state = PackingState(num_circles)
|
| 347 |
+
self.pipeline = []
|
| 348 |
+
|
| 349 |
+
# Define the packing pipeline stages
|
| 350 |
+
self.pipeline.append(GridInitializer(n_grid_circles=25))
|
| 351 |
+
|
| 352 |
+
# New stage: pre-refine the initial 25-circle grid
|
| 353 |
+
self.pipeline.append(InitialGridRefiner(n_grid_circles=25))
|
| 354 |
+
|
| 355 |
+
if self.n > 25:
|
| 356 |
+
# Stage for placing the 26th circle with an enhanced search
|
| 357 |
+
self.pipeline.append(InterstitialSearcher(index_to_place=25, base_circles_count=25, grid_resolution=9))
|
| 358 |
+
|
| 359 |
+
# Local refinement on the newly placed circle and its neighbors
|
| 360 |
+
self.pipeline.append(LocalSARefiner(index_to_perturb=25, num_iterations=500))
|
| 361 |
+
|
| 362 |
+
# Final global SA refinement for the entire packing
|
| 363 |
+
self.pipeline.append(GlobalSARefiner(num_iterations=2000))
|
| 364 |
+
|
| 365 |
+
def construct_packing(self) -> tuple[np.ndarray, np.ndarray]:
|
| 366 |
+
"""
|
| 367 |
+
Executes the predefined packing pipeline, applying each strategy sequentially.
|
| 368 |
+
"""
|
| 369 |
+
np.random.seed(42) # For reproducible SA results
|
| 370 |
+
for step in self.pipeline:
|
| 371 |
+
self.packing_state = step.apply(self.packing_state)
|
| 372 |
+
|
| 373 |
+
# Final global radius calculation after all center placements/refinements are complete
|
| 374 |
+
final_radii = RadiiOptimizer.optimize_radii(self.packing_state.centers, self.n)
|
| 375 |
+
self.packing_state.update_radii(final_radii)
|
| 376 |
+
|
| 377 |
+
return self.packing_state.centers, self.packing_state.radii
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def construct_packing() -> tuple[np.ndarray, np.ndarray]:
|
| 381 |
+
"""
|
| 382 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 383 |
+
pipeline-based optimization strategy to maximize the sum of radii.
|
| 384 |
+
|
| 385 |
+
Returns:
|
| 386 |
+
Tuple of (centers, radii)
|
| 387 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 388 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 389 |
+
"""
|
| 390 |
+
orchestrator = PackingOrchestrator(num_circles=26)
|
| 391 |
+
centers, radii = orchestrator.construct_packing()
|
| 392 |
+
return centers, radii
|
| 393 |
+
# EVOLVE-BLOCK-END
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# This part remains fixed (not evolved)
|
| 397 |
+
def run_packing():
|
| 398 |
+
"""Run the circle packing constructor for n=26"""
|
| 399 |
+
centers, radii = construct_packing()
|
| 400 |
+
# Calculate the sum of radii
|
| 401 |
+
sum_radii = np.sum(radii)
|
| 402 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_105/rewrite.txt
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# --- Data Structure ---
|
| 7 |
+
class PackingState:
|
| 8 |
+
"""Holds the current state of the circle packing (centers, radii, sum_radii)."""
|
| 9 |
+
def __init__(self, n, centers=None, radii=None):
|
| 10 |
+
self.n = n
|
| 11 |
+
self.centers = np.zeros((n, 2)) if centers is None else centers
|
| 12 |
+
self.radii = np.zeros(n) if radii is None else radii
|
| 13 |
+
self.sum_radii = 0.0 # Will be updated after radii optimization
|
| 14 |
+
|
| 15 |
+
def update_radii(self, new_radii):
|
| 16 |
+
"""Updates radii and recalculates the sum of radii."""
|
| 17 |
+
self.radii = new_radii
|
| 18 |
+
self.sum_radii = np.sum(new_radii)
|
| 19 |
+
|
| 20 |
+
def update_centers(self, new_centers):
|
| 21 |
+
"""Updates circle centers."""
|
| 22 |
+
self.centers = new_centers
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# --- Core Optimization Logic (Radii) ---
|
| 26 |
+
class RadiiOptimizer:
|
| 27 |
+
"""
|
| 28 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 29 |
+
an iterative growth and constraint resolution method.
|
| 30 |
+
Incorporates adaptive growth factor and dynamic tolerance with non-linear (exponential) decay.
|
| 31 |
+
"""
|
| 32 |
+
@staticmethod
|
| 33 |
+
def optimize_radii(centers: np.ndarray, n: int) -> np.ndarray:
|
| 34 |
+
"""
|
| 35 |
+
Calculates the maximum radii for `n` circles with given `centers`.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 39 |
+
n (int): Number of circles.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 43 |
+
"""
|
| 44 |
+
radii = np.zeros(n)
|
| 45 |
+
|
| 46 |
+
# Proven parameters from high-scoring implementations
|
| 47 |
+
growth_factor_start = 1.005
|
| 48 |
+
growth_factor_end = 1.002
|
| 49 |
+
outer_iterations = 400
|
| 50 |
+
tolerance_start = 1e-7
|
| 51 |
+
tolerance_end = 1e-11 # Tighter precision for the final stages
|
| 52 |
+
inner_iterations = 20 # Increased for more robust constraint satisfaction
|
| 53 |
+
|
| 54 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 55 |
+
for i in range(n):
|
| 56 |
+
x, y = centers[i]
|
| 57 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 58 |
+
|
| 59 |
+
for k in range(outer_iterations):
|
| 60 |
+
interp_factor = k / (outer_iterations - 1.0 + 1e-9) # Avoid division by zero
|
| 61 |
+
|
| 62 |
+
# Non-linear (exponential) interpolation for smooth parameter transition
|
| 63 |
+
current_growth_factor = growth_factor_start * (growth_factor_end / growth_factor_start)**interp_factor
|
| 64 |
+
current_tolerance = tolerance_start * (tolerance_end / tolerance_start)**interp_factor
|
| 65 |
+
|
| 66 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 67 |
+
|
| 68 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 69 |
+
constraints_changed = False
|
| 70 |
+
|
| 71 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 72 |
+
for i in range(n):
|
| 73 |
+
x, y = centers[i]
|
| 74 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 75 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 76 |
+
radii[i] = boundary_limit
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 80 |
+
for i in range(n):
|
| 81 |
+
for j in range(i + 1, n):
|
| 82 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 83 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 84 |
+
total_radius = radii[i] + radii[j]
|
| 85 |
+
if total_radius > 1e-12: # Avoid division by zero for extremely small radii
|
| 86 |
+
scale = dist / total_radius
|
| 87 |
+
radii[i] *= scale
|
| 88 |
+
radii[j] *= scale
|
| 89 |
+
constraints_changed = True
|
| 90 |
+
|
| 91 |
+
if not constraints_changed:
|
| 92 |
+
break # Inner loop converged
|
| 93 |
+
|
| 94 |
+
return radii
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# --- Placement Strategies (Components of the pipeline) ---
|
| 98 |
+
class GridInitializer:
|
| 99 |
+
"""Places the first N_GRID_CIRCLES in a symmetric grid pattern."""
|
| 100 |
+
def __init__(self, n_grid_circles=25):
|
| 101 |
+
self.n_grid_circles = n_grid_circles
|
| 102 |
+
|
| 103 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 104 |
+
"""Applies the initial grid placement to the packing state."""
|
| 105 |
+
# Calculate grid size (e.g., 5 for 25 circles)
|
| 106 |
+
grid_dim = int(np.sqrt(self.n_grid_circles))
|
| 107 |
+
coords = np.linspace(0.1, 0.9, grid_dim)
|
| 108 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 109 |
+
packing_state.centers[:self.n_grid_circles] = grid_centers
|
| 110 |
+
return packing_state
|
| 111 |
+
|
| 112 |
+
class InitialGridRefiner:
|
| 113 |
+
"""
|
| 114 |
+
Applies a gentle, local SA refinement to the initial N_GRID_CIRCLES
|
| 115 |
+
to break the initial grid rigidity and find a better base packing.
|
| 116 |
+
"""
|
| 117 |
+
def __init__(self, n_grid_circles=25, num_iterations=750, # Increased iterations
|
| 118 |
+
initial_step_size=0.005, initial_temp=0.005, cooling_rate=0.995):
|
| 119 |
+
self.n_grid_circles = n_grid_circles
|
| 120 |
+
self.num_iterations = num_iterations
|
| 121 |
+
self.initial_step_size = initial_step_size
|
| 122 |
+
self.initial_temp = initial_temp
|
| 123 |
+
self.cooling_rate = cooling_rate
|
| 124 |
+
|
| 125 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 126 |
+
"""Applies SA-based perturbation and refinement to the base circles."""
|
| 127 |
+
if self.n_grid_circles > packing_state.n:
|
| 128 |
+
return packing_state
|
| 129 |
+
|
| 130 |
+
current_centers_subset = np.copy(packing_state.centers[:self.n_grid_circles])
|
| 131 |
+
current_radii_subset = RadiiOptimizer.optimize_radii(current_centers_subset, self.n_grid_circles)
|
| 132 |
+
current_sum_radii_subset = np.sum(current_radii_subset)
|
| 133 |
+
|
| 134 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 135 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 136 |
+
|
| 137 |
+
temp = self.initial_temp
|
| 138 |
+
|
| 139 |
+
for k in range(self.num_iterations):
|
| 140 |
+
progress = k / self.num_iterations
|
| 141 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 142 |
+
|
| 143 |
+
# Perturb ONE random circle from the subset
|
| 144 |
+
idx_to_perturb = np.random.randint(self.n_grid_circles)
|
| 145 |
+
trial_centers_subset = np.copy(current_centers_subset)
|
| 146 |
+
|
| 147 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 148 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 149 |
+
trial_centers_subset[idx_to_perturb] += [dx, dy]
|
| 150 |
+
trial_centers_subset[idx_to_perturb] = np.clip(trial_centers_subset[idx_to_perturb], 0.0, 1.0)
|
| 151 |
+
|
| 152 |
+
# Evaluate the new configuration
|
| 153 |
+
trial_radii_subset = RadiiOptimizer.optimize_radii(trial_centers_subset, self.n_grid_circles)
|
| 154 |
+
trial_sum_radii_subset = np.sum(trial_radii_subset)
|
| 155 |
+
|
| 156 |
+
# Metropolis-Hastings acceptance criterion
|
| 157 |
+
delta_energy = trial_sum_radii_subset - current_sum_radii_subset
|
| 158 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 159 |
+
current_centers_subset = trial_centers_subset
|
| 160 |
+
current_sum_radii_subset = trial_sum_radii_subset
|
| 161 |
+
|
| 162 |
+
if current_sum_radii_subset > best_sum_radii_subset_local:
|
| 163 |
+
best_sum_radii_subset_local = current_sum_radii_subset
|
| 164 |
+
best_centers_subset_local = np.copy(current_centers_subset)
|
| 165 |
+
|
| 166 |
+
temp *= self.cooling_rate
|
| 167 |
+
|
| 168 |
+
packing_state.centers[:self.n_grid_circles] = best_centers_subset_local
|
| 169 |
+
return packing_state
|
| 170 |
+
|
| 171 |
+
class InterstitialSearcher:
|
| 172 |
+
"""
|
| 173 |
+
Finds the best initial position for an additional circle (e.g., the 26th)
|
| 174 |
+
by exhaustive search over candidate points.
|
| 175 |
+
"""
|
| 176 |
+
def __init__(self, index_to_place: int, base_circles_count: int = 25, grid_resolution: int = 12): # Increased grid_resolution
|
| 177 |
+
self.index_to_place = index_to_place
|
| 178 |
+
self.base_circles_count = base_circles_count
|
| 179 |
+
self.grid_resolution = grid_resolution # e.g., 12x12 for 144 candidates
|
| 180 |
+
|
| 181 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 182 |
+
"""
|
| 183 |
+
Searches for the optimal position for the circle at `index_to_place`.
|
| 184 |
+
"""
|
| 185 |
+
if self.index_to_place >= packing_state.n:
|
| 186 |
+
return packing_state
|
| 187 |
+
|
| 188 |
+
base_centers = np.copy(packing_state.centers[:self.base_circles_count])
|
| 189 |
+
|
| 190 |
+
# Increased granularity and broader range for interstitial search (Recommendation 1)
|
| 191 |
+
interstitial_coords = np.linspace(0.1, 0.9, self.grid_resolution)
|
| 192 |
+
candidate_points = list(product(interstitial_coords, interstitial_coords))
|
| 193 |
+
|
| 194 |
+
best_sum_radii = -1.0
|
| 195 |
+
optimal_pos = None
|
| 196 |
+
|
| 197 |
+
for candidate_pos in candidate_points:
|
| 198 |
+
trial_centers = np.vstack([base_centers, np.clip(candidate_pos, 0.0, 1.0)])
|
| 199 |
+
# Use RadiiOptimizer for evaluation
|
| 200 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, self.base_circles_count + 1)
|
| 201 |
+
current_sum_radii = np.sum(trial_radii)
|
| 202 |
+
|
| 203 |
+
if current_sum_radii > best_sum_radii:
|
| 204 |
+
best_sum_radii = current_sum_radii
|
| 205 |
+
optimal_pos = np.array(candidate_pos)
|
| 206 |
+
|
| 207 |
+
if optimal_pos is not None:
|
| 208 |
+
packing_state.centers[self.index_to_place] = optimal_pos
|
| 209 |
+
else:
|
| 210 |
+
packing_state.centers[self.index_to_place] = [0.5, 0.5] # Fallback
|
| 211 |
+
|
| 212 |
+
return packing_state
|
| 213 |
+
|
| 214 |
+
class LocalSARefiner:
|
| 215 |
+
"""
|
| 216 |
+
Applies localized Simulated Annealing to fine-tune positions
|
| 217 |
+
of a target circle and its closest neighbors.
|
| 218 |
+
"""
|
| 219 |
+
def __init__(self, index_to_perturb: int, num_neighbors: int = 4, # Increased num_neighbors
|
| 220 |
+
num_iterations: int = 750, # Increased iterations
|
| 221 |
+
initial_step_size: float = 0.01, initial_temp: float = 0.01, cooling_rate: float = 0.98):
|
| 222 |
+
self.index_to_perturb = index_to_perturb
|
| 223 |
+
self.num_neighbors = num_neighbors
|
| 224 |
+
self.num_iterations = num_iterations
|
| 225 |
+
self.initial_step_size = initial_step_size
|
| 226 |
+
self.initial_temp = initial_temp
|
| 227 |
+
self.cooling_rate = cooling_rate
|
| 228 |
+
|
| 229 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 230 |
+
"""
|
| 231 |
+
Refines the position of the target circle and its neighbors using SA.
|
| 232 |
+
"""
|
| 233 |
+
current_centers = np.copy(packing_state.centers)
|
| 234 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 235 |
+
current_sum_radii = np.sum(current_radii)
|
| 236 |
+
|
| 237 |
+
best_centers_local = np.copy(current_centers)
|
| 238 |
+
best_sum_radii_local = current_sum_radii
|
| 239 |
+
|
| 240 |
+
temp = self.initial_temp
|
| 241 |
+
|
| 242 |
+
# Determine which circles to perturb: target and its closest neighbors
|
| 243 |
+
distances = np.linalg.norm(current_centers - current_centers[self.index_to_perturb], axis=1)
|
| 244 |
+
neighbor_indices = np.argsort(distances)[1:1 + self.num_neighbors] # Exclude self
|
| 245 |
+
indices_to_perturb = np.append([self.index_to_perturb], neighbor_indices)
|
| 246 |
+
|
| 247 |
+
for k in range(self.num_iterations):
|
| 248 |
+
progress = k / self.num_iterations
|
| 249 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 250 |
+
|
| 251 |
+
trial_centers = np.copy(current_centers)
|
| 252 |
+
|
| 253 |
+
for idx in indices_to_perturb:
|
| 254 |
+
# Use a slightly smaller step for neighbors to maintain overall structure integrity
|
| 255 |
+
perturb_step_size = step_size if idx == self.index_to_perturb else step_size / 2.0
|
| 256 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 257 |
+
dx, dy = perturb_step_size * np.cos(random_angle), perturb_step_size * np.sin(random_angle)
|
| 258 |
+
trial_centers[idx] += [dx, dy]
|
| 259 |
+
trial_centers[idx] = np.clip(trial_centers[idx], 0.0, 1.0) # Enforce boundary constraints
|
| 260 |
+
|
| 261 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 262 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 263 |
+
|
| 264 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 265 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 266 |
+
current_centers = trial_centers
|
| 267 |
+
current_sum_radii = trial_sum_radii
|
| 268 |
+
|
| 269 |
+
if current_sum_radii > best_sum_radii_local:
|
| 270 |
+
best_sum_radii_local = current_sum_radii
|
| 271 |
+
best_centers_local = np.copy(current_centers)
|
| 272 |
+
|
| 273 |
+
temp *= self.cooling_rate
|
| 274 |
+
|
| 275 |
+
packing_state.update_centers(best_centers_local)
|
| 276 |
+
return packing_state
|
| 277 |
+
|
| 278 |
+
class GlobalSARefiner:
|
| 279 |
+
"""
|
| 280 |
+
Applies a global Simulated Annealing (SA) refinement phase to all circles
|
| 281 |
+
to help the entire packing relax into a potentially better overall configuration.
|
| 282 |
+
This serves as a 'gentle jiggle' to escape subtle local optima.
|
| 283 |
+
"""
|
| 284 |
+
def __init__(self, num_iterations: int = 2500, # Increased iterations
|
| 285 |
+
initial_step_size: float = 0.005,
|
| 286 |
+
initial_temp: float = 0.0007, # Slightly increased initial temp
|
| 287 |
+
cooling_rate: float = 0.997):
|
| 288 |
+
self.num_iterations = num_iterations
|
| 289 |
+
self.initial_step_size = initial_step_size
|
| 290 |
+
self.initial_temp = initial_temp
|
| 291 |
+
self.cooling_rate = cooling_rate
|
| 292 |
+
|
| 293 |
+
def apply(self, packing_state: PackingState) -> PackingState:
|
| 294 |
+
"""
|
| 295 |
+
Refines the positions of all circles using SA.
|
| 296 |
+
"""
|
| 297 |
+
current_centers = np.copy(packing_state.centers)
|
| 298 |
+
current_radii = RadiiOptimizer.optimize_radii(current_centers, packing_state.n)
|
| 299 |
+
current_sum_radii = np.sum(current_radii)
|
| 300 |
+
|
| 301 |
+
best_centers_global = np.copy(current_centers)
|
| 302 |
+
best_sum_radii_global = current_sum_radii
|
| 303 |
+
|
| 304 |
+
temp = self.initial_temp
|
| 305 |
+
|
| 306 |
+
for k in range(self.num_iterations):
|
| 307 |
+
progress = k / self.num_iterations
|
| 308 |
+
step_size = self.initial_step_size * (1.0 - progress) # Linear decay of step size
|
| 309 |
+
|
| 310 |
+
trial_centers = np.copy(current_centers)
|
| 311 |
+
|
| 312 |
+
# Perturb one random circle from the entire set of 'n' circles
|
| 313 |
+
idx_to_perturb = np.random.randint(packing_state.n)
|
| 314 |
+
|
| 315 |
+
random_angle = np.random.uniform(0, 2 * np.pi)
|
| 316 |
+
dx, dy = step_size * np.cos(random_angle), step_size * np.sin(random_angle)
|
| 317 |
+
trial_centers[idx_to_perturb] += [dx, dy]
|
| 318 |
+
trial_centers[idx_to_perturb] = np.clip(trial_centers[idx_to_perturb], 0.0, 1.0) # Enforce boundary constraints
|
| 319 |
+
|
| 320 |
+
trial_radii = RadiiOptimizer.optimize_radii(trial_centers, packing_state.n)
|
| 321 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 322 |
+
|
| 323 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 324 |
+
if delta_energy > 0 or (temp > 0 and np.random.random() < np.exp(delta_energy / temp)):
|
| 325 |
+
current_centers = trial_centers
|
| 326 |
+
current_sum_radii = trial_sum_radii
|
| 327 |
+
|
| 328 |
+
if current_sum_radii > best_sum_radii_global:
|
| 329 |
+
best_sum_radii_global = current_sum_radii
|
| 330 |
+
best_centers_global = np.copy(current_centers)
|
| 331 |
+
|
| 332 |
+
temp *= self.cooling_rate
|
| 333 |
+
|
| 334 |
+
packing_state.update_centers(best_centers_global)
|
| 335 |
+
return packing_state
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
# --- Orchestrator (Main control flow) ---
|
| 339 |
+
class PackingOrchestrator:
|
| 340 |
+
"""
|
| 341 |
+
Orchestrates the circle packing process using a pipeline of strategies.
|
| 342 |
+
This provides a clear structural separation of concerns.
|
| 343 |
+
"""
|
| 344 |
+
def __init__(self, num_circles: int = 26):
|
| 345 |
+
if num_circles != 26:
|
| 346 |
+
raise ValueError("This orchestrator is specialized for exactly 26 circles.")
|
| 347 |
+
|
| 348 |
+
self.n = num_circles
|
| 349 |
+
self.packing_state = PackingState(num_circles)
|
| 350 |
+
self.pipeline = []
|
| 351 |
+
|
| 352 |
+
# Define the packing pipeline stages
|
| 353 |
+
self.pipeline.append(GridInitializer(n_grid_circles=25))
|
| 354 |
+
|
| 355 |
+
# New stage: pre-refine the initial 25-circle grid
|
| 356 |
+
self.pipeline.append(InitialGridRefiner(n_grid_circles=25))
|
| 357 |
+
|
| 358 |
+
if self.n > 25:
|
| 359 |
+
# Stage for placing the 26th circle with an enhanced search
|
| 360 |
+
self.pipeline.append(InterstitialSearcher(index_to_place=25, base_circles_count=25))
|
| 361 |
+
|
| 362 |
+
# Local refinement on the newly placed circle and its neighbors
|
| 363 |
+
self.pipeline.append(LocalSARefiner(index_to_perturb=25))
|
| 364 |
+
|
| 365 |
+
# Final global SA refinement for the entire packing
|
| 366 |
+
self.pipeline.append(GlobalSARefiner())
|
| 367 |
+
|
| 368 |
+
def construct_packing(self) -> tuple[np.ndarray, np.ndarray]:
|
| 369 |
+
"""
|
| 370 |
+
Executes the predefined packing pipeline, applying each strategy sequentially.
|
| 371 |
+
"""
|
| 372 |
+
np.random.seed(42) # For reproducible SA results
|
| 373 |
+
for step in self.pipeline:
|
| 374 |
+
self.packing_state = step.apply(self.packing_state)
|
| 375 |
+
|
| 376 |
+
# Final global radius calculation after all center placements/refinements are complete
|
| 377 |
+
final_radii = RadiiOptimizer.optimize_radii(self.packing_state.centers, self.n)
|
| 378 |
+
self.packing_state.update_radii(final_radii)
|
| 379 |
+
|
| 380 |
+
return self.packing_state.centers, self.packing_state.radii
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def construct_packing() -> tuple[np.ndarray, np.ndarray]:
|
| 384 |
+
"""
|
| 385 |
+
Constructs an arrangement of 26 circles by leveraging a modular,
|
| 386 |
+
pipeline-based optimization strategy to maximize the sum of radii.
|
| 387 |
+
|
| 388 |
+
Returns:
|
| 389 |
+
Tuple of (centers, radii)
|
| 390 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 391 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 392 |
+
"""
|
| 393 |
+
orchestrator = PackingOrchestrator(num_circles=26)
|
| 394 |
+
centers, radii = orchestrator.construct_packing()
|
| 395 |
+
return centers, radii
|
| 396 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/edit.diff
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,356 +1,376 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from itertools import product
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CirclePacker:
|
| 10 |
+
"""
|
| 11 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 12 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 13 |
+
with a localized refinement stage.
|
| 14 |
+
"""
|
| 15 |
+
def __init__(self, num_circles=26):
|
| 16 |
+
self.n = num_circles
|
| 17 |
+
self.centers = np.zeros((self.n, 2))
|
| 18 |
+
self.radii = np.zeros(self.n)
|
| 19 |
+
|
| 20 |
+
@staticmethod
|
| 21 |
+
def _compute_max_radii_static(centers, n):
|
| 22 |
+
"""
|
| 23 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 24 |
+
an iterative growth and constraint resolution method. This static version
|
| 25 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 26 |
+
performance and precision, based on the best prior implementation.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 30 |
+
n (int): Number of circles.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 34 |
+
"""
|
| 35 |
+
radii = np.zeros(n)
|
| 36 |
+
|
| 37 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 38 |
+
growth_factor_initial = 1.005
|
| 39 |
+
growth_factor_final = 1.002
|
| 40 |
+
tolerance_initial = 1e-7
|
| 41 |
+
tolerance_final = 1e-10
|
| 42 |
+
|
| 43 |
+
outer_iterations = 400
|
| 44 |
+
inner_iterations = 20
|
| 45 |
+
|
| 46 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 47 |
+
for i in range(n):
|
| 48 |
+
x, y = centers[i]
|
| 49 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 50 |
+
|
| 51 |
+
for outer_iter_idx in range(outer_iterations):
|
| 52 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 53 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 54 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 55 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 56 |
+
|
| 57 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 58 |
+
|
| 59 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 60 |
+
constraints_changed = False
|
| 61 |
+
|
| 62 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 66 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 67 |
+
radii[i] = boundary_limit
|
| 68 |
+
constraints_changed = True
|
| 69 |
+
|
| 70 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 74 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 75 |
+
total_radius = radii[i] + radii[j]
|
| 76 |
+
if total_radius > tolerance_final:
|
| 77 |
+
scale = dist / total_radius
|
| 78 |
+
radii[i] *= scale
|
| 79 |
+
radii[j] *= scale
|
| 80 |
+
constraints_changed = True
|
| 81 |
+
|
| 82 |
+
if not constraints_changed:
|
| 83 |
+
break
|
| 84 |
+
return radii
|
| 85 |
+
|
| 86 |
+
def _initial_grid_placement(self):
|
| 87 |
+
"""
|
| 88 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 89 |
+
"""
|
| 90 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 91 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 92 |
+
self.centers[:25] = grid_centers
|
| 93 |
+
|
| 94 |
+
def _find_optimal_26th_circle_position(self):
|
| 95 |
+
"""
|
| 96 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 97 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 98 |
+
"""
|
| 99 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 100 |
+
|
| 101 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 102 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 103 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 104 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 105 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 106 |
+
|
| 107 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 108 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 109 |
+
|
| 110 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 111 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 112 |
+
|
| 113 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 114 |
+
# Explore a broader region first to identify promising areas.
|
| 115 |
+
coarse_delta = 0.05
|
| 116 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 117 |
+
|
| 118 |
+
coarse_candidate_points = []
|
| 119 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 120 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 121 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 122 |
+
|
| 123 |
+
for candidate_pos in coarse_candidate_points:
|
| 124 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 125 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 126 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 127 |
+
current_sum_radii = np.sum(trial_radii)
|
| 128 |
+
|
| 129 |
+
if current_sum_radii > best_sum_radii:
|
| 130 |
+
best_sum_radii = current_sum_radii
|
| 131 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 132 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 133 |
+
|
| 134 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 135 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 136 |
+
fine_delta = 0.01
|
| 137 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 138 |
+
|
| 139 |
+
fine_candidate_points = []
|
| 140 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 141 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 142 |
+
|
| 143 |
+
for candidate_pos in fine_candidate_points:
|
| 144 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 145 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 146 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 147 |
+
current_sum_radii = np.sum(trial_radii)
|
| 148 |
+
|
| 149 |
+
if current_sum_radii > best_sum_radii:
|
| 150 |
+
best_sum_radii = current_sum_radii
|
| 151 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 152 |
+
|
| 153 |
+
self.centers[25] = optimal_26th_pos
|
| 154 |
+
|
| 155 |
+
return self.centers, best_sum_radii
|
| 156 |
+
|
| 157 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 158 |
+
"""
|
| 159 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 160 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 161 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 162 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 163 |
+
"""
|
| 164 |
+
current_centers = np.copy(initial_centers)
|
| 165 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 166 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 167 |
+
current_sum_radii = np.sum(current_radii)
|
| 168 |
+
|
| 169 |
+
best_centers_local = np.copy(current_centers)
|
| 170 |
+
best_sum_radii_local = current_sum_radii
|
| 171 |
+
|
| 172 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 173 |
+
num_iterations = 150
|
| 174 |
+
initial_step_size = 0.005
|
| 175 |
+
initial_temp = 0.0001
|
| 176 |
+
cooling_rate = 0.99
|
| 177 |
+
|
| 178 |
+
step_size = initial_step_size
|
| 179 |
+
temp = initial_temp
|
| 180 |
+
|
| 181 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 182 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 183 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 184 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 185 |
+
|
| 186 |
+
# Parameters for dynamic step size adjustment
|
| 187 |
+
acceptance_window = 30
|
| 188 |
+
acceptance_count = 0
|
| 189 |
+
target_acceptance_rate = 0.44
|
| 190 |
+
adjustment_factor = 1.05
|
| 191 |
+
|
| 192 |
+
for i in range(num_iterations):
|
| 193 |
+
# Stress-based selection within the cluster
|
| 194 |
+
cluster_radii = current_radii[cluster_indices]
|
| 195 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 196 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 197 |
+
if np.sum(weights) > 1e-9:
|
| 198 |
+
probabilities = weights / np.sum(weights)
|
| 199 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 200 |
+
else:
|
| 201 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
trial_centers = np.copy(current_centers)
|
| 205 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 206 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 207 |
+
|
| 208 |
+
# Evaluate the new configuration
|
| 209 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 210 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 211 |
+
|
| 212 |
+
# Metropolis-Hastings acceptance criterion
|
| 213 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 214 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 215 |
+
current_centers = trial_centers
|
| 216 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 217 |
+
current_sum_radii = trial_sum_radii
|
| 218 |
+
acceptance_count += 1
|
| 219 |
+
|
| 220 |
+
if current_sum_radii > best_sum_radii_local:
|
| 221 |
+
best_sum_radii_local = current_sum_radii
|
| 222 |
+
best_centers_local = np.copy(current_centers)
|
| 223 |
+
|
| 224 |
+
# Dynamic step size adjustment
|
| 225 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 226 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 227 |
+
if acceptance_rate > target_acceptance_rate:
|
| 228 |
+
step_size *= adjustment_factor
|
| 229 |
+
else:
|
| 230 |
+
step_size /= adjustment_factor
|
| 231 |
+
acceptance_count = 0
|
| 232 |
+
|
| 233 |
+
temp *= cooling_rate
|
| 234 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 235 |
+
|
| 236 |
+
return best_centers_local, best_sum_radii_local
|
| 237 |
+
|
| 238 |
+
def _global_refinement_sa(self, initial_centers):
|
| 239 |
+
"""
|
| 240 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 241 |
+
- the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 242 |
+
- settle the entire packing into a better local optimum. This technique was
|
| 243 |
+
- present in prior high-scoring implementations.
|
| 244 |
+
- """
|
| 245 |
+
- # SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 246 |
+
- sa_iterations = 300
|
| 247 |
+
+ all circle positions. This version incorporates a new "cluster move" to
|
| 248 |
+
+ perturb small, spatially connected groups of circles simultaneously,
|
| 249 |
+
+ enhancing the ability to escape local minima in the rigid grid.
|
| 250 |
+
+ """
|
| 251 |
+
+ # SA parameters adapted from high-performing prior implementations.
|
| 252 |
+
+ # Increased iterations to accommodate the new cluster move type.
|
| 253 |
+
+ sa_iterations = 400
|
| 254 |
+
sa_initial_temp = 5e-6
|
| 255 |
+
sa_cooling_rate = 0.99
|
| 256 |
+
sa_initial_step_size = 0.001
|
| 257 |
+
|
| 258 |
+
+ # New parameters for cluster moves
|
| 259 |
+
+ cluster_move_prob = 0.2 # Probability of performing a cluster move
|
| 260 |
+
+ cluster_size = 3 # Number of circles in a cluster (seed + 2 neighbors)
|
| 261 |
+
+
|
| 262 |
+
current_centers = np.copy(initial_centers)
|
| 263 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 264 |
+
current_sum_radii = np.sum(current_radii)
|
| 265 |
+
|
| 266 |
+
best_centers = np.copy(current_centers)
|
| 267 |
+
best_sum_radii = current_sum_radii
|
| 268 |
+
|
| 269 |
+
temp = sa_initial_temp
|
| 270 |
+
step_size = sa_initial_step_size
|
| 271 |
+
|
| 272 |
+
all_indices = np.arange(self.n)
|
| 273 |
+
|
| 274 |
+
# Parameters for dynamic step size adjustment
|
| 275 |
+
acceptance_window = 30
|
| 276 |
+
acceptance_count = 0
|
| 277 |
+
- target_acceptance_rate = 0.44 # Common target for SA
|
| 278 |
+
+ target_acceptance_rate = 0.44
|
| 279 |
+
adjustment_factor = 1.05
|
| 280 |
+
|
| 281 |
+
for i in range(sa_iterations):
|
| 282 |
+
- # Stress-based selection: prioritize moving circles with smaller radii.
|
| 283 |
+
- inv_radii = 1.0 / (current_radii + 1e-9)
|
| 284 |
+
- weights = (inv_radii - np.min(inv_radii))**2
|
| 285 |
+
- if np.sum(weights) > 1e-9:
|
| 286 |
+
- probabilities = weights / np.sum(weights)
|
| 287 |
+
- idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 288 |
+
- else:
|
| 289 |
+
- idx_to_move = np.random.choice(all_indices)
|
| 290 |
+
-
|
| 291 |
+
trial_centers = np.copy(current_centers)
|
| 292 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 293 |
+
- trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 294 |
+
-
|
| 295 |
+
+
|
| 296 |
+
+ # Decide which indices to move based on move type
|
| 297 |
+
+ if np.random.rand() < cluster_move_prob:
|
| 298 |
+
+ # --- Cluster Move ---
|
| 299 |
+
+ # Select a random seed and its nearest neighbors. This helps to
|
| 300 |
+
+ # perform coordinated shifts and escape grid-like local optima.
|
| 301 |
+
+ seed_idx = np.random.choice(all_indices)
|
| 302 |
+
+ distances = np.linalg.norm(current_centers - current_centers[seed_idx], axis=1)
|
| 303 |
+
+ indices_to_move = np.argsort(distances)[:cluster_size]
|
| 304 |
+
+ else:
|
| 305 |
+
+ # --- Single Move (stress-based) ---
|
| 306 |
+
+ # Prioritize moving circles with smaller radii (higher "stress").
|
| 307 |
+
+ inv_radii = 1.0 / (current_radii + 1e-9)
|
| 308 |
+
+ weights = (inv_radii - np.min(inv_radii))**2
|
| 309 |
+
+ if np.sum(weights) > 1e-9:
|
| 310 |
+
+ probabilities = weights / np.sum(weights)
|
| 311 |
+
+ idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 312 |
+
+ else:
|
| 313 |
+
+ idx_to_move = np.random.choice(all_indices)
|
| 314 |
+
+ indices_to_move = np.array([idx_to_move])
|
| 315 |
+
+
|
| 316 |
+
+ # Apply the move to the selected circle(s)
|
| 317 |
+
+ for idx in indices_to_move:
|
| 318 |
+
+ trial_centers[idx] = np.clip(trial_centers[idx] + move, 0.0, 1.0)
|
| 319 |
+
+
|
| 320 |
+
+ # --- Evaluate and accept/reject the move ---
|
| 321 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 322 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 323 |
+
|
| 324 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 325 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 326 |
+
current_centers = trial_centers
|
| 327 |
+
- current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 328 |
+
+ current_radii = trial_radii # Keep radii in sync for next iteration's stress calc
|
| 329 |
+
current_sum_radii = trial_sum_radii
|
| 330 |
+
acceptance_count += 1
|
| 331 |
+
|
| 332 |
+
if current_sum_radii > best_sum_radii:
|
| 333 |
+
best_sum_radii = current_sum_radii
|
| 334 |
+
best_centers = np.copy(current_centers)
|
| 335 |
+
|
| 336 |
+
- # Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 337 |
+
+ # Periodically adjust step_size based on acceptance rate
|
| 338 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 339 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 340 |
+
if acceptance_rate > target_acceptance_rate:
|
| 341 |
+
step_size *= adjustment_factor
|
| 342 |
+
else:
|
| 343 |
+
step_size /= adjustment_factor
|
| 344 |
+
acceptance_count = 0
|
| 345 |
+
|
| 346 |
+
temp *= sa_cooling_rate
|
| 347 |
+
- step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 348 |
+
+ step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 349 |
+
|
| 350 |
+
return best_centers
|
| 351 |
+
|
| 352 |
+
def construct_packing(self):
|
| 353 |
+
"""
|
| 354 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 355 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 356 |
+
"""
|
| 357 |
+
self._initial_grid_placement()
|
| 358 |
+
|
| 359 |
+
if self.n > 25:
|
| 360 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 361 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 362 |
+
|
| 363 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 364 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 365 |
+
centers_after_search,
|
| 366 |
+
sum_radii_after_search
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 370 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 371 |
+
self.centers = centers_after_global_sa
|
| 372 |
+
|
| 373 |
+
# Final radius calculation for the fully optimized center configuration
|
| 374 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 375 |
+
return self.centers, self.radii
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def construct_packing():
|
| 379 |
+
"""
|
| 380 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 381 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 382 |
+
|
| 383 |
+
Returns:
|
| 384 |
+
Tuple of (centers, radii)
|
| 385 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 386 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 387 |
+
"""
|
| 388 |
+
packer = CirclePacker(num_circles=26)
|
| 389 |
+
centers, radii = packer.construct_packing()
|
| 390 |
+
return centers, radii
|
| 391 |
+
# EVOLVE-BLOCK-END
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
# This part remains fixed (not evolved)
|
| 395 |
+
def run_packing():
|
| 396 |
+
"""Run the circle packing constructor for n=26"""
|
| 397 |
+
centers, radii = construct_packing()
|
| 398 |
+
# Calculate the sum of radii
|
| 399 |
+
sum_radii = np.sum(radii)
|
| 400 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/main.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with a localized refinement stage.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This static version
|
| 22 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 23 |
+
performance and precision, based on the best prior implementation.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 27 |
+
n (int): Number of circles.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
radii = np.zeros(n)
|
| 33 |
+
|
| 34 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 35 |
+
growth_factor_initial = 1.005
|
| 36 |
+
growth_factor_final = 1.002
|
| 37 |
+
tolerance_initial = 1e-7
|
| 38 |
+
tolerance_final = 1e-10
|
| 39 |
+
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
inner_iterations = 20
|
| 42 |
+
|
| 43 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 44 |
+
for i in range(n):
|
| 45 |
+
x, y = centers[i]
|
| 46 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 47 |
+
|
| 48 |
+
for outer_iter_idx in range(outer_iterations):
|
| 49 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 50 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 51 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 52 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 53 |
+
|
| 54 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 55 |
+
|
| 56 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 57 |
+
constraints_changed = False
|
| 58 |
+
|
| 59 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 60 |
+
for i in range(n):
|
| 61 |
+
x, y = centers[i]
|
| 62 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 63 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 64 |
+
radii[i] = boundary_limit
|
| 65 |
+
constraints_changed = True
|
| 66 |
+
|
| 67 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
for j in range(i + 1, n):
|
| 70 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 71 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 72 |
+
total_radius = radii[i] + radii[j]
|
| 73 |
+
if total_radius > tolerance_final:
|
| 74 |
+
scale = dist / total_radius
|
| 75 |
+
radii[i] *= scale
|
| 76 |
+
radii[j] *= scale
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
if not constraints_changed:
|
| 80 |
+
break
|
| 81 |
+
return radii
|
| 82 |
+
|
| 83 |
+
def _initial_grid_placement(self):
|
| 84 |
+
"""
|
| 85 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 86 |
+
"""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 89 |
+
self.centers[:25] = grid_centers
|
| 90 |
+
|
| 91 |
+
def _find_optimal_26th_circle_position(self):
|
| 92 |
+
"""
|
| 93 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 94 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 95 |
+
"""
|
| 96 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 97 |
+
|
| 98 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 99 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 100 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 101 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 102 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 103 |
+
|
| 104 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 105 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 106 |
+
|
| 107 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 108 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 109 |
+
|
| 110 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 111 |
+
# Explore a broader region first to identify promising areas.
|
| 112 |
+
coarse_delta = 0.05
|
| 113 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points = []
|
| 116 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 117 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 118 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 119 |
+
|
| 120 |
+
for candidate_pos in coarse_candidate_points:
|
| 121 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 122 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 123 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 124 |
+
current_sum_radii = np.sum(trial_radii)
|
| 125 |
+
|
| 126 |
+
if current_sum_radii > best_sum_radii:
|
| 127 |
+
best_sum_radii = current_sum_radii
|
| 128 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 129 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 130 |
+
|
| 131 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 132 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 133 |
+
fine_delta = 0.01
|
| 134 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 135 |
+
|
| 136 |
+
fine_candidate_points = []
|
| 137 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 138 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 139 |
+
|
| 140 |
+
for candidate_pos in fine_candidate_points:
|
| 141 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 142 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 143 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 144 |
+
current_sum_radii = np.sum(trial_radii)
|
| 145 |
+
|
| 146 |
+
if current_sum_radii > best_sum_radii:
|
| 147 |
+
best_sum_radii = current_sum_radii
|
| 148 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 149 |
+
|
| 150 |
+
self.centers[25] = optimal_26th_pos
|
| 151 |
+
|
| 152 |
+
return self.centers, best_sum_radii
|
| 153 |
+
|
| 154 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 155 |
+
"""
|
| 156 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 157 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 158 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 159 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 160 |
+
"""
|
| 161 |
+
current_centers = np.copy(initial_centers)
|
| 162 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 163 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 164 |
+
current_sum_radii = np.sum(current_radii)
|
| 165 |
+
|
| 166 |
+
best_centers_local = np.copy(current_centers)
|
| 167 |
+
best_sum_radii_local = current_sum_radii
|
| 168 |
+
|
| 169 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 170 |
+
num_iterations = 150
|
| 171 |
+
initial_step_size = 0.005
|
| 172 |
+
initial_temp = 0.0001
|
| 173 |
+
cooling_rate = 0.99
|
| 174 |
+
|
| 175 |
+
step_size = initial_step_size
|
| 176 |
+
temp = initial_temp
|
| 177 |
+
|
| 178 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 179 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 180 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 181 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 182 |
+
|
| 183 |
+
# Parameters for dynamic step size adjustment
|
| 184 |
+
acceptance_window = 30
|
| 185 |
+
acceptance_count = 0
|
| 186 |
+
target_acceptance_rate = 0.44
|
| 187 |
+
adjustment_factor = 1.05
|
| 188 |
+
|
| 189 |
+
for i in range(num_iterations):
|
| 190 |
+
# Stress-based selection within the cluster
|
| 191 |
+
cluster_radii = current_radii[cluster_indices]
|
| 192 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 193 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 194 |
+
if np.sum(weights) > 1e-9:
|
| 195 |
+
probabilities = weights / np.sum(weights)
|
| 196 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 197 |
+
else:
|
| 198 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
trial_centers = np.copy(current_centers)
|
| 202 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 203 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 204 |
+
|
| 205 |
+
# Evaluate the new configuration
|
| 206 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 207 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 208 |
+
|
| 209 |
+
# Metropolis-Hastings acceptance criterion
|
| 210 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 211 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 212 |
+
current_centers = trial_centers
|
| 213 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 214 |
+
current_sum_radii = trial_sum_radii
|
| 215 |
+
acceptance_count += 1
|
| 216 |
+
|
| 217 |
+
if current_sum_radii > best_sum_radii_local:
|
| 218 |
+
best_sum_radii_local = current_sum_radii
|
| 219 |
+
best_centers_local = np.copy(current_centers)
|
| 220 |
+
|
| 221 |
+
# Dynamic step size adjustment
|
| 222 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 223 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 224 |
+
if acceptance_rate > target_acceptance_rate:
|
| 225 |
+
step_size *= adjustment_factor
|
| 226 |
+
else:
|
| 227 |
+
step_size /= adjustment_factor
|
| 228 |
+
acceptance_count = 0
|
| 229 |
+
|
| 230 |
+
temp *= cooling_rate
|
| 231 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 232 |
+
|
| 233 |
+
return best_centers_local, best_sum_radii_local
|
| 234 |
+
|
| 235 |
+
def _global_refinement_sa(self, initial_centers):
|
| 236 |
+
"""
|
| 237 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 238 |
+
all circle positions. This version incorporates a new "cluster move" to
|
| 239 |
+
perturb small, spatially connected groups of circles simultaneously,
|
| 240 |
+
enhancing the ability to escape local minima in the rigid grid.
|
| 241 |
+
"""
|
| 242 |
+
# SA parameters adapted from high-performing prior implementations.
|
| 243 |
+
# Increased iterations to accommodate the new cluster move type.
|
| 244 |
+
sa_iterations = 400
|
| 245 |
+
sa_initial_temp = 5e-6
|
| 246 |
+
sa_cooling_rate = 0.99
|
| 247 |
+
sa_initial_step_size = 0.001
|
| 248 |
+
|
| 249 |
+
# New parameters for cluster moves
|
| 250 |
+
cluster_move_prob = 0.2 # Probability of performing a cluster move
|
| 251 |
+
cluster_size = 3 # Number of circles in a cluster (seed + 2 neighbors)
|
| 252 |
+
|
| 253 |
+
current_centers = np.copy(initial_centers)
|
| 254 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 255 |
+
current_sum_radii = np.sum(current_radii)
|
| 256 |
+
|
| 257 |
+
best_centers = np.copy(current_centers)
|
| 258 |
+
best_sum_radii = current_sum_radii
|
| 259 |
+
|
| 260 |
+
temp = sa_initial_temp
|
| 261 |
+
step_size = sa_initial_step_size
|
| 262 |
+
|
| 263 |
+
all_indices = np.arange(self.n)
|
| 264 |
+
|
| 265 |
+
# Parameters for dynamic step size adjustment
|
| 266 |
+
acceptance_window = 30
|
| 267 |
+
acceptance_count = 0
|
| 268 |
+
target_acceptance_rate = 0.44
|
| 269 |
+
adjustment_factor = 1.05
|
| 270 |
+
|
| 271 |
+
for i in range(sa_iterations):
|
| 272 |
+
trial_centers = np.copy(current_centers)
|
| 273 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 274 |
+
|
| 275 |
+
# Decide which indices to move based on move type
|
| 276 |
+
if np.random.rand() < cluster_move_prob:
|
| 277 |
+
# --- Cluster Move ---
|
| 278 |
+
# Select a random seed and its nearest neighbors. This helps to
|
| 279 |
+
# perform coordinated shifts and escape grid-like local optima.
|
| 280 |
+
seed_idx = np.random.choice(all_indices)
|
| 281 |
+
distances = np.linalg.norm(current_centers - current_centers[seed_idx], axis=1)
|
| 282 |
+
indices_to_move = np.argsort(distances)[:cluster_size]
|
| 283 |
+
else:
|
| 284 |
+
# --- Single Move (stress-based) ---
|
| 285 |
+
# Prioritize moving circles with smaller radii (higher "stress").
|
| 286 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 287 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 288 |
+
if np.sum(weights) > 1e-9:
|
| 289 |
+
probabilities = weights / np.sum(weights)
|
| 290 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 291 |
+
else:
|
| 292 |
+
idx_to_move = np.random.choice(all_indices)
|
| 293 |
+
indices_to_move = np.array([idx_to_move])
|
| 294 |
+
|
| 295 |
+
# Apply the move to the selected circle(s)
|
| 296 |
+
for idx in indices_to_move:
|
| 297 |
+
trial_centers[idx] = np.clip(trial_centers[idx] + move, 0.0, 1.0)
|
| 298 |
+
|
| 299 |
+
# --- Evaluate and accept/reject the move ---
|
| 300 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 301 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 302 |
+
|
| 303 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 304 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 305 |
+
current_centers = trial_centers
|
| 306 |
+
current_radii = trial_radii # Keep radii in sync for next iteration's stress calc
|
| 307 |
+
current_sum_radii = trial_sum_radii
|
| 308 |
+
acceptance_count += 1
|
| 309 |
+
|
| 310 |
+
if current_sum_radii > best_sum_radii:
|
| 311 |
+
best_sum_radii = current_sum_radii
|
| 312 |
+
best_centers = np.copy(current_centers)
|
| 313 |
+
|
| 314 |
+
# Periodically adjust step_size based on acceptance rate
|
| 315 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 316 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 317 |
+
if acceptance_rate > target_acceptance_rate:
|
| 318 |
+
step_size *= adjustment_factor
|
| 319 |
+
else:
|
| 320 |
+
step_size /= adjustment_factor
|
| 321 |
+
acceptance_count = 0
|
| 322 |
+
|
| 323 |
+
temp *= sa_cooling_rate
|
| 324 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 325 |
+
|
| 326 |
+
return best_centers
|
| 327 |
+
|
| 328 |
+
def construct_packing(self):
|
| 329 |
+
"""
|
| 330 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 331 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 332 |
+
"""
|
| 333 |
+
self._initial_grid_placement()
|
| 334 |
+
|
| 335 |
+
if self.n > 25:
|
| 336 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 337 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 338 |
+
|
| 339 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 340 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 341 |
+
centers_after_search,
|
| 342 |
+
sum_radii_after_search
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 346 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 347 |
+
self.centers = centers_after_global_sa
|
| 348 |
+
|
| 349 |
+
# Final radius calculation for the fully optimized center configuration
|
| 350 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 351 |
+
return self.centers, self.radii
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def construct_packing():
|
| 355 |
+
"""
|
| 356 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 357 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 358 |
+
|
| 359 |
+
Returns:
|
| 360 |
+
Tuple of (centers, radii)
|
| 361 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 362 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 363 |
+
"""
|
| 364 |
+
packer = CirclePacker(num_circles=26)
|
| 365 |
+
centers, radii = packer.construct_packing()
|
| 366 |
+
return centers, radii
|
| 367 |
+
# EVOLVE-BLOCK-END
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
# This part remains fixed (not evolved)
|
| 371 |
+
def run_packing():
|
| 372 |
+
"""Run the circle packing constructor for n=26"""
|
| 373 |
+
centers, radii = construct_packing()
|
| 374 |
+
# Calculate the sum of radii
|
| 375 |
+
sum_radii = np.sum(radii)
|
| 376 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/original.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from itertools import product
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class CirclePacker:
|
| 7 |
+
"""
|
| 8 |
+
A class to construct circle packings within a unit square using a hybrid
|
| 9 |
+
optimization approach. It combines an exhaustive search for initial placement
|
| 10 |
+
with a localized refinement stage.
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, num_circles=26):
|
| 13 |
+
self.n = num_circles
|
| 14 |
+
self.centers = np.zeros((self.n, 2))
|
| 15 |
+
self.radii = np.zeros(self.n)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def _compute_max_radii_static(centers, n):
|
| 19 |
+
"""
|
| 20 |
+
Computes the maximum possible radii for a given set of circle centers using
|
| 21 |
+
an iterative growth and constraint resolution method. This static version
|
| 22 |
+
incorporates an adaptive growth factor and dynamic tolerance for enhanced
|
| 23 |
+
performance and precision, based on the best prior implementation.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
centers (np.array): An array of shape (n, 2) with (x, y) coordinates.
|
| 27 |
+
n (int): Number of circles.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
np.array: An array of shape (n) containing the final radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
radii = np.zeros(n)
|
| 33 |
+
|
| 34 |
+
# Parameters for adaptive growth factor and dynamic tolerance
|
| 35 |
+
growth_factor_initial = 1.005
|
| 36 |
+
growth_factor_final = 1.002
|
| 37 |
+
tolerance_initial = 1e-7
|
| 38 |
+
tolerance_final = 1e-10
|
| 39 |
+
|
| 40 |
+
outer_iterations = 400
|
| 41 |
+
inner_iterations = 20
|
| 42 |
+
|
| 43 |
+
# Initialize radii based on the distance to the square's boundaries.
|
| 44 |
+
for i in range(n):
|
| 45 |
+
x, y = centers[i]
|
| 46 |
+
radii[i] = min(x, 1 - x, y, 1 - y)
|
| 47 |
+
|
| 48 |
+
for outer_iter_idx in range(outer_iterations):
|
| 49 |
+
interp_factor = outer_iter_idx / (outer_iterations - 1.0 + 1e-9)
|
| 50 |
+
# Use exponential decay for smoother, more effective parameter transition, based on prior high-scoring versions.
|
| 51 |
+
current_growth_factor = growth_factor_initial * (growth_factor_final / growth_factor_initial)**interp_factor
|
| 52 |
+
current_tolerance = tolerance_initial * (tolerance_final / tolerance_initial)**interp_factor
|
| 53 |
+
|
| 54 |
+
radii *= current_growth_factor # Tentatively grow all radii
|
| 55 |
+
|
| 56 |
+
for _inner_iter_idx in range(inner_iterations):
|
| 57 |
+
constraints_changed = False
|
| 58 |
+
|
| 59 |
+
# Enforce boundary constraints with dynamic tolerance
|
| 60 |
+
for i in range(n):
|
| 61 |
+
x, y = centers[i]
|
| 62 |
+
boundary_limit = min(x, 1 - x, y, 1 - y)
|
| 63 |
+
if radii[i] > boundary_limit + current_tolerance:
|
| 64 |
+
radii[i] = boundary_limit
|
| 65 |
+
constraints_changed = True
|
| 66 |
+
|
| 67 |
+
# Resolve overlaps between circles with dynamic tolerance
|
| 68 |
+
for i in range(n):
|
| 69 |
+
for j in range(i + 1, n):
|
| 70 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 71 |
+
if radii[i] + radii[j] > dist + current_tolerance:
|
| 72 |
+
total_radius = radii[i] + radii[j]
|
| 73 |
+
if total_radius > tolerance_final:
|
| 74 |
+
scale = dist / total_radius
|
| 75 |
+
radii[i] *= scale
|
| 76 |
+
radii[j] *= scale
|
| 77 |
+
constraints_changed = True
|
| 78 |
+
|
| 79 |
+
if not constraints_changed:
|
| 80 |
+
break
|
| 81 |
+
return radii
|
| 82 |
+
|
| 83 |
+
def _initial_grid_placement(self):
|
| 84 |
+
"""
|
| 85 |
+
Places the first 25 circles in a 5x5 grid pattern within the unit square.
|
| 86 |
+
"""
|
| 87 |
+
coords = np.linspace(0.1, 0.9, 5)
|
| 88 |
+
grid_centers = np.array(list(product(coords, coords)))
|
| 89 |
+
self.centers[:25] = grid_centers
|
| 90 |
+
|
| 91 |
+
def _find_optimal_26th_circle_position(self):
|
| 92 |
+
"""
|
| 93 |
+
Performs a multi-resolution grid search to find the best initial position
|
| 94 |
+
for the 26th circle among a dense set of interstitial candidates.
|
| 95 |
+
"""
|
| 96 |
+
base_25_centers = np.copy(self.centers[:25])
|
| 97 |
+
|
| 98 |
+
# Initialize with a default position (center of the square) and calculate its sum of radii
|
| 99 |
+
optimal_26th_pos = np.array([0.5, 0.5])
|
| 100 |
+
trial_centers_initial = np.vstack([base_25_centers, optimal_26th_pos])
|
| 101 |
+
trial_radii_initial = CirclePacker._compute_max_radii_static(trial_centers_initial, self.n)
|
| 102 |
+
best_sum_radii = np.sum(trial_radii_initial)
|
| 103 |
+
|
| 104 |
+
# Keep track of the best position from the coarse search to center the fine search
|
| 105 |
+
best_coarse_pos_for_fine_tuning = optimal_26th_pos
|
| 106 |
+
|
| 107 |
+
# Expand the coarse search grid to cover a broader area, as per recommendations.
|
| 108 |
+
interstitial_core_coords = np.linspace(0.1, 0.9, 8)
|
| 109 |
+
|
| 110 |
+
# --- Phase 1: Coarse Grid Search ---
|
| 111 |
+
# Explore a broader region first to identify promising areas.
|
| 112 |
+
coarse_delta = 0.05
|
| 113 |
+
coarse_perturbation_offsets = np.array([-coarse_delta, 0, coarse_delta])
|
| 114 |
+
|
| 115 |
+
coarse_candidate_points = []
|
| 116 |
+
for base_x, base_y in product(interstitial_core_coords, interstitial_core_coords):
|
| 117 |
+
for offset_x, offset_y in product(coarse_perturbation_offsets, coarse_perturbation_offsets):
|
| 118 |
+
coarse_candidate_points.append([base_x + offset_x, base_y + offset_y])
|
| 119 |
+
|
| 120 |
+
for candidate_pos in coarse_candidate_points:
|
| 121 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 122 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 123 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 124 |
+
current_sum_radii = np.sum(trial_radii)
|
| 125 |
+
|
| 126 |
+
if current_sum_radii > best_sum_radii:
|
| 127 |
+
best_sum_radii = current_sum_radii
|
| 128 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 129 |
+
best_coarse_pos_for_fine_tuning = clipped_candidate_pos
|
| 130 |
+
|
| 131 |
+
# --- Phase 2: Fine Grid Search around the best coarse position ---
|
| 132 |
+
# Focus the search more precisely around the most promising area identified in Phase 1.
|
| 133 |
+
fine_delta = 0.01
|
| 134 |
+
fine_perturbation_offsets = np.array([-fine_delta, 0, fine_delta])
|
| 135 |
+
|
| 136 |
+
fine_candidate_points = []
|
| 137 |
+
for offset_x, offset_y in product(fine_perturbation_offsets, fine_perturbation_offsets):
|
| 138 |
+
fine_candidate_points.append([best_coarse_pos_for_fine_tuning[0] + offset_x, best_coarse_pos_for_fine_tuning[1] + offset_y])
|
| 139 |
+
|
| 140 |
+
for candidate_pos in fine_candidate_points:
|
| 141 |
+
clipped_candidate_pos = np.clip(candidate_pos, 0.0, 1.0)
|
| 142 |
+
trial_centers = np.vstack([base_25_centers, clipped_candidate_pos])
|
| 143 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 144 |
+
current_sum_radii = np.sum(trial_radii)
|
| 145 |
+
|
| 146 |
+
if current_sum_radii > best_sum_radii:
|
| 147 |
+
best_sum_radii = current_sum_radii
|
| 148 |
+
optimal_26th_pos = clipped_candidate_pos
|
| 149 |
+
|
| 150 |
+
self.centers[25] = optimal_26th_pos
|
| 151 |
+
|
| 152 |
+
return self.centers, best_sum_radii
|
| 153 |
+
|
| 154 |
+
def _local_refinement_cluster_sa(self, initial_centers, initial_sum_radii):
|
| 155 |
+
"""
|
| 156 |
+
Applies a localized Simulated Annealing (SA) search to fine-tune the
|
| 157 |
+
positions of a cluster of circles: the 26th and its nearest neighbors.
|
| 158 |
+
This allows the base grid to relax and better accommodate the interstitial circle.
|
| 159 |
+
This version is enhanced with stress-based selection and dynamic step size adjustment.
|
| 160 |
+
"""
|
| 161 |
+
current_centers = np.copy(initial_centers)
|
| 162 |
+
# Compute radii for initial stress calculation and consistent sum.
|
| 163 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 164 |
+
current_sum_radii = np.sum(current_radii)
|
| 165 |
+
|
| 166 |
+
best_centers_local = np.copy(current_centers)
|
| 167 |
+
best_sum_radii_local = current_sum_radii
|
| 168 |
+
|
| 169 |
+
# SA parameters adapted from high-performing prior implementations for fine-tuning
|
| 170 |
+
num_iterations = 150
|
| 171 |
+
initial_step_size = 0.005
|
| 172 |
+
initial_temp = 0.0001
|
| 173 |
+
cooling_rate = 0.99
|
| 174 |
+
|
| 175 |
+
step_size = initial_step_size
|
| 176 |
+
temp = initial_temp
|
| 177 |
+
|
| 178 |
+
# Identify the cluster: the 26th circle and its 4 closest neighbors.
|
| 179 |
+
distances_to_26th = np.linalg.norm(initial_centers[25] - initial_centers[:25], axis=1)
|
| 180 |
+
closest_neighbor_indices = np.argsort(distances_to_26th)[:4]
|
| 181 |
+
cluster_indices = np.append(closest_neighbor_indices, 25)
|
| 182 |
+
|
| 183 |
+
# Parameters for dynamic step size adjustment
|
| 184 |
+
acceptance_window = 30
|
| 185 |
+
acceptance_count = 0
|
| 186 |
+
target_acceptance_rate = 0.44
|
| 187 |
+
adjustment_factor = 1.05
|
| 188 |
+
|
| 189 |
+
for i in range(num_iterations):
|
| 190 |
+
# Stress-based selection within the cluster
|
| 191 |
+
cluster_radii = current_radii[cluster_indices]
|
| 192 |
+
inv_radii = 1.0 / (cluster_radii + 1e-9)
|
| 193 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 194 |
+
if np.sum(weights) > 1e-9:
|
| 195 |
+
probabilities = weights / np.sum(weights)
|
| 196 |
+
idx_to_move = np.random.choice(cluster_indices, p=probabilities)
|
| 197 |
+
else:
|
| 198 |
+
idx_to_move = np.random.choice(cluster_indices)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
trial_centers = np.copy(current_centers)
|
| 202 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 203 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 204 |
+
|
| 205 |
+
# Evaluate the new configuration
|
| 206 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 207 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 208 |
+
|
| 209 |
+
# Metropolis-Hastings acceptance criterion
|
| 210 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 211 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 212 |
+
current_centers = trial_centers
|
| 213 |
+
current_radii = trial_radii # Update radii for stress calculation
|
| 214 |
+
current_sum_radii = trial_sum_radii
|
| 215 |
+
acceptance_count += 1
|
| 216 |
+
|
| 217 |
+
if current_sum_radii > best_sum_radii_local:
|
| 218 |
+
best_sum_radii_local = current_sum_radii
|
| 219 |
+
best_centers_local = np.copy(current_centers)
|
| 220 |
+
|
| 221 |
+
# Dynamic step size adjustment
|
| 222 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 223 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 224 |
+
if acceptance_rate > target_acceptance_rate:
|
| 225 |
+
step_size *= adjustment_factor
|
| 226 |
+
else:
|
| 227 |
+
step_size /= adjustment_factor
|
| 228 |
+
acceptance_count = 0
|
| 229 |
+
|
| 230 |
+
temp *= cooling_rate
|
| 231 |
+
step_size = max(step_size * cooling_rate, 1e-7)
|
| 232 |
+
|
| 233 |
+
return best_centers_local, best_sum_radii_local
|
| 234 |
+
|
| 235 |
+
def _global_refinement_sa(self, initial_centers):
|
| 236 |
+
"""
|
| 237 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 238 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 239 |
+
settle the entire packing into a better local optimum. This technique was
|
| 240 |
+
present in prior high-scoring implementations.
|
| 241 |
+
"""
|
| 242 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 243 |
+
sa_iterations = 300
|
| 244 |
+
sa_initial_temp = 5e-6
|
| 245 |
+
sa_cooling_rate = 0.99
|
| 246 |
+
sa_initial_step_size = 0.001
|
| 247 |
+
|
| 248 |
+
current_centers = np.copy(initial_centers)
|
| 249 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 250 |
+
current_sum_radii = np.sum(current_radii)
|
| 251 |
+
|
| 252 |
+
best_centers = np.copy(current_centers)
|
| 253 |
+
best_sum_radii = current_sum_radii
|
| 254 |
+
|
| 255 |
+
temp = sa_initial_temp
|
| 256 |
+
step_size = sa_initial_step_size
|
| 257 |
+
|
| 258 |
+
all_indices = np.arange(self.n)
|
| 259 |
+
|
| 260 |
+
# Parameters for dynamic step size adjustment
|
| 261 |
+
acceptance_window = 30
|
| 262 |
+
acceptance_count = 0
|
| 263 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 264 |
+
adjustment_factor = 1.05
|
| 265 |
+
|
| 266 |
+
for i in range(sa_iterations):
|
| 267 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 268 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 269 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 270 |
+
if np.sum(weights) > 1e-9:
|
| 271 |
+
probabilities = weights / np.sum(weights)
|
| 272 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 273 |
+
else:
|
| 274 |
+
idx_to_move = np.random.choice(all_indices)
|
| 275 |
+
|
| 276 |
+
trial_centers = np.copy(current_centers)
|
| 277 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 278 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 279 |
+
|
| 280 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 281 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 282 |
+
|
| 283 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 284 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 285 |
+
current_centers = trial_centers
|
| 286 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 287 |
+
current_sum_radii = trial_sum_radii
|
| 288 |
+
acceptance_count += 1
|
| 289 |
+
|
| 290 |
+
if current_sum_radii > best_sum_radii:
|
| 291 |
+
best_sum_radii = current_sum_radii
|
| 292 |
+
best_centers = np.copy(current_centers)
|
| 293 |
+
|
| 294 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 295 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 296 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 297 |
+
if acceptance_rate > target_acceptance_rate:
|
| 298 |
+
step_size *= adjustment_factor
|
| 299 |
+
else:
|
| 300 |
+
step_size /= adjustment_factor
|
| 301 |
+
acceptance_count = 0
|
| 302 |
+
|
| 303 |
+
temp *= sa_cooling_rate
|
| 304 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 305 |
+
|
| 306 |
+
return best_centers
|
| 307 |
+
|
| 308 |
+
def construct_packing(self):
|
| 309 |
+
"""
|
| 310 |
+
Main method to construct the circle packing, orchestrating a multi-stage
|
| 311 |
+
optimization: initial placement, multi-res search, local SA, and global SA.
|
| 312 |
+
"""
|
| 313 |
+
self._initial_grid_placement()
|
| 314 |
+
|
| 315 |
+
if self.n > 25:
|
| 316 |
+
# Stage 1: Multi-resolution search for the 26th circle's initial position
|
| 317 |
+
centers_after_search, sum_radii_after_search = self._find_optimal_26th_circle_position()
|
| 318 |
+
|
| 319 |
+
# Stage 2: Local refinement of the interstitial circle and its neighbors using SA
|
| 320 |
+
centers_after_local_sa, _ = self._local_refinement_cluster_sa(
|
| 321 |
+
centers_after_search,
|
| 322 |
+
sum_radii_after_search
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# Stage 3: Global "gentle jiggle" refinement on all circles using SA
|
| 326 |
+
centers_after_global_sa = self._global_refinement_sa(centers_after_local_sa)
|
| 327 |
+
self.centers = centers_after_global_sa
|
| 328 |
+
|
| 329 |
+
# Final radius calculation for the fully optimized center configuration
|
| 330 |
+
self.radii = CirclePacker._compute_max_radii_static(self.centers, self.n)
|
| 331 |
+
return self.centers, self.radii
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
def construct_packing():
|
| 335 |
+
"""
|
| 336 |
+
Constructs an arrangement of 26 circles by leveraging a superior three-stage
|
| 337 |
+
optimization strategy: initial grid, dense interstitial search, and localized SA.
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
Tuple of (centers, radii)
|
| 341 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 342 |
+
radii: np.array of shape (26) with final radius of each circle
|
| 343 |
+
"""
|
| 344 |
+
packer = CirclePacker(num_circles=26)
|
| 345 |
+
centers, radii = packer.construct_packing()
|
| 346 |
+
return centers, radii
|
| 347 |
+
# EVOLVE-BLOCK-END
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
# This part remains fixed (not evolved)
|
| 351 |
+
def run_packing():
|
| 352 |
+
"""Run the circle packing constructor for n=26"""
|
| 353 |
+
centers, radii = construct_packing()
|
| 354 |
+
# Calculate the sum of radii
|
| 355 |
+
sum_radii = np.sum(radii)
|
| 356 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results__debug/results_circle_packing_NO_vision_WITH_refined_aux_20260118_205215/gen_106/search_replace.txt
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
cluster_moves_in_global_sa
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DESCRIPTION>
|
| 6 |
+
This edit introduces "cluster moves" into the global simulated annealing stage (`_global_refinement_sa`). With a 20% probability at each step, instead of moving a single circle, the algorithm now moves a small cluster of 3 spatially adjacent circles (a random seed circle and its two nearest neighbors) with the same translation vector.
|
| 7 |
+
|
| 8 |
+
This is a powerful exploration strategy designed to overcome the rigidity of the initial 5x5 grid packing, allowing for coordinated shifts that can unlock superior configurations and escape local optima that single-circle moves struggle with.
|
| 9 |
+
|
| 10 |
+
To support this more complex search, the number of iterations in the global SA stage has been increased from 300 to 400, providing more time for this enhanced exploration to converge on a better solution. This change directly implements a key recommendation to perturb spatially connected clusters, targeting a known weakness of single-particle optimization in highly structured problems.
|
| 11 |
+
</DESCRIPTION>
|
| 12 |
+
|
| 13 |
+
<DIFF>
|
| 14 |
+
<<<<<<< SEARCH
|
| 15 |
+
def _global_refinement_sa(self, initial_centers):
|
| 16 |
+
"""
|
| 17 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 18 |
+
the positions of ALL circles, acting as a "gentle jiggle" phase to
|
| 19 |
+
settle the entire packing into a better local optimum. This technique was
|
| 20 |
+
present in prior high-scoring implementations.
|
| 21 |
+
"""
|
| 22 |
+
# SA parameters adapted from high-performing prior implementations for a thorough yet gentle search.
|
| 23 |
+
sa_iterations = 300
|
| 24 |
+
sa_initial_temp = 5e-6
|
| 25 |
+
sa_cooling_rate = 0.99
|
| 26 |
+
sa_initial_step_size = 0.001
|
| 27 |
+
|
| 28 |
+
current_centers = np.copy(initial_centers)
|
| 29 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 30 |
+
current_sum_radii = np.sum(current_radii)
|
| 31 |
+
|
| 32 |
+
best_centers = np.copy(current_centers)
|
| 33 |
+
best_sum_radii = current_sum_radii
|
| 34 |
+
|
| 35 |
+
temp = sa_initial_temp
|
| 36 |
+
step_size = sa_initial_step_size
|
| 37 |
+
|
| 38 |
+
all_indices = np.arange(self.n)
|
| 39 |
+
|
| 40 |
+
# Parameters for dynamic step size adjustment
|
| 41 |
+
acceptance_window = 30
|
| 42 |
+
acceptance_count = 0
|
| 43 |
+
target_acceptance_rate = 0.44 # Common target for SA
|
| 44 |
+
adjustment_factor = 1.05
|
| 45 |
+
|
| 46 |
+
for i in range(sa_iterations):
|
| 47 |
+
# Stress-based selection: prioritize moving circles with smaller radii.
|
| 48 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 49 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 50 |
+
if np.sum(weights) > 1e-9:
|
| 51 |
+
probabilities = weights / np.sum(weights)
|
| 52 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 53 |
+
else:
|
| 54 |
+
idx_to_move = np.random.choice(all_indices)
|
| 55 |
+
|
| 56 |
+
trial_centers = np.copy(current_centers)
|
| 57 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 58 |
+
trial_centers[idx_to_move] = np.clip(trial_centers[idx_to_move] + move, 0.0, 1.0)
|
| 59 |
+
|
| 60 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 61 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 62 |
+
|
| 63 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 64 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 65 |
+
current_centers = trial_centers
|
| 66 |
+
current_radii = trial_radii # Keep radii in sync for stress calculation
|
| 67 |
+
current_sum_radii = trial_sum_radii
|
| 68 |
+
acceptance_count += 1
|
| 69 |
+
|
| 70 |
+
if current_sum_radii > best_sum_radii:
|
| 71 |
+
best_sum_radii = current_sum_radii
|
| 72 |
+
best_centers = np.copy(current_centers)
|
| 73 |
+
|
| 74 |
+
# Periodically adjust step_size based on acceptance rate to balance exploration/exploitation.
|
| 75 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 76 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 77 |
+
if acceptance_rate > target_acceptance_rate:
|
| 78 |
+
step_size *= adjustment_factor
|
| 79 |
+
else:
|
| 80 |
+
step_size /= adjustment_factor
|
| 81 |
+
acceptance_count = 0
|
| 82 |
+
|
| 83 |
+
temp *= sa_cooling_rate
|
| 84 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8) # Maintain overall cooling trend for step size
|
| 85 |
+
|
| 86 |
+
return best_centers
|
| 87 |
+
=======
|
| 88 |
+
def _global_refinement_sa(self, initial_centers):
|
| 89 |
+
"""
|
| 90 |
+
Applies a global, low-temperature Simulated Annealing search to fine-tune
|
| 91 |
+
all circle positions. This version incorporates a new "cluster move" to
|
| 92 |
+
perturb small, spatially connected groups of circles simultaneously,
|
| 93 |
+
enhancing the ability to escape local minima in the rigid grid.
|
| 94 |
+
"""
|
| 95 |
+
# SA parameters adapted from high-performing prior implementations.
|
| 96 |
+
# Increased iterations to accommodate the new cluster move type.
|
| 97 |
+
sa_iterations = 400
|
| 98 |
+
sa_initial_temp = 5e-6
|
| 99 |
+
sa_cooling_rate = 0.99
|
| 100 |
+
sa_initial_step_size = 0.001
|
| 101 |
+
|
| 102 |
+
# New parameters for cluster moves
|
| 103 |
+
cluster_move_prob = 0.2 # Probability of performing a cluster move
|
| 104 |
+
cluster_size = 3 # Number of circles in a cluster (seed + 2 neighbors)
|
| 105 |
+
|
| 106 |
+
current_centers = np.copy(initial_centers)
|
| 107 |
+
current_radii = CirclePacker._compute_max_radii_static(current_centers, self.n)
|
| 108 |
+
current_sum_radii = np.sum(current_radii)
|
| 109 |
+
|
| 110 |
+
best_centers = np.copy(current_centers)
|
| 111 |
+
best_sum_radii = current_sum_radii
|
| 112 |
+
|
| 113 |
+
temp = sa_initial_temp
|
| 114 |
+
step_size = sa_initial_step_size
|
| 115 |
+
|
| 116 |
+
all_indices = np.arange(self.n)
|
| 117 |
+
|
| 118 |
+
# Parameters for dynamic step size adjustment
|
| 119 |
+
acceptance_window = 30
|
| 120 |
+
acceptance_count = 0
|
| 121 |
+
target_acceptance_rate = 0.44
|
| 122 |
+
adjustment_factor = 1.05
|
| 123 |
+
|
| 124 |
+
for i in range(sa_iterations):
|
| 125 |
+
trial_centers = np.copy(current_centers)
|
| 126 |
+
move = (np.random.rand(2) - 0.5) * 2 * step_size
|
| 127 |
+
|
| 128 |
+
# Decide which indices to move based on move type
|
| 129 |
+
if np.random.rand() < cluster_move_prob:
|
| 130 |
+
# --- Cluster Move ---
|
| 131 |
+
# Select a random seed and its nearest neighbors. This helps to
|
| 132 |
+
# perform coordinated shifts and escape grid-like local optima.
|
| 133 |
+
seed_idx = np.random.choice(all_indices)
|
| 134 |
+
distances = np.linalg.norm(current_centers - current_centers[seed_idx], axis=1)
|
| 135 |
+
indices_to_move = np.argsort(distances)[:cluster_size]
|
| 136 |
+
else:
|
| 137 |
+
# --- Single Move (stress-based) ---
|
| 138 |
+
# Prioritize moving circles with smaller radii (higher "stress").
|
| 139 |
+
inv_radii = 1.0 / (current_radii + 1e-9)
|
| 140 |
+
weights = (inv_radii - np.min(inv_radii))**2
|
| 141 |
+
if np.sum(weights) > 1e-9:
|
| 142 |
+
probabilities = weights / np.sum(weights)
|
| 143 |
+
idx_to_move = np.random.choice(all_indices, p=probabilities)
|
| 144 |
+
else:
|
| 145 |
+
idx_to_move = np.random.choice(all_indices)
|
| 146 |
+
indices_to_move = np.array([idx_to_move])
|
| 147 |
+
|
| 148 |
+
# Apply the move to the selected circle(s)
|
| 149 |
+
for idx in indices_to_move:
|
| 150 |
+
trial_centers[idx] = np.clip(trial_centers[idx] + move, 0.0, 1.0)
|
| 151 |
+
|
| 152 |
+
# --- Evaluate and accept/reject the move ---
|
| 153 |
+
trial_radii = CirclePacker._compute_max_radii_static(trial_centers, self.n)
|
| 154 |
+
trial_sum_radii = np.sum(trial_radii)
|
| 155 |
+
|
| 156 |
+
delta_energy = trial_sum_radii - current_sum_radii
|
| 157 |
+
if delta_energy > 0 or (temp > 1e-9 and np.random.rand() < np.exp(delta_energy / temp)):
|
| 158 |
+
current_centers = trial_centers
|
| 159 |
+
current_radii = trial_radii # Keep radii in sync for next iteration's stress calc
|
| 160 |
+
current_sum_radii = trial_sum_radii
|
| 161 |
+
acceptance_count += 1
|
| 162 |
+
|
| 163 |
+
if current_sum_radii > best_sum_radii:
|
| 164 |
+
best_sum_radii = current_sum_radii
|
| 165 |
+
best_centers = np.copy(current_centers)
|
| 166 |
+
|
| 167 |
+
# Periodically adjust step_size based on acceptance rate
|
| 168 |
+
if (i + 1) % acceptance_window == 0 and acceptance_window > 0:
|
| 169 |
+
acceptance_rate = acceptance_count / acceptance_window
|
| 170 |
+
if acceptance_rate > target_acceptance_rate:
|
| 171 |
+
step_size *= adjustment_factor
|
| 172 |
+
else:
|
| 173 |
+
step_size /= adjustment_factor
|
| 174 |
+
acceptance_count = 0
|
| 175 |
+
|
| 176 |
+
temp *= sa_cooling_rate
|
| 177 |
+
step_size = max(step_size * sa_cooling_rate, 1e-8)
|
| 178 |
+
|
| 179 |
+
return best_centers
|
| 180 |
+
>>>>>>> REPLACE
|
| 181 |
+
</DIFF>
|