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_circle_packing_WITHOUT_vision_20260114_070110/best/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/edit.diff +235 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/main.py +222 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/original.py +216 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/correct.json +4 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/job_log.err +9 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/job_log.out +18 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/metrics.json +16 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/search_replace.txt +93 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/main.py +94 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/correct.json +4 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/job_log.err +9 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/job_log.out +18 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/metrics.json +16 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/edit.diff +125 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/main.py +95 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/original.py +94 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/correct.json +4 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/job_log.err +9 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/job_log.out +18 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/metrics.json +16 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/search_replace.txt +185 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/edit.diff +145 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/main.py +124 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/original.py +128 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/correct.json +4 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/job_log.err +9 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/job_log.out +18 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/metrics.json +16 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/search_replace.txt +60 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/edit.diff +508 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/main.py +250 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/original.py +306 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/results/job_log.err +19 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/results/job_log.out +2 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/rewrite.txt +241 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/__pycache__/main.cpython-311.pyc +0 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/edit.diff +457 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/main.py +315 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/original.py +283 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/correct.json +4 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/job_log.err +9 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/job_log.out +18 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/metrics.json +16 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/rewrite.txt +306 -0
- examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_102/__pycache__/main.cpython-311.pyc +0 -0
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/edit.diff
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,216 +1,222 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
import math
|
| 7 |
+
import random
|
| 8 |
+
|
| 9 |
+
def compute_max_radii(centers, max_iter=500, convergence_threshold=1e-7):
|
| 10 |
+
"""
|
| 11 |
+
Compute maximum radii using iterative proportional scaling.
|
| 12 |
+
This version is optimized for speed during search, with a final high-precision call.
|
| 13 |
+
"""
|
| 14 |
+
n = centers.shape[0]
|
| 15 |
+
if n == 0:
|
| 16 |
+
return np.array([])
|
| 17 |
+
|
| 18 |
+
radii = np.min([
|
| 19 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 20 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 21 |
+
], axis=0)
|
| 22 |
+
|
| 23 |
+
for _ in range(max_iter):
|
| 24 |
+
old_radii = radii.copy()
|
| 25 |
+
updated = False
|
| 26 |
+
for i in range(n):
|
| 27 |
+
for j in range(i + 1, n):
|
| 28 |
+
dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 29 |
+
dist = np.sqrt(dist_sq)
|
| 30 |
+
|
| 31 |
+
if radii[i] + radii[j] > dist:
|
| 32 |
+
if dist < 1e-9:
|
| 33 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 34 |
+
else:
|
| 35 |
+
scale = dist / (radii[i] + radii[j])
|
| 36 |
+
radii[i] *= scale
|
| 37 |
+
radii[j] *= scale
|
| 38 |
+
updated = True
|
| 39 |
+
|
| 40 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 41 |
+
break
|
| 42 |
+
|
| 43 |
+
return np.maximum(radii, 0)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class SimulatedAnnealer:
|
| 47 |
+
"""
|
| 48 |
+
Performs a search for an optimal circle packing using Simulated Annealing.
|
| 49 |
+
"""
|
| 50 |
+
def __init__(self, n, initial_centers, config):
|
| 51 |
+
self.n = n
|
| 52 |
+
self.config = config
|
| 53 |
+
self.centers = initial_centers
|
| 54 |
+
|
| 55 |
+
self.temp = config['t_start']
|
| 56 |
+
self.max_step_size = config['max_step_size']
|
| 57 |
+
|
| 58 |
+
self.energy = self._calculate_energy(self.centers)
|
| 59 |
+
self.best_centers = self.centers.copy()
|
| 60 |
+
self.best_energy = self.energy
|
| 61 |
+
|
| 62 |
+
self.move_weights = config['move_weights']
|
| 63 |
+
self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles]
|
| 64 |
+
|
| 65 |
+
def _calculate_energy(self, centers):
|
| 66 |
+
"""Energy is the negative sum of radii, to be minimized."""
|
| 67 |
+
radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 68 |
+
return -np.sum(radii)
|
| 69 |
+
|
| 70 |
+
def _propose_move(self):
|
| 71 |
+
"""
|
| 72 |
+
Proposes a new state by selecting a move type and applying it.
|
| 73 |
+
The step size is annealed with the temperature.
|
| 74 |
+
"""
|
| 75 |
+
# Choose a move type based on weights
|
| 76 |
+
move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 77 |
+
|
| 78 |
+
# Anneal step size
|
| 79 |
+
t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 80 |
+
current_step_size = self.max_step_size * t_progress
|
| 81 |
+
|
| 82 |
+
return move_func(current_step_size)
|
| 83 |
+
|
| 84 |
+
def _move_single_circle(self, step_size):
|
| 85 |
+
"""Move a single randomly chosen circle."""
|
| 86 |
+
new_centers = self.centers.copy()
|
| 87 |
+
idx = random.randint(0, self.n - 1)
|
| 88 |
+
|
| 89 |
+
displacement = np.random.normal(0, step_size, size=2)
|
| 90 |
+
new_centers[idx] += displacement
|
| 91 |
+
new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 92 |
+
return new_centers
|
| 93 |
+
|
| 94 |
+
def _move_cluster(self, step_size):
|
| 95 |
+
"""Move a small cluster of neighboring circles."""
|
| 96 |
+
new_centers = self.centers.copy()
|
| 97 |
+
cluster_size = self.config['cluster_size']
|
| 98 |
+
|
| 99 |
+
# Pick a seed circle
|
| 100 |
+
seed_idx = random.randint(0, self.n - 1)
|
| 101 |
+
|
| 102 |
+
# Find its neighbors
|
| 103 |
+
dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 104 |
+
neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 105 |
+
|
| 106 |
+
# Apply a common displacement
|
| 107 |
+
displacement = np.random.normal(0, step_size * 0.5, size=2) # Smaller step for clusters
|
| 108 |
+
new_centers[neighbor_indices] += displacement
|
| 109 |
+
new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 110 |
+
return new_centers
|
| 111 |
+
|
| 112 |
+
def _swap_circles(self, step_size):
|
| 113 |
+
"""Swap the positions of two randomly chosen circles."""
|
| 114 |
+
new_centers = self.centers.copy()
|
| 115 |
+
if self.n < 2: return new_centers
|
| 116 |
+
|
| 117 |
+
idx1, idx2 = random.sample(range(self.n), 2)
|
| 118 |
+
new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 119 |
+
return new_centers
|
| 120 |
+
|
| 121 |
+
def run(self):
|
| 122 |
+
"""Executes the simulated annealing search."""
|
| 123 |
+
while self.temp > self.config['t_end']:
|
| 124 |
+
for _ in range(self.config['moves_per_temp']):
|
| 125 |
+
# Propose a new configuration
|
| 126 |
+
new_centers = self._propose_move()
|
| 127 |
+
|
| 128 |
+
# Calculate its energy
|
| 129 |
+
new_energy = self._calculate_energy(new_centers)
|
| 130 |
+
|
| 131 |
+
delta_e = new_energy - self.energy
|
| 132 |
+
|
| 133 |
+
# Acceptance criterion
|
| 134 |
+
if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 135 |
+
self.centers = new_centers
|
| 136 |
+
self.energy = new_energy
|
| 137 |
+
|
| 138 |
+
# Update best-ever solution
|
| 139 |
+
if self.energy < self.best_energy:
|
| 140 |
+
self.best_energy = self.energy
|
| 141 |
+
self.best_centers = self.centers.copy()
|
| 142 |
+
|
| 143 |
+
# Cool down the temperature
|
| 144 |
+
self.temp *= self.config['cooling_rate']
|
| 145 |
+
|
| 146 |
+
return self.best_centers
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def construct_packing():
|
| 150 |
+
"""
|
| 151 |
+
Constructs a packing of 26 circles using a multi-start Simulated Annealing approach.
|
| 152 |
+
"""
|
| 153 |
+
n = 26
|
| 154 |
+
config = {
|
| 155 |
+
- 'num_starts': 12, # Number of independent SA runs
|
| 156 |
+
- 't_start': 0.05, # Initial temperature
|
| 157 |
+
+ 'num_starts': 16, # More starts to explore different basins
|
| 158 |
+
+ 't_start': 0.06, # Higher initial temperature for more exploration
|
| 159 |
+
't_end': 1e-6, # Final temperature
|
| 160 |
+
- 'cooling_rate': 0.998, # Geometric cooling factor
|
| 161 |
+
- 'moves_per_temp': 60, # Number of moves at each temperature step
|
| 162 |
+
- 'max_step_size': 0.15, # Max displacement for a single circle move at T_start
|
| 163 |
+
+ 'cooling_rate': 0.999, # Slower cooling for more thorough search
|
| 164 |
+
+ 'moves_per_temp': 50, # Adjusted moves to balance longer schedule
|
| 165 |
+
+ 'max_step_size': 0.20, # Larger initial step size
|
| 166 |
+
'radius_iter': 250, # Radius calculation iterations during search
|
| 167 |
+
- 'move_weights': [0.75, 0.20, 0.05], # Probabilities for [single, cluster, swap]
|
| 168 |
+
- 'cluster_size': 4,
|
| 169 |
+
+ 'move_weights': [0.70, 0.20, 0.10], # Increased probability for powerful swap move
|
| 170 |
+
+ 'cluster_size': 5, # Slightly larger clusters
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
best_overall_centers = None
|
| 174 |
+
best_overall_score = -1.0
|
| 175 |
+
|
| 176 |
+
# Multi-start loop
|
| 177 |
+
for i in range(config['num_starts']):
|
| 178 |
+
# --- Generate diverse initial configurations ---
|
| 179 |
+
initial_centers = np.zeros((n, 2))
|
| 180 |
+
- if i < config['num_starts'] // 2:
|
| 181 |
+
+ # Prioritize structured starts (12 of 16), which are generally more promising
|
| 182 |
+
+ if i < 12:
|
| 183 |
+
# Start with a perturbed 5x5 grid + 1 extra circle
|
| 184 |
+
num_cells_side = 5
|
| 185 |
+
spacing = 1.0 / num_cells_side
|
| 186 |
+
k = 0
|
| 187 |
+
for row in range(num_cells_side):
|
| 188 |
+
for col in range(num_cells_side):
|
| 189 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 190 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 191 |
+
k += 1
|
| 192 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 193 |
+
|
| 194 |
+
- # Place the 26th circle in a strategic corner or center
|
| 195 |
+
- extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 196 |
+
+ # Use a richer set of candidate positions for the 26th circle
|
| 197 |
+
+ extra_pos_candidates = [
|
| 198 |
+
+ [0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], # Corners (strong)
|
| 199 |
+
+ [0.5, 0.5], # Center
|
| 200 |
+
+ [0.2, 0.2], [0.8, 0.2], [0.2, 0.8], [0.8, 0.8], # Grid interstitial (spacing=0.2)
|
| 201 |
+
+ [0.5, 0.15], [0.15, 0.5], [0.85, 0.5], [0.5, 0.85] # Edge centers (offset)
|
| 202 |
+
+ ]
|
| 203 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 204 |
+
initial_centers = np.clip(initial_centers, 0.0, 1.0)
|
| 205 |
+
else:
|
| 206 |
+
# Start with a purely random configuration
|
| 207 |
+
initial_centers = np.random.rand(n, 2)
|
| 208 |
+
|
| 209 |
+
# Run the annealer for this start
|
| 210 |
+
solver = SimulatedAnnealer(n=n, initial_centers=initial_centers, config=config)
|
| 211 |
+
result_centers = solver.run()
|
| 212 |
+
|
| 213 |
+
# Evaluate and update the best overall result
|
| 214 |
+
radii = compute_max_radii(result_centers, max_iter=2000)
|
| 215 |
+
score = np.sum(radii)
|
| 216 |
+
|
| 217 |
+
if score > best_overall_score:
|
| 218 |
+
best_overall_score = score
|
| 219 |
+
best_overall_centers = result_centers
|
| 220 |
+
|
| 221 |
+
# Final high-precision radius calculation for the best solution found
|
| 222 |
+
final_radii = compute_max_radii(best_overall_centers, max_iter=2000)
|
| 223 |
+
|
| 224 |
+
return best_overall_centers, final_radii
|
| 225 |
+
|
| 226 |
+
# EVOLVE-BLOCK-END
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
# This part remains fixed (not evolved)
|
| 230 |
+
def run_packing():
|
| 231 |
+
"""Run the circle packing constructor for n=26"""
|
| 232 |
+
centers, radii = construct_packing()
|
| 233 |
+
# Calculate the sum of radii
|
| 234 |
+
sum_radii = np.sum(radii)
|
| 235 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/main.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
|
| 6 |
+
def compute_max_radii(centers, max_iter=500, convergence_threshold=1e-7):
|
| 7 |
+
"""
|
| 8 |
+
Compute maximum radii using iterative proportional scaling.
|
| 9 |
+
This version is optimized for speed during search, with a final high-precision call.
|
| 10 |
+
"""
|
| 11 |
+
n = centers.shape[0]
|
| 12 |
+
if n == 0:
|
| 13 |
+
return np.array([])
|
| 14 |
+
|
| 15 |
+
radii = np.min([
|
| 16 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 17 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 18 |
+
], axis=0)
|
| 19 |
+
|
| 20 |
+
for _ in range(max_iter):
|
| 21 |
+
old_radii = radii.copy()
|
| 22 |
+
updated = False
|
| 23 |
+
for i in range(n):
|
| 24 |
+
for j in range(i + 1, n):
|
| 25 |
+
dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 26 |
+
dist = np.sqrt(dist_sq)
|
| 27 |
+
|
| 28 |
+
if radii[i] + radii[j] > dist:
|
| 29 |
+
if dist < 1e-9:
|
| 30 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 31 |
+
else:
|
| 32 |
+
scale = dist / (radii[i] + radii[j])
|
| 33 |
+
radii[i] *= scale
|
| 34 |
+
radii[j] *= scale
|
| 35 |
+
updated = True
|
| 36 |
+
|
| 37 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 38 |
+
break
|
| 39 |
+
|
| 40 |
+
return np.maximum(radii, 0)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class SimulatedAnnealer:
|
| 44 |
+
"""
|
| 45 |
+
Performs a search for an optimal circle packing using Simulated Annealing.
|
| 46 |
+
"""
|
| 47 |
+
def __init__(self, n, initial_centers, config):
|
| 48 |
+
self.n = n
|
| 49 |
+
self.config = config
|
| 50 |
+
self.centers = initial_centers
|
| 51 |
+
|
| 52 |
+
self.temp = config['t_start']
|
| 53 |
+
self.max_step_size = config['max_step_size']
|
| 54 |
+
|
| 55 |
+
self.energy = self._calculate_energy(self.centers)
|
| 56 |
+
self.best_centers = self.centers.copy()
|
| 57 |
+
self.best_energy = self.energy
|
| 58 |
+
|
| 59 |
+
self.move_weights = config['move_weights']
|
| 60 |
+
self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles]
|
| 61 |
+
|
| 62 |
+
def _calculate_energy(self, centers):
|
| 63 |
+
"""Energy is the negative sum of radii, to be minimized."""
|
| 64 |
+
radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 65 |
+
return -np.sum(radii)
|
| 66 |
+
|
| 67 |
+
def _propose_move(self):
|
| 68 |
+
"""
|
| 69 |
+
Proposes a new state by selecting a move type and applying it.
|
| 70 |
+
The step size is annealed with the temperature.
|
| 71 |
+
"""
|
| 72 |
+
# Choose a move type based on weights
|
| 73 |
+
move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 74 |
+
|
| 75 |
+
# Anneal step size
|
| 76 |
+
t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 77 |
+
current_step_size = self.max_step_size * t_progress
|
| 78 |
+
|
| 79 |
+
return move_func(current_step_size)
|
| 80 |
+
|
| 81 |
+
def _move_single_circle(self, step_size):
|
| 82 |
+
"""Move a single randomly chosen circle."""
|
| 83 |
+
new_centers = self.centers.copy()
|
| 84 |
+
idx = random.randint(0, self.n - 1)
|
| 85 |
+
|
| 86 |
+
displacement = np.random.normal(0, step_size, size=2)
|
| 87 |
+
new_centers[idx] += displacement
|
| 88 |
+
new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 89 |
+
return new_centers
|
| 90 |
+
|
| 91 |
+
def _move_cluster(self, step_size):
|
| 92 |
+
"""Move a small cluster of neighboring circles."""
|
| 93 |
+
new_centers = self.centers.copy()
|
| 94 |
+
cluster_size = self.config['cluster_size']
|
| 95 |
+
|
| 96 |
+
# Pick a seed circle
|
| 97 |
+
seed_idx = random.randint(0, self.n - 1)
|
| 98 |
+
|
| 99 |
+
# Find its neighbors
|
| 100 |
+
dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 101 |
+
neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 102 |
+
|
| 103 |
+
# Apply a common displacement
|
| 104 |
+
displacement = np.random.normal(0, step_size * 0.5, size=2) # Smaller step for clusters
|
| 105 |
+
new_centers[neighbor_indices] += displacement
|
| 106 |
+
new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 107 |
+
return new_centers
|
| 108 |
+
|
| 109 |
+
def _swap_circles(self, step_size):
|
| 110 |
+
"""Swap the positions of two randomly chosen circles."""
|
| 111 |
+
new_centers = self.centers.copy()
|
| 112 |
+
if self.n < 2: return new_centers
|
| 113 |
+
|
| 114 |
+
idx1, idx2 = random.sample(range(self.n), 2)
|
| 115 |
+
new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 116 |
+
return new_centers
|
| 117 |
+
|
| 118 |
+
def run(self):
|
| 119 |
+
"""Executes the simulated annealing search."""
|
| 120 |
+
while self.temp > self.config['t_end']:
|
| 121 |
+
for _ in range(self.config['moves_per_temp']):
|
| 122 |
+
# Propose a new configuration
|
| 123 |
+
new_centers = self._propose_move()
|
| 124 |
+
|
| 125 |
+
# Calculate its energy
|
| 126 |
+
new_energy = self._calculate_energy(new_centers)
|
| 127 |
+
|
| 128 |
+
delta_e = new_energy - self.energy
|
| 129 |
+
|
| 130 |
+
# Acceptance criterion
|
| 131 |
+
if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 132 |
+
self.centers = new_centers
|
| 133 |
+
self.energy = new_energy
|
| 134 |
+
|
| 135 |
+
# Update best-ever solution
|
| 136 |
+
if self.energy < self.best_energy:
|
| 137 |
+
self.best_energy = self.energy
|
| 138 |
+
self.best_centers = self.centers.copy()
|
| 139 |
+
|
| 140 |
+
# Cool down the temperature
|
| 141 |
+
self.temp *= self.config['cooling_rate']
|
| 142 |
+
|
| 143 |
+
return self.best_centers
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def construct_packing():
|
| 147 |
+
"""
|
| 148 |
+
Constructs a packing of 26 circles using a multi-start Simulated Annealing approach.
|
| 149 |
+
"""
|
| 150 |
+
n = 26
|
| 151 |
+
config = {
|
| 152 |
+
'num_starts': 16, # More starts to explore different basins
|
| 153 |
+
't_start': 0.06, # Higher initial temperature for more exploration
|
| 154 |
+
't_end': 1e-6, # Final temperature
|
| 155 |
+
'cooling_rate': 0.999, # Slower cooling for more thorough search
|
| 156 |
+
'moves_per_temp': 50, # Adjusted moves to balance longer schedule
|
| 157 |
+
'max_step_size': 0.20, # Larger initial step size
|
| 158 |
+
'radius_iter': 250, # Radius calculation iterations during search
|
| 159 |
+
'move_weights': [0.70, 0.20, 0.10], # Increased probability for powerful swap move
|
| 160 |
+
'cluster_size': 5, # Slightly larger clusters
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
best_overall_centers = None
|
| 164 |
+
best_overall_score = -1.0
|
| 165 |
+
|
| 166 |
+
# Multi-start loop
|
| 167 |
+
for i in range(config['num_starts']):
|
| 168 |
+
# --- Generate diverse initial configurations ---
|
| 169 |
+
initial_centers = np.zeros((n, 2))
|
| 170 |
+
# Prioritize structured starts (12 of 16), which are generally more promising
|
| 171 |
+
if i < 12:
|
| 172 |
+
# Start with a perturbed 5x5 grid + 1 extra circle
|
| 173 |
+
num_cells_side = 5
|
| 174 |
+
spacing = 1.0 / num_cells_side
|
| 175 |
+
k = 0
|
| 176 |
+
for row in range(num_cells_side):
|
| 177 |
+
for col in range(num_cells_side):
|
| 178 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 179 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 180 |
+
k += 1
|
| 181 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 182 |
+
|
| 183 |
+
# Use a richer set of candidate positions for the 26th circle
|
| 184 |
+
extra_pos_candidates = [
|
| 185 |
+
[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], # Corners (strong)
|
| 186 |
+
[0.5, 0.5], # Center
|
| 187 |
+
[0.2, 0.2], [0.8, 0.2], [0.2, 0.8], [0.8, 0.8], # Grid interstitial (spacing=0.2)
|
| 188 |
+
[0.5, 0.15], [0.15, 0.5], [0.85, 0.5], [0.5, 0.85] # Edge centers (offset)
|
| 189 |
+
]
|
| 190 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 191 |
+
initial_centers = np.clip(initial_centers, 0.0, 1.0)
|
| 192 |
+
else:
|
| 193 |
+
# Start with a purely random configuration
|
| 194 |
+
initial_centers = np.random.rand(n, 2)
|
| 195 |
+
|
| 196 |
+
# Run the annealer for this start
|
| 197 |
+
solver = SimulatedAnnealer(n=n, initial_centers=initial_centers, config=config)
|
| 198 |
+
result_centers = solver.run()
|
| 199 |
+
|
| 200 |
+
# Evaluate and update the best overall result
|
| 201 |
+
radii = compute_max_radii(result_centers, max_iter=2000)
|
| 202 |
+
score = np.sum(radii)
|
| 203 |
+
|
| 204 |
+
if score > best_overall_score:
|
| 205 |
+
best_overall_score = score
|
| 206 |
+
best_overall_centers = result_centers
|
| 207 |
+
|
| 208 |
+
# Final high-precision radius calculation for the best solution found
|
| 209 |
+
final_radii = compute_max_radii(best_overall_centers, max_iter=2000)
|
| 210 |
+
|
| 211 |
+
return best_overall_centers, final_radii
|
| 212 |
+
|
| 213 |
+
# EVOLVE-BLOCK-END
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# This part remains fixed (not evolved)
|
| 217 |
+
def run_packing():
|
| 218 |
+
"""Run the circle packing constructor for n=26"""
|
| 219 |
+
centers, radii = construct_packing()
|
| 220 |
+
# Calculate the sum of radii
|
| 221 |
+
sum_radii = np.sum(radii)
|
| 222 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/original.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
|
| 6 |
+
def compute_max_radii(centers, max_iter=500, convergence_threshold=1e-7):
|
| 7 |
+
"""
|
| 8 |
+
Compute maximum radii using iterative proportional scaling.
|
| 9 |
+
This version is optimized for speed during search, with a final high-precision call.
|
| 10 |
+
"""
|
| 11 |
+
n = centers.shape[0]
|
| 12 |
+
if n == 0:
|
| 13 |
+
return np.array([])
|
| 14 |
+
|
| 15 |
+
radii = np.min([
|
| 16 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 17 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 18 |
+
], axis=0)
|
| 19 |
+
|
| 20 |
+
for _ in range(max_iter):
|
| 21 |
+
old_radii = radii.copy()
|
| 22 |
+
updated = False
|
| 23 |
+
for i in range(n):
|
| 24 |
+
for j in range(i + 1, n):
|
| 25 |
+
dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 26 |
+
dist = np.sqrt(dist_sq)
|
| 27 |
+
|
| 28 |
+
if radii[i] + radii[j] > dist:
|
| 29 |
+
if dist < 1e-9:
|
| 30 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 31 |
+
else:
|
| 32 |
+
scale = dist / (radii[i] + radii[j])
|
| 33 |
+
radii[i] *= scale
|
| 34 |
+
radii[j] *= scale
|
| 35 |
+
updated = True
|
| 36 |
+
|
| 37 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 38 |
+
break
|
| 39 |
+
|
| 40 |
+
return np.maximum(radii, 0)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class SimulatedAnnealer:
|
| 44 |
+
"""
|
| 45 |
+
Performs a search for an optimal circle packing using Simulated Annealing.
|
| 46 |
+
"""
|
| 47 |
+
def __init__(self, n, initial_centers, config):
|
| 48 |
+
self.n = n
|
| 49 |
+
self.config = config
|
| 50 |
+
self.centers = initial_centers
|
| 51 |
+
|
| 52 |
+
self.temp = config['t_start']
|
| 53 |
+
self.max_step_size = config['max_step_size']
|
| 54 |
+
|
| 55 |
+
self.energy = self._calculate_energy(self.centers)
|
| 56 |
+
self.best_centers = self.centers.copy()
|
| 57 |
+
self.best_energy = self.energy
|
| 58 |
+
|
| 59 |
+
self.move_weights = config['move_weights']
|
| 60 |
+
self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles]
|
| 61 |
+
|
| 62 |
+
def _calculate_energy(self, centers):
|
| 63 |
+
"""Energy is the negative sum of radii, to be minimized."""
|
| 64 |
+
radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 65 |
+
return -np.sum(radii)
|
| 66 |
+
|
| 67 |
+
def _propose_move(self):
|
| 68 |
+
"""
|
| 69 |
+
Proposes a new state by selecting a move type and applying it.
|
| 70 |
+
The step size is annealed with the temperature.
|
| 71 |
+
"""
|
| 72 |
+
# Choose a move type based on weights
|
| 73 |
+
move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 74 |
+
|
| 75 |
+
# Anneal step size
|
| 76 |
+
t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 77 |
+
current_step_size = self.max_step_size * t_progress
|
| 78 |
+
|
| 79 |
+
return move_func(current_step_size)
|
| 80 |
+
|
| 81 |
+
def _move_single_circle(self, step_size):
|
| 82 |
+
"""Move a single randomly chosen circle."""
|
| 83 |
+
new_centers = self.centers.copy()
|
| 84 |
+
idx = random.randint(0, self.n - 1)
|
| 85 |
+
|
| 86 |
+
displacement = np.random.normal(0, step_size, size=2)
|
| 87 |
+
new_centers[idx] += displacement
|
| 88 |
+
new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 89 |
+
return new_centers
|
| 90 |
+
|
| 91 |
+
def _move_cluster(self, step_size):
|
| 92 |
+
"""Move a small cluster of neighboring circles."""
|
| 93 |
+
new_centers = self.centers.copy()
|
| 94 |
+
cluster_size = self.config['cluster_size']
|
| 95 |
+
|
| 96 |
+
# Pick a seed circle
|
| 97 |
+
seed_idx = random.randint(0, self.n - 1)
|
| 98 |
+
|
| 99 |
+
# Find its neighbors
|
| 100 |
+
dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 101 |
+
neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 102 |
+
|
| 103 |
+
# Apply a common displacement
|
| 104 |
+
displacement = np.random.normal(0, step_size * 0.5, size=2) # Smaller step for clusters
|
| 105 |
+
new_centers[neighbor_indices] += displacement
|
| 106 |
+
new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 107 |
+
return new_centers
|
| 108 |
+
|
| 109 |
+
def _swap_circles(self, step_size):
|
| 110 |
+
"""Swap the positions of two randomly chosen circles."""
|
| 111 |
+
new_centers = self.centers.copy()
|
| 112 |
+
if self.n < 2: return new_centers
|
| 113 |
+
|
| 114 |
+
idx1, idx2 = random.sample(range(self.n), 2)
|
| 115 |
+
new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 116 |
+
return new_centers
|
| 117 |
+
|
| 118 |
+
def run(self):
|
| 119 |
+
"""Executes the simulated annealing search."""
|
| 120 |
+
while self.temp > self.config['t_end']:
|
| 121 |
+
for _ in range(self.config['moves_per_temp']):
|
| 122 |
+
# Propose a new configuration
|
| 123 |
+
new_centers = self._propose_move()
|
| 124 |
+
|
| 125 |
+
# Calculate its energy
|
| 126 |
+
new_energy = self._calculate_energy(new_centers)
|
| 127 |
+
|
| 128 |
+
delta_e = new_energy - self.energy
|
| 129 |
+
|
| 130 |
+
# Acceptance criterion
|
| 131 |
+
if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 132 |
+
self.centers = new_centers
|
| 133 |
+
self.energy = new_energy
|
| 134 |
+
|
| 135 |
+
# Update best-ever solution
|
| 136 |
+
if self.energy < self.best_energy:
|
| 137 |
+
self.best_energy = self.energy
|
| 138 |
+
self.best_centers = self.centers.copy()
|
| 139 |
+
|
| 140 |
+
# Cool down the temperature
|
| 141 |
+
self.temp *= self.config['cooling_rate']
|
| 142 |
+
|
| 143 |
+
return self.best_centers
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def construct_packing():
|
| 147 |
+
"""
|
| 148 |
+
Constructs a packing of 26 circles using a multi-start Simulated Annealing approach.
|
| 149 |
+
"""
|
| 150 |
+
n = 26
|
| 151 |
+
config = {
|
| 152 |
+
'num_starts': 12, # Number of independent SA runs
|
| 153 |
+
't_start': 0.05, # Initial temperature
|
| 154 |
+
't_end': 1e-6, # Final temperature
|
| 155 |
+
'cooling_rate': 0.998, # Geometric cooling factor
|
| 156 |
+
'moves_per_temp': 60, # Number of moves at each temperature step
|
| 157 |
+
'max_step_size': 0.15, # Max displacement for a single circle move at T_start
|
| 158 |
+
'radius_iter': 250, # Radius calculation iterations during search
|
| 159 |
+
'move_weights': [0.75, 0.20, 0.05], # Probabilities for [single, cluster, swap]
|
| 160 |
+
'cluster_size': 4,
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
best_overall_centers = None
|
| 164 |
+
best_overall_score = -1.0
|
| 165 |
+
|
| 166 |
+
# Multi-start loop
|
| 167 |
+
for i in range(config['num_starts']):
|
| 168 |
+
# --- Generate diverse initial configurations ---
|
| 169 |
+
initial_centers = np.zeros((n, 2))
|
| 170 |
+
if i < config['num_starts'] // 2:
|
| 171 |
+
# Start with a perturbed 5x5 grid + 1 extra circle
|
| 172 |
+
num_cells_side = 5
|
| 173 |
+
spacing = 1.0 / num_cells_side
|
| 174 |
+
k = 0
|
| 175 |
+
for row in range(num_cells_side):
|
| 176 |
+
for col in range(num_cells_side):
|
| 177 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 178 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 179 |
+
k += 1
|
| 180 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 181 |
+
|
| 182 |
+
# Place the 26th circle in a strategic corner or center
|
| 183 |
+
extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 184 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 185 |
+
initial_centers = np.clip(initial_centers, 0.0, 1.0)
|
| 186 |
+
else:
|
| 187 |
+
# Start with a purely random configuration
|
| 188 |
+
initial_centers = np.random.rand(n, 2)
|
| 189 |
+
|
| 190 |
+
# Run the annealer for this start
|
| 191 |
+
solver = SimulatedAnnealer(n=n, initial_centers=initial_centers, config=config)
|
| 192 |
+
result_centers = solver.run()
|
| 193 |
+
|
| 194 |
+
# Evaluate and update the best overall result
|
| 195 |
+
radii = compute_max_radii(result_centers, max_iter=2000)
|
| 196 |
+
score = np.sum(radii)
|
| 197 |
+
|
| 198 |
+
if score > best_overall_score:
|
| 199 |
+
best_overall_score = score
|
| 200 |
+
best_overall_centers = result_centers
|
| 201 |
+
|
| 202 |
+
# Final high-precision radius calculation for the best solution found
|
| 203 |
+
final_radii = compute_max_radii(best_overall_centers, max_iter=2000)
|
| 204 |
+
|
| 205 |
+
return best_overall_centers, final_radii
|
| 206 |
+
|
| 207 |
+
# EVOLVE-BLOCK-END
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
# This part remains fixed (not evolved)
|
| 211 |
+
def run_packing():
|
| 212 |
+
"""Run the circle packing constructor for n=26"""
|
| 213 |
+
centers, radii = construct_packing()
|
| 214 |
+
# Calculate the sum of radii
|
| 215 |
+
sum_radii = np.sum(radii)
|
| 216 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/job_log.err
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/job_log.out
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results
|
| 3 |
+
Run 1/1 completed in 32125.27 seconds
|
| 4 |
+
Detailed packing data saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/extra.npz
|
| 5 |
+
Visualization saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/packing_viz.png
|
| 6 |
+
Correctness and error status saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/correct.json
|
| 7 |
+
Metrics saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/metrics.json
|
| 8 |
+
Evaluation and Validation completed successfully.
|
| 9 |
+
Metrics:
|
| 10 |
+
combined_score: 2.5604436893300666
|
| 11 |
+
public: {'centers_str': ' centers[0] = (0.4791, 0.5712)\n centers[1] = (0.2653, 0.7270)\n centers[2] = (0.6797, 0.7584)\n centers[3] = (0.6748, 0.3125)\n centers[4] = (0.4032, 0.2676)\n centers[5] = (0.2807, 0.5100)\n centers[6] = (0.8859, 0.4508)\n centers[7] = (0.0798, 0.0798)\n centers[8] = (0.6921, 0.5575)\n centers[9] = (0.5696, 0.0994)\n centers[10] = (0.9005, 0.2376)\n centers[11] = (0.9297, 0.0703)\n centers[12] = (0.5538, 0.9026)\n centers[13] = (0.3512, 0.8946)\n centers[14] = (0.5075, 0.4749)\n centers[15] = (0.7652, 0.0962)\n centers[16] = (0.0985, 0.6553)\n centers[17] = (0.4703, 0.6972)\n centers[18] = (0.7273, 0.9228)\n centers[19] = (0.2524, 0.0933)\n centers[20] = (0.0739, 0.4847)\n centers[21] = (0.1233, 0.8767)\n centers[22] = (0.8802, 0.6845)\n centers[23] = (0.9016, 0.9016)\n centers[24] = (0.1334, 0.2862)\n centers[25] = (0.4085, 0.0653)', 'num_circles': 26}
|
| 12 |
+
private: {'reported_sum_of_radii': 2.5604436893300666}
|
| 13 |
+
visualization_path: results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/packing_viz.png
|
| 14 |
+
execution_time_mean: 32125.269347094
|
| 15 |
+
execution_time_std: 0.0
|
| 16 |
+
num_valid_runs: 1
|
| 17 |
+
num_invalid_runs: 0
|
| 18 |
+
all_validation_errors: []
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/results/metrics.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 2.5604436893300666,
|
| 3 |
+
"public": {
|
| 4 |
+
"centers_str": " centers[0] = (0.4791, 0.5712)\n centers[1] = (0.2653, 0.7270)\n centers[2] = (0.6797, 0.7584)\n centers[3] = (0.6748, 0.3125)\n centers[4] = (0.4032, 0.2676)\n centers[5] = (0.2807, 0.5100)\n centers[6] = (0.8859, 0.4508)\n centers[7] = (0.0798, 0.0798)\n centers[8] = (0.6921, 0.5575)\n centers[9] = (0.5696, 0.0994)\n centers[10] = (0.9005, 0.2376)\n centers[11] = (0.9297, 0.0703)\n centers[12] = (0.5538, 0.9026)\n centers[13] = (0.3512, 0.8946)\n centers[14] = (0.5075, 0.4749)\n centers[15] = (0.7652, 0.0962)\n centers[16] = (0.0985, 0.6553)\n centers[17] = (0.4703, 0.6972)\n centers[18] = (0.7273, 0.9228)\n centers[19] = (0.2524, 0.0933)\n centers[20] = (0.0739, 0.4847)\n centers[21] = (0.1233, 0.8767)\n centers[22] = (0.8802, 0.6845)\n centers[23] = (0.9016, 0.9016)\n centers[24] = (0.1334, 0.2862)\n centers[25] = (0.4085, 0.0653)",
|
| 5 |
+
"num_circles": 26
|
| 6 |
+
},
|
| 7 |
+
"private": {
|
| 8 |
+
"reported_sum_of_radii": 2.5604436893300666
|
| 9 |
+
},
|
| 10 |
+
"visualization_path": "results_circle_packing_WITHOUT_vision_20260114_070110/gen_106/results/packing_viz.png",
|
| 11 |
+
"execution_time_mean": 32125.269347094,
|
| 12 |
+
"execution_time_std": 0.0,
|
| 13 |
+
"num_valid_runs": 1,
|
| 14 |
+
"num_invalid_runs": 0,
|
| 15 |
+
"all_validation_errors": []
|
| 16 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/best/search_replace.txt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Based on my analysis of the current simulated annealing approach and the problem's geometry, I propose two key improvements. First, I'll refine the annealing schedule and move set to encourage a longer, more thorough, and globally-aware search. Second, I will diversify the initial configurations by increasing the number of structured starts and expanding the candidate positions for the 26th circle, drawing inspiration from previously successful Memetic Algorithms. These changes aim to better explore the search space and increase the probability of discovering a superior packing configuration.
|
| 2 |
+
|
| 3 |
+
<NAME>
|
| 4 |
+
refined_annealing_schedule
|
| 5 |
+
</NAME>
|
| 6 |
+
<DESCRIPTION>
|
| 7 |
+
This change adjusts the simulated annealing hyperparameters to promote a more extensive and effective search.
|
| 8 |
+
- **`num_starts`** is increased from 12 to 16 to explore more regions of the solution space.
|
| 9 |
+
- **`t_start`** is raised to 0.06 and **`max_step_size`** to 0.20 to facilitate more aggressive exploration at the beginning of the search, helping to break out of initial local optima.
|
| 10 |
+
- **`cooling_rate`** is increased to 0.999 for a slower, more patient annealing process, allowing the system more time to settle into better configurations at each temperature.
|
| 11 |
+
- **`moves_per_temp`** is slightly reduced to balance the substantially longer schedule.
|
| 12 |
+
- **`move_weights`** are adjusted to `[0.70, 0.20, 0.10]`, doubling the probability of the powerful global `swap` move, which can be crucial for rearranging trapped circles.
|
| 13 |
+
- **`cluster_size`** is increased to 5 to allow for slightly larger correlated movements.
|
| 14 |
+
Collectively, these changes create a more robust annealing process capable of finding higher-quality solutions.
|
| 15 |
+
</DESCRIPTION>
|
| 16 |
+
<DIFF>
|
| 17 |
+
<<<<<<< SEARCH
|
| 18 |
+
config = {
|
| 19 |
+
'num_starts': 12, # Number of independent SA runs
|
| 20 |
+
't_start': 0.05, # Initial temperature
|
| 21 |
+
't_end': 1e-6, # Final temperature
|
| 22 |
+
'cooling_rate': 0.998, # Geometric cooling factor
|
| 23 |
+
'moves_per_temp': 60, # Number of moves at each temperature step
|
| 24 |
+
'max_step_size': 0.15, # Max displacement for a single circle move at T_start
|
| 25 |
+
'radius_iter': 250, # Radius calculation iterations during search
|
| 26 |
+
'move_weights': [0.75, 0.20, 0.05], # Probabilities for [single, cluster, swap]
|
| 27 |
+
'cluster_size': 4,
|
| 28 |
+
}
|
| 29 |
+
=======
|
| 30 |
+
config = {
|
| 31 |
+
'num_starts': 16, # More starts to explore different basins
|
| 32 |
+
't_start': 0.06, # Higher initial temperature for more exploration
|
| 33 |
+
't_end': 1e-6, # Final temperature
|
| 34 |
+
'cooling_rate': 0.999, # Slower cooling for more thorough search
|
| 35 |
+
'moves_per_temp': 50, # Adjusted moves to balance longer schedule
|
| 36 |
+
'max_step_size': 0.20, # Larger initial step size
|
| 37 |
+
'radius_iter': 250, # Radius calculation iterations during search
|
| 38 |
+
'move_weights': [0.70, 0.20, 0.10], # Increased probability for powerful swap move
|
| 39 |
+
'cluster_size': 5, # Slightly larger clusters
|
| 40 |
+
}
|
| 41 |
+
>>>>>>> REPLACE
|
| 42 |
+
</DIFF>
|
| 43 |
+
<NAME>
|
| 44 |
+
diversify_initial_configurations
|
| 45 |
+
</NAME>
|
| 46 |
+
<DESCRIPTION>
|
| 47 |
+
This edit enhances the multi-start strategy by prioritizing structured initializations and increasing their diversity. The number of grid-based starts is increased from 6 to 12 (out of 16 total starts), as these are more likely to be in a promising region of the search space than purely random configurations. Furthermore, the list of candidate positions for the 26th circle is significantly expanded. By including more strategic points—such as grid interstitial points and offset edge centers—we provide the annealer with a richer and more varied set of high-quality starting configurations, improving the chances of one of the runs converging to a global optimum.
|
| 48 |
+
</DESCRIPTION>
|
| 49 |
+
<DIFF>
|
| 50 |
+
<<<<<<< SEARCH
|
| 51 |
+
if i < config['num_starts'] // 2:
|
| 52 |
+
# Start with a perturbed 5x5 grid + 1 extra circle
|
| 53 |
+
num_cells_side = 5
|
| 54 |
+
spacing = 1.0 / num_cells_side
|
| 55 |
+
k = 0
|
| 56 |
+
for row in range(num_cells_side):
|
| 57 |
+
for col in range(num_cells_side):
|
| 58 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 59 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 60 |
+
k += 1
|
| 61 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 62 |
+
|
| 63 |
+
# Place the 26th circle in a strategic corner or center
|
| 64 |
+
extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 65 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 66 |
+
initial_centers = np.clip(initial_centers, 0.0, 1.0)
|
| 67 |
+
else:
|
| 68 |
+
=======
|
| 69 |
+
# Prioritize structured starts (12 of 16), which are generally more promising
|
| 70 |
+
if i < 12:
|
| 71 |
+
# Start with a perturbed 5x5 grid + 1 extra circle
|
| 72 |
+
num_cells_side = 5
|
| 73 |
+
spacing = 1.0 / num_cells_side
|
| 74 |
+
k = 0
|
| 75 |
+
for row in range(num_cells_side):
|
| 76 |
+
for col in range(num_cells_side):
|
| 77 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 78 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 79 |
+
k += 1
|
| 80 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 81 |
+
|
| 82 |
+
# Use a richer set of candidate positions for the 26th circle
|
| 83 |
+
extra_pos_candidates = [
|
| 84 |
+
[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], # Corners (strong)
|
| 85 |
+
[0.5, 0.5], # Center
|
| 86 |
+
[0.2, 0.2], [0.8, 0.2], [0.2, 0.8], [0.8, 0.8], # Grid interstitial (spacing=0.2)
|
| 87 |
+
[0.5, 0.15], [0.15, 0.5], [0.85, 0.5], [0.5, 0.85] # Edge centers (offset)
|
| 88 |
+
]
|
| 89 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 90 |
+
initial_centers = np.clip(initial_centers, 0.0, 1.0)
|
| 91 |
+
else:
|
| 92 |
+
>>>>>>> REPLACE
|
| 93 |
+
</DIFF>
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (3.42 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/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_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/job_log.err
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/job_log.out
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results
|
| 3 |
+
Run 1/1 completed in 0.00 seconds
|
| 4 |
+
Detailed packing data saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/extra.npz
|
| 5 |
+
Visualization saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/packing_viz.png
|
| 6 |
+
Correctness and error status saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/correct.json
|
| 7 |
+
Metrics saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/metrics.json
|
| 8 |
+
Evaluation and Validation completed successfully.
|
| 9 |
+
Metrics:
|
| 10 |
+
combined_score: 0.9597642169962064
|
| 11 |
+
public: {'centers_str': ' centers[0] = (0.5000, 0.5000)\n centers[1] = (0.8000, 0.5000)\n centers[2] = (0.7121, 0.7121)\n centers[3] = (0.5000, 0.8000)\n centers[4] = (0.2879, 0.7121)\n centers[5] = (0.2000, 0.5000)\n centers[6] = (0.2879, 0.2879)\n centers[7] = (0.5000, 0.2000)\n centers[8] = (0.7121, 0.2879)\n centers[9] = (0.9900, 0.5000)\n centers[10] = (0.9900, 0.7679)\n centers[11] = (0.9900, 0.9900)\n centers[12] = (0.7679, 0.9900)\n centers[13] = (0.5000, 0.9900)\n centers[14] = (0.2321, 0.9900)\n centers[15] = (0.0100, 0.9900)\n centers[16] = (0.0100, 0.7679)\n centers[17] = (0.0100, 0.5000)\n centers[18] = (0.0100, 0.2321)\n centers[19] = (0.0100, 0.0100)\n centers[20] = (0.2321, 0.0100)\n centers[21] = (0.5000, 0.0100)\n centers[22] = (0.7679, 0.0100)\n centers[23] = (0.9900, 0.0100)\n centers[24] = (0.9900, 0.2321)\n centers[25] = (0.0100, 0.0100)', 'num_circles': 26}
|
| 12 |
+
private: {'reported_sum_of_radii': 0.9597642169962064}
|
| 13 |
+
visualization_path: results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/packing_viz.png
|
| 14 |
+
execution_time_mean: 0.00208668177947402
|
| 15 |
+
execution_time_std: 0.0
|
| 16 |
+
num_valid_runs: 1
|
| 17 |
+
num_invalid_runs: 0
|
| 18 |
+
all_validation_errors: []
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/metrics.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 0.9597642169962064,
|
| 3 |
+
"public": {
|
| 4 |
+
"centers_str": " centers[0] = (0.5000, 0.5000)\n centers[1] = (0.8000, 0.5000)\n centers[2] = (0.7121, 0.7121)\n centers[3] = (0.5000, 0.8000)\n centers[4] = (0.2879, 0.7121)\n centers[5] = (0.2000, 0.5000)\n centers[6] = (0.2879, 0.2879)\n centers[7] = (0.5000, 0.2000)\n centers[8] = (0.7121, 0.2879)\n centers[9] = (0.9900, 0.5000)\n centers[10] = (0.9900, 0.7679)\n centers[11] = (0.9900, 0.9900)\n centers[12] = (0.7679, 0.9900)\n centers[13] = (0.5000, 0.9900)\n centers[14] = (0.2321, 0.9900)\n centers[15] = (0.0100, 0.9900)\n centers[16] = (0.0100, 0.7679)\n centers[17] = (0.0100, 0.5000)\n centers[18] = (0.0100, 0.2321)\n centers[19] = (0.0100, 0.0100)\n centers[20] = (0.2321, 0.0100)\n centers[21] = (0.5000, 0.0100)\n centers[22] = (0.7679, 0.0100)\n centers[23] = (0.9900, 0.0100)\n centers[24] = (0.9900, 0.2321)\n centers[25] = (0.0100, 0.0100)",
|
| 5 |
+
"num_circles": 26
|
| 6 |
+
},
|
| 7 |
+
"private": {
|
| 8 |
+
"reported_sum_of_radii": 0.9597642169962064
|
| 9 |
+
},
|
| 10 |
+
"visualization_path": "results_circle_packing_WITHOUT_vision_20260114_070110/gen_0/results/packing_viz.png",
|
| 11 |
+
"execution_time_mean": 0.00208668177947402,
|
| 12 |
+
"execution_time_std": 0.0,
|
| 13 |
+
"num_valid_runs": 1,
|
| 14 |
+
"num_invalid_runs": 0,
|
| 15 |
+
"all_validation_errors": []
|
| 16 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (3.1 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/edit.diff
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,94 +1,95 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def construct_packing():
|
| 11 |
+
"""
|
| 12 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 13 |
+
that attempts to maximize the sum of their radii.
|
| 14 |
+
|
| 15 |
+
Returns:
|
| 16 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 17 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 18 |
+
radii: np.array of shape (26) with radius of each circle
|
| 19 |
+
sum_of_radii: Sum of all radii
|
| 20 |
+
"""
|
| 21 |
+
# Initialize arrays for 26 circles
|
| 22 |
+
n = 26
|
| 23 |
+
centers = np.zeros((n, 2))
|
| 24 |
+
|
| 25 |
+
- # Place circles in a structured pattern
|
| 26 |
+
- # This is a simple pattern - evolution will improve this
|
| 27 |
+
+ # Place 25 circles in a 5x5 grid
|
| 28 |
+
+ # This provides a more even distribution and better use of space
|
| 29 |
+
+ num_cells_side = 5
|
| 30 |
+
+ spacing = 1.0 / num_cells_side
|
| 31 |
+
|
| 32 |
+
- # First, place a large circle in the center
|
| 33 |
+
- centers[0] = [0.5, 0.5]
|
| 34 |
+
+ k = 0
|
| 35 |
+
+ for i in range(num_cells_side):
|
| 36 |
+
+ for j in range(num_cells_side):
|
| 37 |
+
+ centers[k, 0] = (i + 0.5) * spacing
|
| 38 |
+
+ centers[k, 1] = (j + 0.5) * spacing
|
| 39 |
+
+ k += 1
|
| 40 |
+
|
| 41 |
+
- # Place 8 circles around it in a ring
|
| 42 |
+
- for i in range(8):
|
| 43 |
+
- angle = 2 * np.pi * i / 8
|
| 44 |
+
- centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 45 |
+
+ # Place the 26th circle in a corner, slightly offset to allow for radius.
|
| 46 |
+
+ # This location is chosen to utilize corner space efficiently,
|
| 47 |
+
+ # as the grid circles are not directly in the corners.
|
| 48 |
+
+ centers[25] = [0.05, 0.05]
|
| 49 |
+
|
| 50 |
+
- # Place 16 more circles in an outer ring
|
| 51 |
+
- for i in range(16):
|
| 52 |
+
- angle = 2 * np.pi * i / 16
|
| 53 |
+
- centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 54 |
+
-
|
| 55 |
+
- # Additional positioning adjustment to make sure all circles
|
| 56 |
+
- # are inside the square and don't overlap
|
| 57 |
+
- # Clip to ensure everything is inside the unit square
|
| 58 |
+
- centers = np.clip(centers, 0.01, 0.99)
|
| 59 |
+
+ # No need to clip centers, as they are intentionally placed within (0,1) range
|
| 60 |
+
+ # and compute_max_radii handles boundary constraints.
|
| 61 |
+
|
| 62 |
+
# Compute maximum valid radii for this configuration
|
| 63 |
+
radii = compute_max_radii(centers)
|
| 64 |
+
return centers, radii
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def compute_max_radii(centers):
|
| 68 |
+
"""
|
| 69 |
+
Compute the maximum possible radii for each circle position
|
| 70 |
+
such that they don't overlap and stay within the unit square.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
np.array of shape (n) with radius of each circle
|
| 77 |
+
"""
|
| 78 |
+
n = centers.shape[0]
|
| 79 |
+
radii = np.ones(n)
|
| 80 |
+
|
| 81 |
+
# First, limit by distance to square borders
|
| 82 |
+
for i in range(n):
|
| 83 |
+
x, y = centers[i]
|
| 84 |
+
# Distance to borders
|
| 85 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 86 |
+
|
| 87 |
+
# Then, limit by distance to other circles
|
| 88 |
+
# Each pair of circles with centers at distance d can have
|
| 89 |
+
# sum of radii at most d to avoid overlap
|
| 90 |
+
- for i in range(n):
|
| 91 |
+
- for j in range(i + 1, n):
|
| 92 |
+
- dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 93 |
+
+ # This iterative scaling is a greedy approach and the order can matter,
|
| 94 |
+
+ # but for constructor-based approaches, it's a common method.
|
| 95 |
+
+ for _ in range(100): # Iterate multiple times to stabilize radii
|
| 96 |
+
+ for i in range(n):
|
| 97 |
+
+ for j in range(i + 1, n):
|
| 98 |
+
+ dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 99 |
+
|
| 100 |
+
- # If current radii would cause overlap
|
| 101 |
+
- if radii[i] + radii[j] > dist:
|
| 102 |
+
- # Scale both radii proportionally
|
| 103 |
+
- scale = dist / (radii[i] + radii[j])
|
| 104 |
+
- radii[i] *= scale
|
| 105 |
+
- radii[j] *= scale
|
| 106 |
+
-
|
| 107 |
+
+ # If current radii would cause overlap
|
| 108 |
+
+ if radii[i] + radii[j] > dist:
|
| 109 |
+
+ # Scale both radii proportionally to just touch
|
| 110 |
+
+ scale = dist / (radii[i] + radii[j])
|
| 111 |
+
+ radii[i] *= scale
|
| 112 |
+
+ radii[j] *= scale
|
| 113 |
+
return radii
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# EVOLVE-BLOCK-END
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# This part remains fixed (not evolved)
|
| 120 |
+
def run_packing():
|
| 121 |
+
"""Run the circle packing constructor for n=26"""
|
| 122 |
+
centers, radii = construct_packing()
|
| 123 |
+
# Calculate the sum of radii
|
| 124 |
+
sum_radii = np.sum(radii)
|
| 125 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/main.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 25 circles in a 5x5 grid
|
| 23 |
+
# This provides a more even distribution and better use of space
|
| 24 |
+
num_cells_side = 5
|
| 25 |
+
spacing = 1.0 / num_cells_side
|
| 26 |
+
|
| 27 |
+
k = 0
|
| 28 |
+
for i in range(num_cells_side):
|
| 29 |
+
for j in range(num_cells_side):
|
| 30 |
+
centers[k, 0] = (i + 0.5) * spacing
|
| 31 |
+
centers[k, 1] = (j + 0.5) * spacing
|
| 32 |
+
k += 1
|
| 33 |
+
|
| 34 |
+
# Place the 26th circle in a corner, slightly offset to allow for radius.
|
| 35 |
+
# This location is chosen to utilize corner space efficiently,
|
| 36 |
+
# as the grid circles are not directly in the corners.
|
| 37 |
+
centers[25] = [0.05, 0.05]
|
| 38 |
+
|
| 39 |
+
# No need to clip centers, as they are intentionally placed within (0,1) range
|
| 40 |
+
# and compute_max_radii handles boundary constraints.
|
| 41 |
+
|
| 42 |
+
# Compute maximum valid radii for this configuration
|
| 43 |
+
radii = compute_max_radii(centers)
|
| 44 |
+
return centers, radii
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def compute_max_radii(centers):
|
| 48 |
+
"""
|
| 49 |
+
Compute the maximum possible radii for each circle position
|
| 50 |
+
such that they don't overlap and stay within the unit square.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
np.array of shape (n) with radius of each circle
|
| 57 |
+
"""
|
| 58 |
+
n = centers.shape[0]
|
| 59 |
+
radii = np.ones(n)
|
| 60 |
+
|
| 61 |
+
# First, limit by distance to square borders
|
| 62 |
+
for i in range(n):
|
| 63 |
+
x, y = centers[i]
|
| 64 |
+
# Distance to borders
|
| 65 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 66 |
+
|
| 67 |
+
# Then, limit by distance to other circles
|
| 68 |
+
# Each pair of circles with centers at distance d can have
|
| 69 |
+
# sum of radii at most d to avoid overlap
|
| 70 |
+
# This iterative scaling is a greedy approach and the order can matter,
|
| 71 |
+
# but for constructor-based approaches, it's a common method.
|
| 72 |
+
for _ in range(100): # Iterate multiple times to stabilize radii
|
| 73 |
+
for i in range(n):
|
| 74 |
+
for j in range(i + 1, n):
|
| 75 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 76 |
+
|
| 77 |
+
# If current radii would cause overlap
|
| 78 |
+
if radii[i] + radii[j] > dist:
|
| 79 |
+
# Scale both radii proportionally to just touch
|
| 80 |
+
scale = dist / (radii[i] + radii[j])
|
| 81 |
+
radii[i] *= scale
|
| 82 |
+
radii[j] *= scale
|
| 83 |
+
return radii
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# EVOLVE-BLOCK-END
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
# This part remains fixed (not evolved)
|
| 90 |
+
def run_packing():
|
| 91 |
+
"""Run the circle packing constructor for n=26"""
|
| 92 |
+
centers, radii = construct_packing()
|
| 93 |
+
# Calculate the sum of radii
|
| 94 |
+
sum_radii = np.sum(radii)
|
| 95 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/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_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/job_log.err
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/job_log.out
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results
|
| 3 |
+
Run 1/1 completed in 0.17 seconds
|
| 4 |
+
Detailed packing data saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/extra.npz
|
| 5 |
+
Visualization saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/packing_viz.png
|
| 6 |
+
Correctness and error status saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/correct.json
|
| 7 |
+
Metrics saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/metrics.json
|
| 8 |
+
Evaluation and Validation completed successfully.
|
| 9 |
+
Metrics:
|
| 10 |
+
combined_score: 1.8730177484885533
|
| 11 |
+
public: {'centers_str': ' centers[0] = (0.1000, 0.1000)\n centers[1] = (0.1000, 0.3000)\n centers[2] = (0.1000, 0.5000)\n centers[3] = (0.1000, 0.7000)\n centers[4] = (0.1000, 0.9000)\n centers[5] = (0.3000, 0.1000)\n centers[6] = (0.3000, 0.3000)\n centers[7] = (0.3000, 0.5000)\n centers[8] = (0.3000, 0.7000)\n centers[9] = (0.3000, 0.9000)\n centers[10] = (0.5000, 0.1000)\n centers[11] = (0.5000, 0.3000)\n centers[12] = (0.5000, 0.5000)\n centers[13] = (0.5000, 0.7000)\n centers[14] = (0.5000, 0.9000)\n centers[15] = (0.7000, 0.1000)\n centers[16] = (0.7000, 0.3000)\n centers[17] = (0.7000, 0.5000)\n centers[18] = (0.7000, 0.7000)\n centers[19] = (0.7000, 0.9000)\n centers[20] = (0.9000, 0.1000)\n centers[21] = (0.9000, 0.3000)\n centers[22] = (0.9000, 0.5000)\n centers[23] = (0.9000, 0.7000)\n centers[24] = (0.9000, 0.9000)\n centers[25] = (0.0500, 0.0500)', 'num_circles': 26}
|
| 12 |
+
private: {'reported_sum_of_radii': 1.8730177484885533}
|
| 13 |
+
visualization_path: results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/packing_viz.png
|
| 14 |
+
execution_time_mean: 0.17472516791895032
|
| 15 |
+
execution_time_std: 0.0
|
| 16 |
+
num_valid_runs: 1
|
| 17 |
+
num_invalid_runs: 0
|
| 18 |
+
all_validation_errors: []
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/metrics.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 1.8730177484885533,
|
| 3 |
+
"public": {
|
| 4 |
+
"centers_str": " centers[0] = (0.1000, 0.1000)\n centers[1] = (0.1000, 0.3000)\n centers[2] = (0.1000, 0.5000)\n centers[3] = (0.1000, 0.7000)\n centers[4] = (0.1000, 0.9000)\n centers[5] = (0.3000, 0.1000)\n centers[6] = (0.3000, 0.3000)\n centers[7] = (0.3000, 0.5000)\n centers[8] = (0.3000, 0.7000)\n centers[9] = (0.3000, 0.9000)\n centers[10] = (0.5000, 0.1000)\n centers[11] = (0.5000, 0.3000)\n centers[12] = (0.5000, 0.5000)\n centers[13] = (0.5000, 0.7000)\n centers[14] = (0.5000, 0.9000)\n centers[15] = (0.7000, 0.1000)\n centers[16] = (0.7000, 0.3000)\n centers[17] = (0.7000, 0.5000)\n centers[18] = (0.7000, 0.7000)\n centers[19] = (0.7000, 0.9000)\n centers[20] = (0.9000, 0.1000)\n centers[21] = (0.9000, 0.3000)\n centers[22] = (0.9000, 0.5000)\n centers[23] = (0.9000, 0.7000)\n centers[24] = (0.9000, 0.9000)\n centers[25] = (0.0500, 0.0500)",
|
| 5 |
+
"num_circles": 26
|
| 6 |
+
},
|
| 7 |
+
"private": {
|
| 8 |
+
"reported_sum_of_radii": 1.8730177484885533
|
| 9 |
+
},
|
| 10 |
+
"visualization_path": "results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/results/packing_viz.png",
|
| 11 |
+
"execution_time_mean": 0.17472516791895032,
|
| 12 |
+
"execution_time_std": 0.0,
|
| 13 |
+
"num_valid_runs": 1,
|
| 14 |
+
"num_invalid_runs": 0,
|
| 15 |
+
"all_validation_errors": []
|
| 16 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_1/search_replace.txt
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<first_major_refactor>
|
| 2 |
+
<DESCRIPTION>
|
| 3 |
+
The current circle packing strategy relies on concentric rings, which is inefficient for a large number of circles (26). Furthermore, the `np.clip` operation with bounds `0.01` and `0.99` severely limits the radii of circles near the boundaries, leading to wasted space and tiny circles. The initial sum of radii of 0.9598 is far from the best known result of 2.635.
|
| 4 |
+
|
| 5 |
+
This edit proposes two main improvements:
|
| 6 |
+
|
| 7 |
+
1. **Refactor `construct_packing` for better spatial distribution:**
|
| 8 |
+
* The concentric ring pattern is replaced with a 5x5 square grid for 25 circles. This provides a more uniform and denser initial packing, where each circle in the grid starts with a potential radius of `0.1`. This alone significantly increases the sum of radii baseline (e.g., `25 * 0.1 = 2.5`).
|
| 9 |
+
* The 26th circle is explicitly placed in a corner (`[0.05, 0.05]`) slightly offset from the exact corner to allow it to have a non-zero radius. This utilizes the corner space more effectively than the previous implicit placement (`[0.0, 0.0]` clipped to `[0.01, 0.01]`).
|
| 10 |
+
* The `np.clip` line is removed. All centers are now intentionally placed strictly within the `(0,1)` range, allowing `compute_max_radii` to correctly determine their maximum radii based on boundary proximity, rather than artificially limiting them.
|
| 11 |
+
|
| 12 |
+
2. **Improve `compute_max_radii` for better stabilization:**
|
| 13 |
+
* The existing `compute_max_radii` function iterates through circle pairs to resolve overlaps only once. This greedy approach can lead to suboptimal radii, as adjusting one pair might create new overlaps or alleviate existing ones that were previously handled.
|
| 14 |
+
* An outer loop is added to iterate the pairwise adjustment process multiple times (100 iterations). This allows the radii to stabilize more effectively, converging to a locally optimal solution where circles are as large as possible without overlap for the given center positions.
|
| 15 |
+
|
| 16 |
+
These changes aim to drastically increase the `sum_of_radii` by providing a much more efficient initial center arrangement and improving the radius calculation accuracy.
|
| 17 |
+
</DESCRIPTION>
|
| 18 |
+
<DIFF>
|
| 19 |
+
<<<<<<< SEARCH
|
| 20 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
|
| 24 |
+
|
| 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 |
+
|
| 66 |
+
def compute_max_radii(centers):
|
| 67 |
+
"""
|
| 68 |
+
Compute the maximum possible radii for each circle position
|
| 69 |
+
such that they don't overlap and stay within the unit square.
|
| 70 |
+
|
| 71 |
+
Args:
|
| 72 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
np.array of shape (n) with radius of each circle
|
| 76 |
+
"""
|
| 77 |
+
n = centers.shape[0]
|
| 78 |
+
radii = np.ones(n)
|
| 79 |
+
|
| 80 |
+
# First, limit by distance to square borders
|
| 81 |
+
for i in range(n):
|
| 82 |
+
x, y = centers[i]
|
| 83 |
+
# Distance to borders
|
| 84 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 85 |
+
|
| 86 |
+
# Then, limit by distance to other circles
|
| 87 |
+
# Each pair of circles with centers at distance d can have
|
| 88 |
+
# sum of radii at most d to avoid overlap
|
| 89 |
+
for i in range(n):
|
| 90 |
+
for j in range(i + 1, n):
|
| 91 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 92 |
+
|
| 93 |
+
# If current radii would cause overlap
|
| 94 |
+
if radii[i] + radii[j] > dist:
|
| 95 |
+
# Scale both radii proportionally
|
| 96 |
+
scale = dist / (radii[i] + radii[j])
|
| 97 |
+
radii[i] *= scale
|
| 98 |
+
radii[j] *= scale
|
| 99 |
+
|
| 100 |
+
return radii
|
| 101 |
+
=======
|
| 102 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 103 |
+
|
| 104 |
+
import numpy as np
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def construct_packing():
|
| 108 |
+
"""
|
| 109 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 110 |
+
that attempts to maximize the sum of their radii.
|
| 111 |
+
|
| 112 |
+
Returns:
|
| 113 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 114 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 115 |
+
radii: np.array of shape (26) with radius of each circle
|
| 116 |
+
sum_of_radii: Sum of all radii
|
| 117 |
+
"""
|
| 118 |
+
# Initialize arrays for 26 circles
|
| 119 |
+
n = 26
|
| 120 |
+
centers = np.zeros((n, 2))
|
| 121 |
+
|
| 122 |
+
# Place 25 circles in a 5x5 grid
|
| 123 |
+
# This provides a more even distribution and better use of space
|
| 124 |
+
num_cells_side = 5
|
| 125 |
+
spacing = 1.0 / num_cells_side
|
| 126 |
+
|
| 127 |
+
k = 0
|
| 128 |
+
for i in range(num_cells_side):
|
| 129 |
+
for j in range(num_cells_side):
|
| 130 |
+
centers[k, 0] = (i + 0.5) * spacing
|
| 131 |
+
centers[k, 1] = (j + 0.5) * spacing
|
| 132 |
+
k += 1
|
| 133 |
+
|
| 134 |
+
# Place the 26th circle in a corner, slightly offset to allow for radius.
|
| 135 |
+
# This location is chosen to utilize corner space efficiently,
|
| 136 |
+
# as the grid circles are not directly in the corners.
|
| 137 |
+
centers[25] = [0.05, 0.05]
|
| 138 |
+
|
| 139 |
+
# No need to clip centers, as they are intentionally placed within (0,1) range
|
| 140 |
+
# and compute_max_radii handles boundary constraints.
|
| 141 |
+
|
| 142 |
+
# Compute maximum valid radii for this configuration
|
| 143 |
+
radii = compute_max_radii(centers)
|
| 144 |
+
return centers, radii
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def compute_max_radii(centers):
|
| 148 |
+
"""
|
| 149 |
+
Compute the maximum possible radii for each circle position
|
| 150 |
+
such that they don't overlap and stay within the unit square.
|
| 151 |
+
|
| 152 |
+
Args:
|
| 153 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 154 |
+
|
| 155 |
+
Returns:
|
| 156 |
+
np.array of shape (n) with radius of each circle
|
| 157 |
+
"""
|
| 158 |
+
n = centers.shape[0]
|
| 159 |
+
radii = np.ones(n)
|
| 160 |
+
|
| 161 |
+
# First, limit by distance to square borders
|
| 162 |
+
for i in range(n):
|
| 163 |
+
x, y = centers[i]
|
| 164 |
+
# Distance to borders
|
| 165 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 166 |
+
|
| 167 |
+
# Then, limit by distance to other circles
|
| 168 |
+
# Each pair of circles with centers at distance d can have
|
| 169 |
+
# sum of radii at most d to avoid overlap
|
| 170 |
+
# This iterative scaling is a greedy approach and the order can matter,
|
| 171 |
+
# but for constructor-based approaches, it's a common method.
|
| 172 |
+
for _ in range(100): # Iterate multiple times to stabilize radii
|
| 173 |
+
for i in range(n):
|
| 174 |
+
for j in range(i + 1, n):
|
| 175 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 176 |
+
|
| 177 |
+
# If current radii would cause overlap
|
| 178 |
+
if radii[i] + radii[j] > dist:
|
| 179 |
+
# Scale both radii proportionally to just touch
|
| 180 |
+
scale = dist / (radii[i] + radii[j])
|
| 181 |
+
radii[i] *= scale
|
| 182 |
+
radii[j] *= scale
|
| 183 |
+
return radii
|
| 184 |
+
>>>>>>> REPLACE
|
| 185 |
+
</DIFF>
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (4.84 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/edit.diff
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,128 +1,124 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Hybrid circle packing for n=26 circles.
|
| 7 |
+
Starts with a concentric ring pattern and refines positions using
|
| 8 |
+
force-directed relaxation, then computes radii iteratively.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def construct_packing():
|
| 15 |
+
"""
|
| 16 |
+
Constructs an arrangement of 26 circles using a force-directed
|
| 17 |
+
relaxation method, starting from a concentric ring pattern,
|
| 18 |
+
to maximize the sum of radii.
|
| 19 |
+
|
| 20 |
+
Returns:
|
| 21 |
+
Tuple of (centers, radii)
|
| 22 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 23 |
+
radii: np.array of shape (26) with radius of each circle
|
| 24 |
+
"""
|
| 25 |
+
n = 26
|
| 26 |
+
# Use a fixed seed for reproducibility.
|
| 27 |
+
np.random.seed(1337)
|
| 28 |
+
|
| 29 |
+
- # 1. Initial Placement: Start with a structured concentric ring pattern.
|
| 30 |
+
- # This provides a symmetric, ordered starting point for the relaxation,
|
| 31 |
+
- # combining the idea from the first parent program.
|
| 32 |
+
+ # 1. Initial Placement: Start with a structured near-grid pattern (5-5-6-5-5 columns).
|
| 33 |
+
+ # This is a much better starting point than concentric circles, inspired by known optimal packings.
|
| 34 |
+
centers = np.zeros((n, 2))
|
| 35 |
+
+ col_counts = [5, 5, 6, 5, 5]
|
| 36 |
+
+ x_coords = np.linspace(0.1, 0.9, 5)
|
| 37 |
+
+ current_idx = 0
|
| 38 |
+
+ for i, count in enumerate(col_counts):
|
| 39 |
+
+ y_coords = np.linspace(1/(count+1), count/(count+1), count)
|
| 40 |
+
+ for j in range(count):
|
| 41 |
+
+ centers[current_idx] = [x_coords[i], y_coords[j]]
|
| 42 |
+
+ current_idx += 1
|
| 43 |
+
|
| 44 |
+
- # Place a circle in the center
|
| 45 |
+
- centers[0] = [0.5, 0.5]
|
| 46 |
+
-
|
| 47 |
+
- # Place 8 circles in an inner ring
|
| 48 |
+
- inner_ring_radius = 0.28
|
| 49 |
+
- for i in range(8):
|
| 50 |
+
- angle = 2 * np.pi * i / 8
|
| 51 |
+
- centers[i + 1] = [0.5 + inner_ring_radius * np.cos(angle), 0.5 + inner_ring_radius * np.sin(angle)]
|
| 52 |
+
-
|
| 53 |
+
- # Place the remaining 17 circles in an outer ring
|
| 54 |
+
- outer_ring_radius = 0.48
|
| 55 |
+
- for i in range(17):
|
| 56 |
+
- angle = 2 * np.pi * i / 17
|
| 57 |
+
- centers[i + 9] = [0.5 + outer_ring_radius * np.cos(angle), 0.5 + outer_ring_radius * np.sin(angle)]
|
| 58 |
+
+ # Add random jitter to break perfect symmetry and allow for new configurations.
|
| 59 |
+
+ centers += np.random.uniform(-0.02, 0.02, size=centers.shape)
|
| 60 |
+
+ centers = np.clip(centers, 0.01, 0.99) # Ensure they start safely inside.
|
| 61 |
+
|
| 62 |
+
# 2. Iterative Position Refinement (Force Simulation from the second parent)
|
| 63 |
+
- n_iter = 250
|
| 64 |
+
+ n_iter = 500 # Increased iterations for better convergence
|
| 65 |
+
step_size = 1e-4
|
| 66 |
+
wall_strength = 0.5 # Relative strength of wall vs particle repulsion
|
| 67 |
+
|
| 68 |
+
for _ in range(n_iter):
|
| 69 |
+
# Vectorized calculation of repulsion forces between all pairs of circles
|
| 70 |
+
diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 71 |
+
dist_sq = np.sum(diffs**2, axis=-1)
|
| 72 |
+
np.fill_diagonal(dist_sq, 1.0) # Avoid self-repulsion and division by zero
|
| 73 |
+
inv_dist_sq = 1.0 / (dist_sq + 1e-8)
|
| 74 |
+
force_repel_matrix = diffs * inv_dist_sq[:, :, np.newaxis]
|
| 75 |
+
force_repel = np.sum(force_repel_matrix, axis=1)
|
| 76 |
+
|
| 77 |
+
# Wall repulsion force: F_wall is proportional to 1/d - 1/(1-d)
|
| 78 |
+
# This creates a soft potential well keeping circles inside the square.
|
| 79 |
+
clipped_centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 80 |
+
force_wall = 1.0 / clipped_centers - 1.0 / (1.0 - clipped_centers)
|
| 81 |
+
|
| 82 |
+
# Combine forces and update positions
|
| 83 |
+
total_force = force_repel + wall_strength * force_wall
|
| 84 |
+
centers += step_size * total_force
|
| 85 |
+
|
| 86 |
+
# Enforce hard boundaries
|
| 87 |
+
centers = np.clip(centers, 0.0, 1.0)
|
| 88 |
+
|
| 89 |
+
# 3. Compute optimal radii for the final configuration using the convergent method.
|
| 90 |
+
radii = compute_max_radii(centers)
|
| 91 |
+
|
| 92 |
+
return centers, radii
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def compute_max_radii(centers):
|
| 96 |
+
"""
|
| 97 |
+
Compute the maximum possible radii for the given centers using an
|
| 98 |
+
iterative relaxation method until convergence. This is more robust
|
| 99 |
+
than the original single-pass approach.
|
| 100 |
+
"""
|
| 101 |
+
n = centers.shape[0]
|
| 102 |
+
|
| 103 |
+
# Initialize radii based on the distance to the four walls.
|
| 104 |
+
radii = np.min([
|
| 105 |
+
centers[:, 0],
|
| 106 |
+
centers[:, 1],
|
| 107 |
+
1 - centers[:, 0],
|
| 108 |
+
1 - centers[:, 1]
|
| 109 |
+
], axis=0)
|
| 110 |
+
|
| 111 |
+
# Iteratively shrink any overlapping circles until the layout is stable.
|
| 112 |
+
max_iter = 500 # Safety break for the convergence loop
|
| 113 |
+
for _ in range(max_iter):
|
| 114 |
+
changed_in_iter = False
|
| 115 |
+
# Check every pair of circles for overlap
|
| 116 |
+
for i in range(n):
|
| 117 |
+
for j in range(i + 1, n):
|
| 118 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 119 |
+
sum_r = radii[i] + radii[j]
|
| 120 |
+
|
| 121 |
+
if sum_r > dist:
|
| 122 |
+
# Overlap detected. Shrink radii proportionally to their size.
|
| 123 |
+
overlap = sum_r - dist
|
| 124 |
+
if sum_r > 1e-9: # Avoid division by zero
|
| 125 |
+
radii[i] -= overlap * (radii[i] / sum_r)
|
| 126 |
+
radii[j] -= overlap * (radii[j] / sum_r)
|
| 127 |
+
changed_in_iter = True
|
| 128 |
+
|
| 129 |
+
if not changed_in_iter:
|
| 130 |
+
# If a full pass completes with no changes, the radii have converged.
|
| 131 |
+
break
|
| 132 |
+
|
| 133 |
+
return radii
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# EVOLVE-BLOCK-END
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# This part remains fixed (not evolved)
|
| 140 |
+
def run_packing():
|
| 141 |
+
"""Run the circle packing constructor for n=26"""
|
| 142 |
+
centers, radii = construct_packing()
|
| 143 |
+
# Calculate the sum of radii
|
| 144 |
+
sum_radii = np.sum(radii)
|
| 145 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/main.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Hybrid circle packing for n=26 circles.
|
| 4 |
+
Starts with a concentric ring pattern and refines positions using
|
| 5 |
+
force-directed relaxation, then computes radii iteratively.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def construct_packing():
|
| 12 |
+
"""
|
| 13 |
+
Constructs an arrangement of 26 circles using a force-directed
|
| 14 |
+
relaxation method, starting from a concentric ring pattern,
|
| 15 |
+
to maximize the sum of radii.
|
| 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 |
+
# Use a fixed seed for reproducibility.
|
| 24 |
+
np.random.seed(1337)
|
| 25 |
+
|
| 26 |
+
# 1. Initial Placement: Start with a structured near-grid pattern (5-5-6-5-5 columns).
|
| 27 |
+
# This is a much better starting point than concentric circles, inspired by known optimal packings.
|
| 28 |
+
centers = np.zeros((n, 2))
|
| 29 |
+
col_counts = [5, 5, 6, 5, 5]
|
| 30 |
+
x_coords = np.linspace(0.1, 0.9, 5)
|
| 31 |
+
current_idx = 0
|
| 32 |
+
for i, count in enumerate(col_counts):
|
| 33 |
+
y_coords = np.linspace(1/(count+1), count/(count+1), count)
|
| 34 |
+
for j in range(count):
|
| 35 |
+
centers[current_idx] = [x_coords[i], y_coords[j]]
|
| 36 |
+
current_idx += 1
|
| 37 |
+
|
| 38 |
+
# Add random jitter to break perfect symmetry and allow for new configurations.
|
| 39 |
+
centers += np.random.uniform(-0.02, 0.02, size=centers.shape)
|
| 40 |
+
centers = np.clip(centers, 0.01, 0.99) # Ensure they start safely inside.
|
| 41 |
+
|
| 42 |
+
# 2. Iterative Position Refinement (Force Simulation from the second parent)
|
| 43 |
+
n_iter = 500 # Increased iterations for better convergence
|
| 44 |
+
step_size = 1e-4
|
| 45 |
+
wall_strength = 0.5 # Relative strength of wall vs particle repulsion
|
| 46 |
+
|
| 47 |
+
for _ in range(n_iter):
|
| 48 |
+
# Vectorized calculation of repulsion forces between all pairs of circles
|
| 49 |
+
diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 50 |
+
dist_sq = np.sum(diffs**2, axis=-1)
|
| 51 |
+
np.fill_diagonal(dist_sq, 1.0) # Avoid self-repulsion and division by zero
|
| 52 |
+
inv_dist_sq = 1.0 / (dist_sq + 1e-8)
|
| 53 |
+
force_repel_matrix = diffs * inv_dist_sq[:, :, np.newaxis]
|
| 54 |
+
force_repel = np.sum(force_repel_matrix, axis=1)
|
| 55 |
+
|
| 56 |
+
# Wall repulsion force: F_wall is proportional to 1/d - 1/(1-d)
|
| 57 |
+
# This creates a soft potential well keeping circles inside the square.
|
| 58 |
+
clipped_centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 59 |
+
force_wall = 1.0 / clipped_centers - 1.0 / (1.0 - clipped_centers)
|
| 60 |
+
|
| 61 |
+
# Combine forces and update positions
|
| 62 |
+
total_force = force_repel + wall_strength * force_wall
|
| 63 |
+
centers += step_size * total_force
|
| 64 |
+
|
| 65 |
+
# Enforce hard boundaries
|
| 66 |
+
centers = np.clip(centers, 0.0, 1.0)
|
| 67 |
+
|
| 68 |
+
# 3. Compute optimal radii for the final configuration using the convergent method.
|
| 69 |
+
radii = compute_max_radii(centers)
|
| 70 |
+
|
| 71 |
+
return centers, radii
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def compute_max_radii(centers):
|
| 75 |
+
"""
|
| 76 |
+
Compute the maximum possible radii for the given centers using an
|
| 77 |
+
iterative relaxation method until convergence. This is more robust
|
| 78 |
+
than the original single-pass approach.
|
| 79 |
+
"""
|
| 80 |
+
n = centers.shape[0]
|
| 81 |
+
|
| 82 |
+
# Initialize radii based on the distance to the four walls.
|
| 83 |
+
radii = np.min([
|
| 84 |
+
centers[:, 0],
|
| 85 |
+
centers[:, 1],
|
| 86 |
+
1 - centers[:, 0],
|
| 87 |
+
1 - centers[:, 1]
|
| 88 |
+
], axis=0)
|
| 89 |
+
|
| 90 |
+
# Iteratively shrink any overlapping circles until the layout is stable.
|
| 91 |
+
max_iter = 500 # Safety break for the convergence loop
|
| 92 |
+
for _ in range(max_iter):
|
| 93 |
+
changed_in_iter = False
|
| 94 |
+
# Check every pair of circles for overlap
|
| 95 |
+
for i in range(n):
|
| 96 |
+
for j in range(i + 1, n):
|
| 97 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 98 |
+
sum_r = radii[i] + radii[j]
|
| 99 |
+
|
| 100 |
+
if sum_r > dist:
|
| 101 |
+
# Overlap detected. Shrink radii proportionally to their size.
|
| 102 |
+
overlap = sum_r - dist
|
| 103 |
+
if sum_r > 1e-9: # Avoid division by zero
|
| 104 |
+
radii[i] -= overlap * (radii[i] / sum_r)
|
| 105 |
+
radii[j] -= overlap * (radii[j] / sum_r)
|
| 106 |
+
changed_in_iter = True
|
| 107 |
+
|
| 108 |
+
if not changed_in_iter:
|
| 109 |
+
# If a full pass completes with no changes, the radii have converged.
|
| 110 |
+
break
|
| 111 |
+
|
| 112 |
+
return radii
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# EVOLVE-BLOCK-END
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# This part remains fixed (not evolved)
|
| 119 |
+
def run_packing():
|
| 120 |
+
"""Run the circle packing constructor for n=26"""
|
| 121 |
+
centers, radii = construct_packing()
|
| 122 |
+
# Calculate the sum of radii
|
| 123 |
+
sum_radii = np.sum(radii)
|
| 124 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/original.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Hybrid circle packing for n=26 circles.
|
| 4 |
+
Starts with a concentric ring pattern and refines positions using
|
| 5 |
+
force-directed relaxation, then computes radii iteratively.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def construct_packing():
|
| 12 |
+
"""
|
| 13 |
+
Constructs an arrangement of 26 circles using a force-directed
|
| 14 |
+
relaxation method, starting from a concentric ring pattern,
|
| 15 |
+
to maximize the sum of radii.
|
| 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 |
+
# Use a fixed seed for reproducibility.
|
| 24 |
+
np.random.seed(1337)
|
| 25 |
+
|
| 26 |
+
# 1. Initial Placement: Start with a structured concentric ring pattern.
|
| 27 |
+
# This provides a symmetric, ordered starting point for the relaxation,
|
| 28 |
+
# combining the idea from the first parent program.
|
| 29 |
+
centers = np.zeros((n, 2))
|
| 30 |
+
|
| 31 |
+
# Place a circle in the center
|
| 32 |
+
centers[0] = [0.5, 0.5]
|
| 33 |
+
|
| 34 |
+
# Place 8 circles in an inner ring
|
| 35 |
+
inner_ring_radius = 0.28
|
| 36 |
+
for i in range(8):
|
| 37 |
+
angle = 2 * np.pi * i / 8
|
| 38 |
+
centers[i + 1] = [0.5 + inner_ring_radius * np.cos(angle), 0.5 + inner_ring_radius * np.sin(angle)]
|
| 39 |
+
|
| 40 |
+
# Place the remaining 17 circles in an outer ring
|
| 41 |
+
outer_ring_radius = 0.48
|
| 42 |
+
for i in range(17):
|
| 43 |
+
angle = 2 * np.pi * i / 17
|
| 44 |
+
centers[i + 9] = [0.5 + outer_ring_radius * np.cos(angle), 0.5 + outer_ring_radius * np.sin(angle)]
|
| 45 |
+
|
| 46 |
+
# 2. Iterative Position Refinement (Force Simulation from the second parent)
|
| 47 |
+
n_iter = 250
|
| 48 |
+
step_size = 1e-4
|
| 49 |
+
wall_strength = 0.5 # Relative strength of wall vs particle repulsion
|
| 50 |
+
|
| 51 |
+
for _ in range(n_iter):
|
| 52 |
+
# Vectorized calculation of repulsion forces between all pairs of circles
|
| 53 |
+
diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 54 |
+
dist_sq = np.sum(diffs**2, axis=-1)
|
| 55 |
+
np.fill_diagonal(dist_sq, 1.0) # Avoid self-repulsion and division by zero
|
| 56 |
+
inv_dist_sq = 1.0 / (dist_sq + 1e-8)
|
| 57 |
+
force_repel_matrix = diffs * inv_dist_sq[:, :, np.newaxis]
|
| 58 |
+
force_repel = np.sum(force_repel_matrix, axis=1)
|
| 59 |
+
|
| 60 |
+
# Wall repulsion force: F_wall is proportional to 1/d - 1/(1-d)
|
| 61 |
+
# This creates a soft potential well keeping circles inside the square.
|
| 62 |
+
clipped_centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 63 |
+
force_wall = 1.0 / clipped_centers - 1.0 / (1.0 - clipped_centers)
|
| 64 |
+
|
| 65 |
+
# Combine forces and update positions
|
| 66 |
+
total_force = force_repel + wall_strength * force_wall
|
| 67 |
+
centers += step_size * total_force
|
| 68 |
+
|
| 69 |
+
# Enforce hard boundaries
|
| 70 |
+
centers = np.clip(centers, 0.0, 1.0)
|
| 71 |
+
|
| 72 |
+
# 3. Compute optimal radii for the final configuration using the convergent method.
|
| 73 |
+
radii = compute_max_radii(centers)
|
| 74 |
+
|
| 75 |
+
return centers, radii
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def compute_max_radii(centers):
|
| 79 |
+
"""
|
| 80 |
+
Compute the maximum possible radii for the given centers using an
|
| 81 |
+
iterative relaxation method until convergence. This is more robust
|
| 82 |
+
than the original single-pass approach.
|
| 83 |
+
"""
|
| 84 |
+
n = centers.shape[0]
|
| 85 |
+
|
| 86 |
+
# Initialize radii based on the distance to the four walls.
|
| 87 |
+
radii = np.min([
|
| 88 |
+
centers[:, 0],
|
| 89 |
+
centers[:, 1],
|
| 90 |
+
1 - centers[:, 0],
|
| 91 |
+
1 - centers[:, 1]
|
| 92 |
+
], axis=0)
|
| 93 |
+
|
| 94 |
+
# Iteratively shrink any overlapping circles until the layout is stable.
|
| 95 |
+
max_iter = 500 # Safety break for the convergence loop
|
| 96 |
+
for _ in range(max_iter):
|
| 97 |
+
changed_in_iter = False
|
| 98 |
+
# Check every pair of circles for overlap
|
| 99 |
+
for i in range(n):
|
| 100 |
+
for j in range(i + 1, n):
|
| 101 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 102 |
+
sum_r = radii[i] + radii[j]
|
| 103 |
+
|
| 104 |
+
if sum_r > dist:
|
| 105 |
+
# Overlap detected. Shrink radii proportionally to their size.
|
| 106 |
+
overlap = sum_r - dist
|
| 107 |
+
if sum_r > 1e-9: # Avoid division by zero
|
| 108 |
+
radii[i] -= overlap * (radii[i] / sum_r)
|
| 109 |
+
radii[j] -= overlap * (radii[j] / sum_r)
|
| 110 |
+
changed_in_iter = True
|
| 111 |
+
|
| 112 |
+
if not changed_in_iter:
|
| 113 |
+
# If a full pass completes with no changes, the radii have converged.
|
| 114 |
+
break
|
| 115 |
+
|
| 116 |
+
return radii
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# EVOLVE-BLOCK-END
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# This part remains fixed (not evolved)
|
| 123 |
+
def run_packing():
|
| 124 |
+
"""Run the circle packing constructor for n=26"""
|
| 125 |
+
centers, radii = construct_packing()
|
| 126 |
+
# Calculate the sum of radii
|
| 127 |
+
sum_radii = np.sum(radii)
|
| 128 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/job_log.err
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/job_log.out
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results
|
| 3 |
+
Run 1/1 completed in 0.04 seconds
|
| 4 |
+
Detailed packing data saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/extra.npz
|
| 5 |
+
Visualization saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/packing_viz.png
|
| 6 |
+
Correctness and error status saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/correct.json
|
| 7 |
+
Metrics saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/metrics.json
|
| 8 |
+
Evaluation and Validation completed successfully.
|
| 9 |
+
Metrics:
|
| 10 |
+
combined_score: 1.0573677714897678
|
| 11 |
+
public: {'centers_str': ' centers[0] = (0.0207, 0.0142)\n centers[1] = (0.0213, 0.0841)\n centers[2] = (0.0270, 0.4135)\n centers[3] = (0.0186, 0.8366)\n centers[4] = (0.0152, 0.9810)\n centers[5] = (0.1593, 0.0219)\n centers[6] = (0.0253, 0.2238)\n centers[7] = (0.0260, 0.6139)\n centers[8] = (0.1475, 0.8927)\n centers[9] = (0.1040, 0.9841)\n centers[10] = (0.6268, 0.0232)\n centers[11] = (0.3613, 0.0240)\n centers[12] = (0.5167, 0.2242)\n centers[13] = (0.4951, 0.7061)\n centers[14] = (0.6248, 0.9756)\n centers[15] = (0.3783, 0.9764)\n centers[16] = (0.8370, 0.0219)\n centers[17] = (0.9785, 0.0840)\n centers[18] = (0.9707, 0.5939)\n centers[19] = (0.9721, 0.7911)\n centers[20] = (0.8395, 0.9785)\n centers[21] = (0.9790, 0.0141)\n centers[22] = (0.9747, 0.2268)\n centers[23] = (0.9731, 0.4266)\n centers[24] = (0.9789, 0.9190)\n centers[25] = (0.9791, 0.9863)', 'num_circles': 26}
|
| 12 |
+
private: {'reported_sum_of_radii': 1.0573677714897678}
|
| 13 |
+
visualization_path: results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/packing_viz.png
|
| 14 |
+
execution_time_mean: 0.04305929783731699
|
| 15 |
+
execution_time_std: 0.0
|
| 16 |
+
num_valid_runs: 1
|
| 17 |
+
num_invalid_runs: 0
|
| 18 |
+
all_validation_errors: []
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/metrics.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 1.0573677714897678,
|
| 3 |
+
"public": {
|
| 4 |
+
"centers_str": " centers[0] = (0.0207, 0.0142)\n centers[1] = (0.0213, 0.0841)\n centers[2] = (0.0270, 0.4135)\n centers[3] = (0.0186, 0.8366)\n centers[4] = (0.0152, 0.9810)\n centers[5] = (0.1593, 0.0219)\n centers[6] = (0.0253, 0.2238)\n centers[7] = (0.0260, 0.6139)\n centers[8] = (0.1475, 0.8927)\n centers[9] = (0.1040, 0.9841)\n centers[10] = (0.6268, 0.0232)\n centers[11] = (0.3613, 0.0240)\n centers[12] = (0.5167, 0.2242)\n centers[13] = (0.4951, 0.7061)\n centers[14] = (0.6248, 0.9756)\n centers[15] = (0.3783, 0.9764)\n centers[16] = (0.8370, 0.0219)\n centers[17] = (0.9785, 0.0840)\n centers[18] = (0.9707, 0.5939)\n centers[19] = (0.9721, 0.7911)\n centers[20] = (0.8395, 0.9785)\n centers[21] = (0.9790, 0.0141)\n centers[22] = (0.9747, 0.2268)\n centers[23] = (0.9731, 0.4266)\n centers[24] = (0.9789, 0.9190)\n centers[25] = (0.9791, 0.9863)",
|
| 5 |
+
"num_circles": 26
|
| 6 |
+
},
|
| 7 |
+
"private": {
|
| 8 |
+
"reported_sum_of_radii": 1.0573677714897678
|
| 9 |
+
},
|
| 10 |
+
"visualization_path": "results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/results/packing_viz.png",
|
| 11 |
+
"execution_time_mean": 0.04305929783731699,
|
| 12 |
+
"execution_time_std": 0.0,
|
| 13 |
+
"num_valid_runs": 1,
|
| 14 |
+
"num_invalid_runs": 0,
|
| 15 |
+
"all_validation_errors": []
|
| 16 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_10/search_replace.txt
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
grid_based_initialization
|
| 3 |
+
</NAME>
|
| 4 |
+
<DESCRIPTION>
|
| 5 |
+
The current initial placement strategy, based on concentric rings, leads to a suboptimal configuration dominated by a large central circle, resulting in a low sum of radii (1.28). This is a common local minimum for packing problems.
|
| 6 |
+
|
| 7 |
+
To achieve a more efficient packing, I am replacing the concentric ring initialization with a structured, non-uniform grid pattern. Specifically, I will arrange the 26 circles into 5 columns with counts of [5, 5, 6, 5, 5]. This pattern is inspired by the structures of known optimal packings for similar `n` values. It provides a much denser and more promising starting configuration for the relaxation algorithm by breaking the central symmetry and distributing circles more evenly. I've also added a small amount of random jitter to this grid to help the force-directed algorithm explore variations and avoid getting stuck in a rigid grid structure. This should guide the solver towards a significantly better region of the solution space.
|
| 8 |
+
</DESCRIPTION>
|
| 9 |
+
<DIFF>
|
| 10 |
+
<<<<<<< SEARCH
|
| 11 |
+
# 1. Initial Placement: Start with a structured concentric ring pattern.
|
| 12 |
+
# This provides a symmetric, ordered starting point for the relaxation,
|
| 13 |
+
# combining the idea from the first parent program.
|
| 14 |
+
centers = np.zeros((n, 2))
|
| 15 |
+
|
| 16 |
+
# Place a circle in the center
|
| 17 |
+
centers[0] = [0.5, 0.5]
|
| 18 |
+
|
| 19 |
+
# Place 8 circles in an inner ring
|
| 20 |
+
inner_ring_radius = 0.28
|
| 21 |
+
for i in range(8):
|
| 22 |
+
angle = 2 * np.pi * i / 8
|
| 23 |
+
centers[i + 1] = [0.5 + inner_ring_radius * np.cos(angle), 0.5 + inner_ring_radius * np.sin(angle)]
|
| 24 |
+
|
| 25 |
+
# Place the remaining 17 circles in an outer ring
|
| 26 |
+
outer_ring_radius = 0.48
|
| 27 |
+
for i in range(17):
|
| 28 |
+
angle = 2 * np.pi * i / 17
|
| 29 |
+
centers[i + 9] = [0.5 + outer_ring_radius * np.cos(angle), 0.5 + outer_ring_radius * np.sin(angle)]
|
| 30 |
+
=======
|
| 31 |
+
# 1. Initial Placement: Start with a structured near-grid pattern (5-5-6-5-5 columns).
|
| 32 |
+
# This is a much better starting point than concentric circles, inspired by known optimal packings.
|
| 33 |
+
centers = np.zeros((n, 2))
|
| 34 |
+
col_counts = [5, 5, 6, 5, 5]
|
| 35 |
+
x_coords = np.linspace(0.1, 0.9, 5)
|
| 36 |
+
current_idx = 0
|
| 37 |
+
for i, count in enumerate(col_counts):
|
| 38 |
+
y_coords = np.linspace(1/(count+1), count/(count+1), count)
|
| 39 |
+
for j in range(count):
|
| 40 |
+
centers[current_idx] = [x_coords[i], y_coords[j]]
|
| 41 |
+
current_idx += 1
|
| 42 |
+
|
| 43 |
+
# Add random jitter to break perfect symmetry and allow for new configurations.
|
| 44 |
+
centers += np.random.uniform(-0.02, 0.02, size=centers.shape)
|
| 45 |
+
centers = np.clip(centers, 0.01, 0.99) # Ensure they start safely inside.
|
| 46 |
+
>>>>>>> REPLACE
|
| 47 |
+
</DIFF>
|
| 48 |
+
<NAME>
|
| 49 |
+
increase_relaxation_iterations
|
| 50 |
+
</NAME>
|
| 51 |
+
<DESCRIPTION>
|
| 52 |
+
I am increasing the number of iterations for the force-directed relaxation from 250 to 500. The new initial configuration is more complex and will benefit from a longer relaxation time to allow the circles to settle into a better-optimized final arrangement. More iterations reduce the chance of stopping prematurely in a suboptimal state.
|
| 53 |
+
</DESCRIPTION>
|
| 54 |
+
<DIFF>
|
| 55 |
+
<<<<<<< SEARCH
|
| 56 |
+
n_iter = 250
|
| 57 |
+
=======
|
| 58 |
+
n_iter = 500 # Increased iterations for better convergence
|
| 59 |
+
>>>>>>> REPLACE
|
| 60 |
+
</DIFF>
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (14.2 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/edit.diff
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,306 +1,250 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
-"""
|
| 6 |
+
-This solution implements a Memetic Algorithm (MA), which combines a Genetic
|
| 7 |
+
-Algorithm (GA) for global exploration with a powerful, physics-based local search
|
| 8 |
+
-for exploitation. This approach hybridizes the successful population-based
|
| 9 |
+
-framework of prior submissions with the refined, two-stage force-directed
|
| 10 |
+
-simulation from the current program, which will be repurposed as the local
|
| 11 |
+
-search operator.
|
| 12 |
+
-"""
|
| 13 |
+
-
|
| 14 |
+
import numpy as np
|
| 15 |
+
-
|
| 16 |
+
-class LocalSearchRefiner:
|
| 17 |
+
- """
|
| 18 |
+
- Performs a fast, two-stage, force-directed local refinement on a set of centers.
|
| 19 |
+
- This is adapted from the full simulation of the parent program to act as a
|
| 20 |
+
- powerful local search operator within the Memetic Algorithm.
|
| 21 |
+
+import math
|
| 22 |
+
+import random
|
| 23 |
+
+
|
| 24 |
+
+def compute_max_radii(centers, max_iter=800, convergence_threshold=1e-8):
|
| 25 |
+
+ """
|
| 26 |
+
+ Compute maximum radii using iterative proportional scaling.
|
| 27 |
+
+ This is a robust version with a stable convergence check.
|
| 28 |
+
+ """
|
| 29 |
+
+ n = centers.shape[0]
|
| 30 |
+
+ if n == 0:
|
| 31 |
+
+ return np.array([])
|
| 32 |
+
+
|
| 33 |
+
+ radii = np.min([
|
| 34 |
+
+ centers[:, 0], 1 - centers[:, 0],
|
| 35 |
+
+ centers[:, 1], 1 - centers[:, 1]
|
| 36 |
+
+ ], axis=0)
|
| 37 |
+
+
|
| 38 |
+
+ required_converged_iters = 3
|
| 39 |
+
+ converged_in_a_row = 0
|
| 40 |
+
+
|
| 41 |
+
+ for _ in range(max_iter):
|
| 42 |
+
+ old_radii = radii.copy()
|
| 43 |
+
+
|
| 44 |
+
+ for i in range(n):
|
| 45 |
+
+ for j in range(i + 1, n):
|
| 46 |
+
+ dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 47 |
+
+ dist = np.sqrt(dist_sq)
|
| 48 |
+
+
|
| 49 |
+
+ if radii[i] + radii[j] > dist:
|
| 50 |
+
+ if dist < 1e-9:
|
| 51 |
+
+ radii[i], radii[j] = 0.0, 0.0
|
| 52 |
+
+ else:
|
| 53 |
+
+ scale = dist / (radii[i] + radii[j])
|
| 54 |
+
+ radii[i] *= scale
|
| 55 |
+
+ radii[j] *= scale
|
| 56 |
+
+
|
| 57 |
+
+ max_abs_change = np.max(np.abs(radii - old_radii))
|
| 58 |
+
+ if max_abs_change < convergence_threshold:
|
| 59 |
+
+ converged_in_a_row += 1
|
| 60 |
+
+ if converged_in_a_row >= required_converged_iters:
|
| 61 |
+
+ break
|
| 62 |
+
+ else:
|
| 63 |
+
+ converged_in_a_row = 0
|
| 64 |
+
+
|
| 65 |
+
+ return np.maximum(radii, 0)
|
| 66 |
+
+
|
| 67 |
+
+
|
| 68 |
+
+class QuickRefiner:
|
| 69 |
+
+ """
|
| 70 |
+
+ A fast, aggressive, single-phase local search operator for memetic injection.
|
| 71 |
+
"""
|
| 72 |
+
def __init__(self, config):
|
| 73 |
+
self.config = config
|
| 74 |
+
|
| 75 |
+
def refine(self, centers):
|
| 76 |
+
- """Applies a two-stage force-directed refinement to polish a solution."""
|
| 77 |
+
refined_centers = centers.copy()
|
| 78 |
+
- n = refined_centers.shape[0]
|
| 79 |
+
-
|
| 80 |
+
- # --- Stage 1: Aggressive "unfreezing" and resettlement ---
|
| 81 |
+
- for sim_iter in range(self.config['lsr_aggressive_iter']):
|
| 82 |
+
- progress = sim_iter / self.config['lsr_aggressive_iter']
|
| 83 |
+
- current_lr = self.config['lsr_aggressive_lr'] * (1.0 - progress)**2
|
| 84 |
+
- current_pressure = self.config['lsr_aggressive_pressure_end'] + \
|
| 85 |
+
- (self.config['lsr_aggressive_pressure_start'] - self.config['lsr_aggressive_pressure_end']) * (1.0 - progress)**2
|
| 86 |
+
-
|
| 87 |
+
- radii = compute_max_radii(refined_centers)
|
| 88 |
+
+
|
| 89 |
+
+ for sim_iter in range(self.config['refiner_iter']):
|
| 90 |
+
+ progress = sim_iter / self.config['refiner_iter']
|
| 91 |
+
+ current_lr = self.config['refiner_lr'] * (1.0 - progress)**2
|
| 92 |
+
+ current_pressure = self.config['refiner_pressure_end'] + \
|
| 93 |
+
+ (self.config['refiner_pressure_start'] - self.config['refiner_pressure_end']) * (1.0 - progress)**2
|
| 94 |
+
+
|
| 95 |
+
+ # Fast radius calculation for simulation
|
| 96 |
+
+ radii = compute_max_radii(refined_centers, max_iter=100)
|
| 97 |
+
inflated_radii = radii * current_pressure
|
| 98 |
+
|
| 99 |
+
+ # Vectorized force calculations
|
| 100 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 101 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 102 |
+
+ dists[dists < 1e-9] = 1e-9
|
| 103 |
+
|
| 104 |
+
sum_inflated_radii = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 105 |
+
overlaps = np.maximum(0, sum_inflated_radii - dists)
|
| 106 |
+
np.fill_diagonal(overlaps, 0)
|
| 107 |
+
-
|
| 108 |
+
- with np.errstate(divide='ignore', invalid='ignore'):
|
| 109 |
+
- unit_vectors = diffs / (dists[..., np.newaxis] + 1e-9)
|
| 110 |
+
- unit_vectors[dists < 1e-9] = 0
|
| 111 |
+
-
|
| 112 |
+
- circle_forces = np.sum(unit_vectors * overlaps[..., np.newaxis], axis=1)
|
| 113 |
+
+
|
| 114 |
+
+ force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 115 |
+
+ circle_forces = np.sum(force_matrix, axis=1)
|
| 116 |
+
|
| 117 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 118 |
+
- wall_strength = self.config['lsr_wall_strength']
|
| 119 |
+
+ wall_strength = self.config['refiner_wall_strength']
|
| 120 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 121 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 122 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 123 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 124 |
+
|
| 125 |
+
forces = circle_forces + wall_forces
|
| 126 |
+
refined_centers += forces * current_lr
|
| 127 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 128 |
+
-
|
| 129 |
+
- # --- Stage 2: Fine-tuning with subtle growth pressure ---
|
| 130 |
+
- for sim_iter_ft in range(self.config['lsr_finetune_iter']):
|
| 131 |
+
- progress = sim_iter_ft / self.config['lsr_finetune_iter']
|
| 132 |
+
- current_lr = self.config['lsr_finetune_lr'] * (1.0 - progress)
|
| 133 |
+
- current_pressure = self.config['lsr_finetune_pressure_end'] + \
|
| 134 |
+
- (self.config['lsr_finetune_pressure_start'] - self.config['lsr_finetune_pressure_end']) * (1.0 - progress)
|
| 135 |
+
-
|
| 136 |
+
- radii = compute_max_radii(refined_centers)
|
| 137 |
+
- inflated_radii = radii * current_pressure
|
| 138 |
+
-
|
| 139 |
+
- diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 140 |
+
- dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 141 |
+
-
|
| 142 |
+
- sum_radii_pairs = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 143 |
+
- overlaps = np.maximum(0, sum_radii_pairs - dists)
|
| 144 |
+
- np.fill_diagonal(overlaps, 0)
|
| 145 |
+
-
|
| 146 |
+
- with np.errstate(divide='ignore', invalid='ignore'):
|
| 147 |
+
- unit_vectors = diffs / (dists[..., np.newaxis] + 1e-9)
|
| 148 |
+
- unit_vectors[dists < 1e-9] = 0
|
| 149 |
+
-
|
| 150 |
+
- circle_forces = np.sum(unit_vectors * overlaps[..., np.newaxis], axis=1)
|
| 151 |
+
-
|
| 152 |
+
- wall_forces = np.zeros_like(refined_centers)
|
| 153 |
+
- wall_strength = self.config['lsr_wall_strength']
|
| 154 |
+
- wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 155 |
+
- wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 156 |
+
- wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 157 |
+
- wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 158 |
+
-
|
| 159 |
+
- forces = circle_forces + wall_forces
|
| 160 |
+
- refined_centers += forces * current_lr
|
| 161 |
+
- refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 162 |
+
-
|
| 163 |
+
+
|
| 164 |
+
return refined_centers
|
| 165 |
+
|
| 166 |
+
-class MemeticAlgorithm:
|
| 167 |
+
- """Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 168 |
+
- def __init__(self, n, config):
|
| 169 |
+
+
|
| 170 |
+
+class SimulatedAnnealerWithInjection:
|
| 171 |
+
+ """
|
| 172 |
+
+ Performs a search using SA, with a memetic local search integrated as a move operator.
|
| 173 |
+
+ """
|
| 174 |
+
+ def __init__(self, n, initial_centers, config):
|
| 175 |
+
self.n = n
|
| 176 |
+
self.config = config
|
| 177 |
+
- self.population = []
|
| 178 |
+
- self.fitnesses = np.array([])
|
| 179 |
+
- self.best_solution = None
|
| 180 |
+
- self.best_fitness = -1.0
|
| 181 |
+
- self.local_search_refiner = LocalSearchRefiner(config=config)
|
| 182 |
+
-
|
| 183 |
+
- def _initialize_population(self):
|
| 184 |
+
- """Initializes a diverse population with both random and grid-based individuals."""
|
| 185 |
+
- num_grid_based = self.config['population_size'] // 3
|
| 186 |
+
-
|
| 187 |
+
- for i in range(self.config['population_size']):
|
| 188 |
+
- if i < num_grid_based:
|
| 189 |
+
- centers = np.zeros((self.n, 2))
|
| 190 |
+
- num_cells_side = 5
|
| 191 |
+
- spacing = 1.0 / num_cells_side
|
| 192 |
+
- k = 0
|
| 193 |
+
- for row in range(num_cells_side):
|
| 194 |
+
- for col in range(num_cells_side):
|
| 195 |
+
- centers[k, 0] = (col + 0.5) * spacing
|
| 196 |
+
- centers[k, 1] = (row + 0.5) * spacing
|
| 197 |
+
- k += 1
|
| 198 |
+
- centers[:25, :] += np.random.normal(0, spacing * 0.02, size=(25, 2))
|
| 199 |
+
-
|
| 200 |
+
- # Place 26th circle in a random-ish interstitial void
|
| 201 |
+
- interstitial_points = [[0.2, 0.2], [0.2, 0.8], [0.8, 0.2], [0.8, 0.8], [0.5, 0.5]]
|
| 202 |
+
- extra_pos = interstitial_points[i % len(interstitial_points)]
|
| 203 |
+
- centers[25] = extra_pos + np.random.normal(0, spacing * 0.05, size=2)
|
| 204 |
+
- self.population.append(np.clip(centers, 0.0, 1.0))
|
| 205 |
+
- else:
|
| 206 |
+
- self.population.append(np.random.rand(self.n, 2))
|
| 207 |
+
-
|
| 208 |
+
- def _evaluate_population(self):
|
| 209 |
+
- """Calculates fitness for the population and updates the best solution."""
|
| 210 |
+
- fitnesses = []
|
| 211 |
+
- for ind in self.population:
|
| 212 |
+
- radii = compute_max_radii(ind)
|
| 213 |
+
- fitnesses.append(np.sum(radii))
|
| 214 |
+
- self.fitnesses = np.array(fitnesses)
|
| 215 |
+
-
|
| 216 |
+
- best_idx = np.argmax(self.fitnesses)
|
| 217 |
+
- if self.fitnesses[best_idx] > self.best_fitness:
|
| 218 |
+
- self.best_fitness = self.fitnesses[best_idx]
|
| 219 |
+
- self.best_solution = self.population[best_idx].copy()
|
| 220 |
+
-
|
| 221 |
+
- def _select_parent(self):
|
| 222 |
+
- """Selects a parent using tournament selection."""
|
| 223 |
+
- tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 224 |
+
- tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 225 |
+
- winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 226 |
+
- return self.population[winner_idx]
|
| 227 |
+
-
|
| 228 |
+
- def _crossover(self, p1, p2):
|
| 229 |
+
- """Performs blend crossover (BLX-alpha)."""
|
| 230 |
+
- alpha = self.config['crossover_alpha']
|
| 231 |
+
- child = alpha * p1 + (1.0 - alpha) * p2
|
| 232 |
+
- return np.clip(child, 0.0, 1.0)
|
| 233 |
+
-
|
| 234 |
+
- def _mutate(self, individual, strength):
|
| 235 |
+
- """Applies Gaussian mutation and a chance of a strong mutation (jump)."""
|
| 236 |
+
- mutated_ind = individual.copy()
|
| 237 |
+
- mutation_mask = np.random.rand(self.n) < self.config['mutation_rate']
|
| 238 |
+
- noise = np.random.normal(0, strength, size=(np.sum(mutation_mask), 2))
|
| 239 |
+
- mutated_ind[mutation_mask] += noise
|
| 240 |
+
-
|
| 241 |
+
- if np.random.rand() < self.config['jump_mutation_prob']:
|
| 242 |
+
- idx_to_reset = np.random.randint(0, self.n)
|
| 243 |
+
- mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 244 |
+
-
|
| 245 |
+
- return np.clip(mutated_ind, 0.0, 1.0)
|
| 246 |
+
+ self.centers = initial_centers
|
| 247 |
+
+
|
| 248 |
+
+ self.temp = config['t_start']
|
| 249 |
+
+ self.max_step_size = config['max_step_size']
|
| 250 |
+
+
|
| 251 |
+
+ self.energy = self._calculate_energy(self.centers)
|
| 252 |
+
+ self.best_centers = self.centers.copy()
|
| 253 |
+
+ self.best_energy = self.energy
|
| 254 |
+
+
|
| 255 |
+
+ self.move_weights = config['move_weights']
|
| 256 |
+
+ self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles, self._refinement_injection]
|
| 257 |
+
+
|
| 258 |
+
+ self.refiner = QuickRefiner(config)
|
| 259 |
+
+
|
| 260 |
+
+ def _calculate_energy(self, centers):
|
| 261 |
+
+ """Energy is the negative sum of radii, to be minimized."""
|
| 262 |
+
+ radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 263 |
+
+ return -np.sum(radii)
|
| 264 |
+
+
|
| 265 |
+
+ def _propose_move(self):
|
| 266 |
+
+ move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 267 |
+
+ t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 268 |
+
+ current_step_size = self.max_step_size * t_progress
|
| 269 |
+
+ return move_func(current_step_size)
|
| 270 |
+
+
|
| 271 |
+
+ def _move_single_circle(self, step_size):
|
| 272 |
+
+ new_centers = self.centers.copy()
|
| 273 |
+
+ idx = random.randint(0, self.n - 1)
|
| 274 |
+
+ displacement = np.random.normal(0, step_size, size=2)
|
| 275 |
+
+ new_centers[idx] += displacement
|
| 276 |
+
+ new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 277 |
+
+ return new_centers
|
| 278 |
+
+
|
| 279 |
+
+ def _move_cluster(self, step_size):
|
| 280 |
+
+ new_centers = self.centers.copy()
|
| 281 |
+
+ cluster_size = self.config['cluster_size']
|
| 282 |
+
+ seed_idx = random.randint(0, self.n - 1)
|
| 283 |
+
+ dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 284 |
+
+ neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 285 |
+
+ displacement = np.random.normal(0, step_size * 0.5, size=2)
|
| 286 |
+
+ new_centers[neighbor_indices] += displacement
|
| 287 |
+
+ new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 288 |
+
+ return new_centers
|
| 289 |
+
+
|
| 290 |
+
+ def _swap_circles(self, step_size):
|
| 291 |
+
+ new_centers = self.centers.copy()
|
| 292 |
+
+ if self.n < 2: return new_centers
|
| 293 |
+
+ idx1, idx2 = random.sample(range(self.n), 2)
|
| 294 |
+
+ new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 295 |
+
+ return new_centers
|
| 296 |
+
+
|
| 297 |
+
+ def _refinement_injection(self, step_size):
|
| 298 |
+
+ """Applies the quick local search as a single, powerful move."""
|
| 299 |
+
+ return self.refiner.refine(self.centers)
|
| 300 |
+
|
| 301 |
+
def run(self):
|
| 302 |
+
- """Executes the full memetic algorithm evolution."""
|
| 303 |
+
- self._initialize_population()
|
| 304 |
+
-
|
| 305 |
+
- for gen in range(self.config['generations']):
|
| 306 |
+
- self._evaluate_population()
|
| 307 |
+
-
|
| 308 |
+
- new_population = []
|
| 309 |
+
-
|
| 310 |
+
- # Elitism: carry over best individuals
|
| 311 |
+
- elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 312 |
+
- for idx in elite_indices:
|
| 313 |
+
- new_population.append(self.population[idx].copy())
|
| 314 |
+
-
|
| 315 |
+
- # Anneal mutation strength
|
| 316 |
+
- mut_strength = self.config['mut_strength_end'] + \
|
| 317 |
+
- (self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 318 |
+
- (1.0 - (gen / self.config['generations']))**2.0
|
| 319 |
+
-
|
| 320 |
+
- while len(new_population) < self.config['population_size']:
|
| 321 |
+
- p1 = self._select_parent()
|
| 322 |
+
- p2 = self._select_parent()
|
| 323 |
+
-
|
| 324 |
+
- child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 325 |
+
- child = self._mutate(child, mut_strength)
|
| 326 |
+
-
|
| 327 |
+
- # Memetic step: Apply local search probabilistically
|
| 328 |
+
- if np.random.rand() < self.config['local_search_prob']:
|
| 329 |
+
- child = self.local_search_refiner.refine(child)
|
| 330 |
+
-
|
| 331 |
+
- new_population.append(child)
|
| 332 |
+
-
|
| 333 |
+
- self.population = new_population
|
| 334 |
+
-
|
| 335 |
+
- self._evaluate_population()
|
| 336 |
+
- return self.best_solution
|
| 337 |
+
+ """Executes the simulated annealing search."""
|
| 338 |
+
+ while self.temp > self.config['t_end']:
|
| 339 |
+
+ for _ in range(self.config['moves_per_temp']):
|
| 340 |
+
+ new_centers = self._propose_move()
|
| 341 |
+
+ new_energy = self._calculate_energy(new_centers)
|
| 342 |
+
+
|
| 343 |
+
+ delta_e = new_energy - self.energy
|
| 344 |
+
+
|
| 345 |
+
+ if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 346 |
+
+ self.centers = new_centers
|
| 347 |
+
+ self.energy = new_energy
|
| 348 |
+
+
|
| 349 |
+
+ if self.energy < self.best_energy:
|
| 350 |
+
+ self.best_energy = self.energy
|
| 351 |
+
+ self.best_centers = self.centers.copy()
|
| 352 |
+
+
|
| 353 |
+
+ self.temp *= self.config['cooling_rate']
|
| 354 |
+
+
|
| 355 |
+
+ return self.best_centers
|
| 356 |
+
+
|
| 357 |
+
|
| 358 |
+
def construct_packing():
|
| 359 |
+
"""
|
| 360 |
+
- Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 361 |
+
+ Constructs a packing of 26 circles using a multi-start SA with memetic injection.
|
| 362 |
+
"""
|
| 363 |
+
n = 26
|
| 364 |
+
config = {
|
| 365 |
+
- # --- GA Parameters ---
|
| 366 |
+
- 'population_size': 80,
|
| 367 |
+
- 'generations': 350,
|
| 368 |
+
- 'elite_count': 4,
|
| 369 |
+
- 'tournament_size': 6,
|
| 370 |
+
- 'mutation_rate': 0.35, # Per-circle mutation probability
|
| 371 |
+
- 'jump_mutation_prob': 0.03, # Probability of a random reset for one circle
|
| 372 |
+
- 'mut_strength_start': 0.1,
|
| 373 |
+
- 'mut_strength_end': 0.001,
|
| 374 |
+
- 'crossover_rate': 0.9,
|
| 375 |
+
- 'crossover_alpha': 0.5, # For BLX-alpha crossover
|
| 376 |
+
- # --- Memetic Parameters ---
|
| 377 |
+
- 'local_search_prob': 0.20, # Probability of applying local search to a new child
|
| 378 |
+
- # --- Local Search Refiner (LSR) Parameters ---
|
| 379 |
+
- 'lsr_aggressive_iter': 40,
|
| 380 |
+
- 'lsr_aggressive_lr': 0.025,
|
| 381 |
+
- 'lsr_aggressive_pressure_start': 1.06,
|
| 382 |
+
- 'lsr_aggressive_pressure_end': 1.001,
|
| 383 |
+
- 'lsr_finetune_iter': 80,
|
| 384 |
+
- 'lsr_finetune_lr': 0.001,
|
| 385 |
+
- 'lsr_finetune_pressure_start': 1.0008,
|
| 386 |
+
- 'lsr_finetune_pressure_end': 1.00001,
|
| 387 |
+
- 'lsr_wall_strength': 0.5,
|
| 388 |
+
+ # --- Multi-Start Config ---
|
| 389 |
+
+ 'num_starts': 12,
|
| 390 |
+
+ # --- SA Config ---
|
| 391 |
+
+ 't_start': 0.05,
|
| 392 |
+
+ 't_end': 1e-6,
|
| 393 |
+
+ 'cooling_rate': 0.9985, # Slower cooling for deeper search
|
| 394 |
+
+ 'moves_per_temp': 80,
|
| 395 |
+
+ 'max_step_size': 0.15,
|
| 396 |
+
+ 'radius_iter': 250,
|
| 397 |
+
+ 'move_weights': [0.60, 0.20, 0.15, 0.05], # [single, cluster, swap, refine]
|
| 398 |
+
+ 'cluster_size': 4,
|
| 399 |
+
+ # --- Refiner Config (for injection) ---
|
| 400 |
+
+ 'refiner_iter': 50,
|
| 401 |
+
+ 'refiner_lr': 0.02,
|
| 402 |
+
+ 'refiner_pressure_start': 1.05,
|
| 403 |
+
+ 'refiner_pressure_end': 1.001,
|
| 404 |
+
+ 'refiner_wall_strength': 0.5,
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
- solver = MemeticAlgorithm(n=n, config=config)
|
| 408 |
+
- best_centers = solver.run()
|
| 409 |
+
-
|
| 410 |
+
- # Final, high-precision radius calculation for the best solution
|
| 411 |
+
- final_radii = compute_max_radii(best_centers)
|
| 412 |
+
-
|
| 413 |
+
- return best_centers, final_radii
|
| 414 |
+
-
|
| 415 |
+
-
|
| 416 |
+
-def compute_max_radii(centers):
|
| 417 |
+
- """
|
| 418 |
+
- Compute the maximum possible radii for each circle position
|
| 419 |
+
- such that they don't overlap and stay within the unit square.
|
| 420 |
+
- This uses an iterative proportional scaling method.
|
| 421 |
+
- """
|
| 422 |
+
- n = centers.shape[0]
|
| 423 |
+
- radii = np.ones(n)
|
| 424 |
+
-
|
| 425 |
+
- for i in range(n):
|
| 426 |
+
- x, y = centers[i]
|
| 427 |
+
- radii[i] = min(x, y, 1 - x, 1 - y)
|
| 428 |
+
-
|
| 429 |
+
- max_radius_iter = 600 # Maximum iterations for radius calculation
|
| 430 |
+
- convergence_epsilon = 1e-7 # Threshold for maximum absolute change in radii
|
| 431 |
+
- required_converged_iters = 5 # Number of consecutive iterations below epsilon to confirm convergence
|
| 432 |
+
- converged_in_a_row = 0 # Counter for consecutive converged iterations
|
| 433 |
+
-
|
| 434 |
+
- for iteration in range(max_radius_iter):
|
| 435 |
+
- previous_radii = radii.copy() # Store radii from the previous iteration to check for changes
|
| 436 |
+
-
|
| 437 |
+
- for i in range(n):
|
| 438 |
+
- for j in range(i + 1, n):
|
| 439 |
+
- dist = np.linalg.norm(centers[i] - centers[j])
|
| 440 |
+
-
|
| 441 |
+
- if dist < 1e-9: # Centers are practically identical
|
| 442 |
+
- radii[i] = 0.0
|
| 443 |
+
- radii[j] = 0.0
|
| 444 |
+
- # Note: No explicit `updated_in_pass` needed anymore; `max_absolute_change` captures this.
|
| 445 |
+
- continue
|
| 446 |
+
-
|
| 447 |
+
- if radii[i] + radii[j] > dist:
|
| 448 |
+
- scale = dist / (radii[i] + radii[j])
|
| 449 |
+
- radii[i] *= scale
|
| 450 |
+
- radii[j] *= scale
|
| 451 |
+
-
|
| 452 |
+
- # Calculate the maximum absolute change in any radius
|
| 453 |
+
- max_absolute_change = np.max(np.abs(radii - previous_radii)) if n > 0 else 0.0
|
| 454 |
+
-
|
| 455 |
+
- # Check for convergence based on max_absolute_change
|
| 456 |
+
- if max_absolute_change < convergence_epsilon:
|
| 457 |
+
- converged_in_a_row += 1
|
| 458 |
+
- if converged_in_a_row >= required_converged_iters:
|
| 459 |
+
- break # Converged for several consecutive iterations
|
| 460 |
+
+ best_overall_centers = None
|
| 461 |
+
+ best_overall_score = -1.0
|
| 462 |
+
+
|
| 463 |
+
+ for i in range(config['num_starts']):
|
| 464 |
+
+ initial_centers = np.zeros((n, 2))
|
| 465 |
+
+ # --- Strategic Initialization (from best prior SA) ---
|
| 466 |
+
+ if i < config['num_starts'] // 2:
|
| 467 |
+
+ num_cells_side = 5
|
| 468 |
+
+ spacing = 1.0 / num_cells_side
|
| 469 |
+
+ k = 0
|
| 470 |
+
+ for row in range(num_cells_side):
|
| 471 |
+
+ for col in range(num_cells_side):
|
| 472 |
+
+ initial_centers[k, 0] = (col + 0.5) * spacing
|
| 473 |
+
+ initial_centers[k, 1] = (row + 0.5) * spacing
|
| 474 |
+
+ k += 1
|
| 475 |
+
+ initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 476 |
+
+
|
| 477 |
+
+ extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 478 |
+
+ initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 479 |
+
+ initial_centers = np.clip(initial_centers, 0.01, 0.99)
|
| 480 |
+
else:
|
| 481 |
+
- converged_in_a_row = 0 # Reset counter if change is significant
|
| 482 |
+
-
|
| 483 |
+
- return np.maximum(radii, 0.0) # Ensure no negative radii (should be handled by logic, but as a safeguard)
|
| 484 |
+
+ initial_centers = np.random.rand(n, 2)
|
| 485 |
+
+
|
| 486 |
+
+ solver = SimulatedAnnealerWithInjection(n=n, initial_centers=initial_centers, config=config)
|
| 487 |
+
+ result_centers = solver.run()
|
| 488 |
+
+
|
| 489 |
+
+ radii = compute_max_radii(result_centers, max_iter=2500, convergence_threshold=1e-9)
|
| 490 |
+
+ score = np.sum(radii)
|
| 491 |
+
+
|
| 492 |
+
+ if score > best_overall_score:
|
| 493 |
+
+ best_overall_score = score
|
| 494 |
+
+ best_overall_centers = result_centers
|
| 495 |
+
+
|
| 496 |
+
+ final_radii = compute_max_radii(best_overall_centers, max_iter=4000, convergence_threshold=1e-9)
|
| 497 |
+
+
|
| 498 |
+
+ return best_overall_centers, final_radii
|
| 499 |
+
# EVOLVE-BLOCK-END
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
# This part remains fixed (not evolved)
|
| 503 |
+
def run_packing():
|
| 504 |
+
"""Run the circle packing constructor for n=26"""
|
| 505 |
+
centers, radii = construct_packing()
|
| 506 |
+
# Calculate the sum of radii
|
| 507 |
+
sum_radii = np.sum(radii)
|
| 508 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/main.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
|
| 6 |
+
def compute_max_radii(centers, max_iter=800, convergence_threshold=1e-8):
|
| 7 |
+
"""
|
| 8 |
+
Compute maximum radii using iterative proportional scaling.
|
| 9 |
+
This is a robust version with a stable convergence check.
|
| 10 |
+
"""
|
| 11 |
+
n = centers.shape[0]
|
| 12 |
+
if n == 0:
|
| 13 |
+
return np.array([])
|
| 14 |
+
|
| 15 |
+
radii = np.min([
|
| 16 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 17 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 18 |
+
], axis=0)
|
| 19 |
+
|
| 20 |
+
required_converged_iters = 3
|
| 21 |
+
converged_in_a_row = 0
|
| 22 |
+
|
| 23 |
+
for _ in range(max_iter):
|
| 24 |
+
old_radii = radii.copy()
|
| 25 |
+
|
| 26 |
+
for i in range(n):
|
| 27 |
+
for j in range(i + 1, n):
|
| 28 |
+
dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 29 |
+
dist = np.sqrt(dist_sq)
|
| 30 |
+
|
| 31 |
+
if radii[i] + radii[j] > dist:
|
| 32 |
+
if dist < 1e-9:
|
| 33 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 34 |
+
else:
|
| 35 |
+
scale = dist / (radii[i] + radii[j])
|
| 36 |
+
radii[i] *= scale
|
| 37 |
+
radii[j] *= scale
|
| 38 |
+
|
| 39 |
+
max_abs_change = np.max(np.abs(radii - old_radii))
|
| 40 |
+
if max_abs_change < convergence_threshold:
|
| 41 |
+
converged_in_a_row += 1
|
| 42 |
+
if converged_in_a_row >= required_converged_iters:
|
| 43 |
+
break
|
| 44 |
+
else:
|
| 45 |
+
converged_in_a_row = 0
|
| 46 |
+
|
| 47 |
+
return np.maximum(radii, 0)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class QuickRefiner:
|
| 51 |
+
"""
|
| 52 |
+
A fast, aggressive, single-phase local search operator for memetic injection.
|
| 53 |
+
"""
|
| 54 |
+
def __init__(self, config):
|
| 55 |
+
self.config = config
|
| 56 |
+
|
| 57 |
+
def refine(self, centers):
|
| 58 |
+
refined_centers = centers.copy()
|
| 59 |
+
|
| 60 |
+
for sim_iter in range(self.config['refiner_iter']):
|
| 61 |
+
progress = sim_iter / self.config['refiner_iter']
|
| 62 |
+
current_lr = self.config['refiner_lr'] * (1.0 - progress)**2
|
| 63 |
+
current_pressure = self.config['refiner_pressure_end'] + \
|
| 64 |
+
(self.config['refiner_pressure_start'] - self.config['refiner_pressure_end']) * (1.0 - progress)**2
|
| 65 |
+
|
| 66 |
+
# Fast radius calculation for simulation
|
| 67 |
+
radii = compute_max_radii(refined_centers, max_iter=100)
|
| 68 |
+
inflated_radii = radii * current_pressure
|
| 69 |
+
|
| 70 |
+
# Vectorized force calculations
|
| 71 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 72 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 73 |
+
dists[dists < 1e-9] = 1e-9
|
| 74 |
+
|
| 75 |
+
sum_inflated_radii = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 76 |
+
overlaps = np.maximum(0, sum_inflated_radii - dists)
|
| 77 |
+
np.fill_diagonal(overlaps, 0)
|
| 78 |
+
|
| 79 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 80 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 81 |
+
|
| 82 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 83 |
+
wall_strength = self.config['refiner_wall_strength']
|
| 84 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 85 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 86 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 87 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 88 |
+
|
| 89 |
+
forces = circle_forces + wall_forces
|
| 90 |
+
refined_centers += forces * current_lr
|
| 91 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 92 |
+
|
| 93 |
+
return refined_centers
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class SimulatedAnnealerWithInjection:
|
| 97 |
+
"""
|
| 98 |
+
Performs a search using SA, with a memetic local search integrated as a move operator.
|
| 99 |
+
"""
|
| 100 |
+
def __init__(self, n, initial_centers, config):
|
| 101 |
+
self.n = n
|
| 102 |
+
self.config = config
|
| 103 |
+
self.centers = initial_centers
|
| 104 |
+
|
| 105 |
+
self.temp = config['t_start']
|
| 106 |
+
self.max_step_size = config['max_step_size']
|
| 107 |
+
|
| 108 |
+
self.energy = self._calculate_energy(self.centers)
|
| 109 |
+
self.best_centers = self.centers.copy()
|
| 110 |
+
self.best_energy = self.energy
|
| 111 |
+
|
| 112 |
+
self.move_weights = config['move_weights']
|
| 113 |
+
self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles, self._refinement_injection]
|
| 114 |
+
|
| 115 |
+
self.refiner = QuickRefiner(config)
|
| 116 |
+
|
| 117 |
+
def _calculate_energy(self, centers):
|
| 118 |
+
"""Energy is the negative sum of radii, to be minimized."""
|
| 119 |
+
radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 120 |
+
return -np.sum(radii)
|
| 121 |
+
|
| 122 |
+
def _propose_move(self):
|
| 123 |
+
move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 124 |
+
t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 125 |
+
current_step_size = self.max_step_size * t_progress
|
| 126 |
+
return move_func(current_step_size)
|
| 127 |
+
|
| 128 |
+
def _move_single_circle(self, step_size):
|
| 129 |
+
new_centers = self.centers.copy()
|
| 130 |
+
idx = random.randint(0, self.n - 1)
|
| 131 |
+
displacement = np.random.normal(0, step_size, size=2)
|
| 132 |
+
new_centers[idx] += displacement
|
| 133 |
+
new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 134 |
+
return new_centers
|
| 135 |
+
|
| 136 |
+
def _move_cluster(self, step_size):
|
| 137 |
+
new_centers = self.centers.copy()
|
| 138 |
+
cluster_size = self.config['cluster_size']
|
| 139 |
+
seed_idx = random.randint(0, self.n - 1)
|
| 140 |
+
dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 141 |
+
neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 142 |
+
displacement = np.random.normal(0, step_size * 0.5, size=2)
|
| 143 |
+
new_centers[neighbor_indices] += displacement
|
| 144 |
+
new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 145 |
+
return new_centers
|
| 146 |
+
|
| 147 |
+
def _swap_circles(self, step_size):
|
| 148 |
+
new_centers = self.centers.copy()
|
| 149 |
+
if self.n < 2: return new_centers
|
| 150 |
+
idx1, idx2 = random.sample(range(self.n), 2)
|
| 151 |
+
new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 152 |
+
return new_centers
|
| 153 |
+
|
| 154 |
+
def _refinement_injection(self, step_size):
|
| 155 |
+
"""Applies the quick local search as a single, powerful move."""
|
| 156 |
+
return self.refiner.refine(self.centers)
|
| 157 |
+
|
| 158 |
+
def run(self):
|
| 159 |
+
"""Executes the simulated annealing search."""
|
| 160 |
+
while self.temp > self.config['t_end']:
|
| 161 |
+
for _ in range(self.config['moves_per_temp']):
|
| 162 |
+
new_centers = self._propose_move()
|
| 163 |
+
new_energy = self._calculate_energy(new_centers)
|
| 164 |
+
|
| 165 |
+
delta_e = new_energy - self.energy
|
| 166 |
+
|
| 167 |
+
if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 168 |
+
self.centers = new_centers
|
| 169 |
+
self.energy = new_energy
|
| 170 |
+
|
| 171 |
+
if self.energy < self.best_energy:
|
| 172 |
+
self.best_energy = self.energy
|
| 173 |
+
self.best_centers = self.centers.copy()
|
| 174 |
+
|
| 175 |
+
self.temp *= self.config['cooling_rate']
|
| 176 |
+
|
| 177 |
+
return self.best_centers
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def construct_packing():
|
| 181 |
+
"""
|
| 182 |
+
Constructs a packing of 26 circles using a multi-start SA with memetic injection.
|
| 183 |
+
"""
|
| 184 |
+
n = 26
|
| 185 |
+
config = {
|
| 186 |
+
# --- Multi-Start Config ---
|
| 187 |
+
'num_starts': 12,
|
| 188 |
+
# --- SA Config ---
|
| 189 |
+
't_start': 0.05,
|
| 190 |
+
't_end': 1e-6,
|
| 191 |
+
'cooling_rate': 0.9985, # Slower cooling for deeper search
|
| 192 |
+
'moves_per_temp': 80,
|
| 193 |
+
'max_step_size': 0.15,
|
| 194 |
+
'radius_iter': 250,
|
| 195 |
+
'move_weights': [0.60, 0.20, 0.15, 0.05], # [single, cluster, swap, refine]
|
| 196 |
+
'cluster_size': 4,
|
| 197 |
+
# --- Refiner Config (for injection) ---
|
| 198 |
+
'refiner_iter': 50,
|
| 199 |
+
'refiner_lr': 0.02,
|
| 200 |
+
'refiner_pressure_start': 1.05,
|
| 201 |
+
'refiner_pressure_end': 1.001,
|
| 202 |
+
'refiner_wall_strength': 0.5,
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
best_overall_centers = None
|
| 206 |
+
best_overall_score = -1.0
|
| 207 |
+
|
| 208 |
+
for i in range(config['num_starts']):
|
| 209 |
+
initial_centers = np.zeros((n, 2))
|
| 210 |
+
# --- Strategic Initialization (from best prior SA) ---
|
| 211 |
+
if i < config['num_starts'] // 2:
|
| 212 |
+
num_cells_side = 5
|
| 213 |
+
spacing = 1.0 / num_cells_side
|
| 214 |
+
k = 0
|
| 215 |
+
for row in range(num_cells_side):
|
| 216 |
+
for col in range(num_cells_side):
|
| 217 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 218 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 219 |
+
k += 1
|
| 220 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 221 |
+
|
| 222 |
+
extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 223 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 224 |
+
initial_centers = np.clip(initial_centers, 0.01, 0.99)
|
| 225 |
+
else:
|
| 226 |
+
initial_centers = np.random.rand(n, 2)
|
| 227 |
+
|
| 228 |
+
solver = SimulatedAnnealerWithInjection(n=n, initial_centers=initial_centers, config=config)
|
| 229 |
+
result_centers = solver.run()
|
| 230 |
+
|
| 231 |
+
radii = compute_max_radii(result_centers, max_iter=2500, convergence_threshold=1e-9)
|
| 232 |
+
score = np.sum(radii)
|
| 233 |
+
|
| 234 |
+
if score > best_overall_score:
|
| 235 |
+
best_overall_score = score
|
| 236 |
+
best_overall_centers = result_centers
|
| 237 |
+
|
| 238 |
+
final_radii = compute_max_radii(best_overall_centers, max_iter=4000, convergence_threshold=1e-9)
|
| 239 |
+
|
| 240 |
+
return best_overall_centers, final_radii
|
| 241 |
+
# EVOLVE-BLOCK-END
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# This part remains fixed (not evolved)
|
| 245 |
+
def run_packing():
|
| 246 |
+
"""Run the circle packing constructor for n=26"""
|
| 247 |
+
centers, radii = construct_packing()
|
| 248 |
+
# Calculate the sum of radii
|
| 249 |
+
sum_radii = np.sum(radii)
|
| 250 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/original.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
This solution implements a Memetic Algorithm (MA), which combines a Genetic
|
| 4 |
+
Algorithm (GA) for global exploration with a powerful, physics-based local search
|
| 5 |
+
for exploitation. This approach hybridizes the successful population-based
|
| 6 |
+
framework of prior submissions with the refined, two-stage force-directed
|
| 7 |
+
simulation from the current program, which will be repurposed as the local
|
| 8 |
+
search operator.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
class LocalSearchRefiner:
|
| 14 |
+
"""
|
| 15 |
+
Performs a fast, two-stage, force-directed local refinement on a set of centers.
|
| 16 |
+
This is adapted from the full simulation of the parent program to act as a
|
| 17 |
+
powerful local search operator within the Memetic Algorithm.
|
| 18 |
+
"""
|
| 19 |
+
def __init__(self, config):
|
| 20 |
+
self.config = config
|
| 21 |
+
|
| 22 |
+
def refine(self, centers):
|
| 23 |
+
"""Applies a two-stage force-directed refinement to polish a solution."""
|
| 24 |
+
refined_centers = centers.copy()
|
| 25 |
+
n = refined_centers.shape[0]
|
| 26 |
+
|
| 27 |
+
# --- Stage 1: Aggressive "unfreezing" and resettlement ---
|
| 28 |
+
for sim_iter in range(self.config['lsr_aggressive_iter']):
|
| 29 |
+
progress = sim_iter / self.config['lsr_aggressive_iter']
|
| 30 |
+
current_lr = self.config['lsr_aggressive_lr'] * (1.0 - progress)**2
|
| 31 |
+
current_pressure = self.config['lsr_aggressive_pressure_end'] + \
|
| 32 |
+
(self.config['lsr_aggressive_pressure_start'] - self.config['lsr_aggressive_pressure_end']) * (1.0 - progress)**2
|
| 33 |
+
|
| 34 |
+
radii = compute_max_radii(refined_centers)
|
| 35 |
+
inflated_radii = radii * current_pressure
|
| 36 |
+
|
| 37 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 38 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 39 |
+
|
| 40 |
+
sum_inflated_radii = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 41 |
+
overlaps = np.maximum(0, sum_inflated_radii - dists)
|
| 42 |
+
np.fill_diagonal(overlaps, 0)
|
| 43 |
+
|
| 44 |
+
with np.errstate(divide='ignore', invalid='ignore'):
|
| 45 |
+
unit_vectors = diffs / (dists[..., np.newaxis] + 1e-9)
|
| 46 |
+
unit_vectors[dists < 1e-9] = 0
|
| 47 |
+
|
| 48 |
+
circle_forces = np.sum(unit_vectors * overlaps[..., np.newaxis], axis=1)
|
| 49 |
+
|
| 50 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 51 |
+
wall_strength = self.config['lsr_wall_strength']
|
| 52 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 53 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 54 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 55 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 56 |
+
|
| 57 |
+
forces = circle_forces + wall_forces
|
| 58 |
+
refined_centers += forces * current_lr
|
| 59 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 60 |
+
|
| 61 |
+
# --- Stage 2: Fine-tuning with subtle growth pressure ---
|
| 62 |
+
for sim_iter_ft in range(self.config['lsr_finetune_iter']):
|
| 63 |
+
progress = sim_iter_ft / self.config['lsr_finetune_iter']
|
| 64 |
+
current_lr = self.config['lsr_finetune_lr'] * (1.0 - progress)
|
| 65 |
+
current_pressure = self.config['lsr_finetune_pressure_end'] + \
|
| 66 |
+
(self.config['lsr_finetune_pressure_start'] - self.config['lsr_finetune_pressure_end']) * (1.0 - progress)
|
| 67 |
+
|
| 68 |
+
radii = compute_max_radii(refined_centers)
|
| 69 |
+
inflated_radii = radii * current_pressure
|
| 70 |
+
|
| 71 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 72 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 73 |
+
|
| 74 |
+
sum_radii_pairs = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 75 |
+
overlaps = np.maximum(0, sum_radii_pairs - dists)
|
| 76 |
+
np.fill_diagonal(overlaps, 0)
|
| 77 |
+
|
| 78 |
+
with np.errstate(divide='ignore', invalid='ignore'):
|
| 79 |
+
unit_vectors = diffs / (dists[..., np.newaxis] + 1e-9)
|
| 80 |
+
unit_vectors[dists < 1e-9] = 0
|
| 81 |
+
|
| 82 |
+
circle_forces = np.sum(unit_vectors * overlaps[..., np.newaxis], axis=1)
|
| 83 |
+
|
| 84 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 85 |
+
wall_strength = self.config['lsr_wall_strength']
|
| 86 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 87 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 88 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 89 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 90 |
+
|
| 91 |
+
forces = circle_forces + wall_forces
|
| 92 |
+
refined_centers += forces * current_lr
|
| 93 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 94 |
+
|
| 95 |
+
return refined_centers
|
| 96 |
+
|
| 97 |
+
class MemeticAlgorithm:
|
| 98 |
+
"""Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 99 |
+
def __init__(self, n, config):
|
| 100 |
+
self.n = n
|
| 101 |
+
self.config = config
|
| 102 |
+
self.population = []
|
| 103 |
+
self.fitnesses = np.array([])
|
| 104 |
+
self.best_solution = None
|
| 105 |
+
self.best_fitness = -1.0
|
| 106 |
+
self.local_search_refiner = LocalSearchRefiner(config=config)
|
| 107 |
+
|
| 108 |
+
def _initialize_population(self):
|
| 109 |
+
"""Initializes a diverse population with both random and grid-based individuals."""
|
| 110 |
+
num_grid_based = self.config['population_size'] // 3
|
| 111 |
+
|
| 112 |
+
for i in range(self.config['population_size']):
|
| 113 |
+
if i < num_grid_based:
|
| 114 |
+
centers = np.zeros((self.n, 2))
|
| 115 |
+
num_cells_side = 5
|
| 116 |
+
spacing = 1.0 / num_cells_side
|
| 117 |
+
k = 0
|
| 118 |
+
for row in range(num_cells_side):
|
| 119 |
+
for col in range(num_cells_side):
|
| 120 |
+
centers[k, 0] = (col + 0.5) * spacing
|
| 121 |
+
centers[k, 1] = (row + 0.5) * spacing
|
| 122 |
+
k += 1
|
| 123 |
+
centers[:25, :] += np.random.normal(0, spacing * 0.02, size=(25, 2))
|
| 124 |
+
|
| 125 |
+
# Place 26th circle in a random-ish interstitial void
|
| 126 |
+
interstitial_points = [[0.2, 0.2], [0.2, 0.8], [0.8, 0.2], [0.8, 0.8], [0.5, 0.5]]
|
| 127 |
+
extra_pos = interstitial_points[i % len(interstitial_points)]
|
| 128 |
+
centers[25] = extra_pos + np.random.normal(0, spacing * 0.05, size=2)
|
| 129 |
+
self.population.append(np.clip(centers, 0.0, 1.0))
|
| 130 |
+
else:
|
| 131 |
+
self.population.append(np.random.rand(self.n, 2))
|
| 132 |
+
|
| 133 |
+
def _evaluate_population(self):
|
| 134 |
+
"""Calculates fitness for the population and updates the best solution."""
|
| 135 |
+
fitnesses = []
|
| 136 |
+
for ind in self.population:
|
| 137 |
+
radii = compute_max_radii(ind)
|
| 138 |
+
fitnesses.append(np.sum(radii))
|
| 139 |
+
self.fitnesses = np.array(fitnesses)
|
| 140 |
+
|
| 141 |
+
best_idx = np.argmax(self.fitnesses)
|
| 142 |
+
if self.fitnesses[best_idx] > self.best_fitness:
|
| 143 |
+
self.best_fitness = self.fitnesses[best_idx]
|
| 144 |
+
self.best_solution = self.population[best_idx].copy()
|
| 145 |
+
|
| 146 |
+
def _select_parent(self):
|
| 147 |
+
"""Selects a parent using tournament selection."""
|
| 148 |
+
tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 149 |
+
tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 150 |
+
winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 151 |
+
return self.population[winner_idx]
|
| 152 |
+
|
| 153 |
+
def _crossover(self, p1, p2):
|
| 154 |
+
"""Performs blend crossover (BLX-alpha)."""
|
| 155 |
+
alpha = self.config['crossover_alpha']
|
| 156 |
+
child = alpha * p1 + (1.0 - alpha) * p2
|
| 157 |
+
return np.clip(child, 0.0, 1.0)
|
| 158 |
+
|
| 159 |
+
def _mutate(self, individual, strength):
|
| 160 |
+
"""Applies Gaussian mutation and a chance of a strong mutation (jump)."""
|
| 161 |
+
mutated_ind = individual.copy()
|
| 162 |
+
mutation_mask = np.random.rand(self.n) < self.config['mutation_rate']
|
| 163 |
+
noise = np.random.normal(0, strength, size=(np.sum(mutation_mask), 2))
|
| 164 |
+
mutated_ind[mutation_mask] += noise
|
| 165 |
+
|
| 166 |
+
if np.random.rand() < self.config['jump_mutation_prob']:
|
| 167 |
+
idx_to_reset = np.random.randint(0, self.n)
|
| 168 |
+
mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 169 |
+
|
| 170 |
+
return np.clip(mutated_ind, 0.0, 1.0)
|
| 171 |
+
|
| 172 |
+
def run(self):
|
| 173 |
+
"""Executes the full memetic algorithm evolution."""
|
| 174 |
+
self._initialize_population()
|
| 175 |
+
|
| 176 |
+
for gen in range(self.config['generations']):
|
| 177 |
+
self._evaluate_population()
|
| 178 |
+
|
| 179 |
+
new_population = []
|
| 180 |
+
|
| 181 |
+
# Elitism: carry over best individuals
|
| 182 |
+
elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 183 |
+
for idx in elite_indices:
|
| 184 |
+
new_population.append(self.population[idx].copy())
|
| 185 |
+
|
| 186 |
+
# Anneal mutation strength
|
| 187 |
+
mut_strength = self.config['mut_strength_end'] + \
|
| 188 |
+
(self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 189 |
+
(1.0 - (gen / self.config['generations']))**2.0
|
| 190 |
+
|
| 191 |
+
while len(new_population) < self.config['population_size']:
|
| 192 |
+
p1 = self._select_parent()
|
| 193 |
+
p2 = self._select_parent()
|
| 194 |
+
|
| 195 |
+
child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 196 |
+
child = self._mutate(child, mut_strength)
|
| 197 |
+
|
| 198 |
+
# Memetic step: Apply local search probabilistically
|
| 199 |
+
if np.random.rand() < self.config['local_search_prob']:
|
| 200 |
+
child = self.local_search_refiner.refine(child)
|
| 201 |
+
|
| 202 |
+
new_population.append(child)
|
| 203 |
+
|
| 204 |
+
self.population = new_population
|
| 205 |
+
|
| 206 |
+
self._evaluate_population()
|
| 207 |
+
return self.best_solution
|
| 208 |
+
|
| 209 |
+
def construct_packing():
|
| 210 |
+
"""
|
| 211 |
+
Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 212 |
+
"""
|
| 213 |
+
n = 26
|
| 214 |
+
config = {
|
| 215 |
+
# --- GA Parameters ---
|
| 216 |
+
'population_size': 80,
|
| 217 |
+
'generations': 350,
|
| 218 |
+
'elite_count': 4,
|
| 219 |
+
'tournament_size': 6,
|
| 220 |
+
'mutation_rate': 0.35, # Per-circle mutation probability
|
| 221 |
+
'jump_mutation_prob': 0.03, # Probability of a random reset for one circle
|
| 222 |
+
'mut_strength_start': 0.1,
|
| 223 |
+
'mut_strength_end': 0.001,
|
| 224 |
+
'crossover_rate': 0.9,
|
| 225 |
+
'crossover_alpha': 0.5, # For BLX-alpha crossover
|
| 226 |
+
# --- Memetic Parameters ---
|
| 227 |
+
'local_search_prob': 0.20, # Probability of applying local search to a new child
|
| 228 |
+
# --- Local Search Refiner (LSR) Parameters ---
|
| 229 |
+
'lsr_aggressive_iter': 40,
|
| 230 |
+
'lsr_aggressive_lr': 0.025,
|
| 231 |
+
'lsr_aggressive_pressure_start': 1.06,
|
| 232 |
+
'lsr_aggressive_pressure_end': 1.001,
|
| 233 |
+
'lsr_finetune_iter': 80,
|
| 234 |
+
'lsr_finetune_lr': 0.001,
|
| 235 |
+
'lsr_finetune_pressure_start': 1.0008,
|
| 236 |
+
'lsr_finetune_pressure_end': 1.00001,
|
| 237 |
+
'lsr_wall_strength': 0.5,
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
solver = MemeticAlgorithm(n=n, config=config)
|
| 241 |
+
best_centers = solver.run()
|
| 242 |
+
|
| 243 |
+
# Final, high-precision radius calculation for the best solution
|
| 244 |
+
final_radii = compute_max_radii(best_centers)
|
| 245 |
+
|
| 246 |
+
return best_centers, final_radii
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def compute_max_radii(centers):
|
| 250 |
+
"""
|
| 251 |
+
Compute the maximum possible radii for each circle position
|
| 252 |
+
such that they don't overlap and stay within the unit square.
|
| 253 |
+
This uses an iterative proportional scaling method.
|
| 254 |
+
"""
|
| 255 |
+
n = centers.shape[0]
|
| 256 |
+
radii = np.ones(n)
|
| 257 |
+
|
| 258 |
+
for i in range(n):
|
| 259 |
+
x, y = centers[i]
|
| 260 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 261 |
+
|
| 262 |
+
max_radius_iter = 600 # Maximum iterations for radius calculation
|
| 263 |
+
convergence_epsilon = 1e-7 # Threshold for maximum absolute change in radii
|
| 264 |
+
required_converged_iters = 5 # Number of consecutive iterations below epsilon to confirm convergence
|
| 265 |
+
converged_in_a_row = 0 # Counter for consecutive converged iterations
|
| 266 |
+
|
| 267 |
+
for iteration in range(max_radius_iter):
|
| 268 |
+
previous_radii = radii.copy() # Store radii from the previous iteration to check for changes
|
| 269 |
+
|
| 270 |
+
for i in range(n):
|
| 271 |
+
for j in range(i + 1, n):
|
| 272 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 273 |
+
|
| 274 |
+
if dist < 1e-9: # Centers are practically identical
|
| 275 |
+
radii[i] = 0.0
|
| 276 |
+
radii[j] = 0.0
|
| 277 |
+
# Note: No explicit `updated_in_pass` needed anymore; `max_absolute_change` captures this.
|
| 278 |
+
continue
|
| 279 |
+
|
| 280 |
+
if radii[i] + radii[j] > dist:
|
| 281 |
+
scale = dist / (radii[i] + radii[j])
|
| 282 |
+
radii[i] *= scale
|
| 283 |
+
radii[j] *= scale
|
| 284 |
+
|
| 285 |
+
# Calculate the maximum absolute change in any radius
|
| 286 |
+
max_absolute_change = np.max(np.abs(radii - previous_radii)) if n > 0 else 0.0
|
| 287 |
+
|
| 288 |
+
# Check for convergence based on max_absolute_change
|
| 289 |
+
if max_absolute_change < convergence_epsilon:
|
| 290 |
+
converged_in_a_row += 1
|
| 291 |
+
if converged_in_a_row >= required_converged_iters:
|
| 292 |
+
break # Converged for several consecutive iterations
|
| 293 |
+
else:
|
| 294 |
+
converged_in_a_row = 0 # Reset counter if change is significant
|
| 295 |
+
|
| 296 |
+
return np.maximum(radii, 0.0) # Ensure no negative radii (should be handled by logic, but as a safeguard)
|
| 297 |
+
# EVOLVE-BLOCK-END
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# This part remains fixed (not evolved)
|
| 301 |
+
def run_packing():
|
| 302 |
+
"""Run the circle packing constructor for n=26"""
|
| 303 |
+
centers, radii = construct_packing()
|
| 304 |
+
# Calculate the sum of radii
|
| 305 |
+
sum_radii = np.sum(radii)
|
| 306 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/results/job_log.err
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
| 10 |
+
Traceback (most recent call last):
|
| 11 |
+
File "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/evaluate.py", line 302, in <module>
|
| 12 |
+
main(parsed_args.program_path, parsed_args.results_dir)
|
| 13 |
+
File "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/evaluate.py", line 262, in main
|
| 14 |
+
metrics, correct, error_msg = run_shinka_eval(
|
| 15 |
+
^^^^^^^^^^^^^^^^
|
| 16 |
+
File "/home/tengxiao/pj/ShinkaEvolve/shinka/core/wrap_eval.py", line 119, in run_shinka_eval
|
| 17 |
+
run_result = experiment_fn(**kwargs)
|
| 18 |
+
^^^^^^^^^^^^^^^^^^^^^^^
|
| 19 |
+
File "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/main.py", line 247, in run_packing
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/results/job_log.out
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/results
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_100/rewrite.txt
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
|
| 6 |
+
def compute_max_radii(centers, max_iter=800, convergence_threshold=1e-8):
|
| 7 |
+
"""
|
| 8 |
+
Compute maximum radii using iterative proportional scaling.
|
| 9 |
+
This is a robust version with a stable convergence check.
|
| 10 |
+
"""
|
| 11 |
+
n = centers.shape[0]
|
| 12 |
+
if n == 0:
|
| 13 |
+
return np.array([])
|
| 14 |
+
|
| 15 |
+
radii = np.min([
|
| 16 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 17 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 18 |
+
], axis=0)
|
| 19 |
+
|
| 20 |
+
required_converged_iters = 3
|
| 21 |
+
converged_in_a_row = 0
|
| 22 |
+
|
| 23 |
+
for _ in range(max_iter):
|
| 24 |
+
old_radii = radii.copy()
|
| 25 |
+
|
| 26 |
+
for i in range(n):
|
| 27 |
+
for j in range(i + 1, n):
|
| 28 |
+
dist_sq = np.sum((centers[i] - centers[j])**2)
|
| 29 |
+
dist = np.sqrt(dist_sq)
|
| 30 |
+
|
| 31 |
+
if radii[i] + radii[j] > dist:
|
| 32 |
+
if dist < 1e-9:
|
| 33 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 34 |
+
else:
|
| 35 |
+
scale = dist / (radii[i] + radii[j])
|
| 36 |
+
radii[i] *= scale
|
| 37 |
+
radii[j] *= scale
|
| 38 |
+
|
| 39 |
+
max_abs_change = np.max(np.abs(radii - old_radii))
|
| 40 |
+
if max_abs_change < convergence_threshold:
|
| 41 |
+
converged_in_a_row += 1
|
| 42 |
+
if converged_in_a_row >= required_converged_iters:
|
| 43 |
+
break
|
| 44 |
+
else:
|
| 45 |
+
converged_in_a_row = 0
|
| 46 |
+
|
| 47 |
+
return np.maximum(radii, 0)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class QuickRefiner:
|
| 51 |
+
"""
|
| 52 |
+
A fast, aggressive, single-phase local search operator for memetic injection.
|
| 53 |
+
"""
|
| 54 |
+
def __init__(self, config):
|
| 55 |
+
self.config = config
|
| 56 |
+
|
| 57 |
+
def refine(self, centers):
|
| 58 |
+
refined_centers = centers.copy()
|
| 59 |
+
|
| 60 |
+
for sim_iter in range(self.config['refiner_iter']):
|
| 61 |
+
progress = sim_iter / self.config['refiner_iter']
|
| 62 |
+
current_lr = self.config['refiner_lr'] * (1.0 - progress)**2
|
| 63 |
+
current_pressure = self.config['refiner_pressure_end'] + \
|
| 64 |
+
(self.config['refiner_pressure_start'] - self.config['refiner_pressure_end']) * (1.0 - progress)**2
|
| 65 |
+
|
| 66 |
+
# Fast radius calculation for simulation
|
| 67 |
+
radii = compute_max_radii(refined_centers, max_iter=100)
|
| 68 |
+
inflated_radii = radii * current_pressure
|
| 69 |
+
|
| 70 |
+
# Vectorized force calculations
|
| 71 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 72 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 73 |
+
dists[dists < 1e-9] = 1e-9
|
| 74 |
+
|
| 75 |
+
sum_inflated_radii = inflated_radii[:, np.newaxis] + inflated_radii[np.newaxis, :]
|
| 76 |
+
overlaps = np.maximum(0, sum_inflated_radii - dists)
|
| 77 |
+
np.fill_diagonal(overlaps, 0)
|
| 78 |
+
|
| 79 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 80 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 81 |
+
|
| 82 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 83 |
+
wall_strength = self.config['refiner_wall_strength']
|
| 84 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 0])
|
| 85 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + inflated_radii) - 1.0)
|
| 86 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, inflated_radii - refined_centers[:, 1])
|
| 87 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + inflated_radii) - 1.0)
|
| 88 |
+
|
| 89 |
+
forces = circle_forces + wall_forces
|
| 90 |
+
refined_centers += forces * current_lr
|
| 91 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 92 |
+
|
| 93 |
+
return refined_centers
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class SimulatedAnnealerWithInjection:
|
| 97 |
+
"""
|
| 98 |
+
Performs a search using SA, with a memetic local search integrated as a move operator.
|
| 99 |
+
"""
|
| 100 |
+
def __init__(self, n, initial_centers, config):
|
| 101 |
+
self.n = n
|
| 102 |
+
self.config = config
|
| 103 |
+
self.centers = initial_centers
|
| 104 |
+
|
| 105 |
+
self.temp = config['t_start']
|
| 106 |
+
self.max_step_size = config['max_step_size']
|
| 107 |
+
|
| 108 |
+
self.energy = self._calculate_energy(self.centers)
|
| 109 |
+
self.best_centers = self.centers.copy()
|
| 110 |
+
self.best_energy = self.energy
|
| 111 |
+
|
| 112 |
+
self.move_weights = config['move_weights']
|
| 113 |
+
self.move_types = [self._move_single_circle, self._move_cluster, self._swap_circles, self._refinement_injection]
|
| 114 |
+
|
| 115 |
+
self.refiner = QuickRefiner(config)
|
| 116 |
+
|
| 117 |
+
def _calculate_energy(self, centers):
|
| 118 |
+
"""Energy is the negative sum of radii, to be minimized."""
|
| 119 |
+
radii = compute_max_radii(centers, max_iter=self.config['radius_iter'])
|
| 120 |
+
return -np.sum(radii)
|
| 121 |
+
|
| 122 |
+
def _propose_move(self):
|
| 123 |
+
move_func = random.choices(self.move_types, weights=self.move_weights, k=1)[0]
|
| 124 |
+
t_progress = (self.temp - self.config['t_end']) / (self.config['t_start'] - self.config['t_end'])
|
| 125 |
+
current_step_size = self.max_step_size * t_progress
|
| 126 |
+
return move_func(current_step_size)
|
| 127 |
+
|
| 128 |
+
def _move_single_circle(self, step_size):
|
| 129 |
+
new_centers = self.centers.copy()
|
| 130 |
+
idx = random.randint(0, self.n - 1)
|
| 131 |
+
displacement = np.random.normal(0, step_size, size=2)
|
| 132 |
+
new_centers[idx] += displacement
|
| 133 |
+
new_centers[idx] = np.clip(new_centers[idx], 0.0, 1.0)
|
| 134 |
+
return new_centers
|
| 135 |
+
|
| 136 |
+
def _move_cluster(self, step_size):
|
| 137 |
+
new_centers = self.centers.copy()
|
| 138 |
+
cluster_size = self.config['cluster_size']
|
| 139 |
+
seed_idx = random.randint(0, self.n - 1)
|
| 140 |
+
dists = np.linalg.norm(self.centers - self.centers[seed_idx], axis=1)
|
| 141 |
+
neighbor_indices = np.argsort(dists)[:cluster_size]
|
| 142 |
+
displacement = np.random.normal(0, step_size * 0.5, size=2)
|
| 143 |
+
new_centers[neighbor_indices] += displacement
|
| 144 |
+
new_centers[neighbor_indices] = np.clip(new_centers[neighbor_indices], 0.0, 1.0)
|
| 145 |
+
return new_centers
|
| 146 |
+
|
| 147 |
+
def _swap_circles(self, step_size):
|
| 148 |
+
new_centers = self.centers.copy()
|
| 149 |
+
if self.n < 2: return new_centers
|
| 150 |
+
idx1, idx2 = random.sample(range(self.n), 2)
|
| 151 |
+
new_centers[idx1], new_centers[idx2] = new_centers[idx2].copy(), new_centers[idx1].copy()
|
| 152 |
+
return new_centers
|
| 153 |
+
|
| 154 |
+
def _refinement_injection(self, step_size):
|
| 155 |
+
"""Applies the quick local search as a single, powerful move."""
|
| 156 |
+
return self.refiner.refine(self.centers)
|
| 157 |
+
|
| 158 |
+
def run(self):
|
| 159 |
+
"""Executes the simulated annealing search."""
|
| 160 |
+
while self.temp > self.config['t_end']:
|
| 161 |
+
for _ in range(self.config['moves_per_temp']):
|
| 162 |
+
new_centers = self._propose_move()
|
| 163 |
+
new_energy = self._calculate_energy(new_centers)
|
| 164 |
+
|
| 165 |
+
delta_e = new_energy - self.energy
|
| 166 |
+
|
| 167 |
+
if delta_e < 0 or random.random() < math.exp(-delta_e / self.temp):
|
| 168 |
+
self.centers = new_centers
|
| 169 |
+
self.energy = new_energy
|
| 170 |
+
|
| 171 |
+
if self.energy < self.best_energy:
|
| 172 |
+
self.best_energy = self.energy
|
| 173 |
+
self.best_centers = self.centers.copy()
|
| 174 |
+
|
| 175 |
+
self.temp *= self.config['cooling_rate']
|
| 176 |
+
|
| 177 |
+
return self.best_centers
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def construct_packing():
|
| 181 |
+
"""
|
| 182 |
+
Constructs a packing of 26 circles using a multi-start SA with memetic injection.
|
| 183 |
+
"""
|
| 184 |
+
n = 26
|
| 185 |
+
config = {
|
| 186 |
+
# --- Multi-Start Config ---
|
| 187 |
+
'num_starts': 12,
|
| 188 |
+
# --- SA Config ---
|
| 189 |
+
't_start': 0.05,
|
| 190 |
+
't_end': 1e-6,
|
| 191 |
+
'cooling_rate': 0.9985, # Slower cooling for deeper search
|
| 192 |
+
'moves_per_temp': 80,
|
| 193 |
+
'max_step_size': 0.15,
|
| 194 |
+
'radius_iter': 250,
|
| 195 |
+
'move_weights': [0.60, 0.20, 0.15, 0.05], # [single, cluster, swap, refine]
|
| 196 |
+
'cluster_size': 4,
|
| 197 |
+
# --- Refiner Config (for injection) ---
|
| 198 |
+
'refiner_iter': 50,
|
| 199 |
+
'refiner_lr': 0.02,
|
| 200 |
+
'refiner_pressure_start': 1.05,
|
| 201 |
+
'refiner_pressure_end': 1.001,
|
| 202 |
+
'refiner_wall_strength': 0.5,
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
best_overall_centers = None
|
| 206 |
+
best_overall_score = -1.0
|
| 207 |
+
|
| 208 |
+
for i in range(config['num_starts']):
|
| 209 |
+
initial_centers = np.zeros((n, 2))
|
| 210 |
+
# --- Strategic Initialization (from best prior SA) ---
|
| 211 |
+
if i < config['num_starts'] // 2:
|
| 212 |
+
num_cells_side = 5
|
| 213 |
+
spacing = 1.0 / num_cells_side
|
| 214 |
+
k = 0
|
| 215 |
+
for row in range(num_cells_side):
|
| 216 |
+
for col in range(num_cells_side):
|
| 217 |
+
initial_centers[k, 0] = (col + 0.5) * spacing
|
| 218 |
+
initial_centers[k, 1] = (row + 0.5) * spacing
|
| 219 |
+
k += 1
|
| 220 |
+
initial_centers[:25, :] += np.random.normal(0, spacing * 0.1, size=(25, 2))
|
| 221 |
+
|
| 222 |
+
extra_pos_candidates = [[0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9], [0.5, 0.5]]
|
| 223 |
+
initial_centers[25] = extra_pos_candidates[i % len(extra_pos_candidates)]
|
| 224 |
+
initial_centers = np.clip(initial_centers, 0.01, 0.99)
|
| 225 |
+
else:
|
| 226 |
+
initial_centers = np.random.rand(n, 2)
|
| 227 |
+
|
| 228 |
+
solver = SimulatedAnnealerWithInjection(n=n, initial_centers=initial_centers, config=config)
|
| 229 |
+
result_centers = solver.run()
|
| 230 |
+
|
| 231 |
+
radii = compute_max_radii(result_centers, max_iter=2500, convergence_threshold=1e-9)
|
| 232 |
+
score = np.sum(radii)
|
| 233 |
+
|
| 234 |
+
if score > best_overall_score:
|
| 235 |
+
best_overall_score = score
|
| 236 |
+
best_overall_centers = result_centers
|
| 237 |
+
|
| 238 |
+
final_radii = compute_max_radii(best_overall_centers, max_iter=4000, convergence_threshold=1e-9)
|
| 239 |
+
|
| 240 |
+
return best_overall_centers, final_radii
|
| 241 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (18.1 kB). View file
|
|
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/edit.diff
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,283 +1,315 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
def compute_max_radii(centers, max_iter=750, convergence_threshold=1e-8):
|
| 8 |
+
"""
|
| 9 |
+
Compute maximum radii using iterative proportional scaling with a convergence check.
|
| 10 |
+
"""
|
| 11 |
+
n = centers.shape[0]
|
| 12 |
+
+ # Initialize radii to be constrained by walls
|
| 13 |
+
radii = np.min([
|
| 14 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 15 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 16 |
+
], axis=0)
|
| 17 |
+
-
|
| 18 |
+
+
|
| 19 |
+
for _ in range(max_iter):
|
| 20 |
+
old_radii = radii.copy()
|
| 21 |
+
updated = False
|
| 22 |
+
+ # Calculate distances and handle overlaps for all pairs
|
| 23 |
+
+ # Vectorized calculation for distances to improve performance
|
| 24 |
+
+ diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 25 |
+
+ dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 26 |
+
+
|
| 27 |
+
+ # Iterate over upper triangle to avoid duplicate calculations and self-comparison
|
| 28 |
+
for i in range(n):
|
| 29 |
+
for j in range(i + 1, n):
|
| 30 |
+
- dist = np.linalg.norm(centers[i] - centers[j])
|
| 31 |
+
+ dist = dists[i, j] # Pre-calculated distance
|
| 32 |
+
+
|
| 33 |
+
if radii[i] + radii[j] > dist:
|
| 34 |
+
- if dist < 1e-9:
|
| 35 |
+
- radii[i], radii[j] = 0.0, 0.0
|
| 36 |
+
+ if dist < 1e-9: # Handle co-located centers: radii must be zero
|
| 37 |
+
+ if radii[i] > 0 or radii[j] > 0: # Only update if they weren't already zero
|
| 38 |
+
+ radii[i], radii[j] = 0.0, 0.0
|
| 39 |
+
+ updated = True
|
| 40 |
+
else:
|
| 41 |
+
scale = dist / (radii[i] + radii[j])
|
| 42 |
+
radii[i] *= scale
|
| 43 |
+
radii[j] *= scale
|
| 44 |
+
- updated = True
|
| 45 |
+
-
|
| 46 |
+
+ updated = True
|
| 47 |
+
+
|
| 48 |
+
+ # Check for convergence: if no updates were made AND the max change is tiny.
|
| 49 |
+
+ # This prevents infinite loops if changes are smaller than convergence_threshold
|
| 50 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 51 |
+
break
|
| 52 |
+
-
|
| 53 |
+
+
|
| 54 |
+
return np.maximum(radii, 0)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
-class LocalSearchRefiner:
|
| 58 |
+
- """A class to perform fast, local refinement on a set of centers using a two-stage approach."""
|
| 59 |
+
- def __init__(self, config):
|
| 60 |
+
+class HybridLocalSearcher:
|
| 61 |
+
+ """
|
| 62 |
+
+ A powerful local search refiner based on a two-phase physical simulation.
|
| 63 |
+
+ This approach incorporates a "growth pressure" concept for exploration.
|
| 64 |
+
+ """
|
| 65 |
+
+ def __init__(self, n, config):
|
| 66 |
+
+ self.n = n
|
| 67 |
+
self.config = config
|
| 68 |
+
|
| 69 |
+
+ def _compute_radii_for_sim(self, centers):
|
| 70 |
+
+ """A fast radius computation for use inside the simulation loop."""
|
| 71 |
+
+ return compute_max_radii(centers, max_iter=self.config['radius_sim_iter'], convergence_threshold=1e-7)
|
| 72 |
+
+
|
| 73 |
+
def refine(self, centers):
|
| 74 |
+
- """Applies a two-stage force-directed refinement to polish a solution."""
|
| 75 |
+
+ """Applies a two-phase physical simulation to refine a configuration."""
|
| 76 |
+
refined_centers = centers.copy()
|
| 77 |
+
|
| 78 |
+
- # --- Stage 1: Aggressive settling with subtle growth pressure ---
|
| 79 |
+
- for i_agg_iter in range(self.config['ls_aggressive_iter']):
|
| 80 |
+
- progress = i_agg_iter / self.config['ls_aggressive_iter']
|
| 81 |
+
- current_growth_pressure = self.config['ls_aggressive_growth_pressure_end'] + \
|
| 82 |
+
- (self.config['ls_aggressive_growth_pressure_start'] - self.config['ls_aggressive_growth_pressure_end']) * \
|
| 83 |
+
- (1.0 - progress)**1.5 # Cubic decay for growth pressure
|
| 84 |
+
-
|
| 85 |
+
- radii = compute_max_radii(refined_centers, max_iter=self.config['ls_radius_iter_agg'])
|
| 86 |
+
- pressured_radii = radii * current_growth_pressure
|
| 87 |
+
-
|
| 88 |
+
- # Vectorized Force Calculation
|
| 89 |
+
+ # Phase 1: Main refinement with decreasing growth pressure
|
| 90 |
+
+ iterations = self.config['sim_iter']
|
| 91 |
+
+ base_lr = self.config['learning_rate']
|
| 92 |
+
+ wall_strength = self.config['wall_strength']
|
| 93 |
+
+ initial_growth_pressure = self.config['initial_growth_pressure']
|
| 94 |
+
+ final_growth_pressure = self.config['final_growth_pressure']
|
| 95 |
+
+
|
| 96 |
+
+ for i_iter in range(iterations):
|
| 97 |
+
+ progress = i_iter / iterations
|
| 98 |
+
+ # Anneal growth pressure quadratically
|
| 99 |
+
+ current_growth_pressure = final_growth_pressure + (initial_growth_pressure - final_growth_pressure) * (1.0 - progress)**2
|
| 100 |
+
+ current_radii = self._compute_radii_for_sim(refined_centers)
|
| 101 |
+
+ pressured_radii = current_radii * current_growth_pressure
|
| 102 |
+
+
|
| 103 |
+
+ # Vectorized force calculations
|
| 104 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 105 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 106 |
+
dists[dists < 1e-9] = 1e-9 # Prevent division by zero
|
| 107 |
+
-
|
| 108 |
+
radii_sums = pressured_radii[:, np.newaxis] + pressured_radii[np.newaxis, :]
|
| 109 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 110 |
+
np.fill_diagonal(overlaps, 0)
|
| 111 |
+
-
|
| 112 |
+
- force_mags = overlaps
|
| 113 |
+
- force_matrix = diffs * (force_mags / dists)[:, :, np.newaxis]
|
| 114 |
+
+ force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 115 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 116 |
+
|
| 117 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 118 |
+
- wall_forces[:, 0] += self.config['ls_wall_strength'] * np.maximum(0, pressured_radii - refined_centers[:, 0])
|
| 119 |
+
- wall_forces[:, 0] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 0] + pressured_radii) - 1.0)
|
| 120 |
+
- wall_forces[:, 1] += self.config['ls_wall_strength'] * np.maximum(0, pressured_radii - refined_centers[:, 1])
|
| 121 |
+
- wall_forces[:, 1] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 1] + pressured_radii) - 1.0)
|
| 122 |
+
-
|
| 123 |
+
+ # Wall forces calculation
|
| 124 |
+
+ wall_forces[:, 0] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 0])
|
| 125 |
+
+ wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + pressured_radii) - 1.0)
|
| 126 |
+
+ wall_forces[:, 1] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 1])
|
| 127 |
+
+ wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + pressured_radii) - 1.0)
|
| 128 |
+
+
|
| 129 |
+
forces = circle_forces + wall_forces
|
| 130 |
+
-
|
| 131 |
+
- current_lr_agg = self.config['ls_aggressive_lr_end'] + \
|
| 132 |
+
- (self.config['ls_aggressive_lr_start'] - self.config['ls_aggressive_lr_end']) * \
|
| 133 |
+
- (1.0 - progress)**2.0 # Quadratic decay for LR
|
| 134 |
+
-
|
| 135 |
+
- refined_centers += forces * current_lr_agg
|
| 136 |
+
+ # Anneal learning rate quadratically
|
| 137 |
+
+ lr = base_lr * (1.0 - progress)**2
|
| 138 |
+
+ refined_centers += forces * lr
|
| 139 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 140 |
+
|
| 141 |
+
- # --- Stage 2: Fine-tuning without growth pressure ---
|
| 142 |
+
- for _ in range(self.config['ls_fine_tune_iter']):
|
| 143 |
+
- radii = compute_max_radii(refined_centers, max_iter=self.config['ls_radius_iter_ft'])
|
| 144 |
+
-
|
| 145 |
+
- # Vectorized Force Calculation (no growth pressure)
|
| 146 |
+
+ # Phase 2: Fine-tuning with minimal growth pressure
|
| 147 |
+
+ iterations_ft = self.config['fine_tune_iter']
|
| 148 |
+
+ lr_ft = self.config['fine_tune_lr']
|
| 149 |
+
+ for _ in range(iterations_ft):
|
| 150 |
+
+ current_radii = self._compute_radii_for_sim(refined_centers)
|
| 151 |
+
+
|
| 152 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 153 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 154 |
+
dists[dists < 1e-9] = 1e-9
|
| 155 |
+
-
|
| 156 |
+
- radii_sums = radii[:, np.newaxis] + radii[np.newaxis, :]
|
| 157 |
+
+ radii_sums = current_radii[:, np.newaxis] + current_radii[np.newaxis, :]
|
| 158 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 159 |
+
np.fill_diagonal(overlaps, 0)
|
| 160 |
+
-
|
| 161 |
+
- force_mags = overlaps
|
| 162 |
+
- force_matrix = diffs * (force_mags / dists)[:, :, np.newaxis]
|
| 163 |
+
+ force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 164 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 165 |
+
|
| 166 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 167 |
+
- wall_forces[:, 0] += self.config['ls_wall_strength'] * np.maximum(0, radii - refined_centers[:, 0])
|
| 168 |
+
- wall_forces[:, 0] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 0] + radii) - 1.0)
|
| 169 |
+
- wall_forces[:, 1] += self.config['ls_wall_strength'] * np.maximum(0, radii - refined_centers[:, 1])
|
| 170 |
+
- wall_forces[:, 1] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 1] + radii) - 1.0)
|
| 171 |
+
-
|
| 172 |
+
+ # Wall forces calculation
|
| 173 |
+
+ wall_forces[:, 0] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 0])
|
| 174 |
+
+ wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + current_radii) - 1.0)
|
| 175 |
+
+ wall_forces[:, 1] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 1])
|
| 176 |
+
+ wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + current_radii) - 1.0)
|
| 177 |
+
+
|
| 178 |
+
forces = circle_forces + wall_forces
|
| 179 |
+
-
|
| 180 |
+
- refined_centers += forces * self.config['ls_fine_tune_lr']
|
| 181 |
+
+ refined_centers += forces * lr_ft
|
| 182 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 183 |
+
|
| 184 |
+
return refined_centers
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class MemeticAlgorithm:
|
| 188 |
+
"""Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 189 |
+
def __init__(self, n, config):
|
| 190 |
+
self.n = n
|
| 191 |
+
self.config = config
|
| 192 |
+
self.population = []
|
| 193 |
+
self.fitnesses = np.array([])
|
| 194 |
+
self.best_solution = None
|
| 195 |
+
self.best_fitness = -1.0
|
| 196 |
+
- self.local_search_refiner = LocalSearchRefiner(config=config)
|
| 197 |
+
+ # Use the HybridLocalSearcher with its specific config
|
| 198 |
+
+ self.local_search_refiner = HybridLocalSearcher(n=n, config=config['ls_config'])
|
| 199 |
+
|
| 200 |
+
def _initialize_population(self):
|
| 201 |
+
- """Initializes a diverse population with both random and grid-based individuals."""
|
| 202 |
+
- num_grid_based = self.config['population_size'] // 4
|
| 203 |
+
- num_cells_side = 5
|
| 204 |
+
- spacing = 1.0 / num_cells_side
|
| 205 |
+
- interstitial_points = [
|
| 206 |
+
- [spacing, spacing], [spacing, 1 - spacing],
|
| 207 |
+
- [1 - spacing, spacing], [1 - spacing, 1 - spacing]
|
| 208 |
+
- ]
|
| 209 |
+
-
|
| 210 |
+
- for i in range(self.config['population_size']):
|
| 211 |
+
- if i < num_grid_based:
|
| 212 |
+
- centers = np.zeros((self.n, 2))
|
| 213 |
+
- k = 0
|
| 214 |
+
- for row in range(num_cells_side):
|
| 215 |
+
- for col in range(num_cells_side):
|
| 216 |
+
- centers[k, 0] = (col + 0.5) * spacing
|
| 217 |
+
- centers[k, 1] = (row + 0.5) * spacing
|
| 218 |
+
- k += 1
|
| 219 |
+
- centers[:25, :] += np.random.normal(0, spacing * 0.01, size=(25, 2))
|
| 220 |
+
-
|
| 221 |
+
- # Place 26th circle in one of the strategic voids
|
| 222 |
+
- extra_pos = interstitial_points[i % len(interstitial_points)]
|
| 223 |
+
- centers[25] = extra_pos + np.random.normal(0, spacing * 0.01, size=2)
|
| 224 |
+
- self.population.append(np.clip(centers, 0.0, 1.0))
|
| 225 |
+
- else:
|
| 226 |
+
- self.population.append(np.random.rand(self.n, 2))
|
| 227 |
+
-
|
| 228 |
+
+ """Initializes a diverse population using strategic grid-based starts and random individuals."""
|
| 229 |
+
+ spacing = 1.0 / 5
|
| 230 |
+
+ # The explicit list of candidate positions for the 26th circle, from a high-performing prior
|
| 231 |
+
+ candidate_extra_positions = self.config['initial_candidates']
|
| 232 |
+
+
|
| 233 |
+
+ # Base for 25 circles in a 5x5 grid, to be perturbed
|
| 234 |
+
+ base_centers_25 = np.zeros((self.n - 1, 2))
|
| 235 |
+
+ k = 0
|
| 236 |
+
+ for j in range(5):
|
| 237 |
+
+ for i in range(5):
|
| 238 |
+
+ base_centers_25[k, 0] = (i + 0.5) * spacing
|
| 239 |
+
+ base_centers_25[k, 1] = (j + 0.5) * spacing
|
| 240 |
+
+ k += 1
|
| 241 |
+
+
|
| 242 |
+
+ num_strategic_starts = min(len(candidate_extra_positions), self.config['population_size'] // 2)
|
| 243 |
+
+
|
| 244 |
+
+ # Add strategically initialized configurations
|
| 245 |
+
+ for i in range(num_strategic_starts):
|
| 246 |
+
+ centers = np.zeros((self.n, 2))
|
| 247 |
+
+ # Apply perturbation to break symmetry
|
| 248 |
+
+ perturbation = np.random.normal(0, self.config['initial_perturbation_scale'], size=base_centers_25.shape)
|
| 249 |
+
+ perturbed_base_centers = np.clip(base_centers_25 + perturbation, 0.0, 1.0)
|
| 250 |
+
+ centers[:self.n-1] = perturbed_base_centers
|
| 251 |
+
+ # Place the 26th circle at a strategic point
|
| 252 |
+
+ centers[self.n-1] = np.array(candidate_extra_positions[i])
|
| 253 |
+
+ self.population.append(centers)
|
| 254 |
+
+
|
| 255 |
+
+ # Add purely random configurations to ensure broad exploration
|
| 256 |
+
+ num_random_starts = self.config['population_size'] - len(self.population)
|
| 257 |
+
+ for _ in range(num_random_starts):
|
| 258 |
+
+ self.population.append(np.random.rand(self.n, 2))
|
| 259 |
+
+
|
| 260 |
+
def _evaluate_population(self):
|
| 261 |
+
"""Calculates fitness for the population and updates the best solution."""
|
| 262 |
+
self.fitnesses = np.array([np.sum(compute_max_radii(ind)) for ind in self.population])
|
| 263 |
+
best_idx = np.argmax(self.fitnesses)
|
| 264 |
+
if self.fitnesses[best_idx] > self.best_fitness:
|
| 265 |
+
self.best_fitness = self.fitnesses[best_idx]
|
| 266 |
+
self.best_solution = self.population[best_idx].copy()
|
| 267 |
+
-
|
| 268 |
+
+
|
| 269 |
+
def _select_parent(self):
|
| 270 |
+
"""Selects a parent using tournament selection."""
|
| 271 |
+
tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 272 |
+
tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 273 |
+
winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 274 |
+
return self.population[winner_idx]
|
| 275 |
+
|
| 276 |
+
def _crossover(self, p1, p2):
|
| 277 |
+
- """
|
| 278 |
+
- Performs Blend Crossover (BLX-alpha) on continuous variables.
|
| 279 |
+
- Child = alpha * Parent1 + (1-alpha) * Parent2, where alpha is randomly chosen.
|
| 280 |
+
- """
|
| 281 |
+
- # Using a small random range around 0.5 for alpha for averaging blend
|
| 282 |
+
- alpha = np.random.uniform(0.4, 0.6)
|
| 283 |
+
- child = alpha * p1 + (1 - alpha) * p2
|
| 284 |
+
- return np.clip(child, 0.0, 1.0) # Ensure coordinates stay within bounds
|
| 285 |
+
+ """Performs uniform crossover, taking whole circles from either parent."""
|
| 286 |
+
+ child = p1.copy()
|
| 287 |
+
+ mask = np.random.rand(self.n) < 0.5
|
| 288 |
+
+ child[mask] = p2[mask]
|
| 289 |
+
+ return child
|
| 290 |
+
|
| 291 |
+
def _mutate(self, individual, strength, mutation_rate_per_circle):
|
| 292 |
+
- """Applies Gaussian mutation and a chance of a strong mutation."""
|
| 293 |
+
+ """Applies Gaussian mutation and a chance of a strong 'jump' mutation."""
|
| 294 |
+
mutated_ind = individual.copy()
|
| 295 |
+
for i in range(self.n):
|
| 296 |
+
if np.random.rand() < mutation_rate_per_circle: # Use the annealed rate
|
| 297 |
+
noise = np.random.normal(0, strength, size=2)
|
| 298 |
+
mutated_ind[i] += noise
|
| 299 |
+
-
|
| 300 |
+
- if np.random.rand() < 0.02: # 2% chance of a jump mutation
|
| 301 |
+
+
|
| 302 |
+
+ if np.random.rand() < self.config['jump_mutation_prob']: # Jump mutation probability from a good prior
|
| 303 |
+
idx_to_reset = np.random.randint(0, self.n)
|
| 304 |
+
mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 305 |
+
|
| 306 |
+
return np.clip(mutated_ind, 0.0, 1.0)
|
| 307 |
+
|
| 308 |
+
def run(self):
|
| 309 |
+
"""Executes the full memetic algorithm evolution."""
|
| 310 |
+
self._initialize_population()
|
| 311 |
+
-
|
| 312 |
+
+
|
| 313 |
+
for gen in range(self.config['generations']):
|
| 314 |
+
self._evaluate_population()
|
| 315 |
+
-
|
| 316 |
+
+
|
| 317 |
+
new_population = []
|
| 318 |
+
-
|
| 319 |
+
- # Elitism
|
| 320 |
+
+
|
| 321 |
+
+ # Elitism: carry over the best individuals directly
|
| 322 |
+
elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 323 |
+
for idx in elite_indices:
|
| 324 |
+
new_population.append(self.population[idx].copy())
|
| 325 |
+
-
|
| 326 |
+
- # Anneal mutation strength
|
| 327 |
+
+
|
| 328 |
+
+ # Anneal mutation strength over generations with a quadratic decay
|
| 329 |
+
mut_strength = self.config['mut_strength_end'] + \
|
| 330 |
+
(self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 331 |
+
- (1.0 - (gen / self.config['generations']))**2.0 # Cubic decay for strength
|
| 332 |
+
-
|
| 333 |
+
- current_mutation_rate = self.config['mutation_rate_end'] + \
|
| 334 |
+
- (self.config['mutation_rate'] - self.config['mutation_rate_end']) * \
|
| 335 |
+
- (1.0 - (gen / self.config['generations']))**1.5 # Quadratic decay for rate (Recommendation #5)
|
| 336 |
+
+ (1.0 - (gen / self.config['generations']))**2.0
|
| 337 |
+
+
|
| 338 |
+
+ # Anneal mutation rate per circle over generations with a cubic decay
|
| 339 |
+
+ current_mutation_rate_per_circle = self.config['mutation_rate_end_per_circle'] + \
|
| 340 |
+
+ (self.config['mutation_rate_start_per_circle'] - self.config['mutation_rate_end_per_circle']) * \
|
| 341 |
+
+ (1.0 - (gen / self.config['generations']))**1.5
|
| 342 |
+
|
| 343 |
+
while len(new_population) < self.config['population_size']:
|
| 344 |
+
p1 = self._select_parent()
|
| 345 |
+
p2 = self._select_parent()
|
| 346 |
+
-
|
| 347 |
+
+
|
| 348 |
+
+ # Perform crossover, or just copy a parent if crossover doesn't happen
|
| 349 |
+
child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 350 |
+
- child = self._mutate(child, mut_strength, current_mutation_rate) # Pass current_mutation_rate
|
| 351 |
+
-
|
| 352 |
+
- # Memetic step: Apply local search probabilistically
|
| 353 |
+
+ # Apply mutation with annealed strength and rate
|
| 354 |
+
+ child = self._mutate(child, mut_strength, current_mutation_rate_per_circle)
|
| 355 |
+
+
|
| 356 |
+
+ # Memetic step: Apply powerful local search probabilistically
|
| 357 |
+
if np.random.rand() < self.config['local_search_prob']:
|
| 358 |
+
child = self.local_search_refiner.refine(child)
|
| 359 |
+
-
|
| 360 |
+
+
|
| 361 |
+
new_population.append(child)
|
| 362 |
+
-
|
| 363 |
+
+
|
| 364 |
+
self.population = new_population
|
| 365 |
+
-
|
| 366 |
+
+
|
| 367 |
+
# Final evaluation to get the best of the last generation
|
| 368 |
+
self._evaluate_population()
|
| 369 |
+
return self.best_solution
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def construct_packing():
|
| 373 |
+
"""
|
| 374 |
+
Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 375 |
+
"""
|
| 376 |
+
n = 26
|
| 377 |
+
+ spacing = 1.0 / 5
|
| 378 |
+
config = {
|
| 379 |
+
+ # MA parameters
|
| 380 |
+
'population_size': 80, # Increased population size for more diversity
|
| 381 |
+
- 'generations': 400, # Increased generations for more thorough evolution
|
| 382 |
+
- 'elite_count': 4,
|
| 383 |
+
- 'tournament_size': 7, # Increased tournament size for stronger selection pressure
|
| 384 |
+
- 'mutation_rate': 0.25, # Initial mutation rate (probability for a circle to mutate)
|
| 385 |
+
- 'mutation_rate_end': 0.05, # Final mutation rate after annealing (Recommendation #5)
|
| 386 |
+
- 'mut_strength_start': 0.1,
|
| 387 |
+
- 'mut_strength_end': 0.001,
|
| 388 |
+
- 'crossover_rate': 0.9,
|
| 389 |
+
- # Memetic Parameters
|
| 390 |
+
- 'local_search_prob': 0.15, # Probability of applying local search to a new child
|
| 391 |
+
-
|
| 392 |
+
- # Parameters for the two-stage Local Search Refiner (Recommendation #4)
|
| 393 |
+
- 'ls_aggressive_iter': 25, # Iterations for the first, aggressive local search stage
|
| 394 |
+
- 'ls_aggressive_lr_start': 0.005, # Initial learning rate for aggressive stage
|
| 395 |
+
- 'ls_aggressive_lr_end': 0.001, # Final learning rate for aggressive stage
|
| 396 |
+
- 'ls_aggressive_growth_pressure_start': 1.003, # Subtle initial growth pressure for aggressive stage
|
| 397 |
+
- 'ls_aggressive_growth_pressure_end': 1.0001, # Subtle final growth pressure for aggressive stage
|
| 398 |
+
- 'ls_radius_iter_agg': 80, # Radii iterations for aggressive stage (faster)
|
| 399 |
+
-
|
| 400 |
+
- 'ls_fine_tune_iter': 50, # Iterations for the second, fine-tuning local search stage
|
| 401 |
+
- 'ls_fine_tune_lr': 0.0005, # Fixed learning rate for fine-tuning stage (very low for precision)
|
| 402 |
+
- 'ls_radius_iter_ft': 150, # Radii iterations for fine-tuning stage (more precise)
|
| 403 |
+
- 'ls_wall_strength': 0.2, # Wall strength remains common for both LS stages
|
| 404 |
+
+ 'generations': 350, # Sufficient generations for thorough evolution
|
| 405 |
+
+ 'elite_count': 5, # Number of elite individuals to carry over
|
| 406 |
+
+ 'tournament_size': 6, # Tournament size for selection pressure
|
| 407 |
+
+ 'mutation_rate_start_per_circle': 0.45, # Initial probability for a circle to mutate
|
| 408 |
+
+ 'mutation_rate_end_per_circle': 0.05, # Final probability after annealing
|
| 409 |
+
+ 'mut_strength_start': 0.1, # Initial strength of Gaussian mutation
|
| 410 |
+
+ 'mut_strength_end': 0.001, # Final strength of Gaussian mutation
|
| 411 |
+
+ 'crossover_rate': 0.9, # Probability of performing crossover
|
| 412 |
+
+ 'jump_mutation_prob': 0.03, # Probability of a strong "jump" mutation for a circle
|
| 413 |
+
+
|
| 414 |
+
+ # Initialization parameters for _initialize_population
|
| 415 |
+
+ 'initial_perturbation_scale': 0.02, # Scale for perturbing grid-based initial positions
|
| 416 |
+
+ 'initial_candidates': [ # Strategic points for the 26th circle
|
| 417 |
+
+ [spacing, spacing], [spacing, 1 - spacing], [1 - spacing, spacing], [1 - spacing, 1 - spacing],
|
| 418 |
+
+ [0.5, 0.5], [spacing, 0.5], [0.5, spacing], [1 - spacing, 0.5], [0.5, 1 - spacing],
|
| 419 |
+
+ [0.05, 0.05], [0.05, 0.95], [0.95, 0.05], [0.95, 0.95],
|
| 420 |
+
+ [0.1, 0.1], [0.1, 0.9], [0.9, 0.1], [0.9, 0.9]
|
| 421 |
+
+ ],
|
| 422 |
+
+
|
| 423 |
+
+ # Memetic Parameters (Local Search, passed to HybridLocalSearcher)
|
| 424 |
+
+ 'local_search_prob': 0.20, # Probability of applying local search to a new child
|
| 425 |
+
+ 'ls_config': { # Configuration for the HybridLocalSearcher
|
| 426 |
+
+ 'sim_iter': 500, # Iterations for the main simulation phase
|
| 427 |
+
+ 'radius_sim_iter': 150, # Radii calculation iterations during simulation
|
| 428 |
+
+ 'learning_rate': 0.018, # Base learning rate for center updates
|
| 429 |
+
+ 'wall_strength': 0.65, # Strength of repulsion from square walls
|
| 430 |
+
+ 'initial_growth_pressure': 1.05, # Initial multiplier for radii to create expansion pressure
|
| 431 |
+
+ 'final_growth_pressure': 1.001, # Final multiplier for radii
|
| 432 |
+
+ 'fine_tune_iter': 250, # Iterations for the fine-tuning phase
|
| 433 |
+
+ 'fine_tune_lr': 0.0015, # Learning rate for the fine-tuning phase
|
| 434 |
+
+ }
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
solver = MemeticAlgorithm(n=n, config=config)
|
| 438 |
+
best_centers = solver.run()
|
| 439 |
+
-
|
| 440 |
+
+
|
| 441 |
+
# Final, high-precision radius calculation for the best solution
|
| 442 |
+
- final_radii = compute_max_radii(best_centers, max_iter=1500)
|
| 443 |
+
-
|
| 444 |
+
+ final_radii = compute_max_radii(best_centers, max_iter=2000, convergence_threshold=1e-9)
|
| 445 |
+
+
|
| 446 |
+
return best_centers, final_radii
|
| 447 |
+
-
|
| 448 |
+
# EVOLVE-BLOCK-END
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
# This part remains fixed (not evolved)
|
| 452 |
+
def run_packing():
|
| 453 |
+
"""Run the circle packing constructor for n=26"""
|
| 454 |
+
centers, radii = construct_packing()
|
| 455 |
+
# Calculate the sum of radii
|
| 456 |
+
sum_radii = np.sum(radii)
|
| 457 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/main.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def compute_max_radii(centers, max_iter=750, convergence_threshold=1e-8):
|
| 5 |
+
"""
|
| 6 |
+
Compute maximum radii using iterative proportional scaling with a convergence check.
|
| 7 |
+
"""
|
| 8 |
+
n = centers.shape[0]
|
| 9 |
+
# Initialize radii to be constrained by walls
|
| 10 |
+
radii = np.min([
|
| 11 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 12 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 13 |
+
], axis=0)
|
| 14 |
+
|
| 15 |
+
for _ in range(max_iter):
|
| 16 |
+
old_radii = radii.copy()
|
| 17 |
+
updated = False
|
| 18 |
+
# Calculate distances and handle overlaps for all pairs
|
| 19 |
+
# Vectorized calculation for distances to improve performance
|
| 20 |
+
diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 21 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 22 |
+
|
| 23 |
+
# Iterate over upper triangle to avoid duplicate calculations and self-comparison
|
| 24 |
+
for i in range(n):
|
| 25 |
+
for j in range(i + 1, n):
|
| 26 |
+
dist = dists[i, j] # Pre-calculated distance
|
| 27 |
+
|
| 28 |
+
if radii[i] + radii[j] > dist:
|
| 29 |
+
if dist < 1e-9: # Handle co-located centers: radii must be zero
|
| 30 |
+
if radii[i] > 0 or radii[j] > 0: # Only update if they weren't already zero
|
| 31 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 32 |
+
updated = True
|
| 33 |
+
else:
|
| 34 |
+
scale = dist / (radii[i] + radii[j])
|
| 35 |
+
radii[i] *= scale
|
| 36 |
+
radii[j] *= scale
|
| 37 |
+
updated = True
|
| 38 |
+
|
| 39 |
+
# Check for convergence: if no updates were made AND the max change is tiny.
|
| 40 |
+
# This prevents infinite loops if changes are smaller than convergence_threshold
|
| 41 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 42 |
+
break
|
| 43 |
+
|
| 44 |
+
return np.maximum(radii, 0)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class HybridLocalSearcher:
|
| 48 |
+
"""
|
| 49 |
+
A powerful local search refiner based on a two-phase physical simulation.
|
| 50 |
+
This approach incorporates a "growth pressure" concept for exploration.
|
| 51 |
+
"""
|
| 52 |
+
def __init__(self, n, config):
|
| 53 |
+
self.n = n
|
| 54 |
+
self.config = config
|
| 55 |
+
|
| 56 |
+
def _compute_radii_for_sim(self, centers):
|
| 57 |
+
"""A fast radius computation for use inside the simulation loop."""
|
| 58 |
+
return compute_max_radii(centers, max_iter=self.config['radius_sim_iter'], convergence_threshold=1e-7)
|
| 59 |
+
|
| 60 |
+
def refine(self, centers):
|
| 61 |
+
"""Applies a two-phase physical simulation to refine a configuration."""
|
| 62 |
+
refined_centers = centers.copy()
|
| 63 |
+
|
| 64 |
+
# Phase 1: Main refinement with decreasing growth pressure
|
| 65 |
+
iterations = self.config['sim_iter']
|
| 66 |
+
base_lr = self.config['learning_rate']
|
| 67 |
+
wall_strength = self.config['wall_strength']
|
| 68 |
+
initial_growth_pressure = self.config['initial_growth_pressure']
|
| 69 |
+
final_growth_pressure = self.config['final_growth_pressure']
|
| 70 |
+
|
| 71 |
+
for i_iter in range(iterations):
|
| 72 |
+
progress = i_iter / iterations
|
| 73 |
+
# Anneal growth pressure quadratically
|
| 74 |
+
current_growth_pressure = final_growth_pressure + (initial_growth_pressure - final_growth_pressure) * (1.0 - progress)**2
|
| 75 |
+
current_radii = self._compute_radii_for_sim(refined_centers)
|
| 76 |
+
pressured_radii = current_radii * current_growth_pressure
|
| 77 |
+
|
| 78 |
+
# Vectorized force calculations
|
| 79 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 80 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 81 |
+
dists[dists < 1e-9] = 1e-9 # Prevent division by zero
|
| 82 |
+
radii_sums = pressured_radii[:, np.newaxis] + pressured_radii[np.newaxis, :]
|
| 83 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 84 |
+
np.fill_diagonal(overlaps, 0)
|
| 85 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 86 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 87 |
+
|
| 88 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 89 |
+
# Wall forces calculation
|
| 90 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 0])
|
| 91 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + pressured_radii) - 1.0)
|
| 92 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 1])
|
| 93 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + pressured_radii) - 1.0)
|
| 94 |
+
|
| 95 |
+
forces = circle_forces + wall_forces
|
| 96 |
+
# Anneal learning rate quadratically
|
| 97 |
+
lr = base_lr * (1.0 - progress)**2
|
| 98 |
+
refined_centers += forces * lr
|
| 99 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 100 |
+
|
| 101 |
+
# Phase 2: Fine-tuning with minimal growth pressure
|
| 102 |
+
iterations_ft = self.config['fine_tune_iter']
|
| 103 |
+
lr_ft = self.config['fine_tune_lr']
|
| 104 |
+
for _ in range(iterations_ft):
|
| 105 |
+
current_radii = self._compute_radii_for_sim(refined_centers)
|
| 106 |
+
|
| 107 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 108 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 109 |
+
dists[dists < 1e-9] = 1e-9
|
| 110 |
+
radii_sums = current_radii[:, np.newaxis] + current_radii[np.newaxis, :]
|
| 111 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 112 |
+
np.fill_diagonal(overlaps, 0)
|
| 113 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 114 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 115 |
+
|
| 116 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 117 |
+
# Wall forces calculation
|
| 118 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 0])
|
| 119 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + current_radii) - 1.0)
|
| 120 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 1])
|
| 121 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + current_radii) - 1.0)
|
| 122 |
+
|
| 123 |
+
forces = circle_forces + wall_forces
|
| 124 |
+
refined_centers += forces * lr_ft
|
| 125 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 126 |
+
|
| 127 |
+
return refined_centers
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class MemeticAlgorithm:
|
| 131 |
+
"""Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 132 |
+
def __init__(self, n, config):
|
| 133 |
+
self.n = n
|
| 134 |
+
self.config = config
|
| 135 |
+
self.population = []
|
| 136 |
+
self.fitnesses = np.array([])
|
| 137 |
+
self.best_solution = None
|
| 138 |
+
self.best_fitness = -1.0
|
| 139 |
+
# Use the HybridLocalSearcher with its specific config
|
| 140 |
+
self.local_search_refiner = HybridLocalSearcher(n=n, config=config['ls_config'])
|
| 141 |
+
|
| 142 |
+
def _initialize_population(self):
|
| 143 |
+
"""Initializes a diverse population using strategic grid-based starts and random individuals."""
|
| 144 |
+
spacing = 1.0 / 5
|
| 145 |
+
# The explicit list of candidate positions for the 26th circle, from a high-performing prior
|
| 146 |
+
candidate_extra_positions = self.config['initial_candidates']
|
| 147 |
+
|
| 148 |
+
# Base for 25 circles in a 5x5 grid, to be perturbed
|
| 149 |
+
base_centers_25 = np.zeros((self.n - 1, 2))
|
| 150 |
+
k = 0
|
| 151 |
+
for j in range(5):
|
| 152 |
+
for i in range(5):
|
| 153 |
+
base_centers_25[k, 0] = (i + 0.5) * spacing
|
| 154 |
+
base_centers_25[k, 1] = (j + 0.5) * spacing
|
| 155 |
+
k += 1
|
| 156 |
+
|
| 157 |
+
num_strategic_starts = min(len(candidate_extra_positions), self.config['population_size'] // 2)
|
| 158 |
+
|
| 159 |
+
# Add strategically initialized configurations
|
| 160 |
+
for i in range(num_strategic_starts):
|
| 161 |
+
centers = np.zeros((self.n, 2))
|
| 162 |
+
# Apply perturbation to break symmetry
|
| 163 |
+
perturbation = np.random.normal(0, self.config['initial_perturbation_scale'], size=base_centers_25.shape)
|
| 164 |
+
perturbed_base_centers = np.clip(base_centers_25 + perturbation, 0.0, 1.0)
|
| 165 |
+
centers[:self.n-1] = perturbed_base_centers
|
| 166 |
+
# Place the 26th circle at a strategic point
|
| 167 |
+
centers[self.n-1] = np.array(candidate_extra_positions[i])
|
| 168 |
+
self.population.append(centers)
|
| 169 |
+
|
| 170 |
+
# Add purely random configurations to ensure broad exploration
|
| 171 |
+
num_random_starts = self.config['population_size'] - len(self.population)
|
| 172 |
+
for _ in range(num_random_starts):
|
| 173 |
+
self.population.append(np.random.rand(self.n, 2))
|
| 174 |
+
|
| 175 |
+
def _evaluate_population(self):
|
| 176 |
+
"""Calculates fitness for the population and updates the best solution."""
|
| 177 |
+
self.fitnesses = np.array([np.sum(compute_max_radii(ind)) for ind in self.population])
|
| 178 |
+
best_idx = np.argmax(self.fitnesses)
|
| 179 |
+
if self.fitnesses[best_idx] > self.best_fitness:
|
| 180 |
+
self.best_fitness = self.fitnesses[best_idx]
|
| 181 |
+
self.best_solution = self.population[best_idx].copy()
|
| 182 |
+
|
| 183 |
+
def _select_parent(self):
|
| 184 |
+
"""Selects a parent using tournament selection."""
|
| 185 |
+
tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 186 |
+
tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 187 |
+
winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 188 |
+
return self.population[winner_idx]
|
| 189 |
+
|
| 190 |
+
def _crossover(self, p1, p2):
|
| 191 |
+
"""Performs uniform crossover, taking whole circles from either parent."""
|
| 192 |
+
child = p1.copy()
|
| 193 |
+
mask = np.random.rand(self.n) < 0.5
|
| 194 |
+
child[mask] = p2[mask]
|
| 195 |
+
return child
|
| 196 |
+
|
| 197 |
+
def _mutate(self, individual, strength, mutation_rate_per_circle):
|
| 198 |
+
"""Applies Gaussian mutation and a chance of a strong 'jump' mutation."""
|
| 199 |
+
mutated_ind = individual.copy()
|
| 200 |
+
for i in range(self.n):
|
| 201 |
+
if np.random.rand() < mutation_rate_per_circle: # Use the annealed rate
|
| 202 |
+
noise = np.random.normal(0, strength, size=2)
|
| 203 |
+
mutated_ind[i] += noise
|
| 204 |
+
|
| 205 |
+
if np.random.rand() < self.config['jump_mutation_prob']: # Jump mutation probability from a good prior
|
| 206 |
+
idx_to_reset = np.random.randint(0, self.n)
|
| 207 |
+
mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 208 |
+
|
| 209 |
+
return np.clip(mutated_ind, 0.0, 1.0)
|
| 210 |
+
|
| 211 |
+
def run(self):
|
| 212 |
+
"""Executes the full memetic algorithm evolution."""
|
| 213 |
+
self._initialize_population()
|
| 214 |
+
|
| 215 |
+
for gen in range(self.config['generations']):
|
| 216 |
+
self._evaluate_population()
|
| 217 |
+
|
| 218 |
+
new_population = []
|
| 219 |
+
|
| 220 |
+
# Elitism: carry over the best individuals directly
|
| 221 |
+
elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 222 |
+
for idx in elite_indices:
|
| 223 |
+
new_population.append(self.population[idx].copy())
|
| 224 |
+
|
| 225 |
+
# Anneal mutation strength over generations with a quadratic decay
|
| 226 |
+
mut_strength = self.config['mut_strength_end'] + \
|
| 227 |
+
(self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 228 |
+
(1.0 - (gen / self.config['generations']))**2.0
|
| 229 |
+
|
| 230 |
+
# Anneal mutation rate per circle over generations with a cubic decay
|
| 231 |
+
current_mutation_rate_per_circle = self.config['mutation_rate_end_per_circle'] + \
|
| 232 |
+
(self.config['mutation_rate_start_per_circle'] - self.config['mutation_rate_end_per_circle']) * \
|
| 233 |
+
(1.0 - (gen / self.config['generations']))**1.5
|
| 234 |
+
|
| 235 |
+
while len(new_population) < self.config['population_size']:
|
| 236 |
+
p1 = self._select_parent()
|
| 237 |
+
p2 = self._select_parent()
|
| 238 |
+
|
| 239 |
+
# Perform crossover, or just copy a parent if crossover doesn't happen
|
| 240 |
+
child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 241 |
+
# Apply mutation with annealed strength and rate
|
| 242 |
+
child = self._mutate(child, mut_strength, current_mutation_rate_per_circle)
|
| 243 |
+
|
| 244 |
+
# Memetic step: Apply powerful local search probabilistically
|
| 245 |
+
if np.random.rand() < self.config['local_search_prob']:
|
| 246 |
+
child = self.local_search_refiner.refine(child)
|
| 247 |
+
|
| 248 |
+
new_population.append(child)
|
| 249 |
+
|
| 250 |
+
self.population = new_population
|
| 251 |
+
|
| 252 |
+
# Final evaluation to get the best of the last generation
|
| 253 |
+
self._evaluate_population()
|
| 254 |
+
return self.best_solution
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def construct_packing():
|
| 258 |
+
"""
|
| 259 |
+
Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 260 |
+
"""
|
| 261 |
+
n = 26
|
| 262 |
+
spacing = 1.0 / 5
|
| 263 |
+
config = {
|
| 264 |
+
# MA parameters
|
| 265 |
+
'population_size': 80, # Increased population size for more diversity
|
| 266 |
+
'generations': 350, # Sufficient generations for thorough evolution
|
| 267 |
+
'elite_count': 5, # Number of elite individuals to carry over
|
| 268 |
+
'tournament_size': 6, # Tournament size for selection pressure
|
| 269 |
+
'mutation_rate_start_per_circle': 0.45, # Initial probability for a circle to mutate
|
| 270 |
+
'mutation_rate_end_per_circle': 0.05, # Final probability after annealing
|
| 271 |
+
'mut_strength_start': 0.1, # Initial strength of Gaussian mutation
|
| 272 |
+
'mut_strength_end': 0.001, # Final strength of Gaussian mutation
|
| 273 |
+
'crossover_rate': 0.9, # Probability of performing crossover
|
| 274 |
+
'jump_mutation_prob': 0.03, # Probability of a strong "jump" mutation for a circle
|
| 275 |
+
|
| 276 |
+
# Initialization parameters for _initialize_population
|
| 277 |
+
'initial_perturbation_scale': 0.02, # Scale for perturbing grid-based initial positions
|
| 278 |
+
'initial_candidates': [ # Strategic points for the 26th circle
|
| 279 |
+
[spacing, spacing], [spacing, 1 - spacing], [1 - spacing, spacing], [1 - spacing, 1 - spacing],
|
| 280 |
+
[0.5, 0.5], [spacing, 0.5], [0.5, spacing], [1 - spacing, 0.5], [0.5, 1 - spacing],
|
| 281 |
+
[0.05, 0.05], [0.05, 0.95], [0.95, 0.05], [0.95, 0.95],
|
| 282 |
+
[0.1, 0.1], [0.1, 0.9], [0.9, 0.1], [0.9, 0.9]
|
| 283 |
+
],
|
| 284 |
+
|
| 285 |
+
# Memetic Parameters (Local Search, passed to HybridLocalSearcher)
|
| 286 |
+
'local_search_prob': 0.20, # Probability of applying local search to a new child
|
| 287 |
+
'ls_config': { # Configuration for the HybridLocalSearcher
|
| 288 |
+
'sim_iter': 500, # Iterations for the main simulation phase
|
| 289 |
+
'radius_sim_iter': 150, # Radii calculation iterations during simulation
|
| 290 |
+
'learning_rate': 0.018, # Base learning rate for center updates
|
| 291 |
+
'wall_strength': 0.65, # Strength of repulsion from square walls
|
| 292 |
+
'initial_growth_pressure': 1.05, # Initial multiplier for radii to create expansion pressure
|
| 293 |
+
'final_growth_pressure': 1.001, # Final multiplier for radii
|
| 294 |
+
'fine_tune_iter': 250, # Iterations for the fine-tuning phase
|
| 295 |
+
'fine_tune_lr': 0.0015, # Learning rate for the fine-tuning phase
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
solver = MemeticAlgorithm(n=n, config=config)
|
| 300 |
+
best_centers = solver.run()
|
| 301 |
+
|
| 302 |
+
# Final, high-precision radius calculation for the best solution
|
| 303 |
+
final_radii = compute_max_radii(best_centers, max_iter=2000, convergence_threshold=1e-9)
|
| 304 |
+
|
| 305 |
+
return best_centers, final_radii
|
| 306 |
+
# EVOLVE-BLOCK-END
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
# This part remains fixed (not evolved)
|
| 310 |
+
def run_packing():
|
| 311 |
+
"""Run the circle packing constructor for n=26"""
|
| 312 |
+
centers, radii = construct_packing()
|
| 313 |
+
# Calculate the sum of radii
|
| 314 |
+
sum_radii = np.sum(radii)
|
| 315 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/original.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def compute_max_radii(centers, max_iter=750, convergence_threshold=1e-8):
|
| 5 |
+
"""
|
| 6 |
+
Compute maximum radii using iterative proportional scaling with a convergence check.
|
| 7 |
+
"""
|
| 8 |
+
n = centers.shape[0]
|
| 9 |
+
radii = np.min([
|
| 10 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 11 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 12 |
+
], axis=0)
|
| 13 |
+
|
| 14 |
+
for _ in range(max_iter):
|
| 15 |
+
old_radii = radii.copy()
|
| 16 |
+
updated = False
|
| 17 |
+
for i in range(n):
|
| 18 |
+
for j in range(i + 1, n):
|
| 19 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 20 |
+
if radii[i] + radii[j] > dist:
|
| 21 |
+
if dist < 1e-9:
|
| 22 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 23 |
+
else:
|
| 24 |
+
scale = dist / (radii[i] + radii[j])
|
| 25 |
+
radii[i] *= scale
|
| 26 |
+
radii[j] *= scale
|
| 27 |
+
updated = True
|
| 28 |
+
|
| 29 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 30 |
+
break
|
| 31 |
+
|
| 32 |
+
return np.maximum(radii, 0)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class LocalSearchRefiner:
|
| 36 |
+
"""A class to perform fast, local refinement on a set of centers using a two-stage approach."""
|
| 37 |
+
def __init__(self, config):
|
| 38 |
+
self.config = config
|
| 39 |
+
|
| 40 |
+
def refine(self, centers):
|
| 41 |
+
"""Applies a two-stage force-directed refinement to polish a solution."""
|
| 42 |
+
refined_centers = centers.copy()
|
| 43 |
+
|
| 44 |
+
# --- Stage 1: Aggressive settling with subtle growth pressure ---
|
| 45 |
+
for i_agg_iter in range(self.config['ls_aggressive_iter']):
|
| 46 |
+
progress = i_agg_iter / self.config['ls_aggressive_iter']
|
| 47 |
+
current_growth_pressure = self.config['ls_aggressive_growth_pressure_end'] + \
|
| 48 |
+
(self.config['ls_aggressive_growth_pressure_start'] - self.config['ls_aggressive_growth_pressure_end']) * \
|
| 49 |
+
(1.0 - progress)**1.5 # Cubic decay for growth pressure
|
| 50 |
+
|
| 51 |
+
radii = compute_max_radii(refined_centers, max_iter=self.config['ls_radius_iter_agg'])
|
| 52 |
+
pressured_radii = radii * current_growth_pressure
|
| 53 |
+
|
| 54 |
+
# Vectorized Force Calculation
|
| 55 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 56 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 57 |
+
dists[dists < 1e-9] = 1e-9 # Prevent division by zero
|
| 58 |
+
|
| 59 |
+
radii_sums = pressured_radii[:, np.newaxis] + pressured_radii[np.newaxis, :]
|
| 60 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 61 |
+
np.fill_diagonal(overlaps, 0)
|
| 62 |
+
|
| 63 |
+
force_mags = overlaps
|
| 64 |
+
force_matrix = diffs * (force_mags / dists)[:, :, np.newaxis]
|
| 65 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 66 |
+
|
| 67 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 68 |
+
wall_forces[:, 0] += self.config['ls_wall_strength'] * np.maximum(0, pressured_radii - refined_centers[:, 0])
|
| 69 |
+
wall_forces[:, 0] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 0] + pressured_radii) - 1.0)
|
| 70 |
+
wall_forces[:, 1] += self.config['ls_wall_strength'] * np.maximum(0, pressured_radii - refined_centers[:, 1])
|
| 71 |
+
wall_forces[:, 1] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 1] + pressured_radii) - 1.0)
|
| 72 |
+
|
| 73 |
+
forces = circle_forces + wall_forces
|
| 74 |
+
|
| 75 |
+
current_lr_agg = self.config['ls_aggressive_lr_end'] + \
|
| 76 |
+
(self.config['ls_aggressive_lr_start'] - self.config['ls_aggressive_lr_end']) * \
|
| 77 |
+
(1.0 - progress)**2.0 # Quadratic decay for LR
|
| 78 |
+
|
| 79 |
+
refined_centers += forces * current_lr_agg
|
| 80 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 81 |
+
|
| 82 |
+
# --- Stage 2: Fine-tuning without growth pressure ---
|
| 83 |
+
for _ in range(self.config['ls_fine_tune_iter']):
|
| 84 |
+
radii = compute_max_radii(refined_centers, max_iter=self.config['ls_radius_iter_ft'])
|
| 85 |
+
|
| 86 |
+
# Vectorized Force Calculation (no growth pressure)
|
| 87 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 88 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 89 |
+
dists[dists < 1e-9] = 1e-9
|
| 90 |
+
|
| 91 |
+
radii_sums = radii[:, np.newaxis] + radii[np.newaxis, :]
|
| 92 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 93 |
+
np.fill_diagonal(overlaps, 0)
|
| 94 |
+
|
| 95 |
+
force_mags = overlaps
|
| 96 |
+
force_matrix = diffs * (force_mags / dists)[:, :, np.newaxis]
|
| 97 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 98 |
+
|
| 99 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 100 |
+
wall_forces[:, 0] += self.config['ls_wall_strength'] * np.maximum(0, radii - refined_centers[:, 0])
|
| 101 |
+
wall_forces[:, 0] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 0] + radii) - 1.0)
|
| 102 |
+
wall_forces[:, 1] += self.config['ls_wall_strength'] * np.maximum(0, radii - refined_centers[:, 1])
|
| 103 |
+
wall_forces[:, 1] -= self.config['ls_wall_strength'] * np.maximum(0, (refined_centers[:, 1] + radii) - 1.0)
|
| 104 |
+
|
| 105 |
+
forces = circle_forces + wall_forces
|
| 106 |
+
|
| 107 |
+
refined_centers += forces * self.config['ls_fine_tune_lr']
|
| 108 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 109 |
+
|
| 110 |
+
return refined_centers
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class MemeticAlgorithm:
|
| 114 |
+
"""Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 115 |
+
def __init__(self, n, config):
|
| 116 |
+
self.n = n
|
| 117 |
+
self.config = config
|
| 118 |
+
self.population = []
|
| 119 |
+
self.fitnesses = np.array([])
|
| 120 |
+
self.best_solution = None
|
| 121 |
+
self.best_fitness = -1.0
|
| 122 |
+
self.local_search_refiner = LocalSearchRefiner(config=config)
|
| 123 |
+
|
| 124 |
+
def _initialize_population(self):
|
| 125 |
+
"""Initializes a diverse population with both random and grid-based individuals."""
|
| 126 |
+
num_grid_based = self.config['population_size'] // 4
|
| 127 |
+
num_cells_side = 5
|
| 128 |
+
spacing = 1.0 / num_cells_side
|
| 129 |
+
interstitial_points = [
|
| 130 |
+
[spacing, spacing], [spacing, 1 - spacing],
|
| 131 |
+
[1 - spacing, spacing], [1 - spacing, 1 - spacing]
|
| 132 |
+
]
|
| 133 |
+
|
| 134 |
+
for i in range(self.config['population_size']):
|
| 135 |
+
if i < num_grid_based:
|
| 136 |
+
centers = np.zeros((self.n, 2))
|
| 137 |
+
k = 0
|
| 138 |
+
for row in range(num_cells_side):
|
| 139 |
+
for col in range(num_cells_side):
|
| 140 |
+
centers[k, 0] = (col + 0.5) * spacing
|
| 141 |
+
centers[k, 1] = (row + 0.5) * spacing
|
| 142 |
+
k += 1
|
| 143 |
+
centers[:25, :] += np.random.normal(0, spacing * 0.01, size=(25, 2))
|
| 144 |
+
|
| 145 |
+
# Place 26th circle in one of the strategic voids
|
| 146 |
+
extra_pos = interstitial_points[i % len(interstitial_points)]
|
| 147 |
+
centers[25] = extra_pos + np.random.normal(0, spacing * 0.01, size=2)
|
| 148 |
+
self.population.append(np.clip(centers, 0.0, 1.0))
|
| 149 |
+
else:
|
| 150 |
+
self.population.append(np.random.rand(self.n, 2))
|
| 151 |
+
|
| 152 |
+
def _evaluate_population(self):
|
| 153 |
+
"""Calculates fitness for the population and updates the best solution."""
|
| 154 |
+
self.fitnesses = np.array([np.sum(compute_max_radii(ind)) for ind in self.population])
|
| 155 |
+
best_idx = np.argmax(self.fitnesses)
|
| 156 |
+
if self.fitnesses[best_idx] > self.best_fitness:
|
| 157 |
+
self.best_fitness = self.fitnesses[best_idx]
|
| 158 |
+
self.best_solution = self.population[best_idx].copy()
|
| 159 |
+
|
| 160 |
+
def _select_parent(self):
|
| 161 |
+
"""Selects a parent using tournament selection."""
|
| 162 |
+
tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 163 |
+
tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 164 |
+
winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 165 |
+
return self.population[winner_idx]
|
| 166 |
+
|
| 167 |
+
def _crossover(self, p1, p2):
|
| 168 |
+
"""
|
| 169 |
+
Performs Blend Crossover (BLX-alpha) on continuous variables.
|
| 170 |
+
Child = alpha * Parent1 + (1-alpha) * Parent2, where alpha is randomly chosen.
|
| 171 |
+
"""
|
| 172 |
+
# Using a small random range around 0.5 for alpha for averaging blend
|
| 173 |
+
alpha = np.random.uniform(0.4, 0.6)
|
| 174 |
+
child = alpha * p1 + (1 - alpha) * p2
|
| 175 |
+
return np.clip(child, 0.0, 1.0) # Ensure coordinates stay within bounds
|
| 176 |
+
|
| 177 |
+
def _mutate(self, individual, strength, mutation_rate_per_circle):
|
| 178 |
+
"""Applies Gaussian mutation and a chance of a strong mutation."""
|
| 179 |
+
mutated_ind = individual.copy()
|
| 180 |
+
for i in range(self.n):
|
| 181 |
+
if np.random.rand() < mutation_rate_per_circle: # Use the annealed rate
|
| 182 |
+
noise = np.random.normal(0, strength, size=2)
|
| 183 |
+
mutated_ind[i] += noise
|
| 184 |
+
|
| 185 |
+
if np.random.rand() < 0.02: # 2% chance of a jump mutation
|
| 186 |
+
idx_to_reset = np.random.randint(0, self.n)
|
| 187 |
+
mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 188 |
+
|
| 189 |
+
return np.clip(mutated_ind, 0.0, 1.0)
|
| 190 |
+
|
| 191 |
+
def run(self):
|
| 192 |
+
"""Executes the full memetic algorithm evolution."""
|
| 193 |
+
self._initialize_population()
|
| 194 |
+
|
| 195 |
+
for gen in range(self.config['generations']):
|
| 196 |
+
self._evaluate_population()
|
| 197 |
+
|
| 198 |
+
new_population = []
|
| 199 |
+
|
| 200 |
+
# Elitism
|
| 201 |
+
elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 202 |
+
for idx in elite_indices:
|
| 203 |
+
new_population.append(self.population[idx].copy())
|
| 204 |
+
|
| 205 |
+
# Anneal mutation strength
|
| 206 |
+
mut_strength = self.config['mut_strength_end'] + \
|
| 207 |
+
(self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 208 |
+
(1.0 - (gen / self.config['generations']))**2.0 # Cubic decay for strength
|
| 209 |
+
|
| 210 |
+
current_mutation_rate = self.config['mutation_rate_end'] + \
|
| 211 |
+
(self.config['mutation_rate'] - self.config['mutation_rate_end']) * \
|
| 212 |
+
(1.0 - (gen / self.config['generations']))**1.5 # Quadratic decay for rate (Recommendation #5)
|
| 213 |
+
|
| 214 |
+
while len(new_population) < self.config['population_size']:
|
| 215 |
+
p1 = self._select_parent()
|
| 216 |
+
p2 = self._select_parent()
|
| 217 |
+
|
| 218 |
+
child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 219 |
+
child = self._mutate(child, mut_strength, current_mutation_rate) # Pass current_mutation_rate
|
| 220 |
+
|
| 221 |
+
# Memetic step: Apply local search probabilistically
|
| 222 |
+
if np.random.rand() < self.config['local_search_prob']:
|
| 223 |
+
child = self.local_search_refiner.refine(child)
|
| 224 |
+
|
| 225 |
+
new_population.append(child)
|
| 226 |
+
|
| 227 |
+
self.population = new_population
|
| 228 |
+
|
| 229 |
+
# Final evaluation to get the best of the last generation
|
| 230 |
+
self._evaluate_population()
|
| 231 |
+
return self.best_solution
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def construct_packing():
|
| 235 |
+
"""
|
| 236 |
+
Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 237 |
+
"""
|
| 238 |
+
n = 26
|
| 239 |
+
config = {
|
| 240 |
+
'population_size': 80, # Increased population size for more diversity
|
| 241 |
+
'generations': 400, # Increased generations for more thorough evolution
|
| 242 |
+
'elite_count': 4,
|
| 243 |
+
'tournament_size': 7, # Increased tournament size for stronger selection pressure
|
| 244 |
+
'mutation_rate': 0.25, # Initial mutation rate (probability for a circle to mutate)
|
| 245 |
+
'mutation_rate_end': 0.05, # Final mutation rate after annealing (Recommendation #5)
|
| 246 |
+
'mut_strength_start': 0.1,
|
| 247 |
+
'mut_strength_end': 0.001,
|
| 248 |
+
'crossover_rate': 0.9,
|
| 249 |
+
# Memetic Parameters
|
| 250 |
+
'local_search_prob': 0.15, # Probability of applying local search to a new child
|
| 251 |
+
|
| 252 |
+
# Parameters for the two-stage Local Search Refiner (Recommendation #4)
|
| 253 |
+
'ls_aggressive_iter': 25, # Iterations for the first, aggressive local search stage
|
| 254 |
+
'ls_aggressive_lr_start': 0.005, # Initial learning rate for aggressive stage
|
| 255 |
+
'ls_aggressive_lr_end': 0.001, # Final learning rate for aggressive stage
|
| 256 |
+
'ls_aggressive_growth_pressure_start': 1.003, # Subtle initial growth pressure for aggressive stage
|
| 257 |
+
'ls_aggressive_growth_pressure_end': 1.0001, # Subtle final growth pressure for aggressive stage
|
| 258 |
+
'ls_radius_iter_agg': 80, # Radii iterations for aggressive stage (faster)
|
| 259 |
+
|
| 260 |
+
'ls_fine_tune_iter': 50, # Iterations for the second, fine-tuning local search stage
|
| 261 |
+
'ls_fine_tune_lr': 0.0005, # Fixed learning rate for fine-tuning stage (very low for precision)
|
| 262 |
+
'ls_radius_iter_ft': 150, # Radii iterations for fine-tuning stage (more precise)
|
| 263 |
+
'ls_wall_strength': 0.2, # Wall strength remains common for both LS stages
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
solver = MemeticAlgorithm(n=n, config=config)
|
| 267 |
+
best_centers = solver.run()
|
| 268 |
+
|
| 269 |
+
# Final, high-precision radius calculation for the best solution
|
| 270 |
+
final_radii = compute_max_radii(best_centers, max_iter=1500)
|
| 271 |
+
|
| 272 |
+
return best_centers, final_radii
|
| 273 |
+
|
| 274 |
+
# EVOLVE-BLOCK-END
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# This part remains fixed (not evolved)
|
| 278 |
+
def run_packing():
|
| 279 |
+
"""Run the circle packing constructor for n=26"""
|
| 280 |
+
centers, radii = construct_packing()
|
| 281 |
+
# Calculate the sum of radii
|
| 282 |
+
sum_radii = np.sum(radii)
|
| 283 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/job_log.err
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/tengxiao/pj/ShinkaEvolve/.venv/lib/python3.11/site-packages/instructor/providers/gemini/client.py:5: FutureWarning:
|
| 2 |
+
|
| 3 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 4 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 5 |
+
See README for more details:
|
| 6 |
+
|
| 7 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 8 |
+
|
| 9 |
+
import google.generativeai as genai # type: ignore[import-not-found]
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/job_log.out
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating program: results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/main.py
|
| 2 |
+
Saving results to: results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results
|
| 3 |
+
Run 1/1 completed in 2045.39 seconds
|
| 4 |
+
Detailed packing data saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/extra.npz
|
| 5 |
+
Visualization saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/packing_viz.png
|
| 6 |
+
Correctness and error status saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/correct.json
|
| 7 |
+
Metrics saved to results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/metrics.json
|
| 8 |
+
Evaluation and Validation completed successfully.
|
| 9 |
+
Metrics:
|
| 10 |
+
combined_score: 2.3900820406051997
|
| 11 |
+
public: {'centers_str': ' centers[0] = (0.0728, 0.0726)\n centers[1] = (0.3990, 0.0827)\n centers[2] = (0.5807, 0.2651)\n centers[3] = (0.7525, 0.0642)\n centers[4] = (0.9098, 0.0905)\n centers[5] = (0.0960, 0.2383)\n centers[6] = (0.2359, 0.0877)\n centers[7] = (0.5973, 0.7393)\n centers[8] = (0.7753, 0.2323)\n centers[9] = (0.9382, 0.2695)\n centers[10] = (0.0714, 0.4039)\n centers[11] = (0.3449, 0.3151)\n centers[12] = (0.4616, 0.5669)\n centers[13] = (0.6203, 0.3970)\n centers[14] = (0.8670, 0.4547)\n centers[15] = (0.0333, 0.7805)\n centers[16] = (0.1791, 0.6286)\n centers[17] = (0.4664, 0.7655)\n centers[18] = (0.6826, 0.6316)\n centers[19] = (0.8942, 0.7135)\n centers[20] = (0.1022, 0.8980)\n centers[21] = (0.3127, 0.8912)\n centers[22] = (0.5009, 0.9258)\n centers[23] = (0.6991, 0.8763)\n centers[24] = (0.9101, 0.9095)\n centers[25] = (0.5686, 0.1243)', 'num_circles': 26}
|
| 12 |
+
private: {'reported_sum_of_radii': 2.3900820406051997}
|
| 13 |
+
visualization_path: results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/packing_viz.png
|
| 14 |
+
execution_time_mean: 2045.3863690267317
|
| 15 |
+
execution_time_std: 0.0
|
| 16 |
+
num_valid_runs: 1
|
| 17 |
+
num_invalid_runs: 0
|
| 18 |
+
all_validation_errors: []
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/metrics.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 2.3900820406051997,
|
| 3 |
+
"public": {
|
| 4 |
+
"centers_str": " centers[0] = (0.0728, 0.0726)\n centers[1] = (0.3990, 0.0827)\n centers[2] = (0.5807, 0.2651)\n centers[3] = (0.7525, 0.0642)\n centers[4] = (0.9098, 0.0905)\n centers[5] = (0.0960, 0.2383)\n centers[6] = (0.2359, 0.0877)\n centers[7] = (0.5973, 0.7393)\n centers[8] = (0.7753, 0.2323)\n centers[9] = (0.9382, 0.2695)\n centers[10] = (0.0714, 0.4039)\n centers[11] = (0.3449, 0.3151)\n centers[12] = (0.4616, 0.5669)\n centers[13] = (0.6203, 0.3970)\n centers[14] = (0.8670, 0.4547)\n centers[15] = (0.0333, 0.7805)\n centers[16] = (0.1791, 0.6286)\n centers[17] = (0.4664, 0.7655)\n centers[18] = (0.6826, 0.6316)\n centers[19] = (0.8942, 0.7135)\n centers[20] = (0.1022, 0.8980)\n centers[21] = (0.3127, 0.8912)\n centers[22] = (0.5009, 0.9258)\n centers[23] = (0.6991, 0.8763)\n centers[24] = (0.9101, 0.9095)\n centers[25] = (0.5686, 0.1243)",
|
| 5 |
+
"num_circles": 26
|
| 6 |
+
},
|
| 7 |
+
"private": {
|
| 8 |
+
"reported_sum_of_radii": 2.3900820406051997
|
| 9 |
+
},
|
| 10 |
+
"visualization_path": "results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/results/packing_viz.png",
|
| 11 |
+
"execution_time_mean": 2045.3863690267317,
|
| 12 |
+
"execution_time_std": 0.0,
|
| 13 |
+
"num_valid_runs": 1,
|
| 14 |
+
"num_invalid_runs": 0,
|
| 15 |
+
"all_validation_errors": []
|
| 16 |
+
}
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_101/rewrite.txt
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def compute_max_radii(centers, max_iter=750, convergence_threshold=1e-8):
|
| 5 |
+
"""
|
| 6 |
+
Compute maximum radii using iterative proportional scaling with a convergence check.
|
| 7 |
+
"""
|
| 8 |
+
n = centers.shape[0]
|
| 9 |
+
# Initialize radii to be constrained by walls
|
| 10 |
+
radii = np.min([
|
| 11 |
+
centers[:, 0], 1 - centers[:, 0],
|
| 12 |
+
centers[:, 1], 1 - centers[:, 1]
|
| 13 |
+
], axis=0)
|
| 14 |
+
|
| 15 |
+
for _ in range(max_iter):
|
| 16 |
+
old_radii = radii.copy()
|
| 17 |
+
updated = False
|
| 18 |
+
# Calculate distances and handle overlaps for all pairs
|
| 19 |
+
# Vectorized calculation for distances to improve performance
|
| 20 |
+
diffs = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
|
| 21 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 22 |
+
|
| 23 |
+
# Iterate over upper triangle to avoid duplicate calculations and self-comparison
|
| 24 |
+
for i in range(n):
|
| 25 |
+
for j in range(i + 1, n):
|
| 26 |
+
dist = dists[i, j] # Pre-calculated distance
|
| 27 |
+
|
| 28 |
+
if radii[i] + radii[j] > dist:
|
| 29 |
+
if dist < 1e-9: # Handle co-located centers: radii must be zero
|
| 30 |
+
if radii[i] > 0 or radii[j] > 0: # Only update if they weren't already zero
|
| 31 |
+
radii[i], radii[j] = 0.0, 0.0
|
| 32 |
+
updated = True
|
| 33 |
+
else:
|
| 34 |
+
scale = dist / (radii[i] + radii[j])
|
| 35 |
+
radii[i] *= scale
|
| 36 |
+
radii[j] *= scale
|
| 37 |
+
updated = True
|
| 38 |
+
|
| 39 |
+
# Check for convergence: if no updates were made AND the max change is tiny.
|
| 40 |
+
# This prevents infinite loops if changes are smaller than convergence_threshold
|
| 41 |
+
if not updated or np.max(np.abs(radii - old_radii)) < convergence_threshold:
|
| 42 |
+
break
|
| 43 |
+
|
| 44 |
+
return np.maximum(radii, 0)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class HybridLocalSearcher:
|
| 48 |
+
"""
|
| 49 |
+
A powerful local search refiner based on a two-phase physical simulation.
|
| 50 |
+
This approach incorporates a "growth pressure" concept for exploration.
|
| 51 |
+
"""
|
| 52 |
+
def __init__(self, n, config):
|
| 53 |
+
self.n = n
|
| 54 |
+
self.config = config
|
| 55 |
+
|
| 56 |
+
def _compute_radii_for_sim(self, centers):
|
| 57 |
+
"""A fast radius computation for use inside the simulation loop."""
|
| 58 |
+
return compute_max_radii(centers, max_iter=self.config['radius_sim_iter'], convergence_threshold=1e-7)
|
| 59 |
+
|
| 60 |
+
def refine(self, centers):
|
| 61 |
+
"""Applies a two-phase physical simulation to refine a configuration."""
|
| 62 |
+
refined_centers = centers.copy()
|
| 63 |
+
|
| 64 |
+
# Phase 1: Main refinement with decreasing growth pressure
|
| 65 |
+
iterations = self.config['sim_iter']
|
| 66 |
+
base_lr = self.config['learning_rate']
|
| 67 |
+
wall_strength = self.config['wall_strength']
|
| 68 |
+
initial_growth_pressure = self.config['initial_growth_pressure']
|
| 69 |
+
final_growth_pressure = self.config['final_growth_pressure']
|
| 70 |
+
|
| 71 |
+
for i_iter in range(iterations):
|
| 72 |
+
progress = i_iter / iterations
|
| 73 |
+
# Anneal growth pressure quadratically
|
| 74 |
+
current_growth_pressure = final_growth_pressure + (initial_growth_pressure - final_growth_pressure) * (1.0 - progress)**2
|
| 75 |
+
current_radii = self._compute_radii_for_sim(refined_centers)
|
| 76 |
+
pressured_radii = current_radii * current_growth_pressure
|
| 77 |
+
|
| 78 |
+
# Vectorized force calculations
|
| 79 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 80 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 81 |
+
dists[dists < 1e-9] = 1e-9 # Prevent division by zero
|
| 82 |
+
radii_sums = pressured_radii[:, np.newaxis] + pressured_radii[np.newaxis, :]
|
| 83 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 84 |
+
np.fill_diagonal(overlaps, 0)
|
| 85 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 86 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 87 |
+
|
| 88 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 89 |
+
# Wall forces calculation
|
| 90 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 0])
|
| 91 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + pressured_radii) - 1.0)
|
| 92 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, pressured_radii - refined_centers[:, 1])
|
| 93 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + pressured_radii) - 1.0)
|
| 94 |
+
|
| 95 |
+
forces = circle_forces + wall_forces
|
| 96 |
+
# Anneal learning rate quadratically
|
| 97 |
+
lr = base_lr * (1.0 - progress)**2
|
| 98 |
+
refined_centers += forces * lr
|
| 99 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 100 |
+
|
| 101 |
+
# Phase 2: Fine-tuning with minimal growth pressure
|
| 102 |
+
iterations_ft = self.config['fine_tune_iter']
|
| 103 |
+
lr_ft = self.config['fine_tune_lr']
|
| 104 |
+
for _ in range(iterations_ft):
|
| 105 |
+
current_radii = self._compute_radii_for_sim(refined_centers)
|
| 106 |
+
|
| 107 |
+
diffs = refined_centers[:, np.newaxis, :] - refined_centers[np.newaxis, :, :]
|
| 108 |
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
| 109 |
+
dists[dists < 1e-9] = 1e-9
|
| 110 |
+
radii_sums = current_radii[:, np.newaxis] + current_radii[np.newaxis, :]
|
| 111 |
+
overlaps = np.maximum(0, radii_sums - dists)
|
| 112 |
+
np.fill_diagonal(overlaps, 0)
|
| 113 |
+
force_matrix = diffs * (overlaps / dists)[:, :, np.newaxis]
|
| 114 |
+
circle_forces = np.sum(force_matrix, axis=1)
|
| 115 |
+
|
| 116 |
+
wall_forces = np.zeros_like(refined_centers)
|
| 117 |
+
# Wall forces calculation
|
| 118 |
+
wall_forces[:, 0] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 0])
|
| 119 |
+
wall_forces[:, 0] -= wall_strength * np.maximum(0, (refined_centers[:, 0] + current_radii) - 1.0)
|
| 120 |
+
wall_forces[:, 1] += wall_strength * np.maximum(0, current_radii - refined_centers[:, 1])
|
| 121 |
+
wall_forces[:, 1] -= wall_strength * np.maximum(0, (refined_centers[:, 1] + current_radii) - 1.0)
|
| 122 |
+
|
| 123 |
+
forces = circle_forces + wall_forces
|
| 124 |
+
refined_centers += forces * lr_ft
|
| 125 |
+
refined_centers = np.clip(refined_centers, 0.0, 1.0)
|
| 126 |
+
|
| 127 |
+
return refined_centers
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class MemeticAlgorithm:
|
| 131 |
+
"""Encapsulates the entire Memetic Algorithm for circle packing."""
|
| 132 |
+
def __init__(self, n, config):
|
| 133 |
+
self.n = n
|
| 134 |
+
self.config = config
|
| 135 |
+
self.population = []
|
| 136 |
+
self.fitnesses = np.array([])
|
| 137 |
+
self.best_solution = None
|
| 138 |
+
self.best_fitness = -1.0
|
| 139 |
+
# Use the HybridLocalSearcher with its specific config
|
| 140 |
+
self.local_search_refiner = HybridLocalSearcher(n=n, config=config['ls_config'])
|
| 141 |
+
|
| 142 |
+
def _initialize_population(self):
|
| 143 |
+
"""Initializes a diverse population using strategic grid-based starts and random individuals."""
|
| 144 |
+
spacing = 1.0 / 5
|
| 145 |
+
# The explicit list of candidate positions for the 26th circle, from a high-performing prior
|
| 146 |
+
candidate_extra_positions = self.config['initial_candidates']
|
| 147 |
+
|
| 148 |
+
# Base for 25 circles in a 5x5 grid, to be perturbed
|
| 149 |
+
base_centers_25 = np.zeros((self.n - 1, 2))
|
| 150 |
+
k = 0
|
| 151 |
+
for j in range(5):
|
| 152 |
+
for i in range(5):
|
| 153 |
+
base_centers_25[k, 0] = (i + 0.5) * spacing
|
| 154 |
+
base_centers_25[k, 1] = (j + 0.5) * spacing
|
| 155 |
+
k += 1
|
| 156 |
+
|
| 157 |
+
num_strategic_starts = min(len(candidate_extra_positions), self.config['population_size'] // 2)
|
| 158 |
+
|
| 159 |
+
# Add strategically initialized configurations
|
| 160 |
+
for i in range(num_strategic_starts):
|
| 161 |
+
centers = np.zeros((self.n, 2))
|
| 162 |
+
# Apply perturbation to break symmetry
|
| 163 |
+
perturbation = np.random.normal(0, self.config['initial_perturbation_scale'], size=base_centers_25.shape)
|
| 164 |
+
perturbed_base_centers = np.clip(base_centers_25 + perturbation, 0.0, 1.0)
|
| 165 |
+
centers[:self.n-1] = perturbed_base_centers
|
| 166 |
+
# Place the 26th circle at a strategic point
|
| 167 |
+
centers[self.n-1] = np.array(candidate_extra_positions[i])
|
| 168 |
+
self.population.append(centers)
|
| 169 |
+
|
| 170 |
+
# Add purely random configurations to ensure broad exploration
|
| 171 |
+
num_random_starts = self.config['population_size'] - len(self.population)
|
| 172 |
+
for _ in range(num_random_starts):
|
| 173 |
+
self.population.append(np.random.rand(self.n, 2))
|
| 174 |
+
|
| 175 |
+
def _evaluate_population(self):
|
| 176 |
+
"""Calculates fitness for the population and updates the best solution."""
|
| 177 |
+
self.fitnesses = np.array([np.sum(compute_max_radii(ind)) for ind in self.population])
|
| 178 |
+
best_idx = np.argmax(self.fitnesses)
|
| 179 |
+
if self.fitnesses[best_idx] > self.best_fitness:
|
| 180 |
+
self.best_fitness = self.fitnesses[best_idx]
|
| 181 |
+
self.best_solution = self.population[best_idx].copy()
|
| 182 |
+
|
| 183 |
+
def _select_parent(self):
|
| 184 |
+
"""Selects a parent using tournament selection."""
|
| 185 |
+
tourn_indices = np.random.choice(len(self.population), self.config['tournament_size'], replace=False)
|
| 186 |
+
tourn_fitnesses = self.fitnesses[tourn_indices]
|
| 187 |
+
winner_idx = tourn_indices[np.argmax(tourn_fitnesses)]
|
| 188 |
+
return self.population[winner_idx]
|
| 189 |
+
|
| 190 |
+
def _crossover(self, p1, p2):
|
| 191 |
+
"""Performs uniform crossover, taking whole circles from either parent."""
|
| 192 |
+
child = p1.copy()
|
| 193 |
+
mask = np.random.rand(self.n) < 0.5
|
| 194 |
+
child[mask] = p2[mask]
|
| 195 |
+
return child
|
| 196 |
+
|
| 197 |
+
def _mutate(self, individual, strength, mutation_rate_per_circle):
|
| 198 |
+
"""Applies Gaussian mutation and a chance of a strong 'jump' mutation."""
|
| 199 |
+
mutated_ind = individual.copy()
|
| 200 |
+
for i in range(self.n):
|
| 201 |
+
if np.random.rand() < mutation_rate_per_circle: # Use the annealed rate
|
| 202 |
+
noise = np.random.normal(0, strength, size=2)
|
| 203 |
+
mutated_ind[i] += noise
|
| 204 |
+
|
| 205 |
+
if np.random.rand() < self.config['jump_mutation_prob']: # Jump mutation probability from a good prior
|
| 206 |
+
idx_to_reset = np.random.randint(0, self.n)
|
| 207 |
+
mutated_ind[idx_to_reset] = np.random.rand(2)
|
| 208 |
+
|
| 209 |
+
return np.clip(mutated_ind, 0.0, 1.0)
|
| 210 |
+
|
| 211 |
+
def run(self):
|
| 212 |
+
"""Executes the full memetic algorithm evolution."""
|
| 213 |
+
self._initialize_population()
|
| 214 |
+
|
| 215 |
+
for gen in range(self.config['generations']):
|
| 216 |
+
self._evaluate_population()
|
| 217 |
+
|
| 218 |
+
new_population = []
|
| 219 |
+
|
| 220 |
+
# Elitism: carry over the best individuals directly
|
| 221 |
+
elite_indices = np.argsort(self.fitnesses)[-self.config['elite_count']:]
|
| 222 |
+
for idx in elite_indices:
|
| 223 |
+
new_population.append(self.population[idx].copy())
|
| 224 |
+
|
| 225 |
+
# Anneal mutation strength over generations with a quadratic decay
|
| 226 |
+
mut_strength = self.config['mut_strength_end'] + \
|
| 227 |
+
(self.config['mut_strength_start'] - self.config['mut_strength_end']) * \
|
| 228 |
+
(1.0 - (gen / self.config['generations']))**2.0
|
| 229 |
+
|
| 230 |
+
# Anneal mutation rate per circle over generations with a cubic decay
|
| 231 |
+
current_mutation_rate_per_circle = self.config['mutation_rate_end_per_circle'] + \
|
| 232 |
+
(self.config['mutation_rate_start_per_circle'] - self.config['mutation_rate_end_per_circle']) * \
|
| 233 |
+
(1.0 - (gen / self.config['generations']))**1.5
|
| 234 |
+
|
| 235 |
+
while len(new_population) < self.config['population_size']:
|
| 236 |
+
p1 = self._select_parent()
|
| 237 |
+
p2 = self._select_parent()
|
| 238 |
+
|
| 239 |
+
# Perform crossover, or just copy a parent if crossover doesn't happen
|
| 240 |
+
child = self._crossover(p1, p2) if np.random.rand() < self.config['crossover_rate'] else p1.copy()
|
| 241 |
+
# Apply mutation with annealed strength and rate
|
| 242 |
+
child = self._mutate(child, mut_strength, current_mutation_rate_per_circle)
|
| 243 |
+
|
| 244 |
+
# Memetic step: Apply powerful local search probabilistically
|
| 245 |
+
if np.random.rand() < self.config['local_search_prob']:
|
| 246 |
+
child = self.local_search_refiner.refine(child)
|
| 247 |
+
|
| 248 |
+
new_population.append(child)
|
| 249 |
+
|
| 250 |
+
self.population = new_population
|
| 251 |
+
|
| 252 |
+
# Final evaluation to get the best of the last generation
|
| 253 |
+
self._evaluate_population()
|
| 254 |
+
return self.best_solution
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def construct_packing():
|
| 258 |
+
"""
|
| 259 |
+
Constructs a packing of 26 circles using a Memetic Algorithm.
|
| 260 |
+
"""
|
| 261 |
+
n = 26
|
| 262 |
+
spacing = 1.0 / 5
|
| 263 |
+
config = {
|
| 264 |
+
# MA parameters
|
| 265 |
+
'population_size': 80, # Increased population size for more diversity
|
| 266 |
+
'generations': 350, # Sufficient generations for thorough evolution
|
| 267 |
+
'elite_count': 5, # Number of elite individuals to carry over
|
| 268 |
+
'tournament_size': 6, # Tournament size for selection pressure
|
| 269 |
+
'mutation_rate_start_per_circle': 0.45, # Initial probability for a circle to mutate
|
| 270 |
+
'mutation_rate_end_per_circle': 0.05, # Final probability after annealing
|
| 271 |
+
'mut_strength_start': 0.1, # Initial strength of Gaussian mutation
|
| 272 |
+
'mut_strength_end': 0.001, # Final strength of Gaussian mutation
|
| 273 |
+
'crossover_rate': 0.9, # Probability of performing crossover
|
| 274 |
+
'jump_mutation_prob': 0.03, # Probability of a strong "jump" mutation for a circle
|
| 275 |
+
|
| 276 |
+
# Initialization parameters for _initialize_population
|
| 277 |
+
'initial_perturbation_scale': 0.02, # Scale for perturbing grid-based initial positions
|
| 278 |
+
'initial_candidates': [ # Strategic points for the 26th circle
|
| 279 |
+
[spacing, spacing], [spacing, 1 - spacing], [1 - spacing, spacing], [1 - spacing, 1 - spacing],
|
| 280 |
+
[0.5, 0.5], [spacing, 0.5], [0.5, spacing], [1 - spacing, 0.5], [0.5, 1 - spacing],
|
| 281 |
+
[0.05, 0.05], [0.05, 0.95], [0.95, 0.05], [0.95, 0.95],
|
| 282 |
+
[0.1, 0.1], [0.1, 0.9], [0.9, 0.1], [0.9, 0.9]
|
| 283 |
+
],
|
| 284 |
+
|
| 285 |
+
# Memetic Parameters (Local Search, passed to HybridLocalSearcher)
|
| 286 |
+
'local_search_prob': 0.20, # Probability of applying local search to a new child
|
| 287 |
+
'ls_config': { # Configuration for the HybridLocalSearcher
|
| 288 |
+
'sim_iter': 500, # Iterations for the main simulation phase
|
| 289 |
+
'radius_sim_iter': 150, # Radii calculation iterations during simulation
|
| 290 |
+
'learning_rate': 0.018, # Base learning rate for center updates
|
| 291 |
+
'wall_strength': 0.65, # Strength of repulsion from square walls
|
| 292 |
+
'initial_growth_pressure': 1.05, # Initial multiplier for radii to create expansion pressure
|
| 293 |
+
'final_growth_pressure': 1.001, # Final multiplier for radii
|
| 294 |
+
'fine_tune_iter': 250, # Iterations for the fine-tuning phase
|
| 295 |
+
'fine_tune_lr': 0.0015, # Learning rate for the fine-tuning phase
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
solver = MemeticAlgorithm(n=n, config=config)
|
| 300 |
+
best_centers = solver.run()
|
| 301 |
+
|
| 302 |
+
# Final, high-precision radius calculation for the best solution
|
| 303 |
+
final_radii = compute_max_radii(best_centers, max_iter=2000, convergence_threshold=1e-9)
|
| 304 |
+
|
| 305 |
+
return best_centers, final_radii
|
| 306 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results_circle_packing_WITHOUT_vision_20260114_070110/gen_102/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (18.6 kB). View file
|
|
|