Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/__pycache__/main.cpython-313.pyc +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/edit.diff +302 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/main.py +230 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/original.py +190 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/results/correct.json +4 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/results/metrics.json +78 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/rewrite.txt +283 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/search_replace.txt +349 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/EVAL_AGENTS.md +233 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/auxiliary_metrics.py +477 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/service_state.json +608 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_0/main.py +94 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/edit.diff +201 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/main.py +153 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/original.py +94 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/rewrite.txt +144 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/__pycache__/main.cpython-313.pyc +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/edit.diff +184 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/main.py +176 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/original.py +174 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/results/correct.json +4 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/results/metrics.json +25 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/search_replace.txt +25 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/edit.diff +184 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/main.py +174 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/original.py +170 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/search_replace.txt +55 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/edit.diff +241 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/main.py +164 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/original.py +166 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/rewrite.txt +155 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/__pycache__/main.cpython-313.pyc +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/edit.diff +181 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/main.py +168 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/original.py +161 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/results/correct.json +4 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/results/metrics.json +84 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/search_replace.txt +48 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/edit.diff +207 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/main.py +196 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/original.py +189 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/search_replace.txt +46 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/__pycache__/main.cpython-313.pyc +0 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/edit.diff +200 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/main.py +180 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/original.py +174 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/results/correct.json +4 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/results/metrics.json +82 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/search_replace.txt +88 -0
- examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_106/edit.diff +231 -0
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (12.5 kB). View file
|
|
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/edit.diff
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,190 +1,230 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from scipy.optimize import linprog
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import math
|
| 9 |
+
+import random
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class PackingConfig:
|
| 13 |
+
"""
|
| 14 |
+
A unified configuration object holding all tunable parameters for the circle packing problem.
|
| 15 |
+
- This dataclass consolidates the most effective parameters from previous successful attempts,
|
| 16 |
+
- including non-uniform grids, central pair offsets, and anisotropic scaling.
|
| 17 |
+
+ This dataclass consolidates parameters for initial placement and adds parameters for
|
| 18 |
+
+ Simulated Annealing (SA) to refine the packing.
|
| 19 |
+
"""
|
| 20 |
+
n_circles: int = 26
|
| 21 |
+
|
| 22 |
+
- # Grid parameters allowing non-uniform spacing.
|
| 23 |
+
- # The default is the robust 5x5 grid that has performed well.
|
| 24 |
+
+ # Grid parameters for the initial 24 circles.
|
| 25 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 26 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 27 |
+
|
| 28 |
+
- # Central circle parameters. These are tuned based on the best-performing prior program (score 2.52).
|
| 29 |
+
- # Distance between the two central circle centers.
|
| 30 |
+
+ # Central circle parameters for initial placement, tuned from high-scoring runs.
|
| 31 |
+
central_separation_distance: float = 0.125
|
| 32 |
+
- # The angle of the line connecting the two central circles.
|
| 33 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 34 |
+
- # The distance to shift the entire central pair's midpoint from (0.5, 0.5).
|
| 35 |
+
- central_midpoint_offset_dist: float = 0.0015
|
| 36 |
+
- # The angle of the midpoint shift. 180 degrees corresponds to a negative x-offset.
|
| 37 |
+
- central_midpoint_offset_angle_deg: float = 180.0
|
| 38 |
+
- # Anisotropic scaling to create an elliptical void for the central pair.
|
| 39 |
+
+ # Asymmetric offset for the central pair's midpoint.
|
| 40 |
+
+ central_midpoint_offset_dist: float = 0.0018 # From x=-0.0015, y=0.0010
|
| 41 |
+
+ central_midpoint_offset_angle_deg: float = 146.3 # From x=-0.0015, y=0.0010
|
| 42 |
+
+ # Anisotropic scaling for the central pair's void.
|
| 43 |
+
central_x_offset_scale: float = 1.01
|
| 44 |
+
central_y_offset_scale: float = 0.99
|
| 45 |
+
|
| 46 |
+
- # Global rotation for the entire packing arrangement.
|
| 47 |
+
- global_rotation_angle_deg: float = 0.0
|
| 48 |
+
+ # Global rotation for the initial packing arrangement.
|
| 49 |
+
+ global_rotation_angle_deg: float = 0.1
|
| 50 |
+
+
|
| 51 |
+
+ # Simulated Annealing (SA) parameters
|
| 52 |
+
+ initial_temperature: float = 0.005
|
| 53 |
+
+ cooling_rate: float = 0.9999
|
| 54 |
+
+ max_iterations: int = 25000
|
| 55 |
+
+ initial_perturb_scale: float = 0.015
|
| 56 |
+
+ perturb_temp_power: float = 1.0
|
| 57 |
+
+ circles_to_perturb: int = 2
|
| 58 |
+
+ global_nudge_probability: float = 0.01
|
| 59 |
+
+ global_nudge_amount: float = 0.005
|
| 60 |
+
+ global_nudge_temp_power: float = 0.5
|
| 61 |
+
|
| 62 |
+
clip_epsilon: float = 1e-8
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class CirclePackingSolver:
|
| 66 |
+
"""
|
| 67 |
+
- An encapsulated solver for the circle packing problem.
|
| 68 |
+
- This class takes a configuration object and orchestrates the generation of
|
| 69 |
+
- circle centers and the computation of their optimal radii. This new structure
|
| 70 |
+
- centralizes all logic into a single, reusable class.
|
| 71 |
+
+ An encapsulated solver for the circle packing problem, now enhanced with
|
| 72 |
+
+ a Simulated Annealing (SA) optimization loop. It starts with a strong
|
| 73 |
+
+ initial configuration and iteratively refines it to find a better packing.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
def __init__(self, config: PackingConfig):
|
| 77 |
+
"""Initializes the solver with a given packing configuration."""
|
| 78 |
+
self.config = config
|
| 79 |
+
- self.centers = None
|
| 80 |
+
- self.radii = None
|
| 81 |
+
-
|
| 82 |
+
- def _generate_grid_centers(self) -> np.ndarray:
|
| 83 |
+
- """Generates centers for 24 circles arranged in a grid, skipping the central point."""
|
| 84 |
+
+ self.current_temperature = config.initial_temperature
|
| 85 |
+
+
|
| 86 |
+
+ def _generate_initial_centers(self) -> np.ndarray:
|
| 87 |
+
+ """Generates the initial set of circle centers based on the configuration."""
|
| 88 |
+
+ # 1. Generate grid centers
|
| 89 |
+
grid_centers = []
|
| 90 |
+
num_divs = len(self.config.grid_x_coords)
|
| 91 |
+
for i in range(num_divs):
|
| 92 |
+
for j in range(num_divs):
|
| 93 |
+
if i == num_divs // 2 and j == num_divs // 2:
|
| 94 |
+
continue
|
| 95 |
+
grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 96 |
+
- return np.array(grid_centers)
|
| 97 |
+
-
|
| 98 |
+
- def _generate_central_centers(self) -> np.ndarray:
|
| 99 |
+
- """
|
| 100 |
+
- Generates centers for the 2 central circles using a flexible parameterization
|
| 101 |
+
- that includes midpoint offset, orientation, and anisotropic scaling.
|
| 102 |
+
- """
|
| 103 |
+
- # Calculate the central pair's midpoint with an offset from (0.5, 0.5).
|
| 104 |
+
+
|
| 105 |
+
+ # 2. Generate central centers
|
| 106 |
+
offset_rad = math.radians(self.config.central_midpoint_offset_angle_deg)
|
| 107 |
+
mid_x = 0.5 + self.config.central_midpoint_offset_dist * math.cos(offset_rad)
|
| 108 |
+
mid_y = 0.5 + self.config.central_midpoint_offset_dist * math.sin(offset_rad)
|
| 109 |
+
|
| 110 |
+
- # Calculate the displacement vectors for the two circles from their midpoint.
|
| 111 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 112 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 113 |
+
|
| 114 |
+
- # Raw displacement based on orientation angle.
|
| 115 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 116 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 117 |
+
|
| 118 |
+
- # Apply anisotropic scaling.
|
| 119 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 120 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 121 |
+
|
| 122 |
+
- return np.array([
|
| 123 |
+
+ central_centers = np.array([
|
| 124 |
+
[mid_x - dx, mid_y - dy],
|
| 125 |
+
[mid_x + dx, mid_y + dy]
|
| 126 |
+
])
|
| 127 |
+
|
| 128 |
+
- def _apply_global_rotation(self, centers: np.ndarray) -> np.ndarray:
|
| 129 |
+
- """Applies a global rotation to all circle centers around (0.5, 0.5)."""
|
| 130 |
+
- if abs(self.config.global_rotation_angle_deg) < 1e-6:
|
| 131 |
+
- return centers
|
| 132 |
+
-
|
| 133 |
+
- angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 134 |
+
- cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 135 |
+
- rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 136 |
+
-
|
| 137 |
+
- # Translate to origin, rotate, then translate back.
|
| 138 |
+
- return (rotation_matrix @ (centers - 0.5).T).T + 0.5
|
| 139 |
+
+ all_centers = np.vstack((grid_centers, central_centers))
|
| 140 |
+
+
|
| 141 |
+
+ # 3. Apply global rotation
|
| 142 |
+
+ if abs(self.config.global_rotation_angle_deg) > 1e-6:
|
| 143 |
+
+ angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 144 |
+
+ cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 145 |
+
+ rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 146 |
+
+ all_centers = (rotation_matrix @ (all_centers - 0.5).T).T + 0.5
|
| 147 |
+
+
|
| 148 |
+
+ return np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 149 |
+
+
|
| 150 |
+
+ def _perturb_centers(self, centers: np.ndarray) -> np.ndarray:
|
| 151 |
+
+ """Applies local perturbations to a subset of circle centers."""
|
| 152 |
+
+ new_centers = np.copy(centers)
|
| 153 |
+
+ perturb_amount = self.config.initial_perturb_scale * (self.current_temperature / self.config.initial_temperature)**self.config.perturb_temp_power
|
| 154 |
+
+
|
| 155 |
+
+ for _ in range(self.config.circles_to_perturb):
|
| 156 |
+
+ circle_idx = random.randint(0, self.config.n_circles - 1)
|
| 157 |
+
+
|
| 158 |
+
+ dx = random.uniform(-perturb_amount, perturb_amount)
|
| 159 |
+
+ dy = random.uniform(-perturb_amount, perturb_amount)
|
| 160 |
+
+
|
| 161 |
+
+ new_centers[circle_idx, 0] += dx
|
| 162 |
+
+ new_centers[circle_idx, 1] += dy
|
| 163 |
+
+
|
| 164 |
+
+ new_centers[circle_idx, :] = np.clip(new_centers[circle_idx, :], self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 165 |
+
+
|
| 166 |
+
+ return new_centers
|
| 167 |
+
+
|
| 168 |
+
+ def _apply_global_nudge(self, centers: np.ndarray) -> np.ndarray:
|
| 169 |
+
+ """Applies a small global offset to all circles with a certain probability."""
|
| 170 |
+
+ if random.random() < self.config.global_nudge_probability:
|
| 171 |
+
+ nudge_scale = self.config.global_nudge_amount * (self.current_temperature / self.config.initial_temperature)**self.config.global_nudge_temp_power
|
| 172 |
+
+ global_dx = random.uniform(-nudge_scale, nudge_scale)
|
| 173 |
+
+ global_dy = random.uniform(-nudge_scale, nudge_scale)
|
| 174 |
+
+ centers[:, 0] += global_dx
|
| 175 |
+
+ centers[:, 1] += global_dy
|
| 176 |
+
+ centers = np.clip(centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 177 |
+
+ return centers
|
| 178 |
+
|
| 179 |
+
def solve(self) -> tuple[np.ndarray, np.ndarray]:
|
| 180 |
+
"""
|
| 181 |
+
- Executes the full packing and solving pipeline.
|
| 182 |
+
- 1. Generates centers for all circles.
|
| 183 |
+
- 2. Applies global transformations.
|
| 184 |
+
- 3. Computes the maximum possible radii using linear programming.
|
| 185 |
+
- """
|
| 186 |
+
- grid_centers = self._generate_grid_centers()
|
| 187 |
+
- central_centers = self._generate_central_centers()
|
| 188 |
+
-
|
| 189 |
+
- all_centers = np.vstack((grid_centers, central_centers))
|
| 190 |
+
- all_centers = self._apply_global_rotation(all_centers)
|
| 191 |
+
-
|
| 192 |
+
- # Clip to ensure centers are strictly inside the unit square.
|
| 193 |
+
- self.centers = np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 194 |
+
-
|
| 195 |
+
- self.radii = self._compute_max_radii(self.centers)
|
| 196 |
+
-
|
| 197 |
+
- return self.centers, self.radii
|
| 198 |
+
+ Executes the full SA optimization pipeline.
|
| 199 |
+
+ 1. Generates a strong initial configuration.
|
| 200 |
+
+ 2. Iteratively perturbs centers and accepts new states based on SA logic.
|
| 201 |
+
+ 3. Returns the best configuration found.
|
| 202 |
+
+ """
|
| 203 |
+
+ current_centers = self._generate_initial_centers()
|
| 204 |
+
+ current_sum_radii = np.sum(self._compute_max_radii(current_centers))
|
| 205 |
+
+
|
| 206 |
+
+ best_centers = np.copy(current_centers)
|
| 207 |
+
+ best_sum_radii = current_sum_radii
|
| 208 |
+
+
|
| 209 |
+
+ for _ in range(self.config.max_iterations):
|
| 210 |
+
+ candidate_centers = self._perturb_centers(current_centers)
|
| 211 |
+
+ candidate_centers = self._apply_global_nudge(candidate_centers)
|
| 212 |
+
+
|
| 213 |
+
+ candidate_sum_radii = np.sum(self._compute_max_radii(candidate_centers))
|
| 214 |
+
+
|
| 215 |
+
+ delta_E = candidate_sum_radii - current_sum_radii
|
| 216 |
+
+
|
| 217 |
+
+ if delta_E > 0 or (self.current_temperature > 0 and random.random() < math.exp(delta_E / self.current_temperature)):
|
| 218 |
+
+ current_centers = candidate_centers
|
| 219 |
+
+ current_sum_radii = candidate_sum_radii
|
| 220 |
+
+ if current_sum_radii > best_sum_radii:
|
| 221 |
+
+ best_centers = np.copy(current_centers)
|
| 222 |
+
+ best_sum_radii = current_sum_radii
|
| 223 |
+
+
|
| 224 |
+
+ self.current_temperature *= self.config.cooling_rate
|
| 225 |
+
+
|
| 226 |
+
+ final_radii = self._compute_max_radii(best_centers)
|
| 227 |
+
+ return best_centers, final_radii
|
| 228 |
+
|
| 229 |
+
@staticmethod
|
| 230 |
+
def _compute_max_radii(centers: np.ndarray) -> np.ndarray:
|
| 231 |
+
"""
|
| 232 |
+
Computes maximum radii for given centers by solving a Linear Programming problem.
|
| 233 |
+
This is a static method as its logic is independent of any specific solver instance.
|
| 234 |
+
"""
|
| 235 |
+
n = centers.shape[0]
|
| 236 |
+
c = -np.ones(n) # Objective: maximize sum(r_i) -> minimize sum(-r_i)
|
| 237 |
+
|
| 238 |
+
num_boundary_constraints = 4 * n
|
| 239 |
+
num_pairwise_constraints = n * (n - 1) // 2
|
| 240 |
+
num_constraints = num_boundary_constraints + num_pairwise_constraints
|
| 241 |
+
|
| 242 |
+
A_ub = np.zeros((num_constraints, n))
|
| 243 |
+
b_ub = np.zeros(num_constraints)
|
| 244 |
+
|
| 245 |
+
row_idx = 0
|
| 246 |
+
# Boundary constraints: r_i <= dist_to_wall
|
| 247 |
+
for i in range(n):
|
| 248 |
+
x, y = centers[i]
|
| 249 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = x; row_idx += 1
|
| 250 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - x; row_idx += 1
|
| 251 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = y; row_idx += 1
|
| 252 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - y; row_idx += 1
|
| 253 |
+
|
| 254 |
+
# Pairwise non-overlapping constraints: r_i + r_j <= dist(c_i, c_j)
|
| 255 |
+
for i in range(n):
|
| 256 |
+
for j in range(i + 1, n):
|
| 257 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 258 |
+
A_ub[row_idx, i] = 1
|
| 259 |
+
A_ub[row_idx, j] = 1
|
| 260 |
+
b_ub[row_idx] = dist
|
| 261 |
+
row_idx += 1
|
| 262 |
+
|
| 263 |
+
bounds = (0, None) # Radii must be non-negative.
|
| 264 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 265 |
+
|
| 266 |
+
return res.x if res.success else np.zeros(n)
|
| 267 |
+
|
| 268 |
+
def construct_packing():
|
| 269 |
+
"""
|
| 270 |
+
Main entry point for constructing the circle packing.
|
| 271 |
+
- This function now uses the unified CirclePackingSolver class, demonstrating the
|
| 272 |
+
- new, cleaner architecture. The configuration is instantiated with parameters
|
| 273 |
+
- derived from the best-performing historical programs to aim for a new high score.
|
| 274 |
+
- """
|
| 275 |
+
- # This configuration is a refined combination of the best parameters observed
|
| 276 |
+
- # in previous high-scoring runs (scores > 2.51).
|
| 277 |
+
+ This function uses the CirclePackingSolver, which implements a Simulated
|
| 278 |
+
+ Annealing (SA) algorithm. It starts with a strong initial configuration and
|
| 279 |
+
+ then uses SA to search for improvements.
|
| 280 |
+
+ """
|
| 281 |
+
+ # The configuration is instantiated with default parameters that include
|
| 282 |
+
+ # both the initial placement logic and the SA optimization parameters.
|
| 283 |
+
+ # These defaults are tuned from previous high-scoring runs.
|
| 284 |
+
config = PackingConfig()
|
| 285 |
+
|
| 286 |
+
# Create a solver instance with the specified configuration.
|
| 287 |
+
solver = CirclePackingSolver(config)
|
| 288 |
+
|
| 289 |
+
# Run the solver to get the final centers and radii.
|
| 290 |
+
centers, radii = solver.solve()
|
| 291 |
+
|
| 292 |
+
return centers, radii
|
| 293 |
+
# EVOLVE-BLOCK-END
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# This part remains fixed (not evolved)
|
| 297 |
+
def run_packing():
|
| 298 |
+
"""Run the circle packing constructor for n=26"""
|
| 299 |
+
centers, radii = construct_packing()
|
| 300 |
+
# Calculate the sum of radii
|
| 301 |
+
sum_radii = np.sum(radii)
|
| 302 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/main.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from scipy.optimize import linprog
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import math
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class PackingConfig:
|
| 10 |
+
"""
|
| 11 |
+
A unified configuration object holding all tunable parameters for the circle packing problem.
|
| 12 |
+
This dataclass consolidates parameters for initial placement and adds parameters for
|
| 13 |
+
Simulated Annealing (SA) to refine the packing.
|
| 14 |
+
"""
|
| 15 |
+
n_circles: int = 26
|
| 16 |
+
|
| 17 |
+
# Grid parameters for the initial 24 circles.
|
| 18 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 19 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 20 |
+
|
| 21 |
+
# Central circle parameters for initial placement, tuned from high-scoring runs.
|
| 22 |
+
central_separation_distance: float = 0.125
|
| 23 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 24 |
+
# Asymmetric offset for the central pair's midpoint.
|
| 25 |
+
central_midpoint_offset_dist: float = 0.0018 # From x=-0.0015, y=0.0010
|
| 26 |
+
central_midpoint_offset_angle_deg: float = 146.3 # From x=-0.0015, y=0.0010
|
| 27 |
+
# Anisotropic scaling for the central pair's void.
|
| 28 |
+
central_x_offset_scale: float = 1.01
|
| 29 |
+
central_y_offset_scale: float = 0.99
|
| 30 |
+
|
| 31 |
+
# Global rotation for the initial packing arrangement.
|
| 32 |
+
global_rotation_angle_deg: float = 0.1
|
| 33 |
+
|
| 34 |
+
# Simulated Annealing (SA) parameters
|
| 35 |
+
initial_temperature: float = 0.005
|
| 36 |
+
cooling_rate: float = 0.9999
|
| 37 |
+
max_iterations: int = 25000
|
| 38 |
+
initial_perturb_scale: float = 0.015
|
| 39 |
+
perturb_temp_power: float = 1.0
|
| 40 |
+
circles_to_perturb: int = 2
|
| 41 |
+
global_nudge_probability: float = 0.01
|
| 42 |
+
global_nudge_amount: float = 0.005
|
| 43 |
+
global_nudge_temp_power: float = 0.5
|
| 44 |
+
|
| 45 |
+
clip_epsilon: float = 1e-8
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class CirclePackingSolver:
|
| 49 |
+
"""
|
| 50 |
+
An encapsulated solver for the circle packing problem, now enhanced with
|
| 51 |
+
a Simulated Annealing (SA) optimization loop. It starts with a strong
|
| 52 |
+
initial configuration and iteratively refines it to find a better packing.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(self, config: PackingConfig):
|
| 56 |
+
"""Initializes the solver with a given packing configuration."""
|
| 57 |
+
self.config = config
|
| 58 |
+
self.current_temperature = config.initial_temperature
|
| 59 |
+
|
| 60 |
+
def _generate_initial_centers(self) -> np.ndarray:
|
| 61 |
+
"""Generates the initial set of circle centers based on the configuration."""
|
| 62 |
+
# 1. Generate grid centers
|
| 63 |
+
grid_centers = []
|
| 64 |
+
num_divs = len(self.config.grid_x_coords)
|
| 65 |
+
for i in range(num_divs):
|
| 66 |
+
for j in range(num_divs):
|
| 67 |
+
if i == num_divs // 2 and j == num_divs // 2:
|
| 68 |
+
continue
|
| 69 |
+
grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 70 |
+
|
| 71 |
+
# 2. Generate central centers
|
| 72 |
+
offset_rad = math.radians(self.config.central_midpoint_offset_angle_deg)
|
| 73 |
+
mid_x = 0.5 + self.config.central_midpoint_offset_dist * math.cos(offset_rad)
|
| 74 |
+
mid_y = 0.5 + self.config.central_midpoint_offset_dist * math.sin(offset_rad)
|
| 75 |
+
|
| 76 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 77 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 78 |
+
|
| 79 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 80 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 81 |
+
|
| 82 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 83 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 84 |
+
|
| 85 |
+
central_centers = np.array([
|
| 86 |
+
[mid_x - dx, mid_y - dy],
|
| 87 |
+
[mid_x + dx, mid_y + dy]
|
| 88 |
+
])
|
| 89 |
+
|
| 90 |
+
all_centers = np.vstack((grid_centers, central_centers))
|
| 91 |
+
|
| 92 |
+
# 3. Apply global rotation
|
| 93 |
+
if abs(self.config.global_rotation_angle_deg) > 1e-6:
|
| 94 |
+
angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 95 |
+
cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 96 |
+
rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 97 |
+
all_centers = (rotation_matrix @ (all_centers - 0.5).T).T + 0.5
|
| 98 |
+
|
| 99 |
+
return np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 100 |
+
|
| 101 |
+
def _perturb_centers(self, centers: np.ndarray) -> np.ndarray:
|
| 102 |
+
"""Applies local perturbations to a subset of circle centers."""
|
| 103 |
+
new_centers = np.copy(centers)
|
| 104 |
+
perturb_amount = self.config.initial_perturb_scale * (self.current_temperature / self.config.initial_temperature)**self.config.perturb_temp_power
|
| 105 |
+
|
| 106 |
+
for _ in range(self.config.circles_to_perturb):
|
| 107 |
+
circle_idx = random.randint(0, self.config.n_circles - 1)
|
| 108 |
+
|
| 109 |
+
dx = random.uniform(-perturb_amount, perturb_amount)
|
| 110 |
+
dy = random.uniform(-perturb_amount, perturb_amount)
|
| 111 |
+
|
| 112 |
+
new_centers[circle_idx, 0] += dx
|
| 113 |
+
new_centers[circle_idx, 1] += dy
|
| 114 |
+
|
| 115 |
+
new_centers[circle_idx, :] = np.clip(new_centers[circle_idx, :], self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 116 |
+
|
| 117 |
+
return new_centers
|
| 118 |
+
|
| 119 |
+
def _apply_global_nudge(self, centers: np.ndarray) -> np.ndarray:
|
| 120 |
+
"""Applies a small global offset to all circles with a certain probability."""
|
| 121 |
+
if random.random() < self.config.global_nudge_probability:
|
| 122 |
+
nudge_scale = self.config.global_nudge_amount * (self.current_temperature / self.config.initial_temperature)**self.config.global_nudge_temp_power
|
| 123 |
+
global_dx = random.uniform(-nudge_scale, nudge_scale)
|
| 124 |
+
global_dy = random.uniform(-nudge_scale, nudge_scale)
|
| 125 |
+
centers[:, 0] += global_dx
|
| 126 |
+
centers[:, 1] += global_dy
|
| 127 |
+
centers = np.clip(centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 128 |
+
return centers
|
| 129 |
+
|
| 130 |
+
def solve(self) -> tuple[np.ndarray, np.ndarray]:
|
| 131 |
+
"""
|
| 132 |
+
Executes the full SA optimization pipeline.
|
| 133 |
+
1. Generates a strong initial configuration.
|
| 134 |
+
2. Iteratively perturbs centers and accepts new states based on SA logic.
|
| 135 |
+
3. Returns the best configuration found.
|
| 136 |
+
"""
|
| 137 |
+
current_centers = self._generate_initial_centers()
|
| 138 |
+
current_sum_radii = np.sum(self._compute_max_radii(current_centers))
|
| 139 |
+
|
| 140 |
+
best_centers = np.copy(current_centers)
|
| 141 |
+
best_sum_radii = current_sum_radii
|
| 142 |
+
|
| 143 |
+
for _ in range(self.config.max_iterations):
|
| 144 |
+
candidate_centers = self._perturb_centers(current_centers)
|
| 145 |
+
candidate_centers = self._apply_global_nudge(candidate_centers)
|
| 146 |
+
|
| 147 |
+
candidate_sum_radii = np.sum(self._compute_max_radii(candidate_centers))
|
| 148 |
+
|
| 149 |
+
delta_E = candidate_sum_radii - current_sum_radii
|
| 150 |
+
|
| 151 |
+
if delta_E > 0 or (self.current_temperature > 0 and random.random() < math.exp(delta_E / self.current_temperature)):
|
| 152 |
+
current_centers = candidate_centers
|
| 153 |
+
current_sum_radii = candidate_sum_radii
|
| 154 |
+
if current_sum_radii > best_sum_radii:
|
| 155 |
+
best_centers = np.copy(current_centers)
|
| 156 |
+
best_sum_radii = current_sum_radii
|
| 157 |
+
|
| 158 |
+
self.current_temperature *= self.config.cooling_rate
|
| 159 |
+
|
| 160 |
+
final_radii = self._compute_max_radii(best_centers)
|
| 161 |
+
return best_centers, final_radii
|
| 162 |
+
|
| 163 |
+
@staticmethod
|
| 164 |
+
def _compute_max_radii(centers: np.ndarray) -> np.ndarray:
|
| 165 |
+
"""
|
| 166 |
+
Computes maximum radii for given centers by solving a Linear Programming problem.
|
| 167 |
+
This is a static method as its logic is independent of any specific solver instance.
|
| 168 |
+
"""
|
| 169 |
+
n = centers.shape[0]
|
| 170 |
+
c = -np.ones(n) # Objective: maximize sum(r_i) -> minimize sum(-r_i)
|
| 171 |
+
|
| 172 |
+
num_boundary_constraints = 4 * n
|
| 173 |
+
num_pairwise_constraints = n * (n - 1) // 2
|
| 174 |
+
num_constraints = num_boundary_constraints + num_pairwise_constraints
|
| 175 |
+
|
| 176 |
+
A_ub = np.zeros((num_constraints, n))
|
| 177 |
+
b_ub = np.zeros(num_constraints)
|
| 178 |
+
|
| 179 |
+
row_idx = 0
|
| 180 |
+
# Boundary constraints: r_i <= dist_to_wall
|
| 181 |
+
for i in range(n):
|
| 182 |
+
x, y = centers[i]
|
| 183 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = x; row_idx += 1
|
| 184 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - x; row_idx += 1
|
| 185 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = y; row_idx += 1
|
| 186 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - y; row_idx += 1
|
| 187 |
+
|
| 188 |
+
# Pairwise non-overlapping constraints: r_i + r_j <= dist(c_i, c_j)
|
| 189 |
+
for i in range(n):
|
| 190 |
+
for j in range(i + 1, n):
|
| 191 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 192 |
+
A_ub[row_idx, i] = 1
|
| 193 |
+
A_ub[row_idx, j] = 1
|
| 194 |
+
b_ub[row_idx] = dist
|
| 195 |
+
row_idx += 1
|
| 196 |
+
|
| 197 |
+
bounds = (0, None) # Radii must be non-negative.
|
| 198 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 199 |
+
|
| 200 |
+
return res.x if res.success else np.zeros(n)
|
| 201 |
+
|
| 202 |
+
def construct_packing():
|
| 203 |
+
"""
|
| 204 |
+
Main entry point for constructing the circle packing.
|
| 205 |
+
This function uses the CirclePackingSolver, which implements a Simulated
|
| 206 |
+
Annealing (SA) algorithm. It starts with a strong initial configuration and
|
| 207 |
+
then uses SA to search for improvements.
|
| 208 |
+
"""
|
| 209 |
+
# The configuration is instantiated with default parameters that include
|
| 210 |
+
# both the initial placement logic and the SA optimization parameters.
|
| 211 |
+
# These defaults are tuned from previous high-scoring runs.
|
| 212 |
+
config = PackingConfig()
|
| 213 |
+
|
| 214 |
+
# Create a solver instance with the specified configuration.
|
| 215 |
+
solver = CirclePackingSolver(config)
|
| 216 |
+
|
| 217 |
+
# Run the solver to get the final centers and radii.
|
| 218 |
+
centers, radii = solver.solve()
|
| 219 |
+
|
| 220 |
+
return centers, radii
|
| 221 |
+
# EVOLVE-BLOCK-END
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
# This part remains fixed (not evolved)
|
| 225 |
+
def run_packing():
|
| 226 |
+
"""Run the circle packing constructor for n=26"""
|
| 227 |
+
centers, radii = construct_packing()
|
| 228 |
+
# Calculate the sum of radii
|
| 229 |
+
sum_radii = np.sum(radii)
|
| 230 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/original.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from scipy.optimize import linprog
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
@dataclass
|
| 8 |
+
class PackingConfig:
|
| 9 |
+
"""
|
| 10 |
+
A unified configuration object holding all tunable parameters for the circle packing problem.
|
| 11 |
+
This dataclass consolidates the most effective parameters from previous successful attempts,
|
| 12 |
+
including non-uniform grids, central pair offsets, and anisotropic scaling.
|
| 13 |
+
"""
|
| 14 |
+
n_circles: int = 26
|
| 15 |
+
|
| 16 |
+
# Grid parameters allowing non-uniform spacing.
|
| 17 |
+
# The default is the robust 5x5 grid that has performed well.
|
| 18 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 19 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 20 |
+
|
| 21 |
+
# Central circle parameters. These are tuned based on the best-performing prior program (score 2.52).
|
| 22 |
+
# Distance between the two central circle centers.
|
| 23 |
+
central_separation_distance: float = 0.125
|
| 24 |
+
# The angle of the line connecting the two central circles.
|
| 25 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 26 |
+
# The distance to shift the entire central pair's midpoint from (0.5, 0.5).
|
| 27 |
+
central_midpoint_offset_dist: float = 0.0015
|
| 28 |
+
# The angle of the midpoint shift. 180 degrees corresponds to a negative x-offset.
|
| 29 |
+
central_midpoint_offset_angle_deg: float = 180.0
|
| 30 |
+
# Anisotropic scaling to create an elliptical void for the central pair.
|
| 31 |
+
central_x_offset_scale: float = 1.01
|
| 32 |
+
central_y_offset_scale: float = 0.99
|
| 33 |
+
|
| 34 |
+
# Global rotation for the entire packing arrangement.
|
| 35 |
+
global_rotation_angle_deg: float = 0.0
|
| 36 |
+
|
| 37 |
+
clip_epsilon: float = 1e-8
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class CirclePackingSolver:
|
| 41 |
+
"""
|
| 42 |
+
An encapsulated solver for the circle packing problem.
|
| 43 |
+
This class takes a configuration object and orchestrates the generation of
|
| 44 |
+
circle centers and the computation of their optimal radii. This new structure
|
| 45 |
+
centralizes all logic into a single, reusable class.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(self, config: PackingConfig):
|
| 49 |
+
"""Initializes the solver with a given packing configuration."""
|
| 50 |
+
self.config = config
|
| 51 |
+
self.centers = None
|
| 52 |
+
self.radii = None
|
| 53 |
+
|
| 54 |
+
def _generate_grid_centers(self) -> np.ndarray:
|
| 55 |
+
"""Generates centers for 24 circles arranged in a grid, skipping the central point."""
|
| 56 |
+
grid_centers = []
|
| 57 |
+
num_divs = len(self.config.grid_x_coords)
|
| 58 |
+
for i in range(num_divs):
|
| 59 |
+
for j in range(num_divs):
|
| 60 |
+
if i == num_divs // 2 and j == num_divs // 2:
|
| 61 |
+
continue
|
| 62 |
+
grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 63 |
+
return np.array(grid_centers)
|
| 64 |
+
|
| 65 |
+
def _generate_central_centers(self) -> np.ndarray:
|
| 66 |
+
"""
|
| 67 |
+
Generates centers for the 2 central circles using a flexible parameterization
|
| 68 |
+
that includes midpoint offset, orientation, and anisotropic scaling.
|
| 69 |
+
"""
|
| 70 |
+
# Calculate the central pair's midpoint with an offset from (0.5, 0.5).
|
| 71 |
+
offset_rad = math.radians(self.config.central_midpoint_offset_angle_deg)
|
| 72 |
+
mid_x = 0.5 + self.config.central_midpoint_offset_dist * math.cos(offset_rad)
|
| 73 |
+
mid_y = 0.5 + self.config.central_midpoint_offset_dist * math.sin(offset_rad)
|
| 74 |
+
|
| 75 |
+
# Calculate the displacement vectors for the two circles from their midpoint.
|
| 76 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 77 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 78 |
+
|
| 79 |
+
# Raw displacement based on orientation angle.
|
| 80 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 81 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 82 |
+
|
| 83 |
+
# Apply anisotropic scaling.
|
| 84 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 85 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 86 |
+
|
| 87 |
+
return np.array([
|
| 88 |
+
[mid_x - dx, mid_y - dy],
|
| 89 |
+
[mid_x + dx, mid_y + dy]
|
| 90 |
+
])
|
| 91 |
+
|
| 92 |
+
def _apply_global_rotation(self, centers: np.ndarray) -> np.ndarray:
|
| 93 |
+
"""Applies a global rotation to all circle centers around (0.5, 0.5)."""
|
| 94 |
+
if abs(self.config.global_rotation_angle_deg) < 1e-6:
|
| 95 |
+
return centers
|
| 96 |
+
|
| 97 |
+
angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 98 |
+
cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 99 |
+
rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 100 |
+
|
| 101 |
+
# Translate to origin, rotate, then translate back.
|
| 102 |
+
return (rotation_matrix @ (centers - 0.5).T).T + 0.5
|
| 103 |
+
|
| 104 |
+
def solve(self) -> tuple[np.ndarray, np.ndarray]:
|
| 105 |
+
"""
|
| 106 |
+
Executes the full packing and solving pipeline.
|
| 107 |
+
1. Generates centers for all circles.
|
| 108 |
+
2. Applies global transformations.
|
| 109 |
+
3. Computes the maximum possible radii using linear programming.
|
| 110 |
+
"""
|
| 111 |
+
grid_centers = self._generate_grid_centers()
|
| 112 |
+
central_centers = self._generate_central_centers()
|
| 113 |
+
|
| 114 |
+
all_centers = np.vstack((grid_centers, central_centers))
|
| 115 |
+
all_centers = self._apply_global_rotation(all_centers)
|
| 116 |
+
|
| 117 |
+
# Clip to ensure centers are strictly inside the unit square.
|
| 118 |
+
self.centers = np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 119 |
+
|
| 120 |
+
self.radii = self._compute_max_radii(self.centers)
|
| 121 |
+
|
| 122 |
+
return self.centers, self.radii
|
| 123 |
+
|
| 124 |
+
@staticmethod
|
| 125 |
+
def _compute_max_radii(centers: np.ndarray) -> np.ndarray:
|
| 126 |
+
"""
|
| 127 |
+
Computes maximum radii for given centers by solving a Linear Programming problem.
|
| 128 |
+
This is a static method as its logic is independent of any specific solver instance.
|
| 129 |
+
"""
|
| 130 |
+
n = centers.shape[0]
|
| 131 |
+
c = -np.ones(n) # Objective: maximize sum(r_i) -> minimize sum(-r_i)
|
| 132 |
+
|
| 133 |
+
num_boundary_constraints = 4 * n
|
| 134 |
+
num_pairwise_constraints = n * (n - 1) // 2
|
| 135 |
+
num_constraints = num_boundary_constraints + num_pairwise_constraints
|
| 136 |
+
|
| 137 |
+
A_ub = np.zeros((num_constraints, n))
|
| 138 |
+
b_ub = np.zeros(num_constraints)
|
| 139 |
+
|
| 140 |
+
row_idx = 0
|
| 141 |
+
# Boundary constraints: r_i <= dist_to_wall
|
| 142 |
+
for i in range(n):
|
| 143 |
+
x, y = centers[i]
|
| 144 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = x; row_idx += 1
|
| 145 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - x; row_idx += 1
|
| 146 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = y; row_idx += 1
|
| 147 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - y; row_idx += 1
|
| 148 |
+
|
| 149 |
+
# Pairwise non-overlapping constraints: r_i + r_j <= dist(c_i, c_j)
|
| 150 |
+
for i in range(n):
|
| 151 |
+
for j in range(i + 1, n):
|
| 152 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 153 |
+
A_ub[row_idx, i] = 1
|
| 154 |
+
A_ub[row_idx, j] = 1
|
| 155 |
+
b_ub[row_idx] = dist
|
| 156 |
+
row_idx += 1
|
| 157 |
+
|
| 158 |
+
bounds = (0, None) # Radii must be non-negative.
|
| 159 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 160 |
+
|
| 161 |
+
return res.x if res.success else np.zeros(n)
|
| 162 |
+
|
| 163 |
+
def construct_packing():
|
| 164 |
+
"""
|
| 165 |
+
Main entry point for constructing the circle packing.
|
| 166 |
+
This function now uses the unified CirclePackingSolver class, demonstrating the
|
| 167 |
+
new, cleaner architecture. The configuration is instantiated with parameters
|
| 168 |
+
derived from the best-performing historical programs to aim for a new high score.
|
| 169 |
+
"""
|
| 170 |
+
# This configuration is a refined combination of the best parameters observed
|
| 171 |
+
# in previous high-scoring runs (scores > 2.51).
|
| 172 |
+
config = PackingConfig()
|
| 173 |
+
|
| 174 |
+
# Create a solver instance with the specified configuration.
|
| 175 |
+
solver = CirclePackingSolver(config)
|
| 176 |
+
|
| 177 |
+
# Run the solver to get the final centers and radii.
|
| 178 |
+
centers, radii = solver.solve()
|
| 179 |
+
|
| 180 |
+
return centers, radii
|
| 181 |
+
# EVOLVE-BLOCK-END
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# This part remains fixed (not evolved)
|
| 185 |
+
def run_packing():
|
| 186 |
+
"""Run the circle packing constructor for n=26"""
|
| 187 |
+
centers, radii = construct_packing()
|
| 188 |
+
# Calculate the sum of radii
|
| 189 |
+
sum_radii = np.sum(radii)
|
| 190 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/results/metrics.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 2.593165588404451,
|
| 3 |
+
"correct": true,
|
| 4 |
+
"primary": {
|
| 5 |
+
"combined_score": 2.593165588404451,
|
| 6 |
+
"public": {
|
| 7 |
+
"centers_str": " centers[0] = (0.0679, 0.0686)\n centers[1] = (0.0934, 0.2279)\n centers[2] = (0.1147, 0.4305)\n centers[3] = (0.1414, 0.6791)\n centers[4] = (0.0933, 0.9057)\n centers[5] = (0.2277, 0.0932)\n centers[6] = (0.2864, 0.2905)\n centers[7] = (0.3211, 0.5081)\n centers[8] = (0.3826, 0.7171)\n centers[9] = (0.2873, 0.8997)\n centers[10] = (0.4353, 0.1156)\n centers[11] = (0.5345, 0.3583)\n centers[12] = (0.5863, 0.7286)\n centers[13] = (0.4887, 0.8982)\n centers[14] = (0.6521, 0.1018)\n centers[15] = (0.7381, 0.2592)\n centers[16] = (0.7523, 0.4091)\n centers[17] = (0.7642, 0.7091)\n centers[18] = (0.6978, 0.8933)\n centers[19] = (0.8817, 0.1185)\n centers[20] = (0.9023, 0.3319)\n centers[21] = (0.8833, 0.5459)\n centers[22] = (0.9219, 0.7357)\n centers[23] = (0.8991, 0.9038)\n centers[24] = (0.5032, 0.5794)\n centers[25] = (0.6724, 0.5555)",
|
| 8 |
+
"num_circles": 26
|
| 9 |
+
},
|
| 10 |
+
"private": {
|
| 11 |
+
"reported_sum_of_radii": 2.593165588404451
|
| 12 |
+
},
|
| 13 |
+
"execution_time_mean": 101.22933560982347,
|
| 14 |
+
"execution_time_std": 0.0,
|
| 15 |
+
"num_valid_runs": 1,
|
| 16 |
+
"num_invalid_runs": 0,
|
| 17 |
+
"all_validation_errors": [],
|
| 18 |
+
"correct": true,
|
| 19 |
+
"validation_error": null
|
| 20 |
+
},
|
| 21 |
+
"auxiliary": {
|
| 22 |
+
"avg_radius": 0.09973713801555581,
|
| 23 |
+
"std_dev_radius": 0.01845283729104012,
|
| 24 |
+
"min_radius": 0.06786836138638622,
|
| 25 |
+
"max_radius": 0.1465279758088699,
|
| 26 |
+
"median_radius": 0.0978725509471671,
|
| 27 |
+
"num_unique_radii": 26,
|
| 28 |
+
"radius_coefficient_of_variation": 0.18501470623873392,
|
| 29 |
+
"total_area_covered": 0.8403386545769049,
|
| 30 |
+
"packing_density": 0.8403386545769049,
|
| 31 |
+
"empty_space_ratio": 0.15966134542309507,
|
| 32 |
+
"max_circle_overlap_magnitude": 0.0,
|
| 33 |
+
"num_overlapping_pairs": 0,
|
| 34 |
+
"max_boundary_violation_magnitude": 0.0,
|
| 35 |
+
"num_boundary_violations": 0,
|
| 36 |
+
"packing_aspect_ratio_of_centers_bbox": 1.020243012479224,
|
| 37 |
+
"avg_num_touching_neighbors": 1.4615384615384615,
|
| 38 |
+
"avg_quadrant_radii_std_dev": 0.017248158526820698,
|
| 39 |
+
"avg_pairwise_center_distance": 0.529041490849474,
|
| 40 |
+
"avg_distance_from_packing_centroid_normalized": 0.5318890800035825,
|
| 41 |
+
"num_circles_touching_edge": 7,
|
| 42 |
+
"num_circles_touching_corner": 0,
|
| 43 |
+
"num_circles_touching_boundary": 7,
|
| 44 |
+
"avg_min_distance_to_boundary": 0.07903500981585891
|
| 45 |
+
},
|
| 46 |
+
"auxiliary_descriptions": {
|
| 47 |
+
"avg_radius": "The average radius of the 26 circles. This provides a central tendency for circle sizes, helping to understand if the solution favors larger, smaller, or uniformly sized circles.",
|
| 48 |
+
"std_dev_radius": "This metric is critical for identifying potential local optima. If `std_dev_radius` is very low and `sum(radii)` is stagnating, it might suggest the algorithm is stuck with uniform-sized circles. Encouraging greater `std_dev_radius` could lead to exploring more diverse solutions (e.g., using a few large circles and many small ones) that might break current plateaus. Conversely, if `std_dev_radius` is high, it shows exploration of diverse sizes is still active.",
|
| 49 |
+
"min_radius": "The smallest radius among the 26 circles. Useful for identifying if very small circles are being used to fill tiny gaps.",
|
| 50 |
+
"max_radius": "The largest radius among the 26 circles. Indicates the largest circle size successfully placed, which is often a key factor in maximizing total radius sum.",
|
| 51 |
+
"median_radius": "The median radius of all circles. A robust measure of central tendency, less affected by outliers than the mean.",
|
| 52 |
+
"num_unique_radii": "The count of distinct radius values. A higher number suggests more variety in the chosen circle sizes.",
|
| 53 |
+
"total_area_covered": "By tracking this, we can see if increases in `sum(radii)` are also leading to proportional increases in actual area covered. If `sum(radii)` plateaus but `total_area_covered` continues to rise (or maintains a high level), it suggests a more efficient use of space by re-distributing radii or centers to fill gaps more effectively.",
|
| 54 |
+
"packing_density": "`total_area_covered` divided by the area of the unit square (which is 1). Expresses the proportion of the unit square covered by circles (0-1 range). Higher is better.",
|
| 55 |
+
"empty_space_ratio": "Represents the proportion of the unit square's area not covered by circles. This directly indicates the amount of \"wasted\" space. A consistently decreasing `empty_space_ratio` would indicate improving packing efficiency beyond just the sum of radii.",
|
| 56 |
+
"max_circle_overlap_magnitude": "The largest positive value (sum of radii - distance between centers) for any overlapping pair. _Even if the primary evaluator reports no overlap (due to internal tolerances), a small positive value here indicates a \"near miss\" or a potential instability. Should be 0 for truly valid solutions._",
|
| 57 |
+
"num_overlapping_pairs": "The total count of pairs of circles that overlap. _Should be 0 for valid solutions. If > 0, it highlights invalid solutions and indicates the severity of the overlap problem._",
|
| 58 |
+
"max_boundary_violation_magnitude": "The largest extent to which any circle protrudes beyond the unit square boundary. _Similar to overlap magnitude, it identifies near-boundary violations or slight excursions. Should be 0 for truly valid solutions._",
|
| 59 |
+
"num_boundary_violations": "The total count of circles that violate any boundary. _Should be 0 for valid solutions. If > 0, indicates an invalid solution._",
|
| 60 |
+
"packing_aspect_ratio_of_centers_bbox": "This calculates the aspect ratio of the bounding box that encloses all circle centers. It helps determine if the distribution of circles is becoming more square-like (aspect ratio closer to 1) or more elongated, which could indicate a preferred packing orientation. This is useful for detecting structural biases.",
|
| 61 |
+
"avg_num_touching_neighbors": "The average number of circles that each circle is touching (within a small tolerance). A higher value suggests a more interconnected and tightly packed arrangement, which is usually a characteristic of efficient packing. This can act as a proxy for the 'connectivity' of the packing.",
|
| 62 |
+
"avg_quadrant_radii_std_dev": "The average of the standard deviations of radii within each of the four quadrants. This helps reveal if the diversity in circle sizes is localized to certain areas or if it's generally consistent across the entire packing. High values in specific quadrants might point to localized strategies for filling space.",
|
| 63 |
+
"avg_pairwise_center_distance": "The average Euclidean distance between all unique pairs of circle centers. A higher value suggests a more spread-out arrangement of circles, potentially indicating a less \"clustered\" or more uniform distribution of centers. This complements nearest-neighbor distance by considering all pairs and provides a global measure of center dispersion.",
|
| 64 |
+
"avg_distance_from_packing_centroid_normalized": "Measures how concentrated or dispersed the circle centers are. It calculates the average distance of each circle center from the overall centroid of all circle centers, normalized by the maximum possible distance a center could be from the unit square's center (0.5, 0.5) to a corner (0,0). A lower value indicates a more centrally focused and compact packing.",
|
| 65 |
+
"num_circles_touching_edge": "Counts the number of circles whose edges are \"touching\" (within a small tolerance of 1e-6) any of the four boundaries of the unit square. A higher count indicates more aggressive utilization of the available space by pushing circles against the container walls.",
|
| 66 |
+
"num_circles_touching_corner": "Counts the number of circles whose edges are \"touching\" (within a small tolerance of 1e-6) two perpendicular boundaries (i.e., a corner) of the unit square. _A more specific measure of boundary utilization, indicating how well corners are filled._",
|
| 67 |
+
"avg_min_circle_to_boundary_distance": "The average of the minimum distances from each circle's edge to the closest boundary. _Smaller positive values indicate a tighter fit of circles against the edges, while larger values suggest more \"cushion\" space._",
|
| 68 |
+
"avg_min_distance_to_boundary": "The average of the minimum distances from each circle's edge to its closest unit square boundary. Lower values indicate tighter packing against the container walls, which is generally desirable for dense packing.",
|
| 69 |
+
"min_overall_distance_to_boundary": "The single smallest distance from any circle's edge to any boundary. This metric highlights if at least one circle is positioned extremely close to a boundary, indicating a strategy to maximize space.",
|
| 70 |
+
"radius_coefficient_of_variation": "The ratio of the standard deviation to the mean radius. A normalized measure of dispersion, useful for comparing variability across different scales of radii.",
|
| 71 |
+
"num_circles_touching_boundary": "Count of circles whose edge is within a small epsilon (1e-4) of any of the unit square boundaries (x=0, x=1, y=0, y=1). Indicates how many circles are actively utilizing the perimeter of the packing area.",
|
| 72 |
+
"avg_nearest_neighbor_distance_centers": "This metric calculates the average Euclidean distance from each circle's center to the center of its closest neighboring circle. A lower value indicates a more tightly packed or clustered arrangement of circle centers, while a higher value suggests a more dispersed or sparse distribution. This is useful for identifying if the packing strategy is creating dense clusters or leaving significant gaps between groups of circles.",
|
| 73 |
+
"center_quadrant_density_variance": "This metric divides the unit square into four equal quadrants (2x2 grid) and computes the variance of the number of circle centers found within each quadrant. A lower variance indicates a more uniform distribution of circle centers across the entire packing area, suggesting a balanced utilization of space. A higher variance implies that circle centers are concentrated in certain quadrants, potentially indicating inefficient or uneven packing.",
|
| 74 |
+
"avg_min_boundary_distance": "As solutions converge, an efficient packing should optimally utilize the boundaries. A consistently low (approaching zero) `avg_min_boundary_distance` combined with a high primary score indicates a well-optimized solution that is effectively using the edges of the unit square. If this value is still relatively high, it might suggest opportunities to push circles closer to the container's perimeter, thereby creating more space internally for larger circles or more circles."
|
| 75 |
+
},
|
| 76 |
+
"timestamp": 1770505036.2236722,
|
| 77 |
+
"generation": 193
|
| 78 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/rewrite.txt
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
import numpy as np
|
| 3 |
+
from scipy.optimize import linprog
|
| 4 |
+
import math
|
| 5 |
+
import random
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
# The compute_max_radii function is critical and stable, keeping it as a standalone utility.
|
| 9 |
+
def compute_max_radii(centers: np.ndarray) -> np.ndarray:
|
| 10 |
+
"""
|
| 11 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 12 |
+
by solving a Linear Programming (LP) problem. This maximizes the sum of radii
|
| 13 |
+
subject to non-overlapping and boundary constraints, ensuring optimality for fixed centers.
|
| 14 |
+
"""
|
| 15 |
+
n = centers.shape[0]
|
| 16 |
+
c = -np.ones(n) # Objective: maximize sum(r_i) -> minimize sum(-r_i)
|
| 17 |
+
|
| 18 |
+
# Pre-calculate the number of constraints for efficient matrix allocation.
|
| 19 |
+
num_boundary_constraints = 4 * n # 4 walls per circle
|
| 20 |
+
num_pairwise_constraints = n * (n - 1) // 2 # nC2 pairs
|
| 21 |
+
num_constraints = num_boundary_constraints + num_pairwise_constraints
|
| 22 |
+
|
| 23 |
+
A_ub = np.zeros((num_constraints, n))
|
| 24 |
+
b_ub = np.zeros(num_constraints)
|
| 25 |
+
|
| 26 |
+
row_idx = 0
|
| 27 |
+
# 1. Add boundary constraints (r_i <= distance_to_wall).
|
| 28 |
+
for i in range(n):
|
| 29 |
+
x, y = centers[i]
|
| 30 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = x; row_idx += 1 # r_i <= x
|
| 31 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - x; row_idx += 1 # r_i <= 1 - x
|
| 32 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = y; row_idx += 1 # r_i <= y
|
| 33 |
+
A_ub[row_idx, i] = 1; b_ub[row_idx] = 1 - y; row_idx += 1 # r_i <= 1 - y
|
| 34 |
+
|
| 35 |
+
# 2. Add pairwise non-overlapping constraints (r_i + r_j <= dist(c_i, c_j)).
|
| 36 |
+
for i in range(n):
|
| 37 |
+
for j in range(i + 1, n):
|
| 38 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 39 |
+
A_ub[row_idx, i] = 1
|
| 40 |
+
A_ub[row_idx, j] = 1
|
| 41 |
+
b_ub[row_idx] = dist
|
| 42 |
+
row_idx += 1
|
| 43 |
+
|
| 44 |
+
# All radii must be non-negative.
|
| 45 |
+
bounds = (0, None)
|
| 46 |
+
|
| 47 |
+
# Solve the linear programming problem using the 'highs' solver for performance.
|
| 48 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 49 |
+
|
| 50 |
+
if res.success:
|
| 51 |
+
return res.x
|
| 52 |
+
else:
|
| 53 |
+
# Fallback for solver failure: return zeros and print a warning.
|
| 54 |
+
# print(f"Warning: LP solver failed. Status: {res.status}, Message: {res.message}") # Suppress for cleaner output
|
| 55 |
+
return np.zeros(n)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@dataclass
|
| 59 |
+
class CirclePackingConfig:
|
| 60 |
+
"""
|
| 61 |
+
Configuration parameters for the Simulated Annealing circle packing process.
|
| 62 |
+
Encapsulates all tunable parameters for clarity, reproducibility, and easier tuning.
|
| 63 |
+
"""
|
| 64 |
+
n_circles: int = 26
|
| 65 |
+
|
| 66 |
+
# Parameters for the initial 5x5 grid (24 circles)
|
| 67 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 68 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 69 |
+
grid_dim: int = 5 # Should match len(grid_x_coords) and len(grid_y_coords)
|
| 70 |
+
|
| 71 |
+
# Parameters for the 2 central circles
|
| 72 |
+
central_separation_distance: float = 0.125
|
| 73 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 74 |
+
central_midpoint_offset_x: float = -0.0015
|
| 75 |
+
central_midpoint_offset_y: float = 0.0010
|
| 76 |
+
central_x_offset_scale: float = 1.01
|
| 77 |
+
central_y_offset_scale: float = 0.99
|
| 78 |
+
|
| 79 |
+
# Global rotation applied to the *initial* configuration
|
| 80 |
+
initial_global_rotation_angle_deg: float = 0.1
|
| 81 |
+
|
| 82 |
+
# Simulated Annealing parameters
|
| 83 |
+
initial_temperature: float = 0.005
|
| 84 |
+
cooling_rate: float = 0.9999 # Slightly slower cooling for deeper search
|
| 85 |
+
max_iterations: int = 20000 # Increased iterations
|
| 86 |
+
initial_perturb_scale: float = 0.015
|
| 87 |
+
perturb_temp_power: float = 1.0 # Exponent for temperature scaling of perturbation
|
| 88 |
+
circles_to_perturb: int = 2
|
| 89 |
+
global_nudge_probability: float = 0.01 # Probability of applying a global nudge
|
| 90 |
+
global_nudge_amount: float = 0.005 # Max magnitude of global nudge
|
| 91 |
+
global_nudge_temp_power: float = 0.5 # Global nudge scales with temperature too
|
| 92 |
+
|
| 93 |
+
clip_epsilon: float = 1e-8
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class CirclePackingState:
|
| 97 |
+
"""
|
| 98 |
+
Represents a single state in the Simulated Annealing process, holding
|
| 99 |
+
circle centers and their calculated radii and sum of radii.
|
| 100 |
+
"""
|
| 101 |
+
def __init__(self, centers: np.ndarray):
|
| 102 |
+
self.centers = centers
|
| 103 |
+
self.radii = compute_max_radii(self.centers)
|
| 104 |
+
self.sum_radii = np.sum(self.radii)
|
| 105 |
+
|
| 106 |
+
def copy(self):
|
| 107 |
+
"""Creates a deep copy of the current state."""
|
| 108 |
+
return CirclePackingState(np.copy(self.centers))
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class SimulatedAnnealingOptimizer:
|
| 112 |
+
"""
|
| 113 |
+
Performs Simulated Annealing to find an optimal circle packing.
|
| 114 |
+
"""
|
| 115 |
+
def __init__(self, config: CirclePackingConfig):
|
| 116 |
+
self.config = config
|
| 117 |
+
self.best_state = None
|
| 118 |
+
self.current_temperature = self.config.initial_temperature
|
| 119 |
+
|
| 120 |
+
def _generate_initial_centers(self) -> np.ndarray:
|
| 121 |
+
"""
|
| 122 |
+
Generates the initial set of circle centers based on the configuration.
|
| 123 |
+
This combines the grid and central pair placement strategies.
|
| 124 |
+
"""
|
| 125 |
+
initial_grid_centers = []
|
| 126 |
+
for i in range(self.config.grid_dim):
|
| 127 |
+
for j in range(self.config.grid_dim):
|
| 128 |
+
if i == self.config.grid_dim // 2 and j == self.config.grid_dim // 2:
|
| 129 |
+
continue
|
| 130 |
+
initial_grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 131 |
+
|
| 132 |
+
# Calculate central pair's effective midpoint
|
| 133 |
+
effective_mid_x = 0.5 + self.config.central_midpoint_offset_x
|
| 134 |
+
effective_mid_y = 0.5 + self.config.central_midpoint_offset_y
|
| 135 |
+
|
| 136 |
+
# Calculate half-distance for each central circle
|
| 137 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 138 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 139 |
+
|
| 140 |
+
# Calculate raw displacement components
|
| 141 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 142 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 143 |
+
|
| 144 |
+
# Apply anisotropic scaling
|
| 145 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 146 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 147 |
+
|
| 148 |
+
initial_central_centers = np.array([
|
| 149 |
+
[effective_mid_x - dx, effective_mid_y - dy],
|
| 150 |
+
[effective_mid_x + dx, effective_mid_y + dy]
|
| 151 |
+
])
|
| 152 |
+
|
| 153 |
+
initial_centers = np.vstack((initial_grid_centers, initial_central_centers))
|
| 154 |
+
|
| 155 |
+
# Apply global rotation to the initial state if configured
|
| 156 |
+
if abs(self.config.initial_global_rotation_angle_deg) > 1e-6:
|
| 157 |
+
angle_rad = math.radians(self.config.initial_global_rotation_angle_deg)
|
| 158 |
+
cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 159 |
+
rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 160 |
+
translated_centers = initial_centers - 0.5
|
| 161 |
+
rotated_translated_centers = (rotation_matrix @ translated_centers.T).T
|
| 162 |
+
initial_centers = rotated_translated_centers + 0.5
|
| 163 |
+
|
| 164 |
+
initial_centers = np.clip(initial_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 165 |
+
return initial_centers
|
| 166 |
+
|
| 167 |
+
def _perturb_centers(self, centers: np.ndarray) -> np.ndarray:
|
| 168 |
+
"""
|
| 169 |
+
Applies local perturbations to a subset of circle centers.
|
| 170 |
+
The magnitude of perturbation scales with temperature.
|
| 171 |
+
"""
|
| 172 |
+
new_centers = np.copy(centers)
|
| 173 |
+
|
| 174 |
+
# Perturbation scale decreases with temperature
|
| 175 |
+
perturb_amount = self.config.initial_perturb_scale * (self.current_temperature / self.config.initial_temperature)**self.config.perturb_temp_power
|
| 176 |
+
|
| 177 |
+
for _ in range(self.config.circles_to_perturb):
|
| 178 |
+
circle_idx = random.randint(0, self.config.n_circles - 1)
|
| 179 |
+
|
| 180 |
+
dx = random.uniform(-perturb_amount, perturb_amount)
|
| 181 |
+
dy = random.uniform(-perturb_amount, perturb_amount)
|
| 182 |
+
|
| 183 |
+
new_centers[circle_idx, 0] += dx
|
| 184 |
+
new_centers[circle_idx, 1] += dy
|
| 185 |
+
|
| 186 |
+
# Ensure centers stay within unit square boundaries
|
| 187 |
+
new_centers[circle_idx, 0] = np.clip(new_centers[circle_idx, 0], self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 188 |
+
new_centers[circle_idx, 1] = np.clip(new_centers[circle_idx, 1], self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 189 |
+
|
| 190 |
+
return new_centers
|
| 191 |
+
|
| 192 |
+
def _apply_global_nudge(self, centers: np.ndarray) -> np.ndarray:
|
| 193 |
+
"""
|
| 194 |
+
Applies a small global offset to all circles with a certain probability.
|
| 195 |
+
Magnitude scales with temperature.
|
| 196 |
+
"""
|
| 197 |
+
if random.random() < self.config.global_nudge_probability:
|
| 198 |
+
global_nudge_scale = self.config.global_nudge_amount * (self.current_temperature / self.config.initial_temperature)**self.config.global_nudge_temp_power
|
| 199 |
+
global_dx = random.uniform(-global_nudge_scale, global_nudge_scale)
|
| 200 |
+
global_dy = random.uniform(-global_nudge_scale, global_nudge_scale)
|
| 201 |
+
|
| 202 |
+
centers[:, 0] += global_dx
|
| 203 |
+
centers[:, 1] += global_dy
|
| 204 |
+
centers = np.clip(centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 205 |
+
return centers
|
| 206 |
+
|
| 207 |
+
def optimize(self) -> (np.ndarray, np.ndarray):
|
| 208 |
+
"""
|
| 209 |
+
Runs the Simulated Annealing optimization process.
|
| 210 |
+
Returns the centers and radii of the best packing found.
|
| 211 |
+
"""
|
| 212 |
+
initial_centers = self._generate_initial_centers()
|
| 213 |
+
current_state = CirclePackingState(initial_centers)
|
| 214 |
+
self.best_state = current_state.copy()
|
| 215 |
+
|
| 216 |
+
for iteration in range(self.config.max_iterations):
|
| 217 |
+
# Create a new candidate state by perturbing current centers
|
| 218 |
+
candidate_centers = self._perturb_centers(current_state.centers)
|
| 219 |
+
candidate_centers = self._apply_global_nudge(candidate_centers) # Apply global nudge if probabilistic check passes
|
| 220 |
+
|
| 221 |
+
candidate_state = CirclePackingState(candidate_centers)
|
| 222 |
+
|
| 223 |
+
# Determine if the new state is accepted
|
| 224 |
+
delta_E = candidate_state.sum_radii - current_state.sum_radii # Maximizing sum_radii, so positive delta_E is good.
|
| 225 |
+
|
| 226 |
+
if delta_E > 0: # Always accept improvements
|
| 227 |
+
current_state = candidate_state
|
| 228 |
+
if current_state.sum_radii > self.best_state.sum_radii:
|
| 229 |
+
self.best_state = current_state.copy()
|
| 230 |
+
elif self.current_temperature > 0: # Accept worse solutions with probability
|
| 231 |
+
acceptance_probability = math.exp(delta_E / self.current_temperature)
|
| 232 |
+
if random.random() < acceptance_probability:
|
| 233 |
+
current_state = candidate_state
|
| 234 |
+
|
| 235 |
+
# Cool down the system
|
| 236 |
+
self.current_temperature *= self.config.cooling_rate
|
| 237 |
+
|
| 238 |
+
# Optional: Print progress
|
| 239 |
+
# if iteration % (self.config.max_iterations // 10) == 0:
|
| 240 |
+
# print(f"Iter {iteration}/{self.config.max_iterations}, Temp: {self.current_temperature:.6f}, Current Sum R: {current_state.sum_radii:.4f}, Best Sum R: {self.best_state.sum_radii:.4f}")
|
| 241 |
+
|
| 242 |
+
return self.best_state.centers, self.best_state.radii
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def construct_packing():
|
| 246 |
+
"""
|
| 247 |
+
Main entry point for constructing the packing.
|
| 248 |
+
Initializes a CirclePackingConfig with optimized parameters and uses
|
| 249 |
+
SimulatedAnnealingOptimizer to find the best packing.
|
| 250 |
+
"""
|
| 251 |
+
# Optimized configuration parameters, a refinement of the previously successful SA run.
|
| 252 |
+
# Tuned grid_x/y_coords slightly, tightened initial perturb scale, slowed cooling.
|
| 253 |
+
config = CirclePackingConfig(
|
| 254 |
+
n_circles=26,
|
| 255 |
+
grid_x_coords=(0.10, 0.30, 0.50, 0.70, 0.90), # Retained uniform grid points
|
| 256 |
+
grid_y_coords=(0.10, 0.30, 0.50, 0.70, 0.90), # Retained uniform grid points
|
| 257 |
+
grid_dim=5,
|
| 258 |
+
|
| 259 |
+
central_separation_distance=0.125,
|
| 260 |
+
central_pair_orientation_angle_deg=44.5,
|
| 261 |
+
central_midpoint_offset_x=-0.0015,
|
| 262 |
+
central_midpoint_offset_y=0.0010,
|
| 263 |
+
central_x_offset_scale=1.01,
|
| 264 |
+
central_y_offset_scale=0.99,
|
| 265 |
+
initial_global_rotation_angle_deg=0.1,
|
| 266 |
+
|
| 267 |
+
initial_temperature=0.005,
|
| 268 |
+
cooling_rate=0.9999, # Slightly slower cooling
|
| 269 |
+
max_iterations=20000, # Increased iterations for deeper search
|
| 270 |
+
initial_perturb_scale=0.015,
|
| 271 |
+
perturb_temp_power=1.0, # Linear scaling
|
| 272 |
+
circles_to_perturb=2,
|
| 273 |
+
global_nudge_probability=0.01,
|
| 274 |
+
global_nudge_amount=0.005,
|
| 275 |
+
global_nudge_temp_power=0.5, # Global nudge scales slower with temp decay
|
| 276 |
+
clip_epsilon=1e-8
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
optimizer = SimulatedAnnealingOptimizer(config)
|
| 280 |
+
centers, radii = optimizer.optimize()
|
| 281 |
+
|
| 282 |
+
return centers, radii
|
| 283 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/best/search_replace.txt
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Based on my analysis of previous high-performing solutions and the current code's structure, the most promising path to a higher score is to re-introduce a Simulated Annealing (SA) optimization stage. The current deterministic constructor provides an excellent starting point, but an iterative refinement process is necessary to explore the solution space more thoroughly and escape local optima.
|
| 2 |
+
|
| 3 |
+
My proposed edits will integrate the powerful SA logic from a previous successful generation (which scored 2.58) into the cleaner, class-based architecture of the current program. This involves three main changes:
|
| 4 |
+
|
| 5 |
+
1. **Enhancing `PackingConfig`**: I will add SA-specific parameters (like temperature, cooling rate, iterations, and perturbation scales) to the configuration dataclass. I will also fine-tune the initial placement parameters to match those of a previous high-scoring run, reintroducing subtle asymmetries (y-offset, global rotation) that have proven effective at breaking symmetry and unlocking better packings.
|
| 6 |
+
2. **Upgrading `CirclePackingSolver`**: I will transform the current deterministic solver into a full-fledged SA optimizer. The `solve` method will now execute the SA loop, starting with the strong initial configuration and iteratively perturbing the circle centers to search for a better arrangement. This involves adding helper methods for perturbation and probabilistic global nudges, which are key SA strategies.
|
| 7 |
+
3. **Updating Documentation**: I will update the docstrings to accurately reflect the new optimization strategy, making the code's intent clearer.
|
| 8 |
+
|
| 9 |
+
By combining a strong initial guess with a robust optimization algorithm, we give the solver the best chance to find a packing with a sum of radii closer to the theoretical maximum.
|
| 10 |
+
|
| 11 |
+
<NAME>
|
| 12 |
+
reintroduce_simulated_annealing
|
| 13 |
+
</NAME>
|
| 14 |
+
|
| 15 |
+
<DESCRIPTION>
|
| 16 |
+
This edit re-introduces a Simulated Annealing (SA) optimization process into the `CirclePackingSolver`. The current deterministic solver is enhanced to become an SA optimizer. The `PackingConfig` is updated to include SA parameters (temperature, cooling rate, iterations, etc.) and refined initial placement values based on previous high-scoring runs. The `CirclePackingSolver.solve` method is rewritten to perform the SA loop: it generates a strong initial configuration and then iteratively perturbs it, accepting better states or worse ones with a certain probability, to search for a globally better packing. This combines the clean structure of the current code with the proven optimization power of SA.
|
| 17 |
+
</DESCRIPTION>
|
| 18 |
+
|
| 19 |
+
<DIFF>
|
| 20 |
+
<<<<<<< SEARCH
|
| 21 |
+
from dataclasses import dataclass
|
| 22 |
+
import math
|
| 23 |
+
=======
|
| 24 |
+
from dataclasses import dataclass
|
| 25 |
+
import math
|
| 26 |
+
import random
|
| 27 |
+
>>>>>>> REPLACE
|
| 28 |
+
</DIFF>
|
| 29 |
+
|
| 30 |
+
<DIFF>
|
| 31 |
+
<<<<<<< SEARCH
|
| 32 |
+
@dataclass
|
| 33 |
+
class PackingConfig:
|
| 34 |
+
"""
|
| 35 |
+
A unified configuration object holding all tunable parameters for the circle packing problem.
|
| 36 |
+
This dataclass consolidates the most effective parameters from previous successful attempts,
|
| 37 |
+
including non-uniform grids, central pair offsets, and anisotropic scaling.
|
| 38 |
+
"""
|
| 39 |
+
n_circles: int = 26
|
| 40 |
+
|
| 41 |
+
# Grid parameters allowing non-uniform spacing.
|
| 42 |
+
# The default is the robust 5x5 grid that has performed well.
|
| 43 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 44 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 45 |
+
|
| 46 |
+
# Central circle parameters. These are tuned based on the best-performing prior program (score 2.52).
|
| 47 |
+
# Distance between the two central circle centers.
|
| 48 |
+
central_separation_distance: float = 0.125
|
| 49 |
+
# The angle of the line connecting the two central circles.
|
| 50 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 51 |
+
# The distance to shift the entire central pair's midpoint from (0.5, 0.5).
|
| 52 |
+
central_midpoint_offset_dist: float = 0.0015
|
| 53 |
+
# The angle of the midpoint shift. 180 degrees corresponds to a negative x-offset.
|
| 54 |
+
central_midpoint_offset_angle_deg: float = 180.0
|
| 55 |
+
# Anisotropic scaling to create an elliptical void for the central pair.
|
| 56 |
+
central_x_offset_scale: float = 1.01
|
| 57 |
+
central_y_offset_scale: float = 0.99
|
| 58 |
+
|
| 59 |
+
# Global rotation for the entire packing arrangement.
|
| 60 |
+
global_rotation_angle_deg: float = 0.0
|
| 61 |
+
|
| 62 |
+
clip_epsilon: float = 1e-8
|
| 63 |
+
=======
|
| 64 |
+
@dataclass
|
| 65 |
+
class PackingConfig:
|
| 66 |
+
"""
|
| 67 |
+
A unified configuration object holding all tunable parameters for the circle packing problem.
|
| 68 |
+
This dataclass consolidates parameters for initial placement and adds parameters for
|
| 69 |
+
Simulated Annealing (SA) to refine the packing.
|
| 70 |
+
"""
|
| 71 |
+
n_circles: int = 26
|
| 72 |
+
|
| 73 |
+
# Grid parameters for the initial 24 circles.
|
| 74 |
+
grid_x_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 75 |
+
grid_y_coords: tuple = (0.1, 0.3, 0.5, 0.7, 0.9)
|
| 76 |
+
|
| 77 |
+
# Central circle parameters for initial placement, tuned from high-scoring runs.
|
| 78 |
+
central_separation_distance: float = 0.125
|
| 79 |
+
central_pair_orientation_angle_deg: float = 44.5
|
| 80 |
+
# Asymmetric offset for the central pair's midpoint.
|
| 81 |
+
central_midpoint_offset_dist: float = 0.0018 # From x=-0.0015, y=0.0010
|
| 82 |
+
central_midpoint_offset_angle_deg: float = 146.3 # From x=-0.0015, y=0.0010
|
| 83 |
+
# Anisotropic scaling for the central pair's void.
|
| 84 |
+
central_x_offset_scale: float = 1.01
|
| 85 |
+
central_y_offset_scale: float = 0.99
|
| 86 |
+
|
| 87 |
+
# Global rotation for the initial packing arrangement.
|
| 88 |
+
global_rotation_angle_deg: float = 0.1
|
| 89 |
+
|
| 90 |
+
# Simulated Annealing (SA) parameters
|
| 91 |
+
initial_temperature: float = 0.005
|
| 92 |
+
cooling_rate: float = 0.9999
|
| 93 |
+
max_iterations: int = 25000
|
| 94 |
+
initial_perturb_scale: float = 0.015
|
| 95 |
+
perturb_temp_power: float = 1.0
|
| 96 |
+
circles_to_perturb: int = 2
|
| 97 |
+
global_nudge_probability: float = 0.01
|
| 98 |
+
global_nudge_amount: float = 0.005
|
| 99 |
+
global_nudge_temp_power: float = 0.5
|
| 100 |
+
|
| 101 |
+
clip_epsilon: float = 1e-8
|
| 102 |
+
>>>>>>> REPLACE
|
| 103 |
+
</DIFF>
|
| 104 |
+
|
| 105 |
+
<DIFF>
|
| 106 |
+
<<<<<<< SEARCH
|
| 107 |
+
class CirclePackingSolver:
|
| 108 |
+
"""
|
| 109 |
+
An encapsulated solver for the circle packing problem.
|
| 110 |
+
This class takes a configuration object and orchestrates the generation of
|
| 111 |
+
circle centers and the computation of their optimal radii. This new structure
|
| 112 |
+
centralizes all logic into a single, reusable class.
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
def __init__(self, config: PackingConfig):
|
| 116 |
+
"""Initializes the solver with a given packing configuration."""
|
| 117 |
+
self.config = config
|
| 118 |
+
self.centers = None
|
| 119 |
+
self.radii = None
|
| 120 |
+
|
| 121 |
+
def _generate_grid_centers(self) -> np.ndarray:
|
| 122 |
+
"""Generates centers for 24 circles arranged in a grid, skipping the central point."""
|
| 123 |
+
grid_centers = []
|
| 124 |
+
num_divs = len(self.config.grid_x_coords)
|
| 125 |
+
for i in range(num_divs):
|
| 126 |
+
for j in range(num_divs):
|
| 127 |
+
if i == num_divs // 2 and j == num_divs // 2:
|
| 128 |
+
continue
|
| 129 |
+
grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 130 |
+
return np.array(grid_centers)
|
| 131 |
+
|
| 132 |
+
def _generate_central_centers(self) -> np.ndarray:
|
| 133 |
+
"""
|
| 134 |
+
Generates centers for the 2 central circles using a flexible parameterization
|
| 135 |
+
that includes midpoint offset, orientation, and anisotropic scaling.
|
| 136 |
+
"""
|
| 137 |
+
# Calculate the central pair's midpoint with an offset from (0.5, 0.5).
|
| 138 |
+
offset_rad = math.radians(self.config.central_midpoint_offset_angle_deg)
|
| 139 |
+
mid_x = 0.5 + self.config.central_midpoint_offset_dist * math.cos(offset_rad)
|
| 140 |
+
mid_y = 0.5 + self.config.central_midpoint_offset_dist * math.sin(offset_rad)
|
| 141 |
+
|
| 142 |
+
# Calculate the displacement vectors for the two circles from their midpoint.
|
| 143 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 144 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 145 |
+
|
| 146 |
+
# Raw displacement based on orientation angle.
|
| 147 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 148 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 149 |
+
|
| 150 |
+
# Apply anisotropic scaling.
|
| 151 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 152 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 153 |
+
|
| 154 |
+
return np.array([
|
| 155 |
+
[mid_x - dx, mid_y - dy],
|
| 156 |
+
[mid_x + dx, mid_y + dy]
|
| 157 |
+
])
|
| 158 |
+
|
| 159 |
+
def _apply_global_rotation(self, centers: np.ndarray) -> np.ndarray:
|
| 160 |
+
"""Applies a global rotation to all circle centers around (0.5, 0.5)."""
|
| 161 |
+
if abs(self.config.global_rotation_angle_deg) < 1e-6:
|
| 162 |
+
return centers
|
| 163 |
+
|
| 164 |
+
angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 165 |
+
cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 166 |
+
rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 167 |
+
|
| 168 |
+
# Translate to origin, rotate, then translate back.
|
| 169 |
+
return (rotation_matrix @ (centers - 0.5).T).T + 0.5
|
| 170 |
+
|
| 171 |
+
def solve(self) -> tuple[np.ndarray, np.ndarray]:
|
| 172 |
+
"""
|
| 173 |
+
Executes the full packing and solving pipeline.
|
| 174 |
+
1. Generates centers for all circles.
|
| 175 |
+
2. Applies global transformations.
|
| 176 |
+
3. Computes the maximum possible radii using linear programming.
|
| 177 |
+
"""
|
| 178 |
+
grid_centers = self._generate_grid_centers()
|
| 179 |
+
central_centers = self._generate_central_centers()
|
| 180 |
+
|
| 181 |
+
all_centers = np.vstack((grid_centers, central_centers))
|
| 182 |
+
all_centers = self._apply_global_rotation(all_centers)
|
| 183 |
+
|
| 184 |
+
# Clip to ensure centers are strictly inside the unit square.
|
| 185 |
+
self.centers = np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 186 |
+
|
| 187 |
+
self.radii = self._compute_max_radii(self.centers)
|
| 188 |
+
|
| 189 |
+
return self.centers, self.radii
|
| 190 |
+
=======
|
| 191 |
+
class CirclePackingSolver:
|
| 192 |
+
"""
|
| 193 |
+
An encapsulated solver for the circle packing problem, now enhanced with
|
| 194 |
+
a Simulated Annealing (SA) optimization loop. It starts with a strong
|
| 195 |
+
initial configuration and iteratively refines it to find a better packing.
|
| 196 |
+
"""
|
| 197 |
+
|
| 198 |
+
def __init__(self, config: PackingConfig):
|
| 199 |
+
"""Initializes the solver with a given packing configuration."""
|
| 200 |
+
self.config = config
|
| 201 |
+
self.current_temperature = config.initial_temperature
|
| 202 |
+
|
| 203 |
+
def _generate_initial_centers(self) -> np.ndarray:
|
| 204 |
+
"""Generates the initial set of circle centers based on the configuration."""
|
| 205 |
+
# 1. Generate grid centers
|
| 206 |
+
grid_centers = []
|
| 207 |
+
num_divs = len(self.config.grid_x_coords)
|
| 208 |
+
for i in range(num_divs):
|
| 209 |
+
for j in range(num_divs):
|
| 210 |
+
if i == num_divs // 2 and j == num_divs // 2:
|
| 211 |
+
continue
|
| 212 |
+
grid_centers.append([self.config.grid_x_coords[i], self.config.grid_y_coords[j]])
|
| 213 |
+
|
| 214 |
+
# 2. Generate central centers
|
| 215 |
+
offset_rad = math.radians(self.config.central_midpoint_offset_angle_deg)
|
| 216 |
+
mid_x = 0.5 + self.config.central_midpoint_offset_dist * math.cos(offset_rad)
|
| 217 |
+
mid_y = 0.5 + self.config.central_midpoint_offset_dist * math.sin(offset_rad)
|
| 218 |
+
|
| 219 |
+
R_prime = self.config.central_separation_distance / 2.0
|
| 220 |
+
orientation_rad = math.radians(self.config.central_pair_orientation_angle_deg)
|
| 221 |
+
|
| 222 |
+
dx_raw = R_prime * math.cos(orientation_rad)
|
| 223 |
+
dy_raw = R_prime * math.sin(orientation_rad)
|
| 224 |
+
|
| 225 |
+
dx = dx_raw * self.config.central_x_offset_scale
|
| 226 |
+
dy = dy_raw * self.config.central_y_offset_scale
|
| 227 |
+
|
| 228 |
+
central_centers = np.array([
|
| 229 |
+
[mid_x - dx, mid_y - dy],
|
| 230 |
+
[mid_x + dx, mid_y + dy]
|
| 231 |
+
])
|
| 232 |
+
|
| 233 |
+
all_centers = np.vstack((grid_centers, central_centers))
|
| 234 |
+
|
| 235 |
+
# 3. Apply global rotation
|
| 236 |
+
if abs(self.config.global_rotation_angle_deg) > 1e-6:
|
| 237 |
+
angle_rad = math.radians(self.config.global_rotation_angle_deg)
|
| 238 |
+
cos_theta, sin_theta = math.cos(angle_rad), math.sin(angle_rad)
|
| 239 |
+
rotation_matrix = np.array([[cos_theta, -sin_theta], [sin_theta, cos_theta]])
|
| 240 |
+
all_centers = (rotation_matrix @ (all_centers - 0.5).T).T + 0.5
|
| 241 |
+
|
| 242 |
+
return np.clip(all_centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 243 |
+
|
| 244 |
+
def _perturb_centers(self, centers: np.ndarray) -> np.ndarray:
|
| 245 |
+
"""Applies local perturbations to a subset of circle centers."""
|
| 246 |
+
new_centers = np.copy(centers)
|
| 247 |
+
perturb_amount = self.config.initial_perturb_scale * (self.current_temperature / self.config.initial_temperature)**self.config.perturb_temp_power
|
| 248 |
+
|
| 249 |
+
for _ in range(self.config.circles_to_perturb):
|
| 250 |
+
circle_idx = random.randint(0, self.config.n_circles - 1)
|
| 251 |
+
|
| 252 |
+
dx = random.uniform(-perturb_amount, perturb_amount)
|
| 253 |
+
dy = random.uniform(-perturb_amount, perturb_amount)
|
| 254 |
+
|
| 255 |
+
new_centers[circle_idx, 0] += dx
|
| 256 |
+
new_centers[circle_idx, 1] += dy
|
| 257 |
+
|
| 258 |
+
new_centers[circle_idx, :] = np.clip(new_centers[circle_idx, :], self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 259 |
+
|
| 260 |
+
return new_centers
|
| 261 |
+
|
| 262 |
+
def _apply_global_nudge(self, centers: np.ndarray) -> np.ndarray:
|
| 263 |
+
"""Applies a small global offset to all circles with a certain probability."""
|
| 264 |
+
if random.random() < self.config.global_nudge_probability:
|
| 265 |
+
nudge_scale = self.config.global_nudge_amount * (self.current_temperature / self.config.initial_temperature)**self.config.global_nudge_temp_power
|
| 266 |
+
global_dx = random.uniform(-nudge_scale, nudge_scale)
|
| 267 |
+
global_dy = random.uniform(-nudge_scale, nudge_scale)
|
| 268 |
+
centers[:, 0] += global_dx
|
| 269 |
+
centers[:, 1] += global_dy
|
| 270 |
+
centers = np.clip(centers, self.config.clip_epsilon, 1 - self.config.clip_epsilon)
|
| 271 |
+
return centers
|
| 272 |
+
|
| 273 |
+
def solve(self) -> tuple[np.ndarray, np.ndarray]:
|
| 274 |
+
"""
|
| 275 |
+
Executes the full SA optimization pipeline.
|
| 276 |
+
1. Generates a strong initial configuration.
|
| 277 |
+
2. Iteratively perturbs centers and accepts new states based on SA logic.
|
| 278 |
+
3. Returns the best configuration found.
|
| 279 |
+
"""
|
| 280 |
+
current_centers = self._generate_initial_centers()
|
| 281 |
+
current_sum_radii = np.sum(self._compute_max_radii(current_centers))
|
| 282 |
+
|
| 283 |
+
best_centers = np.copy(current_centers)
|
| 284 |
+
best_sum_radii = current_sum_radii
|
| 285 |
+
|
| 286 |
+
for _ in range(self.config.max_iterations):
|
| 287 |
+
candidate_centers = self._perturb_centers(current_centers)
|
| 288 |
+
candidate_centers = self._apply_global_nudge(candidate_centers)
|
| 289 |
+
|
| 290 |
+
candidate_sum_radii = np.sum(self._compute_max_radii(candidate_centers))
|
| 291 |
+
|
| 292 |
+
delta_E = candidate_sum_radii - current_sum_radii
|
| 293 |
+
|
| 294 |
+
if delta_E > 0 or (self.current_temperature > 0 and random.random() < math.exp(delta_E / self.current_temperature)):
|
| 295 |
+
current_centers = candidate_centers
|
| 296 |
+
current_sum_radii = candidate_sum_radii
|
| 297 |
+
if current_sum_radii > best_sum_radii:
|
| 298 |
+
best_centers = np.copy(current_centers)
|
| 299 |
+
best_sum_radii = current_sum_radii
|
| 300 |
+
|
| 301 |
+
self.current_temperature *= self.config.cooling_rate
|
| 302 |
+
|
| 303 |
+
final_radii = self._compute_max_radii(best_centers)
|
| 304 |
+
return best_centers, final_radii
|
| 305 |
+
>>>>>>> REPLACE
|
| 306 |
+
</DIFF>
|
| 307 |
+
|
| 308 |
+
<DIFF>
|
| 309 |
+
<<<<<<< SEARCH
|
| 310 |
+
def construct_packing():
|
| 311 |
+
"""
|
| 312 |
+
Main entry point for constructing the circle packing.
|
| 313 |
+
This function now uses the unified CirclePackingSolver class, demonstrating the
|
| 314 |
+
new, cleaner architecture. The configuration is instantiated with parameters
|
| 315 |
+
derived from the best-performing historical programs to aim for a new high score.
|
| 316 |
+
"""
|
| 317 |
+
# This configuration is a refined combination of the best parameters observed
|
| 318 |
+
# in previous high-scoring runs (scores > 2.51).
|
| 319 |
+
config = PackingConfig()
|
| 320 |
+
|
| 321 |
+
# Create a solver instance with the specified configuration.
|
| 322 |
+
solver = CirclePackingSolver(config)
|
| 323 |
+
|
| 324 |
+
# Run the solver to get the final centers and radii.
|
| 325 |
+
centers, radii = solver.solve()
|
| 326 |
+
|
| 327 |
+
return centers, radii
|
| 328 |
+
=======
|
| 329 |
+
def construct_packing():
|
| 330 |
+
"""
|
| 331 |
+
Main entry point for constructing the circle packing.
|
| 332 |
+
This function uses the CirclePackingSolver, which implements a Simulated
|
| 333 |
+
Annealing (SA) algorithm. It starts with a strong initial configuration and
|
| 334 |
+
then uses SA to search for improvements.
|
| 335 |
+
"""
|
| 336 |
+
# The configuration is instantiated with default parameters that include
|
| 337 |
+
# both the initial placement logic and the SA optimization parameters.
|
| 338 |
+
# These defaults are tuned from previous high-scoring runs.
|
| 339 |
+
config = PackingConfig()
|
| 340 |
+
|
| 341 |
+
# Create a solver instance with the specified configuration.
|
| 342 |
+
solver = CirclePackingSolver(config)
|
| 343 |
+
|
| 344 |
+
# Run the solver to get the final centers and radii.
|
| 345 |
+
centers, radii = solver.solve()
|
| 346 |
+
|
| 347 |
+
return centers, radii
|
| 348 |
+
>>>>>>> REPLACE
|
| 349 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/EVAL_AGENTS.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Generation 164 Evaluation
|
| 2 |
+
|
| 3 |
+
**Auxiliary Metrics Implemented:**
|
| 4 |
+
|
| 5 |
+
This generation is in the **OPTIMIZATION/CONVERGENCE** stage. The focus is on refining solutions and understanding the nuanced characteristics of high-performing packings. The primary metric maximizes the sum of radii, ensuring no overlaps or boundary violations. These auxiliary metrics aim to provide insights into *how* that maximum is achieved and what structural properties define optimal or near-optimal solutions.
|
| 6 |
+
|
| 7 |
+
**Group 1: Radii Statistics**
|
| 8 |
+
* **`avg_radius`**: The average radius of all 26 circles. _Useful for tracking whether the evolution favors uniformly sized circles or allows for a wide range of sizes. Helps to identify trends in average circle size during optimization._
|
| 9 |
+
* **`std_dev_radius`**: The standard deviation of the radii. _A higher value indicates greater diversity in circle sizes, while a lower value suggests a more uniform distribution. This can reveal different packing strategies (e.g., many small vs. few large and few small)._
|
| 10 |
+
* **`min_radius`**: The smallest radius among all circles. _Indicates if the solution relies on very small circles to fill gaps._
|
| 11 |
+
* **`max_radius`**: The largest radius among all circles. _Indicates the size of the dominant circles in the packing._
|
| 12 |
+
* **`median_radius`**: The median radius of all circles. _Provides a robust measure of central tendency for radii, less sensitive to outliers than the mean. Useful for understanding the typical circle size._
|
| 13 |
+
* **`num_unique_radii`**: The count of unique radius values used. _A high number indicates greater variety in circle sizes, potentially suggesting more complex packing patterns._
|
| 14 |
+
|
| 15 |
+
**Group 2: Area Coverage Metrics**
|
| 16 |
+
* **`total_area_covered`**: The sum of the areas (pi * r^2) of all 26 circles. _This provides a direct measure of the physical space occupied by the circles, complementing the primary metric (sum of radii) by showing the actual 2D coverage._
|
| 17 |
+
* **`packing_density`**: The ratio of `total_area_covered` to the unit square's area (which is 1.0). _A normalized measure of how efficiently the square is filled. Higher is better._
|
| 18 |
+
* **`empty_space_ratio`**: The proportion of the unit square's area not covered by circles (`1 - packing_density`). _This should ideally be minimized in optimal solutions._
|
| 19 |
+
|
| 20 |
+
**Group 3: Violation Metrics (Magnitude & Count)**
|
| 21 |
+
* **`max_circle_overlap_magnitude`**: The largest positive value (sum of radii - distance between centers) for any overlapping pair. _Even if the primary evaluator reports no overlap (due to internal tolerances), a small positive value here indicates a "near miss" or a potential instability. Should be 0 for truly valid solutions._
|
| 22 |
+
* **`num_overlapping_pairs`**: The total count of pairs of circles that overlap. _Should be 0 for valid solutions. If > 0, it highlights invalid solutions and indicates the severity of the overlap problem._
|
| 23 |
+
* **`max_boundary_violation_magnitude`**: The largest extent to which any circle protrudes beyond the unit square boundary. _Similar to overlap magnitude, it identifies near-boundary violations or slight excursions. Should be 0 for truly valid solutions._
|
| 24 |
+
* **`num_boundary_violations`**: The total count of circles that violate any boundary. _Should be 0 for valid solutions. If > 0, indicates an invalid solution._
|
| 25 |
+
|
| 26 |
+
**Group 4: Advanced Spatial and Contact Metrics**
|
| 27 |
+
* **`packing_aspect_ratio_of_centers_bbox`**: The aspect ratio of the bounding box that encloses all circle centers. _Reveals the overall shape of the packing arrangement. Values closer to 1 indicate a more square-like distribution of centers, while larger values indicate an elongated or linear arrangement._
|
| 28 |
+
* **`avg_num_touching_neighbors`**: The average number of other circles a given circle is "touching" (within a small tolerance). _Higher values suggest a more tightly packed and interlocked arrangement, often seen in optimal configurations._
|
| 29 |
+
* **`avg_quadrant_radii_std_dev`**: The average of the standard deviations of radii calculated for each of the four quadrants of the unit square. _This helps to understand if radius diversity is localized (e.g., all small circles in one corner) or evenly distributed._
|
| 30 |
+
|
| 31 |
+
**Group 5: Center Distribution Metrics**
|
| 32 |
+
* **`avg_pairwise_center_distance`**: The average Euclidean distance between the centers of all unique pairs of circles. _A lower value might indicate a more clustered or dense arrangement of circles._
|
| 33 |
+
* **`avg_distance_from_packing_centroid_normalized`**: The average distance of each circle's center from the overall centroid (average position) of all circle centers, normalized by the maximum possible distance from the unit square center to a corner. _Lower values indicate that the packing is more centrally concentrated, while higher values suggest a more dispersed pattern._
|
| 34 |
+
|
| 35 |
+
**Group 6: Boundary Contact Metrics**
|
| 36 |
+
* **`num_circles_touching_edge`**: The count of circles whose outer edge is tangent to or nearly touching any of the four boundaries of the unit square (within a small tolerance). _Higher values suggest better utilization of the perimeter of the packing space._
|
| 37 |
+
* **`num_circles_touching_corner`**: The count of circles whose outer edge is tangent to or nearly touching two orthogonal boundaries (i.e., a corner) of the unit square. _A more specific measure of boundary utilization, indicating how well corners are filled._
|
| 38 |
+
|
| 39 |
+
**Group 7: Boundary Utilization (Distance)**
|
| 40 |
+
* **`avg_min_circle_to_boundary_distance`**: The average of the minimum distances from each circle's edge to the closest boundary. _Smaller positive values indicate a tighter fit of circles against the edges, while larger values suggest more "cushion" space._
|
| 41 |
+
|
| 42 |
+
**Observations for Generation 164 and Recommendations:**
|
| 43 |
+
|
| 44 |
+
* **Current Score (Primary Metric):** 2.3750. This is a good reference point for comparative analysis.
|
| 45 |
+
* **Evolution Stage:** Optimization/Convergence. At this stage, solutions should ideally have `num_overlapping_pairs` and `num_boundary_violations` as 0. Small non-zero values for `max_circle_overlap_magnitude` or `max_boundary_violation_magnitude` might indicate fragility or solutions that are barely meeting constraints.
|
| 46 |
+
* **Insights from Auxiliary Metrics (Expected):**
|
| 47 |
+
* High `packing_density` and low `empty_space_ratio` are expected for high primary scores.
|
| 48 |
+
* `avg_num_touching_neighbors` could provide insights into the connectivity of the packing; higher values often correlate with denser packing.
|
| 49 |
+
* `std_dev_radius` and `num_unique_radii` will indicate if the optimal solutions are favoring uniform sizes or exploiting diverse sizes.
|
| 50 |
+
* `avg_min_circle_to_boundary_distance` will show how aggressively circles are placed near the boundaries.
|
| 51 |
+
|
| 52 |
+
**Recommendations:**
|
| 53 |
+
* Monitor `max_circle_overlap_magnitude` and `max_boundary_violation_magnitude`: Even if passing, persistently small positive values could indicate a fragile solution. The evolution should aim for values very close to zero or negative (meaning separation from boundaries/other circles).
|
| 54 |
+
* Analyze trends in `std_dev_radius` and `num_unique_radii`: Are optimal solutions becoming more uniform or more diverse in radii? This can inform further genetic operations.
|
| 55 |
+
* Correlate `avg_num_touching_neighbors` with the primary score: A strong positive correlation would suggest that increasing local density (more touching circles) is a key driver for higher overall radii sums.
|
| 56 |
+
* Observe `avg_distance_from_packing_centroid_normalized`: If this metric stays high, it could mean solutions are concentrating circles in corners or along edges, potentially leaving a large empty space in the middle. If it's low, it suggests a more centralized packing.
|
| 57 |
+
|
| 58 |
+
## Generation 165 Evaluation
|
| 59 |
+
|
| 60 |
+
**Auxiliary Metrics Implemented:**
|
| 61 |
+
|
| 62 |
+
**Group 1: Radii Statistics**
|
| 63 |
+
* **`num_unique_radii`**: The count of distinct radius values among the 26 circles. This metric provides insight into the diversity of circle sizes being used in the packing solution. A higher number of unique radii can indicate a more complex or adaptive packing strategy, potentially filling space more efficiently by using a wider range of circle sizes. A lower number might suggest a simpler strategy focusing on a few dominant sizes.
|
| 64 |
+
|
| 65 |
+
**Observations and Recommendations for Generation 165:**
|
| 66 |
+
|
| 67 |
+
At Generation 165, the evolution is likely in the **CONVERGENCE** or **PLATEAU** stage. The primary metric (sum of radii) may have stabilized, and we need to identify subtle characteristics that could lead to further improvements or signal stagnation.
|
| 68 |
+
|
| 69 |
+
* **`num_unique_radii`**: This metric is particularly useful at this late stage. If `num_unique_radii` is consistently low (e.g., 1 or 2 distinct sizes), it could indicate that the evolution has found a local optimum that relies on a very uniform or simple size distribution. Encouraging a higher diversity in radii might help break this plateau, allowing the algorithm to explore solutions with a wider range of circle sizes that could fit into complex gaps. Conversely, if `num_unique_radii` is already high, it suggests the algorithm is already exploring diverse sizes effectively.
|
| 70 |
+
|
| 71 |
+
By tracking this metric, we can gain insight into the inherent complexity of the radius distributions that the evolution is converging upon, and potentially guide it towards strategies that leverage more varied sizing for better packing.
|
| 72 |
+
|
| 73 |
+
# Evaluation Agent Memory
|
| 74 |
+
|
| 75 |
+
This file tracks the auxiliary evaluation metrics and insights generated across different evolution generations.
|
| 76 |
+
|
| 77 |
+
## Generation 19 Evaluation
|
| 78 |
+
|
| 79 |
+
**Auxiliary Metrics Implemented:**
|
| 80 |
+
|
| 81 |
+
**Group 1: Packing Efficiency**
|
| 82 |
+
* **`total_area_covered`**: Sum of the areas of all 26 circles (pi * r^2 for each). This measures how much of the unit square is occupied by the circles. A higher value indicates better space utilization, focusing on the actual "filled" area rather than just the sum of radii (which is the primary score). Range 0 to ~1 (or slightly more if circles could overlap, but primary prevents that).
|
| 83 |
+
* **`packing_density`**: This is identical to `total_area_covered` because the unit square has an area of 1. It directly represents the percentage of the available area covered by circles. Higher is better, indicating more efficient use of space.
|
| 84 |
+
|
| 85 |
+
**Group 2: Boundary Utilization**
|
| 86 |
+
* **`avg_min_distance_to_boundary`**: The average of the minimum distances from the edge of each circle to any of the four boundaries of the unit square. Smaller values (closer to 0) indicate that circles are packed more tightly against the boundaries, which is often a characteristic of optimal packing solutions. Negative values would imply a violation (circle outside boundary) which the primary metric already checks. Useful for understanding how well the solution leverages the container edges.
|
| 87 |
+
* **`min_overall_distance_to_boundary`**: The single smallest distance from any circle's edge to any boundary. This metric highlights if at least one circle is positioned extremely close to a boundary, indicating a strategy to maximize space.
|
| 88 |
+
|
| 89 |
+
**Group 3: Radii Statistics**
|
| 90 |
+
* **`avg_radius`**: The average radius of the 26 circles. This gives an idea of the typical size of circles in the packing. Can indicate if solutions are favoring uniform or varied circle sizes.
|
| 91 |
+
* **`std_dev_radius`**: The standard deviation of the radii of the 26 circles. A higher standard deviation indicates greater diversity in circle sizes, while a lower one suggests more uniform sizes. This can reveal different packing strategies and encourage diversity.
|
| 92 |
+
* **`min_radius`**: The smallest radius among the 26 circles. Can indicate if the solution is introducing very small circles.
|
| 93 |
+
* **`max_radius`**: The largest radius among the 26 circles. Can indicate if the solution is trying to fit very large circles.
|
| 94 |
+
|
| 95 |
+
## Generation 39 Evaluation
|
| 96 |
+
|
| 97 |
+
**Observations and Recommendations:**
|
| 98 |
+
|
| 99 |
+
For Generation 39, we continue to track the following auxiliary metrics, which complement the primary objective of maximizing `sum(radii)`:
|
| 100 |
+
|
| 101 |
+
* **Packing Efficiency (`total_area_covered`, `packing_density`):** These metrics provide insight into the actual physical space utilized by the circles. By comparing trends in `sum(radii)` (primary score) with `total_area_covered`, we can understand if improvements in `sum(radii)` are consistently translating to better overall area utilization. If not, it might indicate a need to adjust the optimization strategy to consider `r^2` more directly.
|
| 102 |
+
|
| 103 |
+
* **Radii Statistics (`avg_radius`, `std_dev_radius`, `min_radius`, `max_radius`):** At this stage, observing the `std_dev_radius` is crucial. If it's decreasing significantly, it might suggest the algorithm is converging on a specific distribution of circle sizes. If it remains high or fluctuates, it indicates continued exploration of diverse circle size combinations. Monitoring `min_radius` and `max_radius` can reveal if the solution is relying on very small (and potentially less impactful on `sum(radii)` but useful for filling gaps) or very large circles.
|
| 104 |
+
|
| 105 |
+
* **Boundary Utilization (`avg_min_distance_to_boundary`, `min_min_distance_to_boundary`):** These metrics are vital for assessing how aggressively the solutions are pushing circles against the unit square boundaries. Consistently lower (closer to zero or slightly negative, implying tight fit if primary evaluator doesn't flag violations) `avg_min_distance_to_boundary` values over generations would suggest a more efficient packing strategy, where circles are fully leveraging the available space. If these values are relatively high, it might indicate that circles are not being pushed close enough to the edges, leaving valuable space unused.
|
| 106 |
+
|
| 107 |
+
**Overall Strategy Recommendation:**
|
| 108 |
+
|
| 109 |
+
At this stage, we should monitor these auxiliary metrics for signs of stagnation or subtle improvements that the primary score might not fully capture. For instance, a plateau in the primary score coupled with stable or slightly improving `packing_density` and `avg_min_distance_to_boundary` could mean the algorithm is finding more robust or efficient ways to utilize space without significantly increasing the sum of radii. Conversely, a plateau in the primary score combined with stagnant auxiliary metrics might signal a local optimum, requiring a perturbation or shift in evolution strategy.
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
## Generation 60 Evaluation
|
| 115 |
+
|
| 116 |
+
**Auxiliary Metrics Implemented:**
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
## Generation 185 Evaluation
|
| 120 |
+
|
| 121 |
+
**Auxiliary Metrics Implemented:**
|
| 122 |
+
|
| 123 |
+
**Group 1: Radii Distribution Statistics (`_calculate_radii_stats`)**
|
| 124 |
+
* **`avg_radius`**: The arithmetic mean of all circle radii. Provides a central tendency for circle sizes.
|
| 125 |
+
* **`std_dev_radius`**: The standard deviation of all circle radii. Measures the dispersion or variability of circle sizes. A higher value indicates more diverse radii.
|
| 126 |
+
* **`min_radius`**: The smallest radius among all circles. Can highlight the presence of very small "filler" circles.
|
| 127 |
+
* **`max_radius`**: The largest radius among all circles. Indicates the size of the dominant circles.
|
| 128 |
+
* **`median_radius`**: The median radius of all circles. A robust measure of central tendency, less affected by outliers than the mean.
|
| 129 |
+
* **`num_unique_radii`**: The count of distinct radius values. A higher number suggests more variety in the chosen circle sizes.
|
| 130 |
+
* **`radius_coefficient_of_variation`**: The ratio of the standard deviation to the mean radius. A normalized measure of dispersion, useful for comparing variability across different scales of radii.
|
| 131 |
+
|
| 132 |
+
**Group 2: Area Coverage Metrics (`_calculate_area_metrics`)**
|
| 133 |
+
* **`total_area_covered`**: The sum of the areas of all circles ($\sum \pi r^2$). Provides a direct measure of the total physical space occupied by the circles. Directly correlates with the primary score (sum of radii) but gives insight into the actual area coverage.
|
| 134 |
+
* **`packing_density`**: `total_area_covered` divided by the area of the unit square (which is 1). Expresses the proportion of the unit square covered by circles (0-1 range). Higher is better.
|
| 135 |
+
* **`empty_space_ratio`**: 1 - `packing_density`. Represents the proportion of the unit square not covered by circles.
|
| 136 |
+
|
| 137 |
+
**Group 3: Boundary Proximity (`_calculate_boundary_proximity`)**
|
| 138 |
+
* **`num_circles_touching_boundary`**: Count of circles whose edge is within a small epsilon (1e-4) of any of the unit square boundaries (x=0, x=1, y=0, y=1). Indicates how many circles are actively utilizing the perimeter of the packing area.
|
| 139 |
+
* **`avg_min_distance_to_boundary`**: The average of the minimum distances from each circle's edge to its closest unit square boundary. Lower values indicate tighter packing against the container walls, which is generally desirable for dense packing.
|
| 140 |
+
|
| 141 |
+
**Group 4: Advanced Spatial and Contact Metrics (`_calculate_advanced_spatial_and_contact_metrics`)**
|
| 142 |
+
* **`packing_aspect_ratio_of_centers_bbox`**: The aspect ratio of the bounding box that encloses all circle centers. A value close to 1 indicates a more square-like distribution of centers, while values far from 1 suggest a more elongated or asymmetric arrangement. This can hint at the overall shape of the packing.
|
| 143 |
+
* **`avg_num_touching_neighbors`**: The average number of other circles a given circle is "touching" (within a tolerance of 1e-6). A higher value suggests a more interconnected and rigid packing structure, often associated with higher density.
|
| 144 |
+
* **`avg_quadrant_radii_std_dev`**: The average of the standard deviations of radii within each of the four quadrants of the unit square. This metric helps assess the uniformity of circle sizes across different regions of the packing. Lower values might indicate more homogeneous packing in different regions.
|
| 145 |
+
|
| 146 |
+
**Group 5: Pairwise Center Distances (`_calculate_pairwise_center_distances`)**
|
| 147 |
+
* **`avg_pairwise_center_distance`**: The average Euclidean distance between the centers of all unique pairs of circles. Provides a general measure of how spread out or clustered the circle centers are. Smaller values indicate more tightly clustered centers.
|
| 148 |
+
|
| 149 |
+
**Group 6: Center Distribution Metrics (`_calculate_center_distribution_metrics`)**
|
| 150 |
+
* **`avg_distance_from_packing_centroid_normalized`**: The average distance of each circle center from the centroid of all circle centers, normalized by the maximum possible distance from the unit square center to a corner ($\sqrt{0.5}$). Measures how concentrated or dispersed the circles are around their collective center. Lower values suggest a more centrally concentrated packing.
|
| 151 |
+
|
| 152 |
+
**Group 7: Boundary Contact Metrics (`_calculate_boundary_contact_metrics`)**
|
| 153 |
+
* **`num_circles_touching_edge`**: The count of circles that have at least one edge touching any of the four unit square boundaries (within a tolerance of 1e-6).
|
| 154 |
+
* **`num_circles_touching_corner`**: The count of circles that have edges touching two orthogonal boundaries, thus indicating contact with a corner of the unit square (within a tolerance of 1e-6).
|
| 155 |
+
|
| 156 |
+
**Insights and Recommendations for Generation 185:**
|
| 157 |
+
|
| 158 |
+
At Generation 185, the evolution is likely in the "Optimization" or "Convergence" phase. The primary metric focuses solely on maximizing the sum of radii (implicitly, the `total_area_covered`). These auxiliary metrics provide a multi-faceted view of the solutions.
|
| 159 |
+
|
| 160 |
+
* **Radii Distribution**: Analyzing `avg_radius`, `std_dev_radius`, and `num_unique_radii` can reveal if the evolutionary process is settling on a particular strategy regarding circle sizes (e.g., solutions with many small circles vs. a few large ones and some fillers). If the `std_dev_radius` is increasing at this late stage, it might suggest the model is still exploring diverse size combinations, which could be beneficial.
|
| 161 |
+
* **Packing Density and Boundary Utilization**: While `packing_density` directly relates to the primary score, `num_circles_touching_boundary` and `avg_min_distance_to_boundary` offer critical insight into *how* that density is achieved. Optimal solutions often tightly utilize boundaries. If `num_circles_touching_boundary` is low, or `avg_min_distance_to_boundary` is high, it indicates missed opportunities for denser packing by not fully leveraging the container edges.
|
| 162 |
+
* **Structural Efficiency**: `avg_num_touching_neighbors` is a key indicator of structural rigidity and efficiency. Higher values generally mean a more stable and tightly interlocked packing. `packing_aspect_ratio_of_centers_bbox` can detect if solutions are becoming overly elongated or clustered in one part of the square, which might leave empty spaces in others.
|
| 163 |
+
* **Spatial Uniformity**: `avg_quadrant_radii_std_dev` can highlight imbalances in radii distribution across different parts of the square. A high value might indicate that the packing is not uniform, potentially leading to localized inefficiencies. Similarly, `avg_distance_from_packing_centroid_normalized` helps understand if the packing is centrally clustered or more spread out.
|
| 164 |
+
|
| 165 |
+
These metrics collectively help diagnose whether the current high primary scores are due to truly efficient, well-distributed packing or perhaps just a clever arrangement of a few large circles that might still have room for refinement in terms of overall structural integrity and uniform space utilization. If progress on the primary metric plateaus, these auxiliary metrics can point to specific areas for improvement (e.g., better boundary contact, more uniform radii distribution, or more circles making contact with each other). This can guide future evolutionary steps toward more robust and optimal solutions.
|
| 166 |
+
|
| 167 |
+
**Spatial Arrangement Metrics:**
|
| 168 |
+
* **`avg_nearest_neighbor_distance_centers`**: This metric calculates the average Euclidean distance from each circle's center to the center of its closest neighboring circle. A lower value indicates a more tightly packed or clustered arrangement of circle centers, while a higher value suggests a more dispersed or sparse distribution. This is useful for identifying if the packing strategy is creating dense clusters or leaving significant gaps between groups of circles.
|
| 169 |
+
* **`center_quadrant_density_variance`**: This metric divides the unit square into four equal quadrants (2x2 grid) and computes the variance of the number of circle centers found within each quadrant. A lower variance indicates a more uniform distribution of circle centers across the entire packing area, suggesting a balanced utilization of space. A higher variance implies that circle centers are concentrated in certain quadrants, potentially indicating inefficient or uneven packing.
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
## Generation 62 Evaluation
|
| 173 |
+
|
| 174 |
+
**Auxiliary Metrics Implemented and Refined:**
|
| 175 |
+
|
| 176 |
+
**Group 1: Packing Efficiency & Structural Metrics**
|
| 177 |
+
* **`empty_space_ratio`**: Represents the proportion of the unit square's area not covered by circles. This directly indicates the amount of "wasted" space. A consistently decreasing `empty_space_ratio` would indicate improving packing efficiency beyond just the sum of radii.
|
| 178 |
+
* **`packing_aspect_ratio_of_centers_bbox`**: This calculates the aspect ratio of the bounding box that encloses all circle centers. It helps determine if the distribution of circles is becoming more square-like (aspect ratio closer to 1) or more elongated, which could indicate a preferred packing orientation. This is useful for detecting structural biases.
|
| 179 |
+
* **`avg_num_touching_neighbors`**: The average number of circles that each circle is touching (within a small tolerance). A higher value suggests a more interconnected and tightly packed arrangement, which is usually a characteristic of efficient packing. This can act as a proxy for the 'connectivity' of the packing.
|
| 180 |
+
* **`avg_quadrant_radii_std_dev`**: The average of the standard deviations of radii within each of the four quadrants. This helps reveal if the diversity in circle sizes is localized to certain areas or if it's generally consistent across the entire packing. High values in specific quadrants might point to localized strategies for filling space.
|
| 181 |
+
* **`avg_pairwise_center_distance`**: The average Euclidean distance between all unique pairs of circle centers. A higher value suggests a more spread-out arrangement of circles, potentially indicating a less "clustered" or more uniform distribution of centers. This complements nearest-neighbor distance by considering all pairs and provides a global measure of center dispersion.
|
| 182 |
+
|
| 183 |
+
**Observations & Recommendations:**
|
| 184 |
+
With generation 83, the evolution process is likely in the "Optimization" or "Convergence" stage. The existing metrics cover basic packing efficiency, radii statistics, boundary proximity, and local spatial distribution. The newly added `avg_distance_from_unit_center_normalized` and `avg_pairwise_center_distance` will provide:
|
| 185 |
+
1. **Insight into Centralization vs. Edge-utilization**: By normalizing the average distance from the center, we can better track if the solutions are becoming more or less centrally focused, offering a nuanced view beyond just boundary violations.
|
| 186 |
+
2. **Global Dispersion of Centers**: The average pairwise distance offers a macro-level understanding of how spread out the centers are, which can reveal patterns like grid-like structures versus more irregular, clustered arrangements. This is crucial for understanding the overall spatial strategy of the packing, which the primary metric (sum of radii) might not fully capture.
|
| 187 |
+
These metrics can help identify if the evolution is settling into specific positional patterns and offer guidance on encouraging more diverse exploration if stagnation occurs.
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
## Generation 103 Evaluation
|
| 191 |
+
|
| 192 |
+
**Auxiliary Metrics Implemented:**
|
| 193 |
+
|
| 194 |
+
**Group 1: Packing Efficiency**
|
| 195 |
+
* **`total_area_covered`**: The sum of the areas of all 26 circles (π * r^2 for each). This measures the total physical space occupied by the circles within the unit square. A higher value indicates better space utilization, offering a direct view of how much of the container is 'filled'. This metric complements the primary `sum(radii)` by considering the squared impact of radius on area.
|
| 196 |
+
|
| 197 |
+
**Group 2: Radii Statistics**
|
| 198 |
+
* **`avg_radius`**: The average radius of the 26 circles. This provides a central tendency for circle sizes, helping to understand if the solution favors larger, smaller, or uniformly sized circles.
|
| 199 |
+
* **`std_dev_radius`**: The standard deviation of the radii. This measures the dispersion or diversity in circle sizes. A high standard deviation suggests a mix of very large and very small circles, potentially indicating a strategy to fill irregular spaces. A low standard deviation implies more uniform circle sizes.
|
| 200 |
+
* **`min_radius`**: The smallest radius among the 26 circles. Useful for identifying if very small circles are being used to fill tiny gaps.
|
| 201 |
+
* **`max_radius`**: The largest radius among the 26 circles. Indicates the largest circle size successfully placed, which is often a key factor in maximizing total radius sum.
|
| 202 |
+
|
| 203 |
+
**Group 3: Boundary Proximity**
|
| 204 |
+
* **`avg_min_boundary_distance`**: The average of the minimum distances from the edge of each circle to the nearest boundary of the unit square. Smaller (closer to zero) values indicate that circles are packed more tightly against the boundaries, suggesting efficient utilization of the container's edges. This helps assess how well the solution leverages the container's perimeter, which is crucial for optimal packing.
|
| 205 |
+
|
| 206 |
+
**Observations and Recommendations for Generation 103:**
|
| 207 |
+
|
| 208 |
+
At Generation 103, the evolution is deep into the **Optimization/Convergence** stage. The primary score (sum of radii) might be showing diminishing returns. These auxiliary metrics are designed to provide a multi-faceted view of the packing structure:
|
| 209 |
+
|
| 210 |
+
* **`total_area_covered`**: By tracking this, we can see if increases in `sum(radii)` are also leading to proportional increases in actual area covered. If `sum(radii)` plateaus but `total_area_covered` continues to rise (or maintains a high level), it suggests a more efficient use of space by re-distributing radii or centers to fill gaps more effectively.
|
| 211 |
+
* **`std_dev_radius`**: This metric is critical for identifying potential local optima. If `std_dev_radius` is very low and `sum(radii)` is stagnating, it might suggest the algorithm is stuck with uniform-sized circles. Encouraging greater `std_dev_radius` could lead to exploring more diverse solutions (e.g., using a few large circles and many small ones) that might break current plateaus. Conversely, if `std_dev_radius` is high, it shows exploration of diverse sizes is still active.
|
| 212 |
+
* **`avg_min_boundary_distance`**: As solutions converge, an efficient packing should optimally utilize the boundaries. A consistently low (approaching zero) `avg_min_boundary_distance` combined with a high primary score indicates a well-optimized solution that is effectively using the edges of the unit square. If this value is still relatively high, it might suggest opportunities to push circles closer to the container's perimeter, thereby creating more space internally for larger circles or more circles.
|
| 213 |
+
|
| 214 |
+
These metrics, especially in conjunction with historical data, can help diagnose whether the evolution is making structural improvements that aren't immediately reflected in the primary score, or if it's truly stagnating and needs a new evolutionary pressure or strategy.
|
| 215 |
+
|
| 216 |
+
## Generation 124 Evaluation
|
| 217 |
+
|
| 218 |
+
**Auxiliary Metrics Implemented:**
|
| 219 |
+
|
| 220 |
+
**Group 7: Spatial Distribution and Boundary Contact**
|
| 221 |
+
* **`avg_distance_from_packing_centroid_normalized`**: Measures how concentrated or dispersed the circle centers are. It calculates the average distance of each circle center from the overall centroid of all circle centers, normalized by the maximum possible distance a center could be from the unit square's center (0.5, 0.5) to a corner (0,0). A lower value indicates a more centrally focused and compact packing.
|
| 222 |
+
* **`num_circles_touching_edge`**: Counts the number of circles whose edges are "touching" (within a small tolerance of 1e-6) any of the four boundaries of the unit square. A higher count indicates more aggressive utilization of the available space by pushing circles against the container walls.
|
| 223 |
+
* **`num_circles_touching_corner`**: Counts the number of circles whose edges are "touching" (within a small tolerance of 1e-6) two perpendicular boundaries (i.e., a corner) of the unit square. _A more specific measure of boundary utilization, indicating how well corners are filled._
|
| 224 |
+
|
| 225 |
+
**Observations and Recommendations for Generation 124:**
|
| 226 |
+
|
| 227 |
+
At Generation 124, the evolution process is likely in an **OPTIMIZATION** or **CONVERGENCE** stage, where solutions are generally valid and the focus is on performance gains. The primary metric (`reported_sum_of_radii`) guides towards maximizing total radius.
|
| 228 |
+
|
| 229 |
+
These new metrics provide insights into *how* the packing is achieved:
|
| 230 |
+
* `avg_distance_from_packing_centroid_normalized`: Can help identify if solutions are becoming more centrally organized or if they are spreading out. For circle packing, a more compact central arrangement might be beneficial. If this metric shows a trend (e.g., decreasing), it suggests a more centralized packing approach.
|
| 231 |
+
* `num_circles_touching_edge` and `num_circles_touching_corner`: Are crucial for understanding boundary utilization. Optimal packings often involve many circles touching the boundaries. If these numbers are low, it might indicate sub-optimal use of space near the edges, and the evolution could benefit from strategies that encourage boundary contacts. Tracking the trend of these metrics can show if solutions are learning to "grip" the container more effectively.
|
| 232 |
+
|
| 233 |
+
These metrics complement the primary score by shedding light on the structural properties of the packing, which can be critical for escaping local optima or guiding further refinement.
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/auxiliary_metrics.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import json
|
| 4 |
+
import math
|
| 5 |
+
from typing import Dict, Any
|
| 6 |
+
|
| 7 |
+
def evaluate_aux(results_dir: str) -> Dict[str, Any]:
|
| 8 |
+
"""
|
| 9 |
+
Main entry point for all auxiliary metrics.
|
| 10 |
+
This function will be automatically called by the evaluation service.
|
| 11 |
+
"""
|
| 12 |
+
metrics = {}
|
| 13 |
+
try:
|
| 14 |
+
data = _load_generation_data(results_dir)
|
| 15 |
+
if data is None or data["num_circles"] == 0:
|
| 16 |
+
# Return a dict with all metrics indicating data not found or no circles
|
| 17 |
+
# Initialize all possible metrics to 0 or appropriate default
|
| 18 |
+
# This ensures a flat dict is always returned, even on error or empty data
|
| 19 |
+
return {
|
| 20 |
+
"avg_radius": 0.0,
|
| 21 |
+
"std_dev_radius": 0.0,
|
| 22 |
+
"min_radius": 0.0,
|
| 23 |
+
"max_radius": 0.0,
|
| 24 |
+
"median_radius": 0.0,
|
| 25 |
+
"num_unique_radii": 0,
|
| 26 |
+
"total_area_covered": 0.0,
|
| 27 |
+
"packing_density": 0.0,
|
| 28 |
+
"empty_space_ratio": 1.0,
|
| 29 |
+
"max_circle_overlap_magnitude": 0.0,
|
| 30 |
+
"num_overlapping_pairs": 0,
|
| 31 |
+
"max_boundary_violation_magnitude": 0.0,
|
| 32 |
+
"num_boundary_violations": 0,
|
| 33 |
+
"packing_aspect_ratio_of_centers_bbox": 1.0,
|
| 34 |
+
"avg_num_touching_neighbors": 0.0,
|
| 35 |
+
"avg_quadrant_radii_std_dev": 0.0,
|
| 36 |
+
"avg_pairwise_center_distance": 0.0,
|
| 37 |
+
"avg_distance_from_packing_centroid_normalized": 0.0,
|
| 38 |
+
"num_circles_touching_edge": 0,
|
| 39 |
+
"num_circles_touching_corner": 0,
|
| 40 |
+
"avg_min_circle_to_boundary_distance": 0.0,
|
| 41 |
+
"error": "Failed to load generation data (extra.npz not found or corrupted) or no circles present."
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
# Aggregate all metrics in one place
|
| 45 |
+
metrics = {}
|
| 46 |
+
|
| 47 |
+
# Group 1: Radii Statistics
|
| 48 |
+
metrics.update(_calculate_radii_stats(data))
|
| 49 |
+
|
| 50 |
+
# Group 2: Area Coverage
|
| 51 |
+
metrics.update(_calculate_area_metrics(data))
|
| 52 |
+
|
| 53 |
+
# Group 3: Violation Metrics (useful even if primary says "correct" to see closeness to boundary)
|
| 54 |
+
metrics.update(_calculate_violation_metrics(data))
|
| 55 |
+
|
| 56 |
+
# Group 4: Advanced Spatial and Contact Metrics
|
| 57 |
+
metrics.update(_calculate_advanced_spatial_and_contact_metrics(data))
|
| 58 |
+
|
| 59 |
+
# Group 5: Pairwise Center Distances
|
| 60 |
+
metrics.update(_calculate_pairwise_center_distances(data))
|
| 61 |
+
|
| 62 |
+
# Group 6: Center Distribution Metrics
|
| 63 |
+
metrics.update(_calculate_center_distribution_metrics(data))
|
| 64 |
+
|
| 65 |
+
# Group 7: Boundary Contact Metrics
|
| 66 |
+
metrics.update(_calculate_boundary_contact_metrics(data))
|
| 67 |
+
|
| 68 |
+
# Group 8: Boundary Proximity (custom implementation)
|
| 69 |
+
try:
|
| 70 |
+
metrics.update(_calculate_boundary_proximity(data))
|
| 71 |
+
except Exception as e:
|
| 72 |
+
metrics["boundary_proximity_error"] = str(e)
|
| 73 |
+
|
| 74 |
+
except Exception as e:
|
| 75 |
+
metrics["evaluation_error"] = str(e)
|
| 76 |
+
metrics["failed_at"] = "evaluate_aux"
|
| 77 |
+
|
| 78 |
+
return metrics
|
| 79 |
+
|
| 80 |
+
# --- Helper functions ---
|
| 81 |
+
|
| 82 |
+
def _load_generation_data(results_dir: str) -> Dict[str, Any] | None:
|
| 83 |
+
"""
|
| 84 |
+
Load common data files (e.g., extra.npz, metrics.json).
|
| 85 |
+
Handles cases where data might be missing or empty gracefully.
|
| 86 |
+
"""
|
| 87 |
+
extra_npz_path = os.path.join(results_dir, "extra.npz")
|
| 88 |
+
metrics_json_path = os.path.join(results_dir, "metrics.json")
|
| 89 |
+
|
| 90 |
+
centers = np.array([])
|
| 91 |
+
radii = np.array([])
|
| 92 |
+
reported_sum = 0.0
|
| 93 |
+
|
| 94 |
+
if os.path.exists(extra_npz_path):
|
| 95 |
+
try:
|
| 96 |
+
with np.load(extra_npz_path) as data_npz:
|
| 97 |
+
centers = data_npz["centers"]
|
| 98 |
+
radii = data_npz["radii"]
|
| 99 |
+
reported_sum = data_npz.get("reported_sum", 0.0) # Use .get for robustness
|
| 100 |
+
except Exception as e:
|
| 101 |
+
print(f"Error loading extra.npz: {e}")
|
| 102 |
+
|
| 103 |
+
primary_score = None
|
| 104 |
+
if os.path.exists(metrics_json_path):
|
| 105 |
+
try:
|
| 106 |
+
with open(metrics_json_path, 'r') as f:
|
| 107 |
+
metrics_data = json.load(f)
|
| 108 |
+
primary_score = metrics_data.get("combined_score")
|
| 109 |
+
except Exception as e:
|
| 110 |
+
print(f"Error loading metrics.json: {e}")
|
| 111 |
+
|
| 112 |
+
# Always return a dict, even if no circles or data is missing.
|
| 113 |
+
# This allows downstream functions to handle empty arrays without immediately failing.
|
| 114 |
+
num_circles = len(radii) if radii.size > 0 else 0
|
| 115 |
+
|
| 116 |
+
return {
|
| 117 |
+
"centers": centers,
|
| 118 |
+
"radii": radii,
|
| 119 |
+
"reported_sum": reported_sum,
|
| 120 |
+
"primary_score": primary_score,
|
| 121 |
+
"num_circles": num_circles
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
def _calculate_boundary_proximity(data: Dict[str, Any]) -> Dict[str, Any]:
|
| 125 |
+
"""
|
| 126 |
+
Calculates metrics related to how close circles are to the boundaries of the unit square.
|
| 127 |
+
- `num_circles_touching_boundary`: Count of circles whose edge is within epsilon of a boundary.
|
| 128 |
+
- `avg_min_distance_to_boundary`: Average of the minimum distance from each circle's edge to any boundary.
|
| 129 |
+
"""
|
| 130 |
+
metrics = {}
|
| 131 |
+
try:
|
| 132 |
+
centers = data["centers"]
|
| 133 |
+
radii = data["radii"]
|
| 134 |
+
n_circles = data["num_circles"]
|
| 135 |
+
|
| 136 |
+
epsilon = 1e-4 # Threshold for considering a circle 'touching' the boundary
|
| 137 |
+
|
| 138 |
+
if n_circles == 0:
|
| 139 |
+
metrics["num_circles_touching_boundary"] = 0
|
| 140 |
+
metrics["avg_min_distance_to_boundary"] = 1.0 # Max possible distance (assuming unit square)
|
| 141 |
+
return metrics
|
| 142 |
+
|
| 143 |
+
touching_count = 0
|
| 144 |
+
min_distances_to_boundary = []
|
| 145 |
+
|
| 146 |
+
for i in range(n_circles):
|
| 147 |
+
x, y = centers[i]
|
| 148 |
+
r = radii[i]
|
| 149 |
+
|
| 150 |
+
# Distances from circle edge to boundaries
|
| 151 |
+
dist_left = x - r
|
| 152 |
+
dist_right = 1 - (x + r)
|
| 153 |
+
dist_bottom = y - r
|
| 154 |
+
dist_top = 1 - (y + r)
|
| 155 |
+
|
| 156 |
+
current_min_dist = min(dist_left, dist_right, dist_bottom, dist_top)
|
| 157 |
+
min_distances_to_boundary.append(current_min_dist)
|
| 158 |
+
|
| 159 |
+
if current_min_dist <= epsilon:
|
| 160 |
+
touching_count += 1
|
| 161 |
+
|
| 162 |
+
metrics["num_circles_touching_boundary"] = touching_count
|
| 163 |
+
metrics["avg_min_distance_to_boundary"] = float(np.mean(min_distances_to_boundary)) if min_distances_to_boundary else 1.0
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
metrics["boundary_proximity_error"] = str(e)
|
| 167 |
+
return metrics
|
| 168 |
+
|
| 169 |
+
def _calculate_radii_stats(data: Dict[str, Any]) -> Dict[str, float]:
|
| 170 |
+
"""Calculate basic statistical metrics for circle radii."""
|
| 171 |
+
metrics = {}
|
| 172 |
+
try:
|
| 173 |
+
radii = data["radii"]
|
| 174 |
+
if radii.size > 0:
|
| 175 |
+
metrics["avg_radius"] = float(np.mean(radii))
|
| 176 |
+
metrics["std_dev_radius"] = float(np.std(radii))
|
| 177 |
+
metrics["min_radius"] = float(np.min(radii))
|
| 178 |
+
metrics["max_radius"] = float(np.max(radii))
|
| 179 |
+
metrics["median_radius"] = float(np.median(radii))
|
| 180 |
+
metrics["num_unique_radii"] = int(len(np.unique(radii)))
|
| 181 |
+
if metrics["avg_radius"] > 0:
|
| 182 |
+
metrics["radius_coefficient_of_variation"] = float(metrics["std_dev_radius"] / metrics["avg_radius"])
|
| 183 |
+
else:
|
| 184 |
+
metrics["radius_coefficient_of_variation"] = 0.0
|
| 185 |
+
else:
|
| 186 |
+
metrics["avg_radius"] = 0.0
|
| 187 |
+
metrics["std_dev_radius"] = 0.0
|
| 188 |
+
metrics["min_radius"] = 0.0
|
| 189 |
+
metrics["max_radius"] = 0.0
|
| 190 |
+
metrics["median_radius"] = 0.0
|
| 191 |
+
metrics["num_unique_radii"] = 0
|
| 192 |
+
metrics["radius_coefficient_of_variation"] = 0.0
|
| 193 |
+
metrics["radii_stats_info"] = "No radii data available for stats."
|
| 194 |
+
except Exception as e:
|
| 195 |
+
metrics["radii_stats_error"] = str(e)
|
| 196 |
+
return metrics
|
| 197 |
+
|
| 198 |
+
def _calculate_area_metrics(data: Dict[str, Any]) -> Dict[str, float]:
|
| 199 |
+
"""Calculate metrics related to the total area covered by circles."""
|
| 200 |
+
metrics = {}
|
| 201 |
+
try:
|
| 202 |
+
radii = data["radii"]
|
| 203 |
+
if radii.size > 0:
|
| 204 |
+
total_circle_area = np.sum(math.pi * (radii ** 2))
|
| 205 |
+
unit_square_area = 1.0 # Unit square is [0,1]x[0,1]
|
| 206 |
+
|
| 207 |
+
metrics["total_area_covered"] = float(total_circle_area)
|
| 208 |
+
metrics["packing_density"] = float(total_circle_area / unit_square_area)
|
| 209 |
+
metrics["empty_space_ratio"] = float(1.0 - metrics["packing_density"])
|
| 210 |
+
|
| 211 |
+
else:
|
| 212 |
+
metrics["total_area_covered"] = 0.0
|
| 213 |
+
metrics["packing_density"] = 0.0
|
| 214 |
+
metrics["empty_space_ratio"] = 1.0
|
| 215 |
+
metrics["area_metrics_info"] = "No radii data available to calculate area metrics."
|
| 216 |
+
except Exception as e:
|
| 217 |
+
metrics["area_metrics_error"] = str(e)
|
| 218 |
+
return metrics
|
| 219 |
+
|
| 220 |
+
def _calculate_violation_metrics(data: Dict[str, Any]) -> Dict[str, Any]:
|
| 221 |
+
"""Calculate metrics related to overlaps and boundary violations.
|
| 222 |
+
These are typically boolean for passing primary eval, but magnitudes give insight.
|
| 223 |
+
"""
|
| 224 |
+
metrics = {}
|
| 225 |
+
try:
|
| 226 |
+
centers = data["centers"]
|
| 227 |
+
radii = data["radii"]
|
| 228 |
+
n_circles = data["num_circles"]
|
| 229 |
+
|
| 230 |
+
if n_circles == 0:
|
| 231 |
+
metrics["max_circle_overlap_magnitude"] = 0.0
|
| 232 |
+
metrics["num_overlapping_pairs"] = 0
|
| 233 |
+
metrics["max_boundary_violation_magnitude"] = 0.0
|
| 234 |
+
metrics["num_boundary_violations"] = 0
|
| 235 |
+
metrics["violation_metrics_info"] = "No circles to check for violations."
|
| 236 |
+
return metrics
|
| 237 |
+
|
| 238 |
+
max_circle_overlap_magnitude = 0.0
|
| 239 |
+
num_overlapping_pairs = 0
|
| 240 |
+
|
| 241 |
+
# Circle-circle overlaps
|
| 242 |
+
for i in range(n_circles):
|
| 243 |
+
for j in range(i + 1, n_circles):
|
| 244 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 245 |
+
overlap = radii[i] + radii[j] - dist
|
| 246 |
+
if overlap > 0: # An overlap is a violation
|
| 247 |
+
num_overlapping_pairs += 1
|
| 248 |
+
if overlap > max_circle_overlap_magnitude:
|
| 249 |
+
max_circle_overlap_magnitude = overlap
|
| 250 |
+
|
| 251 |
+
metrics["max_circle_overlap_magnitude"] = float(max_circle_overlap_magnitude)
|
| 252 |
+
metrics["num_overlapping_pairs"] = num_overlapping_pairs
|
| 253 |
+
|
| 254 |
+
max_boundary_violation_magnitude = 0.0
|
| 255 |
+
num_boundary_violations = 0
|
| 256 |
+
|
| 257 |
+
# Boundary violations
|
| 258 |
+
for i in range(n_circles):
|
| 259 |
+
x, y = centers[i]
|
| 260 |
+
r = radii[i]
|
| 261 |
+
|
| 262 |
+
# Calculate potential violations for each boundary
|
| 263 |
+
violations_per_circle = [
|
| 264 |
+
r - x, # Left boundary (x - r < 0 => r - x > 0)
|
| 265 |
+
x + r - 1.0, # Right boundary (x + r > 1 => x + r - 1 > 0)
|
| 266 |
+
r - y, # Bottom boundary (y - r < 0 => r - y > 0)
|
| 267 |
+
y + r - 1.0, # Top boundary (y + r > 1 => y + r - 1 > 0)
|
| 268 |
+
]
|
| 269 |
+
|
| 270 |
+
max_violation_for_circle = 0.0
|
| 271 |
+
for v in violations_per_circle:
|
| 272 |
+
if v > 1e-6: # Consider a violation if it's significantly positive
|
| 273 |
+
num_boundary_violations += 1
|
| 274 |
+
if v > max_violation_for_circle:
|
| 275 |
+
max_violation_for_circle = v
|
| 276 |
+
|
| 277 |
+
if max_violation_for_circle > max_boundary_violation_magnitude:
|
| 278 |
+
max_boundary_violation_magnitude = max_violation_for_circle
|
| 279 |
+
|
| 280 |
+
metrics["max_boundary_violation_magnitude"] = float(max_boundary_violation_magnitude)
|
| 281 |
+
metrics["num_boundary_violations"] = num_boundary_violations
|
| 282 |
+
|
| 283 |
+
except Exception as e:
|
| 284 |
+
metrics["violation_metrics_error"] = str(e)
|
| 285 |
+
return metrics
|
| 286 |
+
|
| 287 |
+
def _calculate_advanced_spatial_and_contact_metrics(data: Dict[str, Any]) -> Dict[str, Any]:
|
| 288 |
+
"""
|
| 289 |
+
Calculate advanced spatial distribution metrics:
|
| 290 |
+
1. Aspect Ratio of Bounding Box of Circle Centers
|
| 291 |
+
2. Average Number of Touching Neighbors
|
| 292 |
+
3. Variance of Radii per Quadrant
|
| 293 |
+
"""
|
| 294 |
+
metrics = {}
|
| 295 |
+
try:
|
| 296 |
+
centers = data["centers"]
|
| 297 |
+
radii = data["radii"]
|
| 298 |
+
n_circles = data["num_circles"]
|
| 299 |
+
|
| 300 |
+
if n_circles == 0:
|
| 301 |
+
metrics["packing_aspect_ratio_of_centers_bbox"] = 1.0
|
| 302 |
+
metrics["avg_num_touching_neighbors"] = 0.0
|
| 303 |
+
metrics["avg_quadrant_radii_std_dev"] = 0.0
|
| 304 |
+
metrics["advanced_spatial_and_contact_metrics_info"] = "No circles for advanced spatial metrics."
|
| 305 |
+
return metrics
|
| 306 |
+
|
| 307 |
+
# Metric 1: Aspect Ratio of Bounding Box of Circle Centers
|
| 308 |
+
if n_circles > 1:
|
| 309 |
+
min_x, max_x = np.min(centers[:, 0]), np.max(centers[:, 0])
|
| 310 |
+
min_y, max_y = np.min(centers[:, 1]), np.max(centers[:, 1])
|
| 311 |
+
|
| 312 |
+
width = max_x - min_x
|
| 313 |
+
height = max_y - min_y
|
| 314 |
+
|
| 315 |
+
# Avoid division by zero if all centers are on a line
|
| 316 |
+
if width > 1e-6 and height > 1e-6:
|
| 317 |
+
aspect_ratio = max(width, height) / min(width, height)
|
| 318 |
+
else: # If all points are on a line, or single point, aspect ratio is effectively infinite or 1
|
| 319 |
+
aspect_ratio = 1.0
|
| 320 |
+
metrics["packing_aspect_ratio_of_centers_bbox"] = float(aspect_ratio)
|
| 321 |
+
else:
|
| 322 |
+
metrics["packing_aspect_ratio_of_centers_bbox"] = 1.0 # Single circle, assume perfect aspect ratio
|
| 323 |
+
|
| 324 |
+
# Metric 2: Contact Count (Average Number of Touching Neighbors)
|
| 325 |
+
contact_counts = np.zeros(n_circles)
|
| 326 |
+
touch_tolerance = 1e-6
|
| 327 |
+
|
| 328 |
+
for i in range(n_circles):
|
| 329 |
+
for j in range(i + 1, n_circles):
|
| 330 |
+
dist_centers = np.linalg.norm(centers[i] - centers[j])
|
| 331 |
+
sum_radii = radii[i] + radii[j]
|
| 332 |
+
|
| 333 |
+
if np.isclose(dist_centers, sum_radii, atol=touch_tolerance):
|
| 334 |
+
contact_counts[i] += 1
|
| 335 |
+
contact_counts[j] += 1
|
| 336 |
+
|
| 337 |
+
metrics["avg_num_touching_neighbors"] = float(np.mean(contact_counts)) if n_circles > 0 else 0.0
|
| 338 |
+
|
| 339 |
+
# Metric 3: Variance of Radii per Quadrant
|
| 340 |
+
quadrant_radii = [[], [], [], []]
|
| 341 |
+
for i in range(n_circles):
|
| 342 |
+
x, y = centers[i]
|
| 343 |
+
r = radii[i]
|
| 344 |
+
|
| 345 |
+
if x < 0.5 and y < 0.5:
|
| 346 |
+
quadrant_radii[0].append(r)
|
| 347 |
+
elif x >= 0.5 and y < 0.5:
|
| 348 |
+
quadrant_radii[1].append(r)
|
| 349 |
+
elif x < 0.5 and y >= 0.5:
|
| 350 |
+
quadrant_radii[2].append(r)
|
| 351 |
+
elif x >= 0.5 and y >= 0.5:
|
| 352 |
+
quadrant_radii[3].append(r)
|
| 353 |
+
|
| 354 |
+
quadrant_radii_std_devs = [
|
| 355 |
+
np.std(q) for q in quadrant_radii if len(q) > 1
|
| 356 |
+
] # Only calculate std dev if more than one radius in quadrant
|
| 357 |
+
|
| 358 |
+
metrics["avg_quadrant_radii_std_dev"] = float(np.mean(quadrant_radii_std_devs)) if quadrant_radii_std_devs else 0.0
|
| 359 |
+
|
| 360 |
+
except Exception as e:
|
| 361 |
+
metrics["advanced_spatial_and_contact_metrics_error"] = str(e)
|
| 362 |
+
return metrics
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def _calculate_pairwise_center_distances(data: Dict[str, Any]) -> Dict[str, float]:
|
| 366 |
+
"""Calculate the average pairwise distance between all circle centers."""
|
| 367 |
+
metrics = {}
|
| 368 |
+
try:
|
| 369 |
+
centers = data["centers"]
|
| 370 |
+
n_circles = data["num_circles"]
|
| 371 |
+
|
| 372 |
+
if n_circles < 2:
|
| 373 |
+
metrics["avg_pairwise_center_distance"] = 0.0
|
| 374 |
+
metrics["pairwise_center_distance_info"] = "Need at least 2 circles for pairwise distance."
|
| 375 |
+
return metrics
|
| 376 |
+
|
| 377 |
+
distances = []
|
| 378 |
+
for i in range(n_circles):
|
| 379 |
+
for j in range(i + 1, n_circles):
|
| 380 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 381 |
+
distances.append(dist)
|
| 382 |
+
|
| 383 |
+
if distances:
|
| 384 |
+
metrics["avg_pairwise_center_distance"] = float(np.mean(distances))
|
| 385 |
+
else:
|
| 386 |
+
metrics["avg_pairwise_center_distance"] = 0.0
|
| 387 |
+
|
| 388 |
+
except Exception as e:
|
| 389 |
+
metrics["error_pairwise_center_distance"] = str(e)
|
| 390 |
+
return metrics
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def _calculate_center_distribution_metrics(data: Dict[str, Any]) -> Dict[str, float]:
|
| 394 |
+
"""
|
| 395 |
+
Calculate metrics related to the spatial distribution and concentration of circle centers.
|
| 396 |
+
"""
|
| 397 |
+
metrics = {}
|
| 398 |
+
try:
|
| 399 |
+
centers = data["centers"]
|
| 400 |
+
n_circles = data["num_circles"]
|
| 401 |
+
|
| 402 |
+
if n_circles == 0:
|
| 403 |
+
metrics["avg_distance_from_packing_centroid_normalized"] = 0.0
|
| 404 |
+
metrics["center_distribution_info"] = "No circles for center distribution metrics."
|
| 405 |
+
return metrics
|
| 406 |
+
|
| 407 |
+
# Calculate packing centroid
|
| 408 |
+
packing_centroid = np.mean(centers, axis=0)
|
| 409 |
+
|
| 410 |
+
# Calculate average distance from packing centroid
|
| 411 |
+
distances_from_centroid = np.linalg.norm(centers - packing_centroid, axis=1)
|
| 412 |
+
avg_dist_from_centroid = np.mean(distances_from_centroid)
|
| 413 |
+
|
| 414 |
+
# Normalize by max possible distance from unit square center (0.5, 0.5) to corner (0,0)
|
| 415 |
+
# Max distance from any point in unit square to (0.5, 0.5) is sqrt(0.5^2 + 0.5^2) = sqrt(0.5)
|
| 416 |
+
max_possible_dist_from_unit_center = math.sqrt(0.5)
|
| 417 |
+
|
| 418 |
+
metrics["avg_distance_from_packing_centroid_normalized"] = float(avg_dist_from_centroid / max_possible_dist_from_unit_center)
|
| 419 |
+
|
| 420 |
+
except Exception as e:
|
| 421 |
+
metrics["error_center_distribution_metrics"] = str(e)
|
| 422 |
+
return metrics
|
| 423 |
+
|
| 424 |
+
def _calculate_boundary_contact_metrics(data: Dict[str, Any]) -> Dict[str, int]:
|
| 425 |
+
"""
|
| 426 |
+
Count how many circles are touching the edges or corners of the unit square.
|
| 427 |
+
"""
|
| 428 |
+
metrics = {}
|
| 429 |
+
try:
|
| 430 |
+
centers = data["centers"]
|
| 431 |
+
radii = data["radii"]
|
| 432 |
+
n_circles = data["num_circles"]
|
| 433 |
+
|
| 434 |
+
if n_circles == 0:
|
| 435 |
+
metrics["num_circles_touching_edge"] = 0
|
| 436 |
+
metrics["num_circles_touching_corner"] = 0
|
| 437 |
+
metrics["boundary_contact_info"] = "No circles for boundary contact metrics."
|
| 438 |
+
return metrics
|
| 439 |
+
|
| 440 |
+
num_edge_contacts = 0
|
| 441 |
+
num_corner_contacts = 0
|
| 442 |
+
|
| 443 |
+
tolerance = 1e-6 # Based on primary evaluator's atol
|
| 444 |
+
|
| 445 |
+
for i in range(n_circles):
|
| 446 |
+
x, y = centers[i]
|
| 447 |
+
r = radii[i]
|
| 448 |
+
|
| 449 |
+
touching_left = abs(x - r) < tolerance
|
| 450 |
+
touching_right = abs(x + r - 1.0) < tolerance
|
| 451 |
+
touching_bottom = abs(y - r) < tolerance
|
| 452 |
+
touching_top = abs(y + r - 1.0) < tolerance
|
| 453 |
+
|
| 454 |
+
# Check for edge contact
|
| 455 |
+
if touching_left or touching_right or touching_bottom or touching_top:
|
| 456 |
+
num_edge_contacts += 1
|
| 457 |
+
|
| 458 |
+
# Check for corner contact (touching two perpendicular edges)
|
| 459 |
+
# This logic needs to be careful: a circle touching x=0 and x=1 means it's super wide.
|
| 460 |
+
# We want genuinely touching *corners*, meaning meeting two *orthogonal* boundaries.
|
| 461 |
+
corner_touch_count = 0
|
| 462 |
+
if touching_left and touching_bottom: corner_touch_count += 1
|
| 463 |
+
if touching_left and touching_top: corner_touch_count += 1
|
| 464 |
+
if touching_right and touching_bottom: corner_touch_count += 1
|
| 465 |
+
if touching_right and touching_top: corner_touch_count += 1
|
| 466 |
+
|
| 467 |
+
# If a circle is touching 2 or more *orthogonal* boundaries, it's considered touching a corner.
|
| 468 |
+
# Example: touching_left and touching_bottom means touching the bottom-left corner.
|
| 469 |
+
if corner_touch_count > 0:
|
| 470 |
+
num_corner_contacts += 1
|
| 471 |
+
|
| 472 |
+
metrics["num_circles_touching_edge"] = num_edge_contacts
|
| 473 |
+
metrics["num_circles_touching_corner"] = num_corner_contacts
|
| 474 |
+
|
| 475 |
+
except Exception as e:
|
| 476 |
+
metrics["error_boundary_contact_metrics"] = str(e)
|
| 477 |
+
return metrics
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/eval_agent_memory/service_state.json
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generation_history": [
|
| 3 |
+
{
|
| 4 |
+
"generation": 100,
|
| 5 |
+
"primary_score": 2.2800000000000002,
|
| 6 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/results",
|
| 7 |
+
"timestamp": 1770497536.7834485
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"generation": 101,
|
| 11 |
+
"primary_score": 2.465,
|
| 12 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/results",
|
| 13 |
+
"timestamp": 1770497618.2956038
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"generation": 102,
|
| 17 |
+
"primary_score": 0.0,
|
| 18 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/results",
|
| 19 |
+
"timestamp": 1770497647.555699
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"generation": 103,
|
| 23 |
+
"primary_score": 2.5208244546058056,
|
| 24 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/results",
|
| 25 |
+
"timestamp": 1770497720.1201556
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"generation": 104,
|
| 29 |
+
"primary_score": 2.405,
|
| 30 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/results",
|
| 31 |
+
"timestamp": 1770497811.336844
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"generation": 105,
|
| 35 |
+
"primary_score": 2.4470613318956036,
|
| 36 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_105/results",
|
| 37 |
+
"timestamp": 1770497866.173539
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"generation": 106,
|
| 41 |
+
"primary_score": 2.444,
|
| 42 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_106/results",
|
| 43 |
+
"timestamp": 1770497981.7272277
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"generation": 107,
|
| 47 |
+
"primary_score": 2.4650000000000007,
|
| 48 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_107/results",
|
| 49 |
+
"timestamp": 1770498054.5811007
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"generation": 108,
|
| 53 |
+
"primary_score": 2.4499999999999997,
|
| 54 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_108/results",
|
| 55 |
+
"timestamp": 1770498178.0888407
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"generation": 109,
|
| 59 |
+
"primary_score": 2.5069999999999992,
|
| 60 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_109/results",
|
| 61 |
+
"timestamp": 1770498225.3792388
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"generation": 110,
|
| 65 |
+
"primary_score": 2.5066514305478123,
|
| 66 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_110/results",
|
| 67 |
+
"timestamp": 1770498377.175865
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"generation": 111,
|
| 71 |
+
"primary_score": 2.509079058224618,
|
| 72 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_111/results",
|
| 73 |
+
"timestamp": 1770498432.0109513
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"generation": 112,
|
| 77 |
+
"primary_score": 2.44,
|
| 78 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_112/results",
|
| 79 |
+
"timestamp": 1770498535.7547326
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"generation": 113,
|
| 83 |
+
"primary_score": 2.522059081864002,
|
| 84 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_113/results",
|
| 85 |
+
"timestamp": 1770498593.591211
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"generation": 114,
|
| 89 |
+
"primary_score": 2.477987673473837,
|
| 90 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_114/results",
|
| 91 |
+
"timestamp": 1770498820.0450184
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"generation": 115,
|
| 95 |
+
"primary_score": 2.5069999999999997,
|
| 96 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_115/results",
|
| 97 |
+
"timestamp": 1770498872.2368124
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"generation": 117,
|
| 101 |
+
"primary_score": 2.5071705429430855,
|
| 102 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_117/results",
|
| 103 |
+
"timestamp": 1770498950.9931102
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"generation": 116,
|
| 107 |
+
"primary_score": 0.0,
|
| 108 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_116/results",
|
| 109 |
+
"timestamp": 1770499045.9516294
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"generation": 118,
|
| 113 |
+
"primary_score": 2.5069999999999997,
|
| 114 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_118/results",
|
| 115 |
+
"timestamp": 1770499050.147209
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"generation": 119,
|
| 119 |
+
"primary_score": 2.5069999999999997,
|
| 120 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_119/results",
|
| 121 |
+
"timestamp": 1770499090.2079575
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"generation": 120,
|
| 125 |
+
"primary_score": 2.5072159305410415,
|
| 126 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_120/results",
|
| 127 |
+
"timestamp": 1770499238.8178396
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"generation": 121,
|
| 131 |
+
"primary_score": 2.5218608001632576,
|
| 132 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_121/results",
|
| 133 |
+
"timestamp": 1770499311.4899046
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"generation": 122,
|
| 137 |
+
"primary_score": 2.5199999999999996,
|
| 138 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_122/results",
|
| 139 |
+
"timestamp": 1770499415.1481774
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"generation": 123,
|
| 143 |
+
"primary_score": 2.2851216373415686,
|
| 144 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_123/results",
|
| 145 |
+
"timestamp": 1770499524.1350317
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"generation": 124,
|
| 149 |
+
"primary_score": 2.5208244546058047,
|
| 150 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_124/results",
|
| 151 |
+
"timestamp": 1770499555.8882217
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"generation": 125,
|
| 155 |
+
"primary_score": 2.4650280623580625,
|
| 156 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_125/results",
|
| 157 |
+
"timestamp": 1770499628.8779595
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"generation": 126,
|
| 161 |
+
"primary_score": 2.5218608001632576,
|
| 162 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_126/results",
|
| 163 |
+
"timestamp": 1770499689.4132166
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"generation": 127,
|
| 167 |
+
"primary_score": 2.4290000000000003,
|
| 168 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_127/results",
|
| 169 |
+
"timestamp": 1770499822.837822
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"generation": 128,
|
| 173 |
+
"primary_score": 2.4227558888552587,
|
| 174 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_128/results",
|
| 175 |
+
"timestamp": 1770499895.2269728
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"generation": 129,
|
| 179 |
+
"primary_score": 2.5208244546058056,
|
| 180 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_129/results",
|
| 181 |
+
"timestamp": 1770499934.2850866
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"generation": 130,
|
| 185 |
+
"primary_score": 2.5167649962323697,
|
| 186 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_130/results",
|
| 187 |
+
"timestamp": 1770500005.944343
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"generation": 131,
|
| 191 |
+
"primary_score": 2.5208244546058056,
|
| 192 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_131/results",
|
| 193 |
+
"timestamp": 1770500173.9580781
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"generation": 132,
|
| 197 |
+
"primary_score": 2.5145014798557748,
|
| 198 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_132/results",
|
| 199 |
+
"timestamp": 1770500225.3639982
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"generation": 133,
|
| 203 |
+
"primary_score": 2.4977248512193455,
|
| 204 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_133/results",
|
| 205 |
+
"timestamp": 1770500293.4483747
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"generation": 134,
|
| 209 |
+
"primary_score": 2.3684442106867847,
|
| 210 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_134/results",
|
| 211 |
+
"timestamp": 1770500380.2947817
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"generation": 135,
|
| 215 |
+
"primary_score": 2.521567718144885,
|
| 216 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_135/results",
|
| 217 |
+
"timestamp": 1770500431.7316802
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"generation": 136,
|
| 221 |
+
"primary_score": 2.5169176564937668,
|
| 222 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_136/results",
|
| 223 |
+
"timestamp": 1770500479.809213
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"generation": 137,
|
| 227 |
+
"primary_score": 2.521860800163257,
|
| 228 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_137/results",
|
| 229 |
+
"timestamp": 1770500545.8810725
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"generation": 138,
|
| 233 |
+
"primary_score": 2.5167649962323697,
|
| 234 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_138/results",
|
| 235 |
+
"timestamp": 1770500613.068937
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"generation": 139,
|
| 239 |
+
"primary_score": 2.5208244546058056,
|
| 240 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_139/results",
|
| 241 |
+
"timestamp": 1770500662.9420853
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"generation": 140,
|
| 245 |
+
"primary_score": 2.5152982477911427,
|
| 246 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_140/results",
|
| 247 |
+
"timestamp": 1770500806.6727757
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"generation": 141,
|
| 251 |
+
"primary_score": 2.489,
|
| 252 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_141/results",
|
| 253 |
+
"timestamp": 1770500888.205382
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"generation": 142,
|
| 257 |
+
"primary_score": 2.5208244546058056,
|
| 258 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_142/results",
|
| 259 |
+
"timestamp": 1770500943.419112
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"generation": 143,
|
| 263 |
+
"primary_score": 2.500028148484375,
|
| 264 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_143/results",
|
| 265 |
+
"timestamp": 1770500972.1809523
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"generation": 144,
|
| 269 |
+
"primary_score": 2.5220590818640027,
|
| 270 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_144/results",
|
| 271 |
+
"timestamp": 1770501035.3806226
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"generation": 145,
|
| 275 |
+
"primary_score": 2.3564139033400773,
|
| 276 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_145/results",
|
| 277 |
+
"timestamp": 1770501113.6389701
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"generation": 146,
|
| 281 |
+
"primary_score": 2.5010000000000003,
|
| 282 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_146/results",
|
| 283 |
+
"timestamp": 1770501201.063461
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"generation": 147,
|
| 287 |
+
"primary_score": 2.5121925450980367,
|
| 288 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_147/results",
|
| 289 |
+
"timestamp": 1770501356.2212741
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"generation": 148,
|
| 293 |
+
"primary_score": 2.465028062358063,
|
| 294 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_148/results",
|
| 295 |
+
"timestamp": 1770501480.849577
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"generation": 149,
|
| 299 |
+
"primary_score": 2.494020111763647,
|
| 300 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_149/results",
|
| 301 |
+
"timestamp": 1770501562.2326796
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"generation": 150,
|
| 305 |
+
"primary_score": 2.5166368302058504,
|
| 306 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_150/results",
|
| 307 |
+
"timestamp": 1770501643.7143664
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"generation": 151,
|
| 311 |
+
"primary_score": 2.3152000000000004,
|
| 312 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_151/results",
|
| 313 |
+
"timestamp": 1770501729.8692663
|
| 314 |
+
},
|
| 315 |
+
{
|
| 316 |
+
"generation": 152,
|
| 317 |
+
"primary_score": 2.444327408971052,
|
| 318 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_152/results",
|
| 319 |
+
"timestamp": 1770501799.3386772
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"generation": 153,
|
| 323 |
+
"primary_score": 2.400601531744524,
|
| 324 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_153/results",
|
| 325 |
+
"timestamp": 1770501889.992735
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"generation": 154,
|
| 329 |
+
"primary_score": 2.464061310932331,
|
| 330 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_154/results",
|
| 331 |
+
"timestamp": 1770501975.5777493
|
| 332 |
+
},
|
| 333 |
+
{
|
| 334 |
+
"generation": 155,
|
| 335 |
+
"primary_score": 1.722,
|
| 336 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_155/results",
|
| 337 |
+
"timestamp": 1770502121.2160032
|
| 338 |
+
},
|
| 339 |
+
{
|
| 340 |
+
"generation": 156,
|
| 341 |
+
"primary_score": 2.5000330771530224,
|
| 342 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_156/results",
|
| 343 |
+
"timestamp": 1770502146.0164196
|
| 344 |
+
},
|
| 345 |
+
{
|
| 346 |
+
"generation": 157,
|
| 347 |
+
"primary_score": 2.5158597847082476,
|
| 348 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_157/results",
|
| 349 |
+
"timestamp": 1770502206.5066895
|
| 350 |
+
},
|
| 351 |
+
{
|
| 352 |
+
"generation": 158,
|
| 353 |
+
"primary_score": 2.5187310369370173,
|
| 354 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_158/results",
|
| 355 |
+
"timestamp": 1770502252.9060795
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"generation": 159,
|
| 359 |
+
"primary_score": 2.405,
|
| 360 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_159/results",
|
| 361 |
+
"timestamp": 1770502338.09743
|
| 362 |
+
},
|
| 363 |
+
{
|
| 364 |
+
"generation": 160,
|
| 365 |
+
"primary_score": 2.507448154084946,
|
| 366 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_160/results",
|
| 367 |
+
"timestamp": 1770502427.1638386
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"generation": 161,
|
| 371 |
+
"primary_score": 2.45729952347023,
|
| 372 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_161/results",
|
| 373 |
+
"timestamp": 1770502464.0104778
|
| 374 |
+
},
|
| 375 |
+
{
|
| 376 |
+
"generation": 162,
|
| 377 |
+
"primary_score": 2.355028062358063,
|
| 378 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_162/results",
|
| 379 |
+
"timestamp": 1770502548.8020644
|
| 380 |
+
},
|
| 381 |
+
{
|
| 382 |
+
"generation": 163,
|
| 383 |
+
"primary_score": 2.3906342324181162,
|
| 384 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_163/results",
|
| 385 |
+
"timestamp": 1770502653.7126782
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"generation": 164,
|
| 389 |
+
"primary_score": 2.375,
|
| 390 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_164/results",
|
| 391 |
+
"timestamp": 1770502742.5551453
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"generation": 165,
|
| 395 |
+
"primary_score": 2.4650280623580625,
|
| 396 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_165/results",
|
| 397 |
+
"timestamp": 1770502837.1902678
|
| 398 |
+
},
|
| 399 |
+
{
|
| 400 |
+
"generation": 166,
|
| 401 |
+
"primary_score": 2.4974535092172165,
|
| 402 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_166/results",
|
| 403 |
+
"timestamp": 1770502974.159942
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"generation": 167,
|
| 407 |
+
"primary_score": 2.5221857096486984,
|
| 408 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_167/results",
|
| 409 |
+
"timestamp": 1770503032.474351
|
| 410 |
+
},
|
| 411 |
+
{
|
| 412 |
+
"generation": 168,
|
| 413 |
+
"primary_score": 2.5208244546058056,
|
| 414 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_168/results",
|
| 415 |
+
"timestamp": 1770503122.8883655
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"generation": 169,
|
| 419 |
+
"primary_score": 2.408028735854656,
|
| 420 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_169/results",
|
| 421 |
+
"timestamp": 1770503217.3845317
|
| 422 |
+
},
|
| 423 |
+
{
|
| 424 |
+
"generation": 170,
|
| 425 |
+
"primary_score": 2.4659999999999997,
|
| 426 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_170/results",
|
| 427 |
+
"timestamp": 1770503361.6872094
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"generation": 171,
|
| 431 |
+
"primary_score": 2.458419304336672,
|
| 432 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_171/results",
|
| 433 |
+
"timestamp": 1770503450.1529338
|
| 434 |
+
},
|
| 435 |
+
{
|
| 436 |
+
"generation": 172,
|
| 437 |
+
"primary_score": 2.410029184852385,
|
| 438 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_172/results",
|
| 439 |
+
"timestamp": 1770503545.988076
|
| 440 |
+
},
|
| 441 |
+
{
|
| 442 |
+
"generation": 173,
|
| 443 |
+
"primary_score": 2.3770631504141666,
|
| 444 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_173/results",
|
| 445 |
+
"timestamp": 1770503617.5602458
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"generation": 174,
|
| 449 |
+
"primary_score": 2.5208244546058056,
|
| 450 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_174/results",
|
| 451 |
+
"timestamp": 1770503659.8928645
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"generation": 175,
|
| 455 |
+
"primary_score": 2.499976257191056,
|
| 456 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_175/results",
|
| 457 |
+
"timestamp": 1770503740.7310476
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"generation": 176,
|
| 461 |
+
"primary_score": 2.47954789853076,
|
| 462 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_176/results",
|
| 463 |
+
"timestamp": 1770503796.8694088
|
| 464 |
+
},
|
| 465 |
+
{
|
| 466 |
+
"generation": 177,
|
| 467 |
+
"primary_score": 2.4650280623580625,
|
| 468 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_177/results",
|
| 469 |
+
"timestamp": 1770503854.1147568
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"generation": 178,
|
| 473 |
+
"primary_score": 2.5373504944627845,
|
| 474 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_178/results",
|
| 475 |
+
"timestamp": 1770503975.0813587
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"generation": 179,
|
| 479 |
+
"primary_score": 2.4710280623580627,
|
| 480 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_179/results",
|
| 481 |
+
"timestamp": 1770504019.5344222
|
| 482 |
+
},
|
| 483 |
+
{
|
| 484 |
+
"generation": 180,
|
| 485 |
+
"primary_score": 2.5220087596017353,
|
| 486 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_180/results",
|
| 487 |
+
"timestamp": 1770504154.3909054
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"generation": 181,
|
| 491 |
+
"primary_score": 2.5166368302058513,
|
| 492 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_181/results",
|
| 493 |
+
"timestamp": 1770504174.088877
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"generation": 182,
|
| 497 |
+
"primary_score": 2.5049983209898663,
|
| 498 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_182/results",
|
| 499 |
+
"timestamp": 1770504222.2504957
|
| 500 |
+
},
|
| 501 |
+
{
|
| 502 |
+
"generation": 184,
|
| 503 |
+
"primary_score": 2.5121925450980367,
|
| 504 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_184/results",
|
| 505 |
+
"timestamp": 1770504302.5232344
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
"generation": 183,
|
| 509 |
+
"primary_score": 2.5462385637519325,
|
| 510 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_183/results",
|
| 511 |
+
"timestamp": 1770504326.7040734
|
| 512 |
+
},
|
| 513 |
+
{
|
| 514 |
+
"generation": 185,
|
| 515 |
+
"primary_score": 2.5166368302058513,
|
| 516 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_185/results",
|
| 517 |
+
"timestamp": 1770504388.9125996
|
| 518 |
+
},
|
| 519 |
+
{
|
| 520 |
+
"generation": 186,
|
| 521 |
+
"primary_score": 2.5166368302058513,
|
| 522 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_186/results",
|
| 523 |
+
"timestamp": 1770504424.4280624
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"generation": 187,
|
| 527 |
+
"primary_score": 2.405,
|
| 528 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_187/results",
|
| 529 |
+
"timestamp": 1770504481.1124032
|
| 530 |
+
},
|
| 531 |
+
{
|
| 532 |
+
"generation": 189,
|
| 533 |
+
"primary_score": 2.5208244546058056,
|
| 534 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_189/results",
|
| 535 |
+
"timestamp": 1770504611.9533465
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"generation": 188,
|
| 539 |
+
"primary_score": 2.5783841891678096,
|
| 540 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_188/results",
|
| 541 |
+
"timestamp": 1770504612.5975885
|
| 542 |
+
},
|
| 543 |
+
{
|
| 544 |
+
"generation": 190,
|
| 545 |
+
"primary_score": 2.5166368302058504,
|
| 546 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_190/results",
|
| 547 |
+
"timestamp": 1770504728.5890408
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"generation": 191,
|
| 551 |
+
"primary_score": 2.5208244546058056,
|
| 552 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_191/results",
|
| 553 |
+
"timestamp": 1770504741.938369
|
| 554 |
+
},
|
| 555 |
+
{
|
| 556 |
+
"generation": 192,
|
| 557 |
+
"primary_score": 2.5166368302058513,
|
| 558 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_192/results",
|
| 559 |
+
"timestamp": 1770504749.9977677
|
| 560 |
+
},
|
| 561 |
+
{
|
| 562 |
+
"generation": 194,
|
| 563 |
+
"primary_score": 2.5208244546058056,
|
| 564 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_194/results",
|
| 565 |
+
"timestamp": 1770505016.9855785
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
"generation": 193,
|
| 569 |
+
"primary_score": 2.593165588404451,
|
| 570 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_193/results",
|
| 571 |
+
"timestamp": 1770505036.2274702
|
| 572 |
+
},
|
| 573 |
+
{
|
| 574 |
+
"generation": 195,
|
| 575 |
+
"primary_score": 2.5929895930074904,
|
| 576 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_195/results",
|
| 577 |
+
"timestamp": 1770505139.6044135
|
| 578 |
+
},
|
| 579 |
+
{
|
| 580 |
+
"generation": 197,
|
| 581 |
+
"primary_score": 2.5238500413244105,
|
| 582 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_197/results",
|
| 583 |
+
"timestamp": 1770505252.0155315
|
| 584 |
+
},
|
| 585 |
+
{
|
| 586 |
+
"generation": 196,
|
| 587 |
+
"primary_score": 0.0,
|
| 588 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_196/results",
|
| 589 |
+
"timestamp": 1770505276.414447
|
| 590 |
+
},
|
| 591 |
+
{
|
| 592 |
+
"generation": 198,
|
| 593 |
+
"primary_score": 2.5238500413244105,
|
| 594 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_198/results",
|
| 595 |
+
"timestamp": 1770505348.782338
|
| 596 |
+
},
|
| 597 |
+
{
|
| 598 |
+
"generation": 199,
|
| 599 |
+
"primary_score": 2.5208244546058056,
|
| 600 |
+
"results_dir": "/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_199/results",
|
| 601 |
+
"timestamp": 1770505403.1935954
|
| 602 |
+
}
|
| 603 |
+
],
|
| 604 |
+
"last_agent_trigger_gen": 185,
|
| 605 |
+
"total_notifications": 199,
|
| 606 |
+
"total_agent_runs": 18,
|
| 607 |
+
"last_update": 1770512444.365326
|
| 608 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_0/main.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def construct_packing():
|
| 8 |
+
"""
|
| 9 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 10 |
+
that attempts to maximize the sum of their radii.
|
| 11 |
+
|
| 12 |
+
Returns:
|
| 13 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 14 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 15 |
+
radii: np.array of shape (26) with radius of each circle
|
| 16 |
+
sum_of_radii: Sum of all radii
|
| 17 |
+
"""
|
| 18 |
+
# Initialize arrays for 26 circles
|
| 19 |
+
n = 26
|
| 20 |
+
centers = np.zeros((n, 2))
|
| 21 |
+
|
| 22 |
+
# Place circles in a structured pattern
|
| 23 |
+
# This is a simple pattern - evolution will improve this
|
| 24 |
+
|
| 25 |
+
# First, place a large circle in the center
|
| 26 |
+
centers[0] = [0.5, 0.5]
|
| 27 |
+
|
| 28 |
+
# Place 8 circles around it in a ring
|
| 29 |
+
for i in range(8):
|
| 30 |
+
angle = 2 * np.pi * i / 8
|
| 31 |
+
centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 32 |
+
|
| 33 |
+
# Place 16 more circles in an outer ring
|
| 34 |
+
for i in range(16):
|
| 35 |
+
angle = 2 * np.pi * i / 16
|
| 36 |
+
centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 37 |
+
|
| 38 |
+
# Additional positioning adjustment to make sure all circles
|
| 39 |
+
# are inside the square and don't overlap
|
| 40 |
+
# Clip to ensure everything is inside the unit square
|
| 41 |
+
centers = np.clip(centers, 0.01, 0.99)
|
| 42 |
+
|
| 43 |
+
# Compute maximum valid radii for this configuration
|
| 44 |
+
radii = compute_max_radii(centers)
|
| 45 |
+
return centers, radii
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def compute_max_radii(centers):
|
| 49 |
+
"""
|
| 50 |
+
Compute the maximum possible radii for each circle position
|
| 51 |
+
such that they don't overlap and stay within the unit square.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
np.array of shape (n) with radius of each circle
|
| 58 |
+
"""
|
| 59 |
+
n = centers.shape[0]
|
| 60 |
+
radii = np.ones(n)
|
| 61 |
+
|
| 62 |
+
# First, limit by distance to square borders
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
# Distance to borders
|
| 66 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 67 |
+
|
| 68 |
+
# Then, limit by distance to other circles
|
| 69 |
+
# Each pair of circles with centers at distance d can have
|
| 70 |
+
# sum of radii at most d to avoid overlap
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 74 |
+
|
| 75 |
+
# If current radii would cause overlap
|
| 76 |
+
if radii[i] + radii[j] > dist:
|
| 77 |
+
# Scale both radii proportionally
|
| 78 |
+
scale = dist / (radii[i] + radii[j])
|
| 79 |
+
radii[i] *= scale
|
| 80 |
+
radii[j] *= scale
|
| 81 |
+
|
| 82 |
+
return radii
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# EVOLVE-BLOCK-END
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# This part remains fixed (not evolved)
|
| 89 |
+
def run_packing():
|
| 90 |
+
"""Run the circle packing constructor for n=26"""
|
| 91 |
+
centers, radii = construct_packing()
|
| 92 |
+
# Calculate the sum of radii
|
| 93 |
+
sum_radii = np.sum(radii)
|
| 94 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/edit.diff
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,94 +1,153 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
-"""Constructor-based circle packing for n=26 circles"""
|
| 6 |
+
+"""
|
| 7 |
+
+Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 8 |
+
+and linear programming for optimal radii.
|
| 9 |
+
+"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
+from scipy.optimize import linprog
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def construct_packing():
|
| 16 |
+
"""
|
| 17 |
+
- Construct a specific arrangement of 26 circles in a unit square
|
| 18 |
+
- that attempts to maximize the sum of their radii.
|
| 19 |
+
+ Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 20 |
+
+ The radii are then optimized using a linear programming solver to maximize their sum.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
- Tuple of (centers, radii, sum_of_radii)
|
| 24 |
+
- centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 25 |
+
- radii: np.array of shape (26) with radius of each circle
|
| 26 |
+
- sum_of_radii: Sum of all radii
|
| 27 |
+
+ Tuple of (centers, radii)
|
| 28 |
+
+ centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 29 |
+
+ radii: np.array of shape (26) with radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
- # Initialize arrays for 26 circles
|
| 32 |
+
n = 26
|
| 33 |
+
centers = np.zeros((n, 2))
|
| 34 |
+
|
| 35 |
+
- # Place circles in a structured pattern
|
| 36 |
+
- # This is a simple pattern - evolution will improve this
|
| 37 |
+
+ # Parameters for the hierarchical grid. These are chosen to create a
|
| 38 |
+
+ # balanced initial configuration. R is the conceptual radius for the main
|
| 39 |
+
+ # grid, which determines the overall scale. d splits the central gap.
|
| 40 |
+
+ R = 0.121 # Base radius for the 4x4 grid
|
| 41 |
+
+ d = 0.055 # Displacement for the two central circles
|
| 42 |
+
|
| 43 |
+
- # First, place a large circle in the center
|
| 44 |
+
- centers[0] = [0.5, 0.5]
|
| 45 |
+
+ # Center the entire structure within the unit square
|
| 46 |
+
+ margin = (1.0 - 8 * R) / 2.0
|
| 47 |
+
|
| 48 |
+
- # Place 8 circles around it in a ring
|
| 49 |
+
- for i in range(8):
|
| 50 |
+
- angle = 2 * np.pi * i / 8
|
| 51 |
+
- centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 52 |
+
+ k = 0
|
| 53 |
+
|
| 54 |
+
- # Place 16 more circles in an outer ring
|
| 55 |
+
- for i in range(16):
|
| 56 |
+
- angle = 2 * np.pi * i / 16
|
| 57 |
+
- centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 58 |
+
+ # 1. Place 16 primary circles in a 4x4 grid
|
| 59 |
+
+ for i in range(4):
|
| 60 |
+
+ for j in range(4):
|
| 61 |
+
+ x = margin + (2 * i + 1) * R
|
| 62 |
+
+ y = margin + (2 * j + 1) * R
|
| 63 |
+
+ centers[k] = [x, y]
|
| 64 |
+
+ k += 1
|
| 65 |
+
|
| 66 |
+
- # Additional positioning adjustment to make sure all circles
|
| 67 |
+
- # are inside the square and don't overlap
|
| 68 |
+
- # Clip to ensure everything is inside the unit square
|
| 69 |
+
- centers = np.clip(centers, 0.01, 0.99)
|
| 70 |
+
+ # 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center
|
| 71 |
+
+ for i in range(3):
|
| 72 |
+
+ for j in range(3):
|
| 73 |
+
+ if i == 1 and j == 1:
|
| 74 |
+
+ continue
|
| 75 |
+
+ x = margin + (2 * (i + 1)) * R
|
| 76 |
+
+ y = margin + (2 * (j + 1)) * R
|
| 77 |
+
+ centers[k] = [x, y]
|
| 78 |
+
+ k += 1
|
| 79 |
+
|
| 80 |
+
- # Compute maximum valid radii for this configuration
|
| 81 |
+
+ # 3. Place 2 tertiary circles in the central gap, split by 'd'
|
| 82 |
+
+ center_point = margin + 4 * R
|
| 83 |
+
+ centers[k] = [center_point, center_point - d]
|
| 84 |
+
+ k += 1
|
| 85 |
+
+ centers[k] = [center_point, center_point + d]
|
| 86 |
+
+ k += 1
|
| 87 |
+
+
|
| 88 |
+
+ # For the given centers, compute the radii that maximize the sum.
|
| 89 |
+
radii = compute_max_radii(centers)
|
| 90 |
+
+
|
| 91 |
+
return centers, radii
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def compute_max_radii(centers):
|
| 95 |
+
"""
|
| 96 |
+
- Compute the maximum possible radii for each circle position
|
| 97 |
+
- such that they don't overlap and stay within the unit square.
|
| 98 |
+
+ Computes the maximum possible radii for a given set of circle centers
|
| 99 |
+
+ by solving a linear programming problem. This maximizes the sum of radii
|
| 100 |
+
+ subject to non-overlapping and boundary constraints.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
- centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 104 |
+
+ centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 105 |
+
|
| 106 |
+
Returns:
|
| 107 |
+
- np.array of shape (n) with radius of each circle
|
| 108 |
+
+ np.array of shape (n) with the optimal radius for each circle.
|
| 109 |
+
"""
|
| 110 |
+
n = centers.shape[0]
|
| 111 |
+
- radii = np.ones(n)
|
| 112 |
+
|
| 113 |
+
- # First, limit by distance to square borders
|
| 114 |
+
+ # The objective is to maximize sum(radii), which is equivalent to
|
| 115 |
+
+ # minimizing sum(-radii).
|
| 116 |
+
+ c = -np.ones(n)
|
| 117 |
+
+
|
| 118 |
+
+ # Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 119 |
+
+ # 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 120 |
+
+ # 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 121 |
+
+ constraints = []
|
| 122 |
+
+ b_vector = []
|
| 123 |
+
+
|
| 124 |
+
+ # Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 125 |
+
for i in range(n):
|
| 126 |
+
- x, y = centers[i]
|
| 127 |
+
- # Distance to borders
|
| 128 |
+
- radii[i] = min(x, y, 1 - x, 1 - y)
|
| 129 |
+
+ # r_i <= x_i
|
| 130 |
+
+ row = np.zeros(n)
|
| 131 |
+
+ row[i] = 1
|
| 132 |
+
+ constraints.append(row)
|
| 133 |
+
+ b_vector.append(centers[i, 0])
|
| 134 |
+
|
| 135 |
+
- # Then, limit by distance to other circles
|
| 136 |
+
- # Each pair of circles with centers at distance d can have
|
| 137 |
+
- # sum of radii at most d to avoid overlap
|
| 138 |
+
+ # r_i <= 1 - x_i
|
| 139 |
+
+ row = np.zeros(n)
|
| 140 |
+
+ row[i] = 1
|
| 141 |
+
+ constraints.append(row)
|
| 142 |
+
+ b_vector.append(1 - centers[i, 0])
|
| 143 |
+
+
|
| 144 |
+
+ # r_i <= y_i
|
| 145 |
+
+ row = np.zeros(n)
|
| 146 |
+
+ row[i] = 1
|
| 147 |
+
+ constraints.append(row)
|
| 148 |
+
+ b_vector.append(centers[i, 1])
|
| 149 |
+
+
|
| 150 |
+
+ # r_i <= 1 - y_i
|
| 151 |
+
+ row = np.zeros(n)
|
| 152 |
+
+ row[i] = 1
|
| 153 |
+
+ constraints.append(row)
|
| 154 |
+
+ b_vector.append(1 - centers[i, 1])
|
| 155 |
+
+
|
| 156 |
+
+ # Pair constraints: r_i + r_j <= d_ij
|
| 157 |
+
for i in range(n):
|
| 158 |
+
for j in range(i + 1, n):
|
| 159 |
+
- dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 160 |
+
+ dist = np.linalg.norm(centers[i] - centers[j])
|
| 161 |
+
+ row = np.zeros(n)
|
| 162 |
+
+ row[i] = 1
|
| 163 |
+
+ row[j] = 1
|
| 164 |
+
+ constraints.append(row)
|
| 165 |
+
+ b_vector.append(dist)
|
| 166 |
+
|
| 167 |
+
- # If current radii would cause overlap
|
| 168 |
+
- if radii[i] + radii[j] > dist:
|
| 169 |
+
- # Scale both radii proportionally
|
| 170 |
+
- scale = dist / (radii[i] + radii[j])
|
| 171 |
+
- radii[i] *= scale
|
| 172 |
+
- radii[j] *= scale
|
| 173 |
+
+ A_ub = np.array(constraints)
|
| 174 |
+
+ b_ub = np.array(b_vector)
|
| 175 |
+
|
| 176 |
+
- return radii
|
| 177 |
+
+ # All radii must be non-negative.
|
| 178 |
+
+ bounds = [(0, None) for _ in range(n)]
|
| 179 |
+
+
|
| 180 |
+
+ # Solve the linear program. 'highs' is a fast and reliable solver.
|
| 181 |
+
+ res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 182 |
+
+
|
| 183 |
+
+ if res.success:
|
| 184 |
+
+ # Return the optimal radii
|
| 185 |
+
+ return res.x
|
| 186 |
+
+ else:
|
| 187 |
+
+ # Fallback in case of solver failure
|
| 188 |
+
+ # An empty array will result in a sum of 0, signaling a problem.
|
| 189 |
+
+ return np.zeros(n)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
# EVOLVE-BLOCK-END
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# This part remains fixed (not evolved)
|
| 196 |
+
def run_packing():
|
| 197 |
+
"""Run the circle packing constructor for n=26"""
|
| 198 |
+
centers, radii = construct_packing()
|
| 199 |
+
# Calculate the sum of radii
|
| 200 |
+
sum_radii = np.sum(radii)
|
| 201 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/main.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.optimize import linprog
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def construct_packing():
|
| 12 |
+
"""
|
| 13 |
+
Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 14 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
Tuple of (centers, radii)
|
| 18 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 19 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 20 |
+
"""
|
| 21 |
+
n = 26
|
| 22 |
+
centers = np.zeros((n, 2))
|
| 23 |
+
|
| 24 |
+
# Parameters for the hierarchical grid. These are chosen to create a
|
| 25 |
+
# balanced initial configuration. R is the conceptual radius for the main
|
| 26 |
+
# grid, which determines the overall scale. d splits the central gap.
|
| 27 |
+
R = 0.121 # Base radius for the 4x4 grid
|
| 28 |
+
d = 0.055 # Displacement for the two central circles
|
| 29 |
+
|
| 30 |
+
# Center the entire structure within the unit square
|
| 31 |
+
margin = (1.0 - 8 * R) / 2.0
|
| 32 |
+
|
| 33 |
+
k = 0
|
| 34 |
+
|
| 35 |
+
# 1. Place 16 primary circles in a 4x4 grid
|
| 36 |
+
for i in range(4):
|
| 37 |
+
for j in range(4):
|
| 38 |
+
x = margin + (2 * i + 1) * R
|
| 39 |
+
y = margin + (2 * j + 1) * R
|
| 40 |
+
centers[k] = [x, y]
|
| 41 |
+
k += 1
|
| 42 |
+
|
| 43 |
+
# 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center
|
| 44 |
+
for i in range(3):
|
| 45 |
+
for j in range(3):
|
| 46 |
+
if i == 1 and j == 1:
|
| 47 |
+
continue
|
| 48 |
+
x = margin + (2 * (i + 1)) * R
|
| 49 |
+
y = margin + (2 * (j + 1)) * R
|
| 50 |
+
centers[k] = [x, y]
|
| 51 |
+
k += 1
|
| 52 |
+
|
| 53 |
+
# 3. Place 2 tertiary circles in the central gap, split by 'd'
|
| 54 |
+
center_point = margin + 4 * R
|
| 55 |
+
centers[k] = [center_point, center_point - d]
|
| 56 |
+
k += 1
|
| 57 |
+
centers[k] = [center_point, center_point + d]
|
| 58 |
+
k += 1
|
| 59 |
+
|
| 60 |
+
# For the given centers, compute the radii that maximize the sum.
|
| 61 |
+
radii = compute_max_radii(centers)
|
| 62 |
+
|
| 63 |
+
return centers, radii
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def compute_max_radii(centers):
|
| 67 |
+
"""
|
| 68 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 69 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 70 |
+
subject to non-overlapping and boundary constraints.
|
| 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 the optimal radius for each circle.
|
| 77 |
+
"""
|
| 78 |
+
n = centers.shape[0]
|
| 79 |
+
|
| 80 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 81 |
+
# minimizing sum(-radii).
|
| 82 |
+
c = -np.ones(n)
|
| 83 |
+
|
| 84 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 85 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 86 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 87 |
+
constraints = []
|
| 88 |
+
b_vector = []
|
| 89 |
+
|
| 90 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 91 |
+
for i in range(n):
|
| 92 |
+
# r_i <= x_i
|
| 93 |
+
row = np.zeros(n)
|
| 94 |
+
row[i] = 1
|
| 95 |
+
constraints.append(row)
|
| 96 |
+
b_vector.append(centers[i, 0])
|
| 97 |
+
|
| 98 |
+
# r_i <= 1 - x_i
|
| 99 |
+
row = np.zeros(n)
|
| 100 |
+
row[i] = 1
|
| 101 |
+
constraints.append(row)
|
| 102 |
+
b_vector.append(1 - centers[i, 0])
|
| 103 |
+
|
| 104 |
+
# r_i <= y_i
|
| 105 |
+
row = np.zeros(n)
|
| 106 |
+
row[i] = 1
|
| 107 |
+
constraints.append(row)
|
| 108 |
+
b_vector.append(centers[i, 1])
|
| 109 |
+
|
| 110 |
+
# r_i <= 1 - y_i
|
| 111 |
+
row = np.zeros(n)
|
| 112 |
+
row[i] = 1
|
| 113 |
+
constraints.append(row)
|
| 114 |
+
b_vector.append(1 - centers[i, 1])
|
| 115 |
+
|
| 116 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 117 |
+
for i in range(n):
|
| 118 |
+
for j in range(i + 1, n):
|
| 119 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 120 |
+
row = np.zeros(n)
|
| 121 |
+
row[i] = 1
|
| 122 |
+
row[j] = 1
|
| 123 |
+
constraints.append(row)
|
| 124 |
+
b_vector.append(dist)
|
| 125 |
+
|
| 126 |
+
A_ub = np.array(constraints)
|
| 127 |
+
b_ub = np.array(b_vector)
|
| 128 |
+
|
| 129 |
+
# All radii must be non-negative.
|
| 130 |
+
bounds = [(0, None) for _ in range(n)]
|
| 131 |
+
|
| 132 |
+
# Solve the linear program. 'highs' is a fast and reliable solver.
|
| 133 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 134 |
+
|
| 135 |
+
if res.success:
|
| 136 |
+
# Return the optimal radii
|
| 137 |
+
return res.x
|
| 138 |
+
else:
|
| 139 |
+
# Fallback in case of solver failure
|
| 140 |
+
# An empty array will result in a sum of 0, signaling a problem.
|
| 141 |
+
return np.zeros(n)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# EVOLVE-BLOCK-END
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
# This part remains fixed (not evolved)
|
| 148 |
+
def run_packing():
|
| 149 |
+
"""Run the circle packing constructor for n=26"""
|
| 150 |
+
centers, radii = construct_packing()
|
| 151 |
+
# Calculate the sum of radii
|
| 152 |
+
sum_radii = np.sum(radii)
|
| 153 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/original.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""Constructor-based circle packing for n=26 circles"""
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def construct_packing():
|
| 8 |
+
"""
|
| 9 |
+
Construct a specific arrangement of 26 circles in a unit square
|
| 10 |
+
that attempts to maximize the sum of their radii.
|
| 11 |
+
|
| 12 |
+
Returns:
|
| 13 |
+
Tuple of (centers, radii, sum_of_radii)
|
| 14 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates
|
| 15 |
+
radii: np.array of shape (26) with radius of each circle
|
| 16 |
+
sum_of_radii: Sum of all radii
|
| 17 |
+
"""
|
| 18 |
+
# Initialize arrays for 26 circles
|
| 19 |
+
n = 26
|
| 20 |
+
centers = np.zeros((n, 2))
|
| 21 |
+
|
| 22 |
+
# Place circles in a structured pattern
|
| 23 |
+
# This is a simple pattern - evolution will improve this
|
| 24 |
+
|
| 25 |
+
# First, place a large circle in the center
|
| 26 |
+
centers[0] = [0.5, 0.5]
|
| 27 |
+
|
| 28 |
+
# Place 8 circles around it in a ring
|
| 29 |
+
for i in range(8):
|
| 30 |
+
angle = 2 * np.pi * i / 8
|
| 31 |
+
centers[i + 1] = [0.5 + 0.3 * np.cos(angle), 0.5 + 0.3 * np.sin(angle)]
|
| 32 |
+
|
| 33 |
+
# Place 16 more circles in an outer ring
|
| 34 |
+
for i in range(16):
|
| 35 |
+
angle = 2 * np.pi * i / 16
|
| 36 |
+
centers[i + 9] = [0.5 + 0.7 * np.cos(angle), 0.5 + 0.7 * np.sin(angle)]
|
| 37 |
+
|
| 38 |
+
# Additional positioning adjustment to make sure all circles
|
| 39 |
+
# are inside the square and don't overlap
|
| 40 |
+
# Clip to ensure everything is inside the unit square
|
| 41 |
+
centers = np.clip(centers, 0.01, 0.99)
|
| 42 |
+
|
| 43 |
+
# Compute maximum valid radii for this configuration
|
| 44 |
+
radii = compute_max_radii(centers)
|
| 45 |
+
return centers, radii
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def compute_max_radii(centers):
|
| 49 |
+
"""
|
| 50 |
+
Compute the maximum possible radii for each circle position
|
| 51 |
+
such that they don't overlap and stay within the unit square.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
np.array of shape (n) with radius of each circle
|
| 58 |
+
"""
|
| 59 |
+
n = centers.shape[0]
|
| 60 |
+
radii = np.ones(n)
|
| 61 |
+
|
| 62 |
+
# First, limit by distance to square borders
|
| 63 |
+
for i in range(n):
|
| 64 |
+
x, y = centers[i]
|
| 65 |
+
# Distance to borders
|
| 66 |
+
radii[i] = min(x, y, 1 - x, 1 - y)
|
| 67 |
+
|
| 68 |
+
# Then, limit by distance to other circles
|
| 69 |
+
# Each pair of circles with centers at distance d can have
|
| 70 |
+
# sum of radii at most d to avoid overlap
|
| 71 |
+
for i in range(n):
|
| 72 |
+
for j in range(i + 1, n):
|
| 73 |
+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
|
| 74 |
+
|
| 75 |
+
# If current radii would cause overlap
|
| 76 |
+
if radii[i] + radii[j] > dist:
|
| 77 |
+
# Scale both radii proportionally
|
| 78 |
+
scale = dist / (radii[i] + radii[j])
|
| 79 |
+
radii[i] *= scale
|
| 80 |
+
radii[j] *= scale
|
| 81 |
+
|
| 82 |
+
return radii
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# EVOLVE-BLOCK-END
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# This part remains fixed (not evolved)
|
| 89 |
+
def run_packing():
|
| 90 |
+
"""Run the circle packing constructor for n=26"""
|
| 91 |
+
centers, radii = construct_packing()
|
| 92 |
+
# Calculate the sum of radii
|
| 93 |
+
sum_radii = np.sum(radii)
|
| 94 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_1/rewrite.txt
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.optimize import linprog
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def construct_packing():
|
| 12 |
+
"""
|
| 13 |
+
Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 14 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
Tuple of (centers, radii)
|
| 18 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 19 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 20 |
+
"""
|
| 21 |
+
n = 26
|
| 22 |
+
centers = np.zeros((n, 2))
|
| 23 |
+
|
| 24 |
+
# Parameters for the hierarchical grid. These are chosen to create a
|
| 25 |
+
# balanced initial configuration. R is the conceptual radius for the main
|
| 26 |
+
# grid, which determines the overall scale. d splits the central gap.
|
| 27 |
+
R = 0.121 # Base radius for the 4x4 grid
|
| 28 |
+
d = 0.055 # Displacement for the two central circles
|
| 29 |
+
|
| 30 |
+
# Center the entire structure within the unit square
|
| 31 |
+
margin = (1.0 - 8 * R) / 2.0
|
| 32 |
+
|
| 33 |
+
k = 0
|
| 34 |
+
|
| 35 |
+
# 1. Place 16 primary circles in a 4x4 grid
|
| 36 |
+
for i in range(4):
|
| 37 |
+
for j in range(4):
|
| 38 |
+
x = margin + (2 * i + 1) * R
|
| 39 |
+
y = margin + (2 * j + 1) * R
|
| 40 |
+
centers[k] = [x, y]
|
| 41 |
+
k += 1
|
| 42 |
+
|
| 43 |
+
# 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center
|
| 44 |
+
for i in range(3):
|
| 45 |
+
for j in range(3):
|
| 46 |
+
if i == 1 and j == 1:
|
| 47 |
+
continue
|
| 48 |
+
x = margin + (2 * (i + 1)) * R
|
| 49 |
+
y = margin + (2 * (j + 1)) * R
|
| 50 |
+
centers[k] = [x, y]
|
| 51 |
+
k += 1
|
| 52 |
+
|
| 53 |
+
# 3. Place 2 tertiary circles in the central gap, split by 'd'
|
| 54 |
+
center_point = margin + 4 * R
|
| 55 |
+
centers[k] = [center_point, center_point - d]
|
| 56 |
+
k += 1
|
| 57 |
+
centers[k] = [center_point, center_point + d]
|
| 58 |
+
k += 1
|
| 59 |
+
|
| 60 |
+
# For the given centers, compute the radii that maximize the sum.
|
| 61 |
+
radii = compute_max_radii(centers)
|
| 62 |
+
|
| 63 |
+
return centers, radii
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def compute_max_radii(centers):
|
| 67 |
+
"""
|
| 68 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 69 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 70 |
+
subject to non-overlapping and boundary constraints.
|
| 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 the optimal radius for each circle.
|
| 77 |
+
"""
|
| 78 |
+
n = centers.shape[0]
|
| 79 |
+
|
| 80 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 81 |
+
# minimizing sum(-radii).
|
| 82 |
+
c = -np.ones(n)
|
| 83 |
+
|
| 84 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 85 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 86 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 87 |
+
constraints = []
|
| 88 |
+
b_vector = []
|
| 89 |
+
|
| 90 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 91 |
+
for i in range(n):
|
| 92 |
+
# r_i <= x_i
|
| 93 |
+
row = np.zeros(n)
|
| 94 |
+
row[i] = 1
|
| 95 |
+
constraints.append(row)
|
| 96 |
+
b_vector.append(centers[i, 0])
|
| 97 |
+
|
| 98 |
+
# r_i <= 1 - x_i
|
| 99 |
+
row = np.zeros(n)
|
| 100 |
+
row[i] = 1
|
| 101 |
+
constraints.append(row)
|
| 102 |
+
b_vector.append(1 - centers[i, 0])
|
| 103 |
+
|
| 104 |
+
# r_i <= y_i
|
| 105 |
+
row = np.zeros(n)
|
| 106 |
+
row[i] = 1
|
| 107 |
+
constraints.append(row)
|
| 108 |
+
b_vector.append(centers[i, 1])
|
| 109 |
+
|
| 110 |
+
# r_i <= 1 - y_i
|
| 111 |
+
row = np.zeros(n)
|
| 112 |
+
row[i] = 1
|
| 113 |
+
constraints.append(row)
|
| 114 |
+
b_vector.append(1 - centers[i, 1])
|
| 115 |
+
|
| 116 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 117 |
+
for i in range(n):
|
| 118 |
+
for j in range(i + 1, n):
|
| 119 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 120 |
+
row = np.zeros(n)
|
| 121 |
+
row[i] = 1
|
| 122 |
+
row[j] = 1
|
| 123 |
+
constraints.append(row)
|
| 124 |
+
b_vector.append(dist)
|
| 125 |
+
|
| 126 |
+
A_ub = np.array(constraints)
|
| 127 |
+
b_ub = np.array(b_vector)
|
| 128 |
+
|
| 129 |
+
# All radii must be non-negative.
|
| 130 |
+
bounds = [(0, None) for _ in range(n)]
|
| 131 |
+
|
| 132 |
+
# Solve the linear program. 'highs' is a fast and reliable solver.
|
| 133 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 134 |
+
|
| 135 |
+
if res.success:
|
| 136 |
+
# Return the optimal radii
|
| 137 |
+
return res.x
|
| 138 |
+
else:
|
| 139 |
+
# Fallback in case of solver failure
|
| 140 |
+
# An empty array will result in a sum of 0, signaling a problem.
|
| 141 |
+
return np.zeros(n)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (5.32 kB). View file
|
|
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/edit.diff
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,174 +1,176 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 7 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 8 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.optimize import linprog
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def construct_packing():
|
| 16 |
+
"""
|
| 17 |
+
Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 18 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 19 |
+
|
| 20 |
+
This implementation performs a 'crossover' by taking the highly effective
|
| 21 |
+
hierarchical grid placement strategy from one program and refining its
|
| 22 |
+
parameters based on analysis of the best performing solution, while retaining
|
| 23 |
+
the robust linear programming approach for radii calculation from all high-performing
|
| 24 |
+
programs.
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
Tuple of (centers, radii)
|
| 28 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 29 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
n = 26
|
| 32 |
+
centers = np.zeros((n, 2))
|
| 33 |
+
|
| 34 |
+
- # Refined parameters for the hierarchical grid. These values are a perturbation
|
| 35 |
+
- # of the previously best-performing parameters (R=0.121, d=0.055), aiming
|
| 36 |
+
- # to better utilize space near the boundaries and optimize central circle separation.
|
| 37 |
+
- R = 0.1225 # Increased slightly to push primary grid circles closer to boundaries
|
| 38 |
+
- d = 0.056 # Adjusted central displacement for potential radius gain
|
| 39 |
+
+ # This configuration sets the grid to perfectly fit the unit square, which is
|
| 40 |
+
+ # a critical point in the parameter space. R=0.125 means the conceptual
|
| 41 |
+
+ # 8x8 grid of centers has a side length of 1.0 (8 * 0.125 = 1.0).
|
| 42 |
+
+ R = 0.125
|
| 43 |
+
+ # The central displacement `d` is set to 1/16, a value that balances the
|
| 44 |
+
+ # key spatial constraints on the two central circles.
|
| 45 |
+
+ d = 0.0625
|
| 46 |
+
|
| 47 |
+
# Center the entire structure within the unit square
|
| 48 |
+
# margin ensures the overall grid is centered.
|
| 49 |
+
margin = (1.0 - 8 * R) / 2.0
|
| 50 |
+
|
| 51 |
+
k = 0
|
| 52 |
+
|
| 53 |
+
# 1. Place 16 primary circles in a 4x4 grid
|
| 54 |
+
# These circles are positioned at odd multiples of R from the margin.
|
| 55 |
+
for i in range(4):
|
| 56 |
+
for j in range(4):
|
| 57 |
+
x = margin + (2 * i + 1) * R
|
| 58 |
+
y = margin + (2 * j + 1) * R
|
| 59 |
+
centers[k] = [x, y]
|
| 60 |
+
k += 1
|
| 61 |
+
|
| 62 |
+
# 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center.
|
| 63 |
+
# These are positioned at even multiples of R from the margin.
|
| 64 |
+
for i in range(3):
|
| 65 |
+
for j in range(3):
|
| 66 |
+
# Skip the very central interstitial spot as two other circles will occupy it.
|
| 67 |
+
if i == 1 and j == 1:
|
| 68 |
+
continue
|
| 69 |
+
x = margin + (2 * (i + 1)) * R
|
| 70 |
+
y = margin + (2 * (j + 1)) * R
|
| 71 |
+
centers[k] = [x, y]
|
| 72 |
+
k += 1
|
| 73 |
+
|
| 74 |
+
# 3. Place 2 tertiary circles in the central gap, split by 'd'.
|
| 75 |
+
# These circles break the symmetry at the square's center.
|
| 76 |
+
center_point = margin + 4 * R # This calculates the exact center of the grid structure
|
| 77 |
+
centers[k] = [center_point, center_point - d]
|
| 78 |
+
k += 1
|
| 79 |
+
centers[k] = [center_point, center_point + d]
|
| 80 |
+
k += 1
|
| 81 |
+
|
| 82 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 83 |
+
# at boundaries when calculating radii. This also borrows from concepts
|
| 84 |
+
# in the class-based placement strategy for robustness.
|
| 85 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 86 |
+
|
| 87 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 88 |
+
radii = compute_max_radii(centers)
|
| 89 |
+
|
| 90 |
+
return centers, radii
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def compute_max_radii(centers):
|
| 94 |
+
"""
|
| 95 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 96 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 97 |
+
subject to non-overlapping and boundary constraints.
|
| 98 |
+
This function is retained from the best performing solution due to its
|
| 99 |
+
mathematical optimality for fixed centers.
|
| 100 |
+
|
| 101 |
+
Args:
|
| 102 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 103 |
+
|
| 104 |
+
Returns:
|
| 105 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 106 |
+
"""
|
| 107 |
+
n = centers.shape[0]
|
| 108 |
+
|
| 109 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 110 |
+
# minimizing sum(-radii).
|
| 111 |
+
c = -np.ones(n)
|
| 112 |
+
|
| 113 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 114 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 115 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 116 |
+
constraints = []
|
| 117 |
+
b_vector = []
|
| 118 |
+
|
| 119 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 120 |
+
# These ensure circles stay within the unit square.
|
| 121 |
+
for i in range(n):
|
| 122 |
+
# r_i <= x_i
|
| 123 |
+
row = np.zeros(n)
|
| 124 |
+
row[i] = 1
|
| 125 |
+
constraints.append(row)
|
| 126 |
+
b_vector.append(centers[i, 0])
|
| 127 |
+
|
| 128 |
+
# r_i <= 1 - x_i
|
| 129 |
+
row = np.zeros(n)
|
| 130 |
+
row[i] = 1
|
| 131 |
+
constraints.append(row)
|
| 132 |
+
b_vector.append(1 - centers[i, 0])
|
| 133 |
+
|
| 134 |
+
# r_i <= y_i
|
| 135 |
+
row = np.zeros(n)
|
| 136 |
+
row[i] = 1
|
| 137 |
+
constraints.append(row)
|
| 138 |
+
b_vector.append(centers[i, 1])
|
| 139 |
+
|
| 140 |
+
# r_i <= 1 - y_i
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
constraints.append(row)
|
| 144 |
+
b_vector.append(1 - centers[i, 1])
|
| 145 |
+
|
| 146 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 147 |
+
# These prevent circles from overlapping.
|
| 148 |
+
for i in range(n):
|
| 149 |
+
for j in range(i + 1, n):
|
| 150 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 151 |
+
row = np.zeros(n)
|
| 152 |
+
row[i] = 1
|
| 153 |
+
row[j] = 1
|
| 154 |
+
constraints.append(row)
|
| 155 |
+
b_vector.append(dist)
|
| 156 |
+
|
| 157 |
+
A_ub = np.array(constraints)
|
| 158 |
+
b_ub = np.array(b_vector)
|
| 159 |
+
|
| 160 |
+
# All radii must be non-negative.
|
| 161 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 162 |
+
bounds = [(0, None) for _ in range(n)]
|
| 163 |
+
|
| 164 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 165 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 166 |
+
|
| 167 |
+
if res.success:
|
| 168 |
+
# Return the optimal radii found by the solver.
|
| 169 |
+
return res.x
|
| 170 |
+
else:
|
| 171 |
+
# Fallback in case of solver failure.
|
| 172 |
+
# Returning zeros implies no valid radii could be determined.
|
| 173 |
+
print(f"LP solver failed: {res.message}")
|
| 174 |
+
return np.zeros(n)
|
| 175 |
+
# EVOLVE-BLOCK-END
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
# This part remains fixed (not evolved)
|
| 179 |
+
def run_packing():
|
| 180 |
+
"""Run the circle packing constructor for n=26"""
|
| 181 |
+
centers, radii = construct_packing()
|
| 182 |
+
# Calculate the sum of radii
|
| 183 |
+
sum_radii = np.sum(radii)
|
| 184 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/main.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def construct_packing():
|
| 13 |
+
"""
|
| 14 |
+
Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 15 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 16 |
+
|
| 17 |
+
This implementation performs a 'crossover' by taking the highly effective
|
| 18 |
+
hierarchical grid placement strategy from one program and refining its
|
| 19 |
+
parameters based on analysis of the best performing solution, while retaining
|
| 20 |
+
the robust linear programming approach for radii calculation from all high-performing
|
| 21 |
+
programs.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
Tuple of (centers, radii)
|
| 25 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 26 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 27 |
+
"""
|
| 28 |
+
n = 26
|
| 29 |
+
centers = np.zeros((n, 2))
|
| 30 |
+
|
| 31 |
+
# This configuration sets the grid to perfectly fit the unit square, which is
|
| 32 |
+
# a critical point in the parameter space. R=0.125 means the conceptual
|
| 33 |
+
# 8x8 grid of centers has a side length of 1.0 (8 * 0.125 = 1.0).
|
| 34 |
+
R = 0.125
|
| 35 |
+
# The central displacement `d` is set to 1/16, a value that balances the
|
| 36 |
+
# key spatial constraints on the two central circles.
|
| 37 |
+
d = 0.0625
|
| 38 |
+
|
| 39 |
+
# Center the entire structure within the unit square
|
| 40 |
+
# margin ensures the overall grid is centered.
|
| 41 |
+
margin = (1.0 - 8 * R) / 2.0
|
| 42 |
+
|
| 43 |
+
k = 0
|
| 44 |
+
|
| 45 |
+
# 1. Place 16 primary circles in a 4x4 grid
|
| 46 |
+
# These circles are positioned at odd multiples of R from the margin.
|
| 47 |
+
for i in range(4):
|
| 48 |
+
for j in range(4):
|
| 49 |
+
x = margin + (2 * i + 1) * R
|
| 50 |
+
y = margin + (2 * j + 1) * R
|
| 51 |
+
centers[k] = [x, y]
|
| 52 |
+
k += 1
|
| 53 |
+
|
| 54 |
+
# 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center.
|
| 55 |
+
# These are positioned at even multiples of R from the margin.
|
| 56 |
+
for i in range(3):
|
| 57 |
+
for j in range(3):
|
| 58 |
+
# Skip the very central interstitial spot as two other circles will occupy it.
|
| 59 |
+
if i == 1 and j == 1:
|
| 60 |
+
continue
|
| 61 |
+
x = margin + (2 * (i + 1)) * R
|
| 62 |
+
y = margin + (2 * (j + 1)) * R
|
| 63 |
+
centers[k] = [x, y]
|
| 64 |
+
k += 1
|
| 65 |
+
|
| 66 |
+
# 3. Place 2 tertiary circles in the central gap, split by 'd'.
|
| 67 |
+
# These circles break the symmetry at the square's center.
|
| 68 |
+
center_point = margin + 4 * R # This calculates the exact center of the grid structure
|
| 69 |
+
centers[k] = [center_point, center_point - d]
|
| 70 |
+
k += 1
|
| 71 |
+
centers[k] = [center_point, center_point + d]
|
| 72 |
+
k += 1
|
| 73 |
+
|
| 74 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 75 |
+
# at boundaries when calculating radii. This also borrows from concepts
|
| 76 |
+
# in the class-based placement strategy for robustness.
|
| 77 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 78 |
+
|
| 79 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 80 |
+
radii = compute_max_radii(centers)
|
| 81 |
+
|
| 82 |
+
return centers, radii
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def compute_max_radii(centers):
|
| 86 |
+
"""
|
| 87 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 88 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 89 |
+
subject to non-overlapping and boundary constraints.
|
| 90 |
+
This function is retained from the best performing solution due to its
|
| 91 |
+
mathematical optimality for fixed centers.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 98 |
+
"""
|
| 99 |
+
n = centers.shape[0]
|
| 100 |
+
|
| 101 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 102 |
+
# minimizing sum(-radii).
|
| 103 |
+
c = -np.ones(n)
|
| 104 |
+
|
| 105 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 106 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 107 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 108 |
+
constraints = []
|
| 109 |
+
b_vector = []
|
| 110 |
+
|
| 111 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 112 |
+
# These ensure circles stay within the unit square.
|
| 113 |
+
for i in range(n):
|
| 114 |
+
# r_i <= x_i
|
| 115 |
+
row = np.zeros(n)
|
| 116 |
+
row[i] = 1
|
| 117 |
+
constraints.append(row)
|
| 118 |
+
b_vector.append(centers[i, 0])
|
| 119 |
+
|
| 120 |
+
# r_i <= 1 - x_i
|
| 121 |
+
row = np.zeros(n)
|
| 122 |
+
row[i] = 1
|
| 123 |
+
constraints.append(row)
|
| 124 |
+
b_vector.append(1 - centers[i, 0])
|
| 125 |
+
|
| 126 |
+
# r_i <= y_i
|
| 127 |
+
row = np.zeros(n)
|
| 128 |
+
row[i] = 1
|
| 129 |
+
constraints.append(row)
|
| 130 |
+
b_vector.append(centers[i, 1])
|
| 131 |
+
|
| 132 |
+
# r_i <= 1 - y_i
|
| 133 |
+
row = np.zeros(n)
|
| 134 |
+
row[i] = 1
|
| 135 |
+
constraints.append(row)
|
| 136 |
+
b_vector.append(1 - centers[i, 1])
|
| 137 |
+
|
| 138 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 139 |
+
# These prevent circles from overlapping.
|
| 140 |
+
for i in range(n):
|
| 141 |
+
for j in range(i + 1, n):
|
| 142 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 143 |
+
row = np.zeros(n)
|
| 144 |
+
row[i] = 1
|
| 145 |
+
row[j] = 1
|
| 146 |
+
constraints.append(row)
|
| 147 |
+
b_vector.append(dist)
|
| 148 |
+
|
| 149 |
+
A_ub = np.array(constraints)
|
| 150 |
+
b_ub = np.array(b_vector)
|
| 151 |
+
|
| 152 |
+
# All radii must be non-negative.
|
| 153 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 154 |
+
bounds = [(0, None) for _ in range(n)]
|
| 155 |
+
|
| 156 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 157 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 158 |
+
|
| 159 |
+
if res.success:
|
| 160 |
+
# Return the optimal radii found by the solver.
|
| 161 |
+
return res.x
|
| 162 |
+
else:
|
| 163 |
+
# Fallback in case of solver failure.
|
| 164 |
+
# Returning zeros implies no valid radii could be determined.
|
| 165 |
+
print(f"LP solver failed: {res.message}")
|
| 166 |
+
return np.zeros(n)
|
| 167 |
+
# EVOLVE-BLOCK-END
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# This part remains fixed (not evolved)
|
| 171 |
+
def run_packing():
|
| 172 |
+
"""Run the circle packing constructor for n=26"""
|
| 173 |
+
centers, radii = construct_packing()
|
| 174 |
+
# Calculate the sum of radii
|
| 175 |
+
sum_radii = np.sum(radii)
|
| 176 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/original.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def construct_packing():
|
| 13 |
+
"""
|
| 14 |
+
Constructs a packing of 26 circles based on a hierarchical grid structure.
|
| 15 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 16 |
+
|
| 17 |
+
This implementation performs a 'crossover' by taking the highly effective
|
| 18 |
+
hierarchical grid placement strategy from one program and refining its
|
| 19 |
+
parameters based on analysis of the best performing solution, while retaining
|
| 20 |
+
the robust linear programming approach for radii calculation from all high-performing
|
| 21 |
+
programs.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
Tuple of (centers, radii)
|
| 25 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 26 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 27 |
+
"""
|
| 28 |
+
n = 26
|
| 29 |
+
centers = np.zeros((n, 2))
|
| 30 |
+
|
| 31 |
+
# Refined parameters for the hierarchical grid. These values are a perturbation
|
| 32 |
+
# of the previously best-performing parameters (R=0.121, d=0.055), aiming
|
| 33 |
+
# to better utilize space near the boundaries and optimize central circle separation.
|
| 34 |
+
R = 0.1225 # Increased slightly to push primary grid circles closer to boundaries
|
| 35 |
+
d = 0.056 # Adjusted central displacement for potential radius gain
|
| 36 |
+
|
| 37 |
+
# Center the entire structure within the unit square
|
| 38 |
+
# margin ensures the overall grid is centered.
|
| 39 |
+
margin = (1.0 - 8 * R) / 2.0
|
| 40 |
+
|
| 41 |
+
k = 0
|
| 42 |
+
|
| 43 |
+
# 1. Place 16 primary circles in a 4x4 grid
|
| 44 |
+
# These circles are positioned at odd multiples of R from the margin.
|
| 45 |
+
for i in range(4):
|
| 46 |
+
for j in range(4):
|
| 47 |
+
x = margin + (2 * i + 1) * R
|
| 48 |
+
y = margin + (2 * j + 1) * R
|
| 49 |
+
centers[k] = [x, y]
|
| 50 |
+
k += 1
|
| 51 |
+
|
| 52 |
+
# 2. Place 8 secondary circles in the 3x3 interstitial gaps, skipping the center.
|
| 53 |
+
# These are positioned at even multiples of R from the margin.
|
| 54 |
+
for i in range(3):
|
| 55 |
+
for j in range(3):
|
| 56 |
+
# Skip the very central interstitial spot as two other circles will occupy it.
|
| 57 |
+
if i == 1 and j == 1:
|
| 58 |
+
continue
|
| 59 |
+
x = margin + (2 * (i + 1)) * R
|
| 60 |
+
y = margin + (2 * (j + 1)) * R
|
| 61 |
+
centers[k] = [x, y]
|
| 62 |
+
k += 1
|
| 63 |
+
|
| 64 |
+
# 3. Place 2 tertiary circles in the central gap, split by 'd'.
|
| 65 |
+
# These circles break the symmetry at the square's center.
|
| 66 |
+
center_point = margin + 4 * R # This calculates the exact center of the grid structure
|
| 67 |
+
centers[k] = [center_point, center_point - d]
|
| 68 |
+
k += 1
|
| 69 |
+
centers[k] = [center_point, center_point + d]
|
| 70 |
+
k += 1
|
| 71 |
+
|
| 72 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 73 |
+
# at boundaries when calculating radii. This also borrows from concepts
|
| 74 |
+
# in the class-based placement strategy for robustness.
|
| 75 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 76 |
+
|
| 77 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 78 |
+
radii = compute_max_radii(centers)
|
| 79 |
+
|
| 80 |
+
return centers, radii
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def compute_max_radii(centers):
|
| 84 |
+
"""
|
| 85 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 86 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 87 |
+
subject to non-overlapping and boundary constraints.
|
| 88 |
+
This function is retained from the best performing solution due to its
|
| 89 |
+
mathematical optimality for fixed centers.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 96 |
+
"""
|
| 97 |
+
n = centers.shape[0]
|
| 98 |
+
|
| 99 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 100 |
+
# minimizing sum(-radii).
|
| 101 |
+
c = -np.ones(n)
|
| 102 |
+
|
| 103 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 104 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 105 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 106 |
+
constraints = []
|
| 107 |
+
b_vector = []
|
| 108 |
+
|
| 109 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 110 |
+
# These ensure circles stay within the unit square.
|
| 111 |
+
for i in range(n):
|
| 112 |
+
# r_i <= x_i
|
| 113 |
+
row = np.zeros(n)
|
| 114 |
+
row[i] = 1
|
| 115 |
+
constraints.append(row)
|
| 116 |
+
b_vector.append(centers[i, 0])
|
| 117 |
+
|
| 118 |
+
# r_i <= 1 - x_i
|
| 119 |
+
row = np.zeros(n)
|
| 120 |
+
row[i] = 1
|
| 121 |
+
constraints.append(row)
|
| 122 |
+
b_vector.append(1 - centers[i, 0])
|
| 123 |
+
|
| 124 |
+
# r_i <= y_i
|
| 125 |
+
row = np.zeros(n)
|
| 126 |
+
row[i] = 1
|
| 127 |
+
constraints.append(row)
|
| 128 |
+
b_vector.append(centers[i, 1])
|
| 129 |
+
|
| 130 |
+
# r_i <= 1 - y_i
|
| 131 |
+
row = np.zeros(n)
|
| 132 |
+
row[i] = 1
|
| 133 |
+
constraints.append(row)
|
| 134 |
+
b_vector.append(1 - centers[i, 1])
|
| 135 |
+
|
| 136 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 137 |
+
# These prevent circles from overlapping.
|
| 138 |
+
for i in range(n):
|
| 139 |
+
for j in range(i + 1, n):
|
| 140 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
row[j] = 1
|
| 144 |
+
constraints.append(row)
|
| 145 |
+
b_vector.append(dist)
|
| 146 |
+
|
| 147 |
+
A_ub = np.array(constraints)
|
| 148 |
+
b_ub = np.array(b_vector)
|
| 149 |
+
|
| 150 |
+
# All radii must be non-negative.
|
| 151 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 152 |
+
bounds = [(0, None) for _ in range(n)]
|
| 153 |
+
|
| 154 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 155 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 156 |
+
|
| 157 |
+
if res.success:
|
| 158 |
+
# Return the optimal radii found by the solver.
|
| 159 |
+
return res.x
|
| 160 |
+
else:
|
| 161 |
+
# Fallback in case of solver failure.
|
| 162 |
+
# Returning zeros implies no valid radii could be determined.
|
| 163 |
+
print(f"LP solver failed: {res.message}")
|
| 164 |
+
return np.zeros(n)
|
| 165 |
+
# EVOLVE-BLOCK-END
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# This part remains fixed (not evolved)
|
| 169 |
+
def run_packing():
|
| 170 |
+
"""Run the circle packing constructor for n=26"""
|
| 171 |
+
centers, radii = construct_packing()
|
| 172 |
+
# Calculate the sum of radii
|
| 173 |
+
sum_radii = np.sum(radii)
|
| 174 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/results/metrics.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 2.443722059560568,
|
| 3 |
+
"correct": true,
|
| 4 |
+
"primary": {
|
| 5 |
+
"combined_score": 2.443722059560568,
|
| 6 |
+
"public": {
|
| 7 |
+
"centers_str": " centers[0] = (0.1250, 0.1250)\n centers[1] = (0.1250, 0.3750)\n centers[2] = (0.1250, 0.6250)\n centers[3] = (0.1250, 0.8750)\n centers[4] = (0.3750, 0.1250)\n centers[5] = (0.3750, 0.3750)\n centers[6] = (0.3750, 0.6250)\n centers[7] = (0.3750, 0.8750)\n centers[8] = (0.6250, 0.1250)\n centers[9] = (0.6250, 0.3750)\n centers[10] = (0.6250, 0.6250)\n centers[11] = (0.6250, 0.8750)\n centers[12] = (0.8750, 0.1250)\n centers[13] = (0.8750, 0.3750)\n centers[14] = (0.8750, 0.6250)\n centers[15] = (0.8750, 0.8750)\n centers[16] = (0.2500, 0.2500)\n centers[17] = (0.2500, 0.5000)\n centers[18] = (0.2500, 0.7500)\n centers[19] = (0.5000, 0.2500)\n centers[20] = (0.5000, 0.7500)\n centers[21] = (0.7500, 0.2500)\n centers[22] = (0.7500, 0.5000)\n centers[23] = (0.7500, 0.7500)\n centers[24] = (0.5000, 0.4375)\n centers[25] = (0.5000, 0.5625)",
|
| 8 |
+
"num_circles": 26
|
| 9 |
+
},
|
| 10 |
+
"private": {
|
| 11 |
+
"reported_sum_of_radii": 2.443722059560568
|
| 12 |
+
},
|
| 13 |
+
"execution_time_mean": 0.04552629217505455,
|
| 14 |
+
"execution_time_std": 0.0,
|
| 15 |
+
"num_valid_runs": 1,
|
| 16 |
+
"num_invalid_runs": 0,
|
| 17 |
+
"all_validation_errors": [],
|
| 18 |
+
"correct": true,
|
| 19 |
+
"validation_error": null
|
| 20 |
+
},
|
| 21 |
+
"auxiliary": {},
|
| 22 |
+
"auxiliary_descriptions": {},
|
| 23 |
+
"timestamp": 1770490043.661491,
|
| 24 |
+
"generation": 10
|
| 25 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_10/search_replace.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
grid_perfect_fit
|
| 3 |
+
</NAME>
|
| 4 |
+
<DESCRIPTION>
|
| 5 |
+
The current best strategy uses a hierarchical grid with parameters `R` and `d`. The performance has been improving as `R` increases, pushing the grid outwards. This edit takes that trend to its logical conclusion by setting `R=0.125`. This specific value makes the conceptual 8x8 grid of circle centers (from the 4x4 primary grid) have a total width of `8 * R = 1.0`, meaning the grid structure perfectly fits the unit square with zero margin. This is a critical configuration that forces the outer circles to be tangent to the boundaries, maximizing their potential size.
|
| 6 |
+
|
| 7 |
+
With this change in `R`, the parameter `d` for the two central circles is also re-evaluated. The central circles are constrained by each other (distance `2d`) and by the surrounding interstitial circles. A new value of `d=0.0625` (which is 1/16) is chosen. This value is derived by balancing the main constraints on the central circles' radii: the constraint from their twin (`r_central <= d`) and the constraint from the next interstitial circle along the y-axis (`r_central` is also limited by the space `0.25 - d`). Setting the bounds `d` and `0.125-d` to be equal gives `d=0.0625`, a choice that heuristically equalizes the spatial pressure on these central circles.
|
| 8 |
+
</DESCRIPTION>
|
| 9 |
+
<DIFF>
|
| 10 |
+
<<<<<<< SEARCH
|
| 11 |
+
# Refined parameters for the hierarchical grid. These values are a perturbation
|
| 12 |
+
# of the previously best-performing parameters (R=0.121, d=0.055), aiming
|
| 13 |
+
# to better utilize space near the boundaries and optimize central circle separation.
|
| 14 |
+
R = 0.1225 # Increased slightly to push primary grid circles closer to boundaries
|
| 15 |
+
d = 0.056 # Adjusted central displacement for potential radius gain
|
| 16 |
+
=======
|
| 17 |
+
# This configuration sets the grid to perfectly fit the unit square, which is
|
| 18 |
+
# a critical point in the parameter space. R=0.125 means the conceptual
|
| 19 |
+
# 8x8 grid of centers has a side length of 1.0 (8 * 0.125 = 1.0).
|
| 20 |
+
R = 0.125
|
| 21 |
+
# The central displacement `d` is set to 1/16, a value that balances the
|
| 22 |
+
# key spatial constraints on the two central circles.
|
| 23 |
+
d = 0.0625
|
| 24 |
+
>>>>>>> REPLACE
|
| 25 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/edit.diff
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,170 +1,174 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 7 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 8 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.optimize import linprog
|
| 13 |
+
import math # Imported for trigonometric functions in central placement
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def construct_packing():
|
| 17 |
+
"""
|
| 18 |
+
Constructs a packing of 26 circles by performing a crossover between two
|
| 19 |
+
successful strategies. It uses a 5x5 grid base and incorporates a superior
|
| 20 |
+
central placement logic.
|
| 21 |
+
|
| 22 |
+
The base structure is a 5x5 grid with the center removed, from the "Current Program".
|
| 23 |
+
This provides a robust scaffold for 24 circles.
|
| 24 |
+
|
| 25 |
+
The crossover improvement comes from replacing the simple vertical split for the
|
| 26 |
+
two central circles with the parameterized asymmetric diagonal placement from the
|
| 27 |
+
"Inspiration Program", which is proven to yield a higher score.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
Tuple of (centers, radii)
|
| 31 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 32 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 33 |
+
"""
|
| 34 |
+
n = 26
|
| 35 |
+
centers = np.zeros((n, 2))
|
| 36 |
+
k = 0
|
| 37 |
+
|
| 38 |
+
- # 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 39 |
+
- # This structure is inherited from the effective "Current Program".
|
| 40 |
+
+ # 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 41 |
+
+ # The grid is expanded in the center to create more space for the central
|
| 42 |
+
+ # circles and the circles on the medial axes. This is a trade-off, as it
|
| 43 |
+
+ # reduces space for circles in the outer grid regions.
|
| 44 |
+
num_grid_divs = 5
|
| 45 |
+
- coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 46 |
+
+ # Original coords: np.linspace(0.1, 0.9, 5) -> [0.1, 0.3, 0.5, 0.7, 0.9]
|
| 47 |
+
+ # We move the inner grid lines (0.3, 0.7) outwards to (0.28, 0.72).
|
| 48 |
+
+ coords = np.array([0.1, 0.28, 0.5, 0.72, 0.9])
|
| 49 |
+
|
| 50 |
+
for i in range(num_grid_divs):
|
| 51 |
+
for j in range(num_grid_divs):
|
| 52 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 53 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 54 |
+
continue
|
| 55 |
+
centers[k] = [coords[i], coords[j]]
|
| 56 |
+
k += 1
|
| 57 |
+
|
| 58 |
+
- # 2. Crossover: Place 2 circles in the central gap using the superior logic
|
| 59 |
+
- # from the "Inspiration Program".
|
| 60 |
+
- # Parameters are taken from the best-performing variant (score 2.51).
|
| 61 |
+
- central_separation_distance = 0.107
|
| 62 |
+
+ # 2. Crossover: Place 2 circles in the now larger central gap.
|
| 63 |
+
+ # The separation distance is increased to utilize the extra space created by
|
| 64 |
+
+ # the non-uniform grid. The asymmetry angle is kept.
|
| 65 |
+
+ central_separation_distance = 0.120
|
| 66 |
+
central_asymmetry_angle_deg = 44.5
|
| 67 |
+
|
| 68 |
+
# R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 69 |
+
R_prime = central_separation_distance / 2.0
|
| 70 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 71 |
+
|
| 72 |
+
# Calculate x and y offsets for the asymmetric diagonal placement.
|
| 73 |
+
dx = R_prime * math.cos(angle_rad)
|
| 74 |
+
dy = R_prime * math.sin(angle_rad)
|
| 75 |
+
|
| 76 |
+
center_point = 0.5
|
| 77 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 78 |
+
k += 1
|
| 79 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 80 |
+
k += 1
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 84 |
+
# at boundaries when calculating radii.
|
| 85 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 86 |
+
|
| 87 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 88 |
+
radii = compute_max_radii(centers)
|
| 89 |
+
|
| 90 |
+
return centers, radii
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def compute_max_radii(centers):
|
| 94 |
+
"""
|
| 95 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 96 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 97 |
+
subject to non-overlapping and boundary constraints.
|
| 98 |
+
This function is retained from the best performing solution due to its
|
| 99 |
+
mathematical optimality for fixed centers.
|
| 100 |
+
|
| 101 |
+
Args:
|
| 102 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 103 |
+
|
| 104 |
+
Returns:
|
| 105 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 106 |
+
"""
|
| 107 |
+
n = centers.shape[0]
|
| 108 |
+
|
| 109 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 110 |
+
# minimizing sum(-radii).
|
| 111 |
+
c = -np.ones(n)
|
| 112 |
+
|
| 113 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 114 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 115 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 116 |
+
constraints = []
|
| 117 |
+
b_vector = []
|
| 118 |
+
|
| 119 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 120 |
+
# These ensure circles stay within the unit square.
|
| 121 |
+
for i in range(n):
|
| 122 |
+
# r_i <= x_i
|
| 123 |
+
row = np.zeros(n)
|
| 124 |
+
row[i] = 1
|
| 125 |
+
constraints.append(row)
|
| 126 |
+
b_vector.append(centers[i, 0])
|
| 127 |
+
|
| 128 |
+
# r_i <= 1 - x_i
|
| 129 |
+
row = np.zeros(n)
|
| 130 |
+
row[i] = 1
|
| 131 |
+
constraints.append(row)
|
| 132 |
+
b_vector.append(1 - centers[i, 0])
|
| 133 |
+
|
| 134 |
+
# r_i <= y_i
|
| 135 |
+
row = np.zeros(n)
|
| 136 |
+
row[i] = 1
|
| 137 |
+
constraints.append(row)
|
| 138 |
+
b_vector.append(centers[i, 1])
|
| 139 |
+
|
| 140 |
+
# r_i <= 1 - y_i
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
constraints.append(row)
|
| 144 |
+
b_vector.append(1 - centers[i, 1])
|
| 145 |
+
|
| 146 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 147 |
+
# These prevent circles from overlapping.
|
| 148 |
+
for i in range(n):
|
| 149 |
+
for j in range(i + 1, n):
|
| 150 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 151 |
+
row = np.zeros(n)
|
| 152 |
+
row[i] = 1
|
| 153 |
+
row[j] = 1
|
| 154 |
+
constraints.append(row)
|
| 155 |
+
b_vector.append(dist)
|
| 156 |
+
|
| 157 |
+
A_ub = np.array(constraints)
|
| 158 |
+
b_ub = np.array(b_vector)
|
| 159 |
+
|
| 160 |
+
# All radii must be non-negative.
|
| 161 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 162 |
+
bounds = [(0, None) for _ in range(n)]
|
| 163 |
+
|
| 164 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 165 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 166 |
+
|
| 167 |
+
if res.success:
|
| 168 |
+
# Return the optimal radii found by the solver.
|
| 169 |
+
return res.x
|
| 170 |
+
else:
|
| 171 |
+
# Fallback in case of solver failure.
|
| 172 |
+
# Returning zeros implies no valid radii could be determined.
|
| 173 |
+
print(f"LP solver failed: {res.message}")
|
| 174 |
+
return np.zeros(n)
|
| 175 |
+
# EVOLVE-BLOCK-END
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
# This part remains fixed (not evolved)
|
| 179 |
+
def run_packing():
|
| 180 |
+
"""Run the circle packing constructor for n=26"""
|
| 181 |
+
centers, radii = construct_packing()
|
| 182 |
+
# Calculate the sum of radii
|
| 183 |
+
sum_radii = np.sum(radii)
|
| 184 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/main.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Imported for trigonometric functions in central placement
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles by performing a crossover between two
|
| 16 |
+
successful strategies. It uses a 5x5 grid base and incorporates a superior
|
| 17 |
+
central placement logic.
|
| 18 |
+
|
| 19 |
+
The base structure is a 5x5 grid with the center removed, from the "Current Program".
|
| 20 |
+
This provides a robust scaffold for 24 circles.
|
| 21 |
+
|
| 22 |
+
The crossover improvement comes from replacing the simple vertical split for the
|
| 23 |
+
two central circles with the parameterized asymmetric diagonal placement from the
|
| 24 |
+
"Inspiration Program", which is proven to yield a higher score.
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
Tuple of (centers, radii)
|
| 28 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 29 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
n = 26
|
| 32 |
+
centers = np.zeros((n, 2))
|
| 33 |
+
k = 0
|
| 34 |
+
|
| 35 |
+
# 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 36 |
+
# The grid is expanded in the center to create more space for the central
|
| 37 |
+
# circles and the circles on the medial axes. This is a trade-off, as it
|
| 38 |
+
# reduces space for circles in the outer grid regions.
|
| 39 |
+
num_grid_divs = 5
|
| 40 |
+
# Original coords: np.linspace(0.1, 0.9, 5) -> [0.1, 0.3, 0.5, 0.7, 0.9]
|
| 41 |
+
# We move the inner grid lines (0.3, 0.7) outwards to (0.28, 0.72).
|
| 42 |
+
coords = np.array([0.1, 0.28, 0.5, 0.72, 0.9])
|
| 43 |
+
|
| 44 |
+
for i in range(num_grid_divs):
|
| 45 |
+
for j in range(num_grid_divs):
|
| 46 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 47 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 48 |
+
continue
|
| 49 |
+
centers[k] = [coords[i], coords[j]]
|
| 50 |
+
k += 1
|
| 51 |
+
|
| 52 |
+
# 2. Crossover: Place 2 circles in the now larger central gap.
|
| 53 |
+
# The separation distance is increased to utilize the extra space created by
|
| 54 |
+
# the non-uniform grid. The asymmetry angle is kept.
|
| 55 |
+
central_separation_distance = 0.120
|
| 56 |
+
central_asymmetry_angle_deg = 44.5
|
| 57 |
+
|
| 58 |
+
# R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 59 |
+
R_prime = central_separation_distance / 2.0
|
| 60 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 61 |
+
|
| 62 |
+
# Calculate x and y offsets for the asymmetric diagonal placement.
|
| 63 |
+
dx = R_prime * math.cos(angle_rad)
|
| 64 |
+
dy = R_prime * math.sin(angle_rad)
|
| 65 |
+
|
| 66 |
+
center_point = 0.5
|
| 67 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 68 |
+
k += 1
|
| 69 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 70 |
+
k += 1
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 74 |
+
# at boundaries when calculating radii.
|
| 75 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 76 |
+
|
| 77 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 78 |
+
radii = compute_max_radii(centers)
|
| 79 |
+
|
| 80 |
+
return centers, radii
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def compute_max_radii(centers):
|
| 84 |
+
"""
|
| 85 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 86 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 87 |
+
subject to non-overlapping and boundary constraints.
|
| 88 |
+
This function is retained from the best performing solution due to its
|
| 89 |
+
mathematical optimality for fixed centers.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 96 |
+
"""
|
| 97 |
+
n = centers.shape[0]
|
| 98 |
+
|
| 99 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 100 |
+
# minimizing sum(-radii).
|
| 101 |
+
c = -np.ones(n)
|
| 102 |
+
|
| 103 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 104 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 105 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 106 |
+
constraints = []
|
| 107 |
+
b_vector = []
|
| 108 |
+
|
| 109 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 110 |
+
# These ensure circles stay within the unit square.
|
| 111 |
+
for i in range(n):
|
| 112 |
+
# r_i <= x_i
|
| 113 |
+
row = np.zeros(n)
|
| 114 |
+
row[i] = 1
|
| 115 |
+
constraints.append(row)
|
| 116 |
+
b_vector.append(centers[i, 0])
|
| 117 |
+
|
| 118 |
+
# r_i <= 1 - x_i
|
| 119 |
+
row = np.zeros(n)
|
| 120 |
+
row[i] = 1
|
| 121 |
+
constraints.append(row)
|
| 122 |
+
b_vector.append(1 - centers[i, 0])
|
| 123 |
+
|
| 124 |
+
# r_i <= y_i
|
| 125 |
+
row = np.zeros(n)
|
| 126 |
+
row[i] = 1
|
| 127 |
+
constraints.append(row)
|
| 128 |
+
b_vector.append(centers[i, 1])
|
| 129 |
+
|
| 130 |
+
# r_i <= 1 - y_i
|
| 131 |
+
row = np.zeros(n)
|
| 132 |
+
row[i] = 1
|
| 133 |
+
constraints.append(row)
|
| 134 |
+
b_vector.append(1 - centers[i, 1])
|
| 135 |
+
|
| 136 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 137 |
+
# These prevent circles from overlapping.
|
| 138 |
+
for i in range(n):
|
| 139 |
+
for j in range(i + 1, n):
|
| 140 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
row[j] = 1
|
| 144 |
+
constraints.append(row)
|
| 145 |
+
b_vector.append(dist)
|
| 146 |
+
|
| 147 |
+
A_ub = np.array(constraints)
|
| 148 |
+
b_ub = np.array(b_vector)
|
| 149 |
+
|
| 150 |
+
# All radii must be non-negative.
|
| 151 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 152 |
+
bounds = [(0, None) for _ in range(n)]
|
| 153 |
+
|
| 154 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 155 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 156 |
+
|
| 157 |
+
if res.success:
|
| 158 |
+
# Return the optimal radii found by the solver.
|
| 159 |
+
return res.x
|
| 160 |
+
else:
|
| 161 |
+
# Fallback in case of solver failure.
|
| 162 |
+
# Returning zeros implies no valid radii could be determined.
|
| 163 |
+
print(f"LP solver failed: {res.message}")
|
| 164 |
+
return np.zeros(n)
|
| 165 |
+
# EVOLVE-BLOCK-END
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# This part remains fixed (not evolved)
|
| 169 |
+
def run_packing():
|
| 170 |
+
"""Run the circle packing constructor for n=26"""
|
| 171 |
+
centers, radii = construct_packing()
|
| 172 |
+
# Calculate the sum of radii
|
| 173 |
+
sum_radii = np.sum(radii)
|
| 174 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/original.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Imported for trigonometric functions in central placement
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles by performing a crossover between two
|
| 16 |
+
successful strategies. It uses a 5x5 grid base and incorporates a superior
|
| 17 |
+
central placement logic.
|
| 18 |
+
|
| 19 |
+
The base structure is a 5x5 grid with the center removed, from the "Current Program".
|
| 20 |
+
This provides a robust scaffold for 24 circles.
|
| 21 |
+
|
| 22 |
+
The crossover improvement comes from replacing the simple vertical split for the
|
| 23 |
+
two central circles with the parameterized asymmetric diagonal placement from the
|
| 24 |
+
"Inspiration Program", which is proven to yield a higher score.
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
Tuple of (centers, radii)
|
| 28 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 29 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 30 |
+
"""
|
| 31 |
+
n = 26
|
| 32 |
+
centers = np.zeros((n, 2))
|
| 33 |
+
k = 0
|
| 34 |
+
|
| 35 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 36 |
+
# This structure is inherited from the effective "Current Program".
|
| 37 |
+
num_grid_divs = 5
|
| 38 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 39 |
+
|
| 40 |
+
for i in range(num_grid_divs):
|
| 41 |
+
for j in range(num_grid_divs):
|
| 42 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 43 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 44 |
+
continue
|
| 45 |
+
centers[k] = [coords[i], coords[j]]
|
| 46 |
+
k += 1
|
| 47 |
+
|
| 48 |
+
# 2. Crossover: Place 2 circles in the central gap using the superior logic
|
| 49 |
+
# from the "Inspiration Program".
|
| 50 |
+
# Parameters are taken from the best-performing variant (score 2.51).
|
| 51 |
+
central_separation_distance = 0.107
|
| 52 |
+
central_asymmetry_angle_deg = 44.5
|
| 53 |
+
|
| 54 |
+
# R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 55 |
+
R_prime = central_separation_distance / 2.0
|
| 56 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 57 |
+
|
| 58 |
+
# Calculate x and y offsets for the asymmetric diagonal placement.
|
| 59 |
+
dx = R_prime * math.cos(angle_rad)
|
| 60 |
+
dy = R_prime * math.sin(angle_rad)
|
| 61 |
+
|
| 62 |
+
center_point = 0.5
|
| 63 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 64 |
+
k += 1
|
| 65 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 66 |
+
k += 1
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 70 |
+
# at boundaries when calculating radii.
|
| 71 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 72 |
+
|
| 73 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 74 |
+
radii = compute_max_radii(centers)
|
| 75 |
+
|
| 76 |
+
return centers, radii
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def compute_max_radii(centers):
|
| 80 |
+
"""
|
| 81 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 82 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 83 |
+
subject to non-overlapping and boundary constraints.
|
| 84 |
+
This function is retained from the best performing solution due to its
|
| 85 |
+
mathematical optimality for fixed centers.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 92 |
+
"""
|
| 93 |
+
n = centers.shape[0]
|
| 94 |
+
|
| 95 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 96 |
+
# minimizing sum(-radii).
|
| 97 |
+
c = -np.ones(n)
|
| 98 |
+
|
| 99 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 100 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 101 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 102 |
+
constraints = []
|
| 103 |
+
b_vector = []
|
| 104 |
+
|
| 105 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 106 |
+
# These ensure circles stay within the unit square.
|
| 107 |
+
for i in range(n):
|
| 108 |
+
# r_i <= x_i
|
| 109 |
+
row = np.zeros(n)
|
| 110 |
+
row[i] = 1
|
| 111 |
+
constraints.append(row)
|
| 112 |
+
b_vector.append(centers[i, 0])
|
| 113 |
+
|
| 114 |
+
# r_i <= 1 - x_i
|
| 115 |
+
row = np.zeros(n)
|
| 116 |
+
row[i] = 1
|
| 117 |
+
constraints.append(row)
|
| 118 |
+
b_vector.append(1 - centers[i, 0])
|
| 119 |
+
|
| 120 |
+
# r_i <= y_i
|
| 121 |
+
row = np.zeros(n)
|
| 122 |
+
row[i] = 1
|
| 123 |
+
constraints.append(row)
|
| 124 |
+
b_vector.append(centers[i, 1])
|
| 125 |
+
|
| 126 |
+
# r_i <= 1 - y_i
|
| 127 |
+
row = np.zeros(n)
|
| 128 |
+
row[i] = 1
|
| 129 |
+
constraints.append(row)
|
| 130 |
+
b_vector.append(1 - centers[i, 1])
|
| 131 |
+
|
| 132 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 133 |
+
# These prevent circles from overlapping.
|
| 134 |
+
for i in range(n):
|
| 135 |
+
for j in range(i + 1, n):
|
| 136 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 137 |
+
row = np.zeros(n)
|
| 138 |
+
row[i] = 1
|
| 139 |
+
row[j] = 1
|
| 140 |
+
constraints.append(row)
|
| 141 |
+
b_vector.append(dist)
|
| 142 |
+
|
| 143 |
+
A_ub = np.array(constraints)
|
| 144 |
+
b_ub = np.array(b_vector)
|
| 145 |
+
|
| 146 |
+
# All radii must be non-negative.
|
| 147 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 148 |
+
bounds = [(0, None) for _ in range(n)]
|
| 149 |
+
|
| 150 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 151 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 152 |
+
|
| 153 |
+
if res.success:
|
| 154 |
+
# Return the optimal radii found by the solver.
|
| 155 |
+
return res.x
|
| 156 |
+
else:
|
| 157 |
+
# Fallback in case of solver failure.
|
| 158 |
+
# Returning zeros implies no valid radii could be determined.
|
| 159 |
+
print(f"LP solver failed: {res.message}")
|
| 160 |
+
return np.zeros(n)
|
| 161 |
+
# EVOLVE-BLOCK-END
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# This part remains fixed (not evolved)
|
| 165 |
+
def run_packing():
|
| 166 |
+
"""Run the circle packing constructor for n=26"""
|
| 167 |
+
centers, radii = construct_packing()
|
| 168 |
+
# Calculate the sum of radii
|
| 169 |
+
sum_radii = np.sum(radii)
|
| 170 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_100/search_replace.txt
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
non_uniform_grid_expansion
|
| 3 |
+
</NAME>
|
| 4 |
+
<DESCRIPTION>
|
| 5 |
+
This edit introduces a non-uniform grid structure to create more space in the center of the packing, a feature common in optimal solutions which rarely conform to perfect grids. I am modifying the grid definition from the uniform `np.linspace(0.1, 0.9, 5)` to a non-uniform `np.array([0.1, 0.28, 0.5, 0.72, 0.9])`. This change moves the inner grid lines at 0.3 and 0.7 outwards, effectively expanding the central void where the two extra circles are placed.
|
| 6 |
+
|
| 7 |
+
To capitalize on this newly created space, I am also increasing the separation distance of the two central circles from `0.107` to `0.120`. This allows them to move further apart and potentially grow larger.
|
| 8 |
+
|
| 9 |
+
This coordinated change represents a strategic trade-off: space is reduced for the 16 circles in the outer grid regions to provide more room for the 8 inner grid circles and the 2 central circles. The hypothesis is that the increase in radii for these 10 centrally located circles will more than compensate for the decrease in radii of the outer circles, leading to a net increase in the total sum of radii.
|
| 10 |
+
</DESCRIPTION>
|
| 11 |
+
<DIFF>
|
| 12 |
+
<<<<<<< SEARCH
|
| 13 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 14 |
+
# This structure is inherited from the effective "Current Program".
|
| 15 |
+
num_grid_divs = 5
|
| 16 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 17 |
+
|
| 18 |
+
for i in range(num_grid_divs):
|
| 19 |
+
for j in range(num_grid_divs):
|
| 20 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 21 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 22 |
+
continue
|
| 23 |
+
centers[k] = [coords[i], coords[j]]
|
| 24 |
+
k += 1
|
| 25 |
+
|
| 26 |
+
# 2. Crossover: Place 2 circles in the central gap using the superior logic
|
| 27 |
+
# from the "Inspiration Program".
|
| 28 |
+
# Parameters are taken from the best-performing variant (score 2.51).
|
| 29 |
+
central_separation_distance = 0.107
|
| 30 |
+
central_asymmetry_angle_deg = 44.5
|
| 31 |
+
=======
|
| 32 |
+
# 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 33 |
+
# The grid is expanded in the center to create more space for the central
|
| 34 |
+
# circles and the circles on the medial axes. This is a trade-off, as it
|
| 35 |
+
# reduces space for circles in the outer grid regions.
|
| 36 |
+
num_grid_divs = 5
|
| 37 |
+
# Original coords: np.linspace(0.1, 0.9, 5) -> [0.1, 0.3, 0.5, 0.7, 0.9]
|
| 38 |
+
# We move the inner grid lines (0.3, 0.7) outwards to (0.28, 0.72).
|
| 39 |
+
coords = np.array([0.1, 0.28, 0.5, 0.72, 0.9])
|
| 40 |
+
|
| 41 |
+
for i in range(num_grid_divs):
|
| 42 |
+
for j in range(num_grid_divs):
|
| 43 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 44 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 45 |
+
continue
|
| 46 |
+
centers[k] = [coords[i], coords[j]]
|
| 47 |
+
k += 1
|
| 48 |
+
|
| 49 |
+
# 2. Crossover: Place 2 circles in the now larger central gap.
|
| 50 |
+
# The separation distance is increased to utilize the extra space created by
|
| 51 |
+
# the non-uniform grid. The asymmetry angle is kept.
|
| 52 |
+
central_separation_distance = 0.120
|
| 53 |
+
central_asymmetry_angle_deg = 44.5
|
| 54 |
+
>>>>>>> REPLACE
|
| 55 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/edit.diff
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,166 +1,164 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
-Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 7 |
+
-and linear programming for optimal radii.
|
| 8 |
+
+Constructor-based circle packing for n=26 circles using an adaptive grid
|
| 9 |
+
+and linear programming for optimal radii. This version introduces a non-uniform
|
| 10 |
+
+grid to overcome the limitations of a rigid structure.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
from scipy.optimize import linprog
|
| 15 |
+
import math
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def construct_packing():
|
| 19 |
+
"""
|
| 20 |
+
- Constructs a packing of 26 circles based on a 5x5 grid with a modified center.
|
| 21 |
+
- The radii are then optimized using a linear programming solver to maximize their sum.
|
| 22 |
+
+ Constructs a packing of 26 circles using an "adaptive grid relaxation" technique.
|
| 23 |
+
+ This method modifies the standard 5x5 grid to create more space for the central circles,
|
| 24 |
+
+ aiming to overcome the limitations of a rigid grid structure.
|
| 25 |
+
|
| 26 |
+
- This structure is based on a nearly optimal packing for 25 circles (a 5x5 grid)
|
| 27 |
+
- by removing the central circle and inserting two smaller circles in its place.
|
| 28 |
+
- This is a common motif in circle packing to increase density and is adopted from
|
| 29 |
+
- a previous higher-performing solution.
|
| 30 |
+
+ The strategy is as follows:
|
| 31 |
+
+ 1. Instead of a uniform `linspace(0.1, 0.9, 5)` grid, we construct a non-uniform
|
| 32 |
+
+ grid. The outer grid lines are pulled slightly inward (margin from 0.1 to 0.095),
|
| 33 |
+
+ and the inner grid lines are pushed slightly outward from the center (spacing from
|
| 34 |
+
+ center changes from 0.2 to 0.205).
|
| 35 |
+
+ 2. This "relaxation" creates a larger central area, reducing the geometric pressure on
|
| 36 |
+
+ the two central circles, which are often the bottleneck in the packing.
|
| 37 |
+
+ 3. The placement of the 24 grid circles follows this new adaptive grid.
|
| 38 |
+
+ 4. The two central circles are placed using the asymmetric diagonal split configuration
|
| 39 |
+
+ that proved most effective in previous high-scoring runs. This leverages past
|
| 40 |
+
+ discoveries within the new, more flexible framework.
|
| 41 |
+
+ 5. The final radii are optimized using linear programming, which can now exploit the
|
| 42 |
+
+ additional space created in the center.
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
Tuple of (centers, radii)
|
| 46 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 47 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 48 |
+
"""
|
| 49 |
+
n = 26
|
| 50 |
+
centers = np.zeros((n, 2))
|
| 51 |
+
+ k = 0
|
| 52 |
+
|
| 53 |
+
- # 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 54 |
+
- # The grid coordinates are chosen so the grid perfectly fits the unit square.
|
| 55 |
+
- # This corresponds to ideal radius of 0.1 for the 24 grid circles.
|
| 56 |
+
- num_grid_divs = 5
|
| 57 |
+
- coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 58 |
+
+ # --- NOVEL PART: Adaptive Grid Relaxation ---
|
| 59 |
+
+ # Define the non-uniform grid coordinates. This is the core innovation.
|
| 60 |
+
+ # Pushing the inner grid outward creates more room for the central pair.
|
| 61 |
+
+ # Pulling the outer grid inward is a trade-off to enable this.
|
| 62 |
+
+ outer_margin = 0.095 # Original: 0.1. Pulled INWARD.
|
| 63 |
+
+ inner_spacing_from_center = 0.205 # Original: 0.2. Pushed OUTWARD.
|
| 64 |
+
|
| 65 |
+
- k = 0
|
| 66 |
+
- for i in range(num_grid_divs):
|
| 67 |
+
- for j in range(num_grid_divs):
|
| 68 |
+
- # Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 69 |
+
- if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 70 |
+
+ coords = np.array([
|
| 71 |
+
+ outer_margin,
|
| 72 |
+
+ 0.5 - inner_spacing_from_center,
|
| 73 |
+
+ 0.5,
|
| 74 |
+
+ 0.5 + inner_spacing_from_center,
|
| 75 |
+
+ 1.0 - outer_margin
|
| 76 |
+
+ ])
|
| 77 |
+
+
|
| 78 |
+
+ # 1. Place 24 circles on the new adaptive grid, skipping the central point.
|
| 79 |
+
+ for i in range(5):
|
| 80 |
+
+ for j in range(5):
|
| 81 |
+
+ # Skip the center of the grid (index 2, 2)
|
| 82 |
+
+ if i == 2 and j == 2:
|
| 83 |
+
continue
|
| 84 |
+
centers[k] = [coords[i], coords[j]]
|
| 85 |
+
k += 1
|
| 86 |
+
|
| 87 |
+
- # 2. Place 2 circles in the central gap created by removing the center grid circle.
|
| 88 |
+
- # Based on insights from higher-performing runs, an asymmetric diagonal split
|
| 89 |
+
- # with specific separation distance and angle provides better packing for the central circles.
|
| 90 |
+
- # The parameters are chosen to replicate a previously successful configuration.
|
| 91 |
+
- central_separation_distance = 0.107
|
| 92 |
+
- central_asymmetry_angle_deg = 44.5 # A slight perturbation from 45.0 degrees
|
| 93 |
+
+ # 2. Place 2 circles in the central gap, using the best known parameters.
|
| 94 |
+
+ # This configuration is retained from the highest-scoring previous attempts.
|
| 95 |
+
+ central_separation_distance = 0.125
|
| 96 |
+
+ central_asymmetry_angle_deg = 44.5
|
| 97 |
+
+ pair_center_offset_x = -0.0015 # Small empirically-derived offset
|
| 98 |
+
|
| 99 |
+
- # R_prime is the distance from the center (0.5, 0.5) to one of the central circles.
|
| 100 |
+
- R_prime = central_separation_distance / 2.0
|
| 101 |
+
+ angle_rad = np.deg2rad(central_asymmetry_angle_deg)
|
| 102 |
+
+ half_sep = central_separation_distance / 2.0
|
| 103 |
+
|
| 104 |
+
- # Convert the asymmetry angle from degrees to radians.
|
| 105 |
+
- angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 106 |
+
+ # Calculate displacement vector for the pair
|
| 107 |
+
+ delta = np.array([half_sep * np.cos(angle_rad), half_sep * np.sin(angle_rad)])
|
| 108 |
+
|
| 109 |
+
- # Calculate x and y offsets (dx, dy) from the center (0.5, 0.5)
|
| 110 |
+
- dx = R_prime * math.cos(angle_rad)
|
| 111 |
+
- dy = R_prime * math.sin(angle_rad)
|
| 112 |
+
+ # Define the center of the pair with the offset
|
| 113 |
+
+ center_point = np.array([0.5 + pair_center_offset_x, 0.5])
|
| 114 |
+
|
| 115 |
+
- center_point = 0.5
|
| 116 |
+
- centers[k] = [center_point - dx, center_point - dy]
|
| 117 |
+
+ centers[k] = center_point - delta
|
| 118 |
+
k += 1
|
| 119 |
+
- centers[k] = [center_point + dx, center_point + dy]
|
| 120 |
+
+ centers[k] = center_point + delta
|
| 121 |
+
k += 1
|
| 122 |
+
|
| 123 |
+
- # Clip centers to be strictly within the unit square to avoid numerical
|
| 124 |
+
- # issues with the LP solver at the boundaries. A smaller epsilon allows centers
|
| 125 |
+
- # to be placed even closer to the boundaries for marginal gains.
|
| 126 |
+
+ # Clip centers to be strictly within (0,1) to avoid numerical issues.
|
| 127 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 128 |
+
|
| 129 |
+
- # For the given centers, compute the radii that maximize the sum.
|
| 130 |
+
+ # For the given centers, compute the radii that maximize the sum using LP.
|
| 131 |
+
radii = compute_max_radii(centers)
|
| 132 |
+
|
| 133 |
+
return centers, radii
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def compute_max_radii(centers):
|
| 137 |
+
"""
|
| 138 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 139 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 140 |
+
- subject to non-overlapping and boundary constraints.
|
| 141 |
+
+ subject to non-overlapping and boundary constraints. This function is
|
| 142 |
+
+ retained for its proven optimality for a fixed set of centers.
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
np.array of shape (n) with the optimal radius for each circle.
|
| 149 |
+
"""
|
| 150 |
+
n = centers.shape[0]
|
| 151 |
+
|
| 152 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 153 |
+
# minimizing sum(-radii).
|
| 154 |
+
c = -np.ones(n)
|
| 155 |
+
|
| 156 |
+
- # Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 157 |
+
- # 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 158 |
+
- # 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 159 |
+
+ # Inequality constraints (A_ub * r <= b_ub)
|
| 160 |
+
constraints = []
|
| 161 |
+
b_vector = []
|
| 162 |
+
|
| 163 |
+
- # Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 164 |
+
+ # Wall constraints: r_i <= dist_to_wall
|
| 165 |
+
for i in range(n):
|
| 166 |
+
- # r_i <= x_i
|
| 167 |
+
- row = np.zeros(n)
|
| 168 |
+
- row[i] = 1
|
| 169 |
+
- constraints.append(row)
|
| 170 |
+
- b_vector.append(centers[i, 0])
|
| 171 |
+
+ x, y = centers[i]
|
| 172 |
+
+ # r_i <= x
|
| 173 |
+
+ row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(x)
|
| 174 |
+
+ # r_i <= 1 - x
|
| 175 |
+
+ row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - x)
|
| 176 |
+
+ # r_i <= y
|
| 177 |
+
+ row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(y)
|
| 178 |
+
+ # r_i <= 1 - y
|
| 179 |
+
+ row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - y)
|
| 180 |
+
|
| 181 |
+
- # r_i <= 1 - x_i
|
| 182 |
+
- row = np.zeros(n)
|
| 183 |
+
- row[i] = 1
|
| 184 |
+
- constraints.append(row)
|
| 185 |
+
- b_vector.append(1 - centers[i, 0])
|
| 186 |
+
-
|
| 187 |
+
- # r_i <= y_i
|
| 188 |
+
- row = np.zeros(n)
|
| 189 |
+
- row[i] = 1
|
| 190 |
+
- constraints.append(row)
|
| 191 |
+
- b_vector.append(centers[i, 1])
|
| 192 |
+
-
|
| 193 |
+
- # r_i <= 1 - y_i
|
| 194 |
+
- row = np.zeros(n)
|
| 195 |
+
- row[i] = 1
|
| 196 |
+
- constraints.append(row)
|
| 197 |
+
- b_vector.append(1 - centers[i, 1])
|
| 198 |
+
-
|
| 199 |
+
- # Pair constraints: r_i + r_j <= d_ij
|
| 200 |
+
+ # Pair constraints: r_i + r_j <= dist(c_i, c_j)
|
| 201 |
+
for i in range(n):
|
| 202 |
+
for j in range(i + 1, n):
|
| 203 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 204 |
+
row = np.zeros(n)
|
| 205 |
+
row[i] = 1
|
| 206 |
+
row[j] = 1
|
| 207 |
+
constraints.append(row)
|
| 208 |
+
b_vector.append(dist)
|
| 209 |
+
|
| 210 |
+
A_ub = np.array(constraints)
|
| 211 |
+
b_ub = np.array(b_vector)
|
| 212 |
+
|
| 213 |
+
# All radii must be non-negative.
|
| 214 |
+
- bounds = [(0, None) for _ in range(n)]
|
| 215 |
+
+ bounds = (0, None)
|
| 216 |
+
|
| 217 |
+
- # Solve the linear program. 'highs' is a fast and reliable solver.
|
| 218 |
+
+ # Solve the linear program using the 'highs' solver for performance.
|
| 219 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 220 |
+
|
| 221 |
+
if res.success:
|
| 222 |
+
- # Return the optimal radii
|
| 223 |
+
return res.x
|
| 224 |
+
else:
|
| 225 |
+
- # Fallback in case of solver failure
|
| 226 |
+
- # An empty array will result in a sum of 0, signaling a problem.
|
| 227 |
+
+ # Fallback in case of solver failure.
|
| 228 |
+
+ print(f"LP solver failed: {res.message}")
|
| 229 |
+
return np.zeros(n)
|
| 230 |
+
-
|
| 231 |
+
-
|
| 232 |
+
# EVOLVE-BLOCK-END
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
# This part remains fixed (not evolved)
|
| 236 |
+
def run_packing():
|
| 237 |
+
"""Run the circle packing constructor for n=26"""
|
| 238 |
+
centers, radii = construct_packing()
|
| 239 |
+
# Calculate the sum of radii
|
| 240 |
+
sum_radii = np.sum(radii)
|
| 241 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/main.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using an adaptive grid
|
| 4 |
+
and linear programming for optimal radii. This version introduces a non-uniform
|
| 5 |
+
grid to overcome the limitations of a rigid structure.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles using an "adaptive grid relaxation" technique.
|
| 16 |
+
This method modifies the standard 5x5 grid to create more space for the central circles,
|
| 17 |
+
aiming to overcome the limitations of a rigid grid structure.
|
| 18 |
+
|
| 19 |
+
The strategy is as follows:
|
| 20 |
+
1. Instead of a uniform `linspace(0.1, 0.9, 5)` grid, we construct a non-uniform
|
| 21 |
+
grid. The outer grid lines are pulled slightly inward (margin from 0.1 to 0.095),
|
| 22 |
+
and the inner grid lines are pushed slightly outward from the center (spacing from
|
| 23 |
+
center changes from 0.2 to 0.205).
|
| 24 |
+
2. This "relaxation" creates a larger central area, reducing the geometric pressure on
|
| 25 |
+
the two central circles, which are often the bottleneck in the packing.
|
| 26 |
+
3. The placement of the 24 grid circles follows this new adaptive grid.
|
| 27 |
+
4. The two central circles are placed using the asymmetric diagonal split configuration
|
| 28 |
+
that proved most effective in previous high-scoring runs. This leverages past
|
| 29 |
+
discoveries within the new, more flexible framework.
|
| 30 |
+
5. The final radii are optimized using linear programming, which can now exploit the
|
| 31 |
+
additional space created in the center.
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
Tuple of (centers, radii)
|
| 35 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 36 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 37 |
+
"""
|
| 38 |
+
n = 26
|
| 39 |
+
centers = np.zeros((n, 2))
|
| 40 |
+
k = 0
|
| 41 |
+
|
| 42 |
+
# --- NOVEL PART: Adaptive Grid Relaxation ---
|
| 43 |
+
# Define the non-uniform grid coordinates. This is the core innovation.
|
| 44 |
+
# Pushing the inner grid outward creates more room for the central pair.
|
| 45 |
+
# Pulling the outer grid inward is a trade-off to enable this.
|
| 46 |
+
outer_margin = 0.095 # Original: 0.1. Pulled INWARD.
|
| 47 |
+
inner_spacing_from_center = 0.205 # Original: 0.2. Pushed OUTWARD.
|
| 48 |
+
|
| 49 |
+
coords = np.array([
|
| 50 |
+
outer_margin,
|
| 51 |
+
0.5 - inner_spacing_from_center,
|
| 52 |
+
0.5,
|
| 53 |
+
0.5 + inner_spacing_from_center,
|
| 54 |
+
1.0 - outer_margin
|
| 55 |
+
])
|
| 56 |
+
|
| 57 |
+
# 1. Place 24 circles on the new adaptive grid, skipping the central point.
|
| 58 |
+
for i in range(5):
|
| 59 |
+
for j in range(5):
|
| 60 |
+
# Skip the center of the grid (index 2, 2)
|
| 61 |
+
if i == 2 and j == 2:
|
| 62 |
+
continue
|
| 63 |
+
centers[k] = [coords[i], coords[j]]
|
| 64 |
+
k += 1
|
| 65 |
+
|
| 66 |
+
# 2. Place 2 circles in the central gap, using the best known parameters.
|
| 67 |
+
# This configuration is retained from the highest-scoring previous attempts.
|
| 68 |
+
central_separation_distance = 0.125
|
| 69 |
+
central_asymmetry_angle_deg = 44.5
|
| 70 |
+
pair_center_offset_x = -0.0015 # Small empirically-derived offset
|
| 71 |
+
|
| 72 |
+
angle_rad = np.deg2rad(central_asymmetry_angle_deg)
|
| 73 |
+
half_sep = central_separation_distance / 2.0
|
| 74 |
+
|
| 75 |
+
# Calculate displacement vector for the pair
|
| 76 |
+
delta = np.array([half_sep * np.cos(angle_rad), half_sep * np.sin(angle_rad)])
|
| 77 |
+
|
| 78 |
+
# Define the center of the pair with the offset
|
| 79 |
+
center_point = np.array([0.5 + pair_center_offset_x, 0.5])
|
| 80 |
+
|
| 81 |
+
centers[k] = center_point - delta
|
| 82 |
+
k += 1
|
| 83 |
+
centers[k] = center_point + delta
|
| 84 |
+
k += 1
|
| 85 |
+
|
| 86 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues.
|
| 87 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 88 |
+
|
| 89 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 90 |
+
radii = compute_max_radii(centers)
|
| 91 |
+
|
| 92 |
+
return centers, radii
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def compute_max_radii(centers):
|
| 96 |
+
"""
|
| 97 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 98 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 99 |
+
subject to non-overlapping and boundary constraints. This function is
|
| 100 |
+
retained for its proven optimality for a fixed set of centers.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
np.array of shape (n) with the optimal radius for each circle.
|
| 107 |
+
"""
|
| 108 |
+
n = centers.shape[0]
|
| 109 |
+
|
| 110 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 111 |
+
# minimizing sum(-radii).
|
| 112 |
+
c = -np.ones(n)
|
| 113 |
+
|
| 114 |
+
# Inequality constraints (A_ub * r <= b_ub)
|
| 115 |
+
constraints = []
|
| 116 |
+
b_vector = []
|
| 117 |
+
|
| 118 |
+
# Wall constraints: r_i <= dist_to_wall
|
| 119 |
+
for i in range(n):
|
| 120 |
+
x, y = centers[i]
|
| 121 |
+
# r_i <= x
|
| 122 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(x)
|
| 123 |
+
# r_i <= 1 - x
|
| 124 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - x)
|
| 125 |
+
# r_i <= y
|
| 126 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(y)
|
| 127 |
+
# r_i <= 1 - y
|
| 128 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - y)
|
| 129 |
+
|
| 130 |
+
# Pair constraints: r_i + r_j <= dist(c_i, c_j)
|
| 131 |
+
for i in range(n):
|
| 132 |
+
for j in range(i + 1, n):
|
| 133 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 134 |
+
row = np.zeros(n)
|
| 135 |
+
row[i] = 1
|
| 136 |
+
row[j] = 1
|
| 137 |
+
constraints.append(row)
|
| 138 |
+
b_vector.append(dist)
|
| 139 |
+
|
| 140 |
+
A_ub = np.array(constraints)
|
| 141 |
+
b_ub = np.array(b_vector)
|
| 142 |
+
|
| 143 |
+
# All radii must be non-negative.
|
| 144 |
+
bounds = (0, None)
|
| 145 |
+
|
| 146 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 147 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 148 |
+
|
| 149 |
+
if res.success:
|
| 150 |
+
return res.x
|
| 151 |
+
else:
|
| 152 |
+
# Fallback in case of solver failure.
|
| 153 |
+
print(f"LP solver failed: {res.message}")
|
| 154 |
+
return np.zeros(n)
|
| 155 |
+
# EVOLVE-BLOCK-END
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# This part remains fixed (not evolved)
|
| 159 |
+
def run_packing():
|
| 160 |
+
"""Run the circle packing constructor for n=26"""
|
| 161 |
+
centers, radii = construct_packing()
|
| 162 |
+
# Calculate the sum of radii
|
| 163 |
+
sum_radii = np.sum(radii)
|
| 164 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/original.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.optimize import linprog
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def construct_packing():
|
| 13 |
+
"""
|
| 14 |
+
Constructs a packing of 26 circles based on a 5x5 grid with a modified center.
|
| 15 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 16 |
+
|
| 17 |
+
This structure is based on a nearly optimal packing for 25 circles (a 5x5 grid)
|
| 18 |
+
by removing the central circle and inserting two smaller circles in its place.
|
| 19 |
+
This is a common motif in circle packing to increase density and is adopted from
|
| 20 |
+
a previous higher-performing solution.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Tuple of (centers, radii)
|
| 24 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 25 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 26 |
+
"""
|
| 27 |
+
n = 26
|
| 28 |
+
centers = np.zeros((n, 2))
|
| 29 |
+
|
| 30 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 31 |
+
# The grid coordinates are chosen so the grid perfectly fits the unit square.
|
| 32 |
+
# This corresponds to ideal radius of 0.1 for the 24 grid circles.
|
| 33 |
+
num_grid_divs = 5
|
| 34 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 35 |
+
|
| 36 |
+
k = 0
|
| 37 |
+
for i in range(num_grid_divs):
|
| 38 |
+
for j in range(num_grid_divs):
|
| 39 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 40 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 41 |
+
continue
|
| 42 |
+
centers[k] = [coords[i], coords[j]]
|
| 43 |
+
k += 1
|
| 44 |
+
|
| 45 |
+
# 2. Place 2 circles in the central gap created by removing the center grid circle.
|
| 46 |
+
# Based on insights from higher-performing runs, an asymmetric diagonal split
|
| 47 |
+
# with specific separation distance and angle provides better packing for the central circles.
|
| 48 |
+
# The parameters are chosen to replicate a previously successful configuration.
|
| 49 |
+
central_separation_distance = 0.107
|
| 50 |
+
central_asymmetry_angle_deg = 44.5 # A slight perturbation from 45.0 degrees
|
| 51 |
+
|
| 52 |
+
# R_prime is the distance from the center (0.5, 0.5) to one of the central circles.
|
| 53 |
+
R_prime = central_separation_distance / 2.0
|
| 54 |
+
|
| 55 |
+
# Convert the asymmetry angle from degrees to radians.
|
| 56 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 57 |
+
|
| 58 |
+
# Calculate x and y offsets (dx, dy) from the center (0.5, 0.5)
|
| 59 |
+
dx = R_prime * math.cos(angle_rad)
|
| 60 |
+
dy = R_prime * math.sin(angle_rad)
|
| 61 |
+
|
| 62 |
+
center_point = 0.5
|
| 63 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 64 |
+
k += 1
|
| 65 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 66 |
+
k += 1
|
| 67 |
+
|
| 68 |
+
# Clip centers to be strictly within the unit square to avoid numerical
|
| 69 |
+
# issues with the LP solver at the boundaries. A smaller epsilon allows centers
|
| 70 |
+
# to be placed even closer to the boundaries for marginal gains.
|
| 71 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 72 |
+
|
| 73 |
+
# For the given centers, compute the radii that maximize the sum.
|
| 74 |
+
radii = compute_max_radii(centers)
|
| 75 |
+
|
| 76 |
+
return centers, radii
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def compute_max_radii(centers):
|
| 80 |
+
"""
|
| 81 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 82 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 83 |
+
subject to non-overlapping and boundary constraints.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
np.array of shape (n) with the optimal radius for each circle.
|
| 90 |
+
"""
|
| 91 |
+
n = centers.shape[0]
|
| 92 |
+
|
| 93 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 94 |
+
# minimizing sum(-radii).
|
| 95 |
+
c = -np.ones(n)
|
| 96 |
+
|
| 97 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 98 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 99 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 100 |
+
constraints = []
|
| 101 |
+
b_vector = []
|
| 102 |
+
|
| 103 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 104 |
+
for i in range(n):
|
| 105 |
+
# r_i <= x_i
|
| 106 |
+
row = np.zeros(n)
|
| 107 |
+
row[i] = 1
|
| 108 |
+
constraints.append(row)
|
| 109 |
+
b_vector.append(centers[i, 0])
|
| 110 |
+
|
| 111 |
+
# r_i <= 1 - x_i
|
| 112 |
+
row = np.zeros(n)
|
| 113 |
+
row[i] = 1
|
| 114 |
+
constraints.append(row)
|
| 115 |
+
b_vector.append(1 - centers[i, 0])
|
| 116 |
+
|
| 117 |
+
# r_i <= y_i
|
| 118 |
+
row = np.zeros(n)
|
| 119 |
+
row[i] = 1
|
| 120 |
+
constraints.append(row)
|
| 121 |
+
b_vector.append(centers[i, 1])
|
| 122 |
+
|
| 123 |
+
# r_i <= 1 - y_i
|
| 124 |
+
row = np.zeros(n)
|
| 125 |
+
row[i] = 1
|
| 126 |
+
constraints.append(row)
|
| 127 |
+
b_vector.append(1 - centers[i, 1])
|
| 128 |
+
|
| 129 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 130 |
+
for i in range(n):
|
| 131 |
+
for j in range(i + 1, n):
|
| 132 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 133 |
+
row = np.zeros(n)
|
| 134 |
+
row[i] = 1
|
| 135 |
+
row[j] = 1
|
| 136 |
+
constraints.append(row)
|
| 137 |
+
b_vector.append(dist)
|
| 138 |
+
|
| 139 |
+
A_ub = np.array(constraints)
|
| 140 |
+
b_ub = np.array(b_vector)
|
| 141 |
+
|
| 142 |
+
# All radii must be non-negative.
|
| 143 |
+
bounds = [(0, None) for _ in range(n)]
|
| 144 |
+
|
| 145 |
+
# Solve the linear program. 'highs' is a fast and reliable solver.
|
| 146 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 147 |
+
|
| 148 |
+
if res.success:
|
| 149 |
+
# Return the optimal radii
|
| 150 |
+
return res.x
|
| 151 |
+
else:
|
| 152 |
+
# Fallback in case of solver failure
|
| 153 |
+
# An empty array will result in a sum of 0, signaling a problem.
|
| 154 |
+
return np.zeros(n)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# EVOLVE-BLOCK-END
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# This part remains fixed (not evolved)
|
| 161 |
+
def run_packing():
|
| 162 |
+
"""Run the circle packing constructor for n=26"""
|
| 163 |
+
centers, radii = construct_packing()
|
| 164 |
+
# Calculate the sum of radii
|
| 165 |
+
sum_radii = np.sum(radii)
|
| 166 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_101/rewrite.txt
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using an adaptive grid
|
| 4 |
+
and linear programming for optimal radii. This version introduces a non-uniform
|
| 5 |
+
grid to overcome the limitations of a rigid structure.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles using an "adaptive grid relaxation" technique.
|
| 16 |
+
This method modifies the standard 5x5 grid to create more space for the central circles,
|
| 17 |
+
aiming to overcome the limitations of a rigid grid structure.
|
| 18 |
+
|
| 19 |
+
The strategy is as follows:
|
| 20 |
+
1. Instead of a uniform `linspace(0.1, 0.9, 5)` grid, we construct a non-uniform
|
| 21 |
+
grid. The outer grid lines are pulled slightly inward (margin from 0.1 to 0.095),
|
| 22 |
+
and the inner grid lines are pushed slightly outward from the center (spacing from
|
| 23 |
+
center changes from 0.2 to 0.205).
|
| 24 |
+
2. This "relaxation" creates a larger central area, reducing the geometric pressure on
|
| 25 |
+
the two central circles, which are often the bottleneck in the packing.
|
| 26 |
+
3. The placement of the 24 grid circles follows this new adaptive grid.
|
| 27 |
+
4. The two central circles are placed using the asymmetric diagonal split configuration
|
| 28 |
+
that proved most effective in previous high-scoring runs. This leverages past
|
| 29 |
+
discoveries within the new, more flexible framework.
|
| 30 |
+
5. The final radii are optimized using linear programming, which can now exploit the
|
| 31 |
+
additional space created in the center.
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
Tuple of (centers, radii)
|
| 35 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 36 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 37 |
+
"""
|
| 38 |
+
n = 26
|
| 39 |
+
centers = np.zeros((n, 2))
|
| 40 |
+
k = 0
|
| 41 |
+
|
| 42 |
+
# --- NOVEL PART: Adaptive Grid Relaxation ---
|
| 43 |
+
# Define the non-uniform grid coordinates. This is the core innovation.
|
| 44 |
+
# Pushing the inner grid outward creates more room for the central pair.
|
| 45 |
+
# Pulling the outer grid inward is a trade-off to enable this.
|
| 46 |
+
outer_margin = 0.095 # Original: 0.1. Pulled INWARD.
|
| 47 |
+
inner_spacing_from_center = 0.205 # Original: 0.2. Pushed OUTWARD.
|
| 48 |
+
|
| 49 |
+
coords = np.array([
|
| 50 |
+
outer_margin,
|
| 51 |
+
0.5 - inner_spacing_from_center,
|
| 52 |
+
0.5,
|
| 53 |
+
0.5 + inner_spacing_from_center,
|
| 54 |
+
1.0 - outer_margin
|
| 55 |
+
])
|
| 56 |
+
|
| 57 |
+
# 1. Place 24 circles on the new adaptive grid, skipping the central point.
|
| 58 |
+
for i in range(5):
|
| 59 |
+
for j in range(5):
|
| 60 |
+
# Skip the center of the grid (index 2, 2)
|
| 61 |
+
if i == 2 and j == 2:
|
| 62 |
+
continue
|
| 63 |
+
centers[k] = [coords[i], coords[j]]
|
| 64 |
+
k += 1
|
| 65 |
+
|
| 66 |
+
# 2. Place 2 circles in the central gap, using the best known parameters.
|
| 67 |
+
# This configuration is retained from the highest-scoring previous attempts.
|
| 68 |
+
central_separation_distance = 0.125
|
| 69 |
+
central_asymmetry_angle_deg = 44.5
|
| 70 |
+
pair_center_offset_x = -0.0015 # Small empirically-derived offset
|
| 71 |
+
|
| 72 |
+
angle_rad = np.deg2rad(central_asymmetry_angle_deg)
|
| 73 |
+
half_sep = central_separation_distance / 2.0
|
| 74 |
+
|
| 75 |
+
# Calculate displacement vector for the pair
|
| 76 |
+
delta = np.array([half_sep * np.cos(angle_rad), half_sep * np.sin(angle_rad)])
|
| 77 |
+
|
| 78 |
+
# Define the center of the pair with the offset
|
| 79 |
+
center_point = np.array([0.5 + pair_center_offset_x, 0.5])
|
| 80 |
+
|
| 81 |
+
centers[k] = center_point - delta
|
| 82 |
+
k += 1
|
| 83 |
+
centers[k] = center_point + delta
|
| 84 |
+
k += 1
|
| 85 |
+
|
| 86 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues.
|
| 87 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 88 |
+
|
| 89 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 90 |
+
radii = compute_max_radii(centers)
|
| 91 |
+
|
| 92 |
+
return centers, radii
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def compute_max_radii(centers):
|
| 96 |
+
"""
|
| 97 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 98 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 99 |
+
subject to non-overlapping and boundary constraints. This function is
|
| 100 |
+
retained for its proven optimality for a fixed set of centers.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
np.array of shape (n) with the optimal radius for each circle.
|
| 107 |
+
"""
|
| 108 |
+
n = centers.shape[0]
|
| 109 |
+
|
| 110 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 111 |
+
# minimizing sum(-radii).
|
| 112 |
+
c = -np.ones(n)
|
| 113 |
+
|
| 114 |
+
# Inequality constraints (A_ub * r <= b_ub)
|
| 115 |
+
constraints = []
|
| 116 |
+
b_vector = []
|
| 117 |
+
|
| 118 |
+
# Wall constraints: r_i <= dist_to_wall
|
| 119 |
+
for i in range(n):
|
| 120 |
+
x, y = centers[i]
|
| 121 |
+
# r_i <= x
|
| 122 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(x)
|
| 123 |
+
# r_i <= 1 - x
|
| 124 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - x)
|
| 125 |
+
# r_i <= y
|
| 126 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(y)
|
| 127 |
+
# r_i <= 1 - y
|
| 128 |
+
row = np.zeros(n); row[i] = 1; constraints.append(row); b_vector.append(1 - y)
|
| 129 |
+
|
| 130 |
+
# Pair constraints: r_i + r_j <= dist(c_i, c_j)
|
| 131 |
+
for i in range(n):
|
| 132 |
+
for j in range(i + 1, n):
|
| 133 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 134 |
+
row = np.zeros(n)
|
| 135 |
+
row[i] = 1
|
| 136 |
+
row[j] = 1
|
| 137 |
+
constraints.append(row)
|
| 138 |
+
b_vector.append(dist)
|
| 139 |
+
|
| 140 |
+
A_ub = np.array(constraints)
|
| 141 |
+
b_ub = np.array(b_vector)
|
| 142 |
+
|
| 143 |
+
# All radii must be non-negative.
|
| 144 |
+
bounds = (0, None)
|
| 145 |
+
|
| 146 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 147 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 148 |
+
|
| 149 |
+
if res.success:
|
| 150 |
+
return res.x
|
| 151 |
+
else:
|
| 152 |
+
# Fallback in case of solver failure.
|
| 153 |
+
print(f"LP solver failed: {res.message}")
|
| 154 |
+
return np.zeros(n)
|
| 155 |
+
# EVOLVE-BLOCK-END
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (5.35 kB). View file
|
|
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/edit.diff
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,161 +1,168 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 7 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 8 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.optimize import linprog
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def construct_packing():
|
| 16 |
+
"""
|
| 17 |
+
Constructs a packing of 26 circles based on a 5x5 grid with a modified center.
|
| 18 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 19 |
+
|
| 20 |
+
This new structure moves away from the previous 4x4 hierarchical grid. It is
|
| 21 |
+
based on a nearly optimal packing for 25 circles (a 5x5 grid) by removing the
|
| 22 |
+
central circle and inserting two smaller circles in its place. This is a common
|
| 23 |
+
motif in circle packing to increase density.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
Tuple of (centers, radii)
|
| 27 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 28 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 29 |
+
"""
|
| 30 |
+
n = 26
|
| 31 |
+
centers = np.zeros((n, 2))
|
| 32 |
+
k = 0
|
| 33 |
+
|
| 34 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 35 |
+
# The grid coordinates are chosen so the grid perfectly fits the unit square.
|
| 36 |
+
# This corresponds to ideal radius of 0.1 for the 24 grid circles.
|
| 37 |
+
num_grid_divs = 5
|
| 38 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 39 |
+
|
| 40 |
+
for i in range(num_grid_divs):
|
| 41 |
+
for j in range(num_grid_divs):
|
| 42 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 43 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 44 |
+
continue
|
| 45 |
+
centers[k] = [coords[i], coords[j]]
|
| 46 |
+
k += 1
|
| 47 |
+
|
| 48 |
+
- # 2. Place 2 circles in the central gap with a diagonal split.
|
| 49 |
+
- # This placement, with a separation of 0.107, is adopted from previous high-scoring
|
| 50 |
+
- # configurations. It breaks the axis-aligned symmetry of the vertical split,
|
| 51 |
+
- # creating a more balanced set of distances to the 8 surrounding grid circles.
|
| 52 |
+
- # This typically allows for a more efficient packing in the central region.
|
| 53 |
+
- separation = 0.107
|
| 54 |
+
- # Calculate the x and y offsets for a 45-degree diagonal placement.
|
| 55 |
+
- delta = separation / (2 * np.sqrt(2))
|
| 56 |
+
+ # 2. Place 2 circles in the central gap using a parameterized asymmetric diagonal split.
|
| 57 |
+
+ # This placement, with a separation of 0.107 and an angle of 44.5 degrees,
|
| 58 |
+
+ # is adopted from previous high-scoring configurations. It breaks the axis-aligned
|
| 59 |
+
+ # symmetry to create a more balanced set of distances to the 8 surrounding grid circles,
|
| 60 |
+
+ # leading to more efficient packing in the central region.
|
| 61 |
+
+ central_separation_distance = 0.107
|
| 62 |
+
+ central_asymmetry_angle_deg = 44.5
|
| 63 |
+
+
|
| 64 |
+
+ # R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 65 |
+
+ R_prime = central_separation_distance / 2.0
|
| 66 |
+
+ angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 67 |
+
+
|
| 68 |
+
+ # Calculate x and y offsets (dx, dy) for the asymmetric diagonal placement.
|
| 69 |
+
+ dx = R_prime * math.cos(angle_rad)
|
| 70 |
+
+ dy = R_prime * math.sin(angle_rad)
|
| 71 |
+
|
| 72 |
+
center_point = 0.5
|
| 73 |
+
- centers[k] = [center_point - delta, center_point - delta]
|
| 74 |
+
+ centers[k] = [center_point - dx, center_point - dy]
|
| 75 |
+
k += 1
|
| 76 |
+
- centers[k] = [center_point + delta, center_point + delta]
|
| 77 |
+
+ centers[k] = [center_point + dx, center_point + dy]
|
| 78 |
+
k += 1
|
| 79 |
+
|
| 80 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 81 |
+
# at boundaries when calculating radii.
|
| 82 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 83 |
+
|
| 84 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 85 |
+
radii = compute_max_radii(centers)
|
| 86 |
+
|
| 87 |
+
return centers, radii
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def compute_max_radii(centers):
|
| 91 |
+
"""
|
| 92 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 93 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 94 |
+
subject to non-overlapping and boundary constraints.
|
| 95 |
+
This function is retained from the best performing solution due to its
|
| 96 |
+
mathematical optimality for fixed centers.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 100 |
+
|
| 101 |
+
Returns:
|
| 102 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 103 |
+
"""
|
| 104 |
+
n = centers.shape[0]
|
| 105 |
+
|
| 106 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 107 |
+
# minimizing sum(-radii).
|
| 108 |
+
c = -np.ones(n)
|
| 109 |
+
|
| 110 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 111 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 112 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 113 |
+
constraints = []
|
| 114 |
+
b_vector = []
|
| 115 |
+
|
| 116 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 117 |
+
# These ensure circles stay within the unit square.
|
| 118 |
+
for i in range(n):
|
| 119 |
+
# r_i <= x_i
|
| 120 |
+
row = np.zeros(n)
|
| 121 |
+
row[i] = 1
|
| 122 |
+
constraints.append(row)
|
| 123 |
+
b_vector.append(centers[i, 0])
|
| 124 |
+
|
| 125 |
+
# r_i <= 1 - x_i
|
| 126 |
+
row = np.zeros(n)
|
| 127 |
+
row[i] = 1
|
| 128 |
+
constraints.append(row)
|
| 129 |
+
b_vector.append(1 - centers[i, 0])
|
| 130 |
+
|
| 131 |
+
# r_i <= y_i
|
| 132 |
+
row = np.zeros(n)
|
| 133 |
+
row[i] = 1
|
| 134 |
+
constraints.append(row)
|
| 135 |
+
b_vector.append(centers[i, 1])
|
| 136 |
+
|
| 137 |
+
# r_i <= 1 - y_i
|
| 138 |
+
row = np.zeros(n)
|
| 139 |
+
row[i] = 1
|
| 140 |
+
constraints.append(row)
|
| 141 |
+
b_vector.append(1 - centers[i, 1])
|
| 142 |
+
|
| 143 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 144 |
+
# These prevent circles from overlapping.
|
| 145 |
+
for i in range(n):
|
| 146 |
+
for j in range(i + 1, n):
|
| 147 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 148 |
+
row = np.zeros(n)
|
| 149 |
+
row[i] = 1
|
| 150 |
+
row[j] = 1
|
| 151 |
+
constraints.append(row)
|
| 152 |
+
b_vector.append(dist)
|
| 153 |
+
|
| 154 |
+
A_ub = np.array(constraints)
|
| 155 |
+
b_ub = np.array(b_vector)
|
| 156 |
+
|
| 157 |
+
# All radii must be non-negative.
|
| 158 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 159 |
+
bounds = [(0, None) for _ in range(n)]
|
| 160 |
+
|
| 161 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 162 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 163 |
+
|
| 164 |
+
if res.success:
|
| 165 |
+
# Return the optimal radii found by the solver.
|
| 166 |
+
return res.x
|
| 167 |
+
else:
|
| 168 |
+
# Fallback in case of solver failure.
|
| 169 |
+
# Returning zeros implies no valid radii could be determined.
|
| 170 |
+
print(f"LP solver failed: {res.message}")
|
| 171 |
+
return np.zeros(n)
|
| 172 |
+
# EVOLVE-BLOCK-END
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
# This part remains fixed (not evolved)
|
| 176 |
+
def run_packing():
|
| 177 |
+
"""Run the circle packing constructor for n=26"""
|
| 178 |
+
centers, radii = construct_packing()
|
| 179 |
+
# Calculate the sum of radii
|
| 180 |
+
sum_radii = np.sum(radii)
|
| 181 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/main.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def construct_packing():
|
| 13 |
+
"""
|
| 14 |
+
Constructs a packing of 26 circles based on a 5x5 grid with a modified center.
|
| 15 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 16 |
+
|
| 17 |
+
This new structure moves away from the previous 4x4 hierarchical grid. It is
|
| 18 |
+
based on a nearly optimal packing for 25 circles (a 5x5 grid) by removing the
|
| 19 |
+
central circle and inserting two smaller circles in its place. This is a common
|
| 20 |
+
motif in circle packing to increase density.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Tuple of (centers, radii)
|
| 24 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 25 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 26 |
+
"""
|
| 27 |
+
n = 26
|
| 28 |
+
centers = np.zeros((n, 2))
|
| 29 |
+
k = 0
|
| 30 |
+
|
| 31 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 32 |
+
# The grid coordinates are chosen so the grid perfectly fits the unit square.
|
| 33 |
+
# This corresponds to ideal radius of 0.1 for the 24 grid circles.
|
| 34 |
+
num_grid_divs = 5
|
| 35 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 36 |
+
|
| 37 |
+
for i in range(num_grid_divs):
|
| 38 |
+
for j in range(num_grid_divs):
|
| 39 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 40 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 41 |
+
continue
|
| 42 |
+
centers[k] = [coords[i], coords[j]]
|
| 43 |
+
k += 1
|
| 44 |
+
|
| 45 |
+
# 2. Place 2 circles in the central gap using a parameterized asymmetric diagonal split.
|
| 46 |
+
# This placement, with a separation of 0.107 and an angle of 44.5 degrees,
|
| 47 |
+
# is adopted from previous high-scoring configurations. It breaks the axis-aligned
|
| 48 |
+
# symmetry to create a more balanced set of distances to the 8 surrounding grid circles,
|
| 49 |
+
# leading to more efficient packing in the central region.
|
| 50 |
+
central_separation_distance = 0.107
|
| 51 |
+
central_asymmetry_angle_deg = 44.5
|
| 52 |
+
|
| 53 |
+
# R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 54 |
+
R_prime = central_separation_distance / 2.0
|
| 55 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 56 |
+
|
| 57 |
+
# Calculate x and y offsets (dx, dy) for the asymmetric diagonal placement.
|
| 58 |
+
dx = R_prime * math.cos(angle_rad)
|
| 59 |
+
dy = R_prime * math.sin(angle_rad)
|
| 60 |
+
|
| 61 |
+
center_point = 0.5
|
| 62 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 63 |
+
k += 1
|
| 64 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 65 |
+
k += 1
|
| 66 |
+
|
| 67 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 68 |
+
# at boundaries when calculating radii.
|
| 69 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 70 |
+
|
| 71 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 72 |
+
radii = compute_max_radii(centers)
|
| 73 |
+
|
| 74 |
+
return centers, radii
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def compute_max_radii(centers):
|
| 78 |
+
"""
|
| 79 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 80 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 81 |
+
subject to non-overlapping and boundary constraints.
|
| 82 |
+
This function is retained from the best performing solution due to its
|
| 83 |
+
mathematical optimality for fixed centers.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 90 |
+
"""
|
| 91 |
+
n = centers.shape[0]
|
| 92 |
+
|
| 93 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 94 |
+
# minimizing sum(-radii).
|
| 95 |
+
c = -np.ones(n)
|
| 96 |
+
|
| 97 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 98 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 99 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 100 |
+
constraints = []
|
| 101 |
+
b_vector = []
|
| 102 |
+
|
| 103 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 104 |
+
# These ensure circles stay within the unit square.
|
| 105 |
+
for i in range(n):
|
| 106 |
+
# r_i <= x_i
|
| 107 |
+
row = np.zeros(n)
|
| 108 |
+
row[i] = 1
|
| 109 |
+
constraints.append(row)
|
| 110 |
+
b_vector.append(centers[i, 0])
|
| 111 |
+
|
| 112 |
+
# r_i <= 1 - x_i
|
| 113 |
+
row = np.zeros(n)
|
| 114 |
+
row[i] = 1
|
| 115 |
+
constraints.append(row)
|
| 116 |
+
b_vector.append(1 - centers[i, 0])
|
| 117 |
+
|
| 118 |
+
# r_i <= y_i
|
| 119 |
+
row = np.zeros(n)
|
| 120 |
+
row[i] = 1
|
| 121 |
+
constraints.append(row)
|
| 122 |
+
b_vector.append(centers[i, 1])
|
| 123 |
+
|
| 124 |
+
# r_i <= 1 - y_i
|
| 125 |
+
row = np.zeros(n)
|
| 126 |
+
row[i] = 1
|
| 127 |
+
constraints.append(row)
|
| 128 |
+
b_vector.append(1 - centers[i, 1])
|
| 129 |
+
|
| 130 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 131 |
+
# These prevent circles from overlapping.
|
| 132 |
+
for i in range(n):
|
| 133 |
+
for j in range(i + 1, n):
|
| 134 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 135 |
+
row = np.zeros(n)
|
| 136 |
+
row[i] = 1
|
| 137 |
+
row[j] = 1
|
| 138 |
+
constraints.append(row)
|
| 139 |
+
b_vector.append(dist)
|
| 140 |
+
|
| 141 |
+
A_ub = np.array(constraints)
|
| 142 |
+
b_ub = np.array(b_vector)
|
| 143 |
+
|
| 144 |
+
# All radii must be non-negative.
|
| 145 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 146 |
+
bounds = [(0, None) for _ in range(n)]
|
| 147 |
+
|
| 148 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 149 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 150 |
+
|
| 151 |
+
if res.success:
|
| 152 |
+
# Return the optimal radii found by the solver.
|
| 153 |
+
return res.x
|
| 154 |
+
else:
|
| 155 |
+
# Fallback in case of solver failure.
|
| 156 |
+
# Returning zeros implies no valid radii could be determined.
|
| 157 |
+
print(f"LP solver failed: {res.message}")
|
| 158 |
+
return np.zeros(n)
|
| 159 |
+
# EVOLVE-BLOCK-END
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# This part remains fixed (not evolved)
|
| 163 |
+
def run_packing():
|
| 164 |
+
"""Run the circle packing constructor for n=26"""
|
| 165 |
+
centers, radii = construct_packing()
|
| 166 |
+
# Calculate the sum of radii
|
| 167 |
+
sum_radii = np.sum(radii)
|
| 168 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/original.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a hierarchical grid
|
| 4 |
+
and linear programming for optimal radii. This version incorporates refined
|
| 5 |
+
parameters for the hierarchical grid to maximize the sum of radii.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def construct_packing():
|
| 13 |
+
"""
|
| 14 |
+
Constructs a packing of 26 circles based on a 5x5 grid with a modified center.
|
| 15 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 16 |
+
|
| 17 |
+
This new structure moves away from the previous 4x4 hierarchical grid. It is
|
| 18 |
+
based on a nearly optimal packing for 25 circles (a 5x5 grid) by removing the
|
| 19 |
+
central circle and inserting two smaller circles in its place. This is a common
|
| 20 |
+
motif in circle packing to increase density.
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Tuple of (centers, radii)
|
| 24 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 25 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 26 |
+
"""
|
| 27 |
+
n = 26
|
| 28 |
+
centers = np.zeros((n, 2))
|
| 29 |
+
k = 0
|
| 30 |
+
|
| 31 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 32 |
+
# The grid coordinates are chosen so the grid perfectly fits the unit square.
|
| 33 |
+
# This corresponds to ideal radius of 0.1 for the 24 grid circles.
|
| 34 |
+
num_grid_divs = 5
|
| 35 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 36 |
+
|
| 37 |
+
for i in range(num_grid_divs):
|
| 38 |
+
for j in range(num_grid_divs):
|
| 39 |
+
# Skip the center of the 5x5 grid, which is at index (2, 2)
|
| 40 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 41 |
+
continue
|
| 42 |
+
centers[k] = [coords[i], coords[j]]
|
| 43 |
+
k += 1
|
| 44 |
+
|
| 45 |
+
# 2. Place 2 circles in the central gap with a diagonal split.
|
| 46 |
+
# This placement, with a separation of 0.107, is adopted from previous high-scoring
|
| 47 |
+
# configurations. It breaks the axis-aligned symmetry of the vertical split,
|
| 48 |
+
# creating a more balanced set of distances to the 8 surrounding grid circles.
|
| 49 |
+
# This typically allows for a more efficient packing in the central region.
|
| 50 |
+
separation = 0.107
|
| 51 |
+
# Calculate the x and y offsets for a 45-degree diagonal placement.
|
| 52 |
+
delta = separation / (2 * np.sqrt(2))
|
| 53 |
+
|
| 54 |
+
center_point = 0.5
|
| 55 |
+
centers[k] = [center_point - delta, center_point - delta]
|
| 56 |
+
k += 1
|
| 57 |
+
centers[k] = [center_point + delta, center_point + delta]
|
| 58 |
+
k += 1
|
| 59 |
+
|
| 60 |
+
# Ensure centers are strictly within (0,1) to avoid numerical issues
|
| 61 |
+
# at boundaries when calculating radii.
|
| 62 |
+
centers = np.clip(centers, 1e-6, 1 - 1e-6)
|
| 63 |
+
|
| 64 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 65 |
+
radii = compute_max_radii(centers)
|
| 66 |
+
|
| 67 |
+
return centers, radii
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def compute_max_radii(centers):
|
| 71 |
+
"""
|
| 72 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 73 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 74 |
+
subject to non-overlapping and boundary constraints.
|
| 75 |
+
This function is retained from the best performing solution due to its
|
| 76 |
+
mathematical optimality for fixed centers.
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 83 |
+
"""
|
| 84 |
+
n = centers.shape[0]
|
| 85 |
+
|
| 86 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 87 |
+
# minimizing sum(-radii).
|
| 88 |
+
c = -np.ones(n)
|
| 89 |
+
|
| 90 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 91 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 92 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 93 |
+
constraints = []
|
| 94 |
+
b_vector = []
|
| 95 |
+
|
| 96 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 97 |
+
# These ensure circles stay within the unit square.
|
| 98 |
+
for i in range(n):
|
| 99 |
+
# r_i <= x_i
|
| 100 |
+
row = np.zeros(n)
|
| 101 |
+
row[i] = 1
|
| 102 |
+
constraints.append(row)
|
| 103 |
+
b_vector.append(centers[i, 0])
|
| 104 |
+
|
| 105 |
+
# r_i <= 1 - x_i
|
| 106 |
+
row = np.zeros(n)
|
| 107 |
+
row[i] = 1
|
| 108 |
+
constraints.append(row)
|
| 109 |
+
b_vector.append(1 - centers[i, 0])
|
| 110 |
+
|
| 111 |
+
# r_i <= y_i
|
| 112 |
+
row = np.zeros(n)
|
| 113 |
+
row[i] = 1
|
| 114 |
+
constraints.append(row)
|
| 115 |
+
b_vector.append(centers[i, 1])
|
| 116 |
+
|
| 117 |
+
# r_i <= 1 - y_i
|
| 118 |
+
row = np.zeros(n)
|
| 119 |
+
row[i] = 1
|
| 120 |
+
constraints.append(row)
|
| 121 |
+
b_vector.append(1 - centers[i, 1])
|
| 122 |
+
|
| 123 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 124 |
+
# These prevent circles from overlapping.
|
| 125 |
+
for i in range(n):
|
| 126 |
+
for j in range(i + 1, n):
|
| 127 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 128 |
+
row = np.zeros(n)
|
| 129 |
+
row[i] = 1
|
| 130 |
+
row[j] = 1
|
| 131 |
+
constraints.append(row)
|
| 132 |
+
b_vector.append(dist)
|
| 133 |
+
|
| 134 |
+
A_ub = np.array(constraints)
|
| 135 |
+
b_ub = np.array(b_vector)
|
| 136 |
+
|
| 137 |
+
# All radii must be non-negative.
|
| 138 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 139 |
+
bounds = [(0, None) for _ in range(n)]
|
| 140 |
+
|
| 141 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 142 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 143 |
+
|
| 144 |
+
if res.success:
|
| 145 |
+
# Return the optimal radii found by the solver.
|
| 146 |
+
return res.x
|
| 147 |
+
else:
|
| 148 |
+
# Fallback in case of solver failure.
|
| 149 |
+
# Returning zeros implies no valid radii could be determined.
|
| 150 |
+
print(f"LP solver failed: {res.message}")
|
| 151 |
+
return np.zeros(n)
|
| 152 |
+
# EVOLVE-BLOCK-END
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# This part remains fixed (not evolved)
|
| 156 |
+
def run_packing():
|
| 157 |
+
"""Run the circle packing constructor for n=26"""
|
| 158 |
+
centers, radii = construct_packing()
|
| 159 |
+
# Calculate the sum of radii
|
| 160 |
+
sum_radii = np.sum(radii)
|
| 161 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": false,
|
| 3 |
+
"error": "NameError: name 'math' is not defined"
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/results/metrics.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 0.0,
|
| 3 |
+
"correct": false,
|
| 4 |
+
"primary": {
|
| 5 |
+
"combined_score": 0.0,
|
| 6 |
+
"execution_time_mean": 0.0,
|
| 7 |
+
"execution_time_std": 0.0,
|
| 8 |
+
"num_successful_runs": 0,
|
| 9 |
+
"num_valid_runs": 0,
|
| 10 |
+
"num_invalid_runs": 0,
|
| 11 |
+
"all_validation_errors": [],
|
| 12 |
+
"correct": false,
|
| 13 |
+
"validation_error": "NameError: name 'math' is not defined"
|
| 14 |
+
},
|
| 15 |
+
"auxiliary": {
|
| 16 |
+
"max_circle_overlap_magnitude": 0.0,
|
| 17 |
+
"num_overlapping_pairs": 0,
|
| 18 |
+
"max_boundary_violation_magnitude": 0.0,
|
| 19 |
+
"num_boundary_violations": 0,
|
| 20 |
+
"violation_metrics_info": "No circles to check for violations.",
|
| 21 |
+
"total_area_covered": 0.0,
|
| 22 |
+
"packing_density": 0.0,
|
| 23 |
+
"empty_space_ratio": 1.0,
|
| 24 |
+
"area_metrics_info": "No radii data available to calculate area metrics.",
|
| 25 |
+
"avg_gap_between_circles": 0.0,
|
| 26 |
+
"min_gap_between_circles": 0.0,
|
| 27 |
+
"num_circles": 0,
|
| 28 |
+
"avg_radius": 0.0,
|
| 29 |
+
"std_dev_radius": 0.0,
|
| 30 |
+
"min_radius": 0.0,
|
| 31 |
+
"max_radius": 0.0,
|
| 32 |
+
"radii_coefficient_of_variation": 0.0,
|
| 33 |
+
"radius_stats_info": "No radii data available for radius statistics.",
|
| 34 |
+
"num_unique_radii": 0,
|
| 35 |
+
"diversity_metrics_info": "No radii data available for diversity metrics.",
|
| 36 |
+
"avg_distance_from_unit_center": 0.0,
|
| 37 |
+
"avg_distance_from_unit_center_normalized": 0.0,
|
| 38 |
+
"max_distance_from_unit_center": 0.0,
|
| 39 |
+
"center_x_std_dev": 0.0,
|
| 40 |
+
"center_y_std_dev": 0.0,
|
| 41 |
+
"positional_distribution_info": "No circles for positional distribution metrics.",
|
| 42 |
+
"avg_nearest_neighbor_distance_centers": 0.0,
|
| 43 |
+
"center_quadrant_density_variance": 0.0,
|
| 44 |
+
"spatial_metrics_info": "No circle centers for spatial metrics.",
|
| 45 |
+
"avg_min_distance_to_boundary": 0.0,
|
| 46 |
+
"min_overall_distance_to_boundary": 0.0,
|
| 47 |
+
"boundary_proximity_info": "No circles to calculate boundary proximity.",
|
| 48 |
+
"avg_pairwise_center_distance": 0.0,
|
| 49 |
+
"pairwise_center_distance_info": "Need at least 2 circles for pairwise distance.",
|
| 50 |
+
"normalized_score_per_circle": 0.0,
|
| 51 |
+
"packing_aspect_ratio_of_centers_bbox": 1.0,
|
| 52 |
+
"avg_num_touching_neighbors": 0.0,
|
| 53 |
+
"avg_quadrant_radii_std_dev": 0.0,
|
| 54 |
+
"advanced_spatial_metrics_info": "No circles for advanced spatial metrics.",
|
| 55 |
+
"primary_combined_score": 0.0
|
| 56 |
+
},
|
| 57 |
+
"auxiliary_descriptions": {
|
| 58 |
+
"total_area_covered": "Sum of the areas of all 26 circles (pi * r^2 for each). This measures how much of the unit square is occupied by the circles. A higher value indicates better space utilization, focusing on the actual \"filled\" area rather than just the sum of radii (which is the primary score). Range 0 to ~1 (or slightly more if circles could overlap, but primary prevents that).",
|
| 59 |
+
"packing_density": "This is identical to `total_area_covered` because the unit square has an area of 1. It directly represents the percentage of the available area covered by circles. Higher is better, indicating more efficient use of space.",
|
| 60 |
+
"avg_min_distance_to_boundary": "The average of the minimum distances from the edge of each circle to any of the four boundaries of the unit square. Smaller values (closer to 0) indicate that circles are packed more tightly against the boundaries, which is often a characteristic of optimal packing solutions. Negative values would imply a violation (circle outside boundary) which the primary metric already checks. Useful for understanding how well the solution leverages the container edges.",
|
| 61 |
+
"min_overall_distance_to_boundary": "The single smallest distance from any circle's edge to any boundary. This metric highlights if at least one circle is positioned extremely close to a boundary, indicating a strategy to maximize space.",
|
| 62 |
+
"avg_radius": "The average radius of the 26 circles. This gives an idea of the typical size of circles in the packing. Can indicate if solutions are favoring uniform or varied circle sizes.",
|
| 63 |
+
"std_dev_radius": "The standard deviation of the radii of the 26 circles. A higher standard deviation indicates greater diversity in circle sizes, while a lower one suggests more uniform sizes. This can reveal different packing strategies and encourage diversity.",
|
| 64 |
+
"min_radius": "The smallest radius among the 26 circles. Can indicate if the solution is introducing very small circles.",
|
| 65 |
+
"max_radius": "The largest radius among the 26 circles. Can indicate if the solution is trying to fit very large circles.",
|
| 66 |
+
"avg_nearest_neighbor_distance_centers": "This metric calculates the average Euclidean distance from each circle's center to the center of its closest neighboring circle. A lower value indicates a more tightly packed or clustered arrangement of circle centers, while a higher value suggests a more dispersed or sparse distribution. This is useful for identifying if the packing strategy is creating dense clusters or leaving significant gaps between groups of circles.",
|
| 67 |
+
"center_quadrant_density_variance": "This metric divides the unit square into four equal quadrants (2x2 grid) and computes the variance of the number of circle centers found within each quadrant. A lower variance indicates a more uniform distribution of circle centers across the entire packing area, suggesting a balanced utilization of space. A higher variance implies that circle centers are concentrated in certain quadrants, potentially indicating inefficient or uneven packing.",
|
| 68 |
+
"empty_space_ratio": "A decreasing trend in this metric, even with a stable `sum(radii)`, indicates the solution is becoming more efficient in terms of area utilization. If it's stagnant, it suggests wasted space.",
|
| 69 |
+
"avg_gap_between_circles": "The average positive distance between the edges of any two non-overlapping circles. A smaller average gap suggests a tighter packing configuration where circles are placed closer to each other without violating overlap constraints.",
|
| 70 |
+
"min_gap_between_circles": "The smallest positive distance between the edges of any two non-overlapping circles. A value near zero indicates that at least one pair of circles is almost touching, which is characteristic of optimal packing where circles are tightly constrained.",
|
| 71 |
+
"radii_coefficient_of_variation": "Changes in this metric can reveal if the solution is exploring new combinations of circle sizes. A sudden increase might suggest an attempt to break free from a local optimum by introducing more diverse sizes, while a decrease might indicate convergence towards a more uniform or specific size distribution.",
|
| 72 |
+
"avg_distance_from_unit_center": "The average Euclidean distance of all circle centers from the center of the unit square (0.5, 0.5). A lower value suggests circles are clustered more towards the center of the packing area, while a higher value indicates a more peripheral or spread-out distribution.",
|
| 73 |
+
"max_distance_from_unit_center": "The maximum Euclidean distance of any circle center from the center of the unit square (0.5, 0.5). This indicates how far out the outermost circle is placed, providing insight into the spread of the packing.",
|
| 74 |
+
"center_x_std_dev": "The standard deviation of the x-coordinates of all circle centers. A lower value indicates less spread horizontally.",
|
| 75 |
+
"center_y_std_dev": "The standard deviation of the y-coordinates of all circle centers. A lower value indicates less spread vertically. Together with `center_x_std_dev`, these metrics describe the spatial distribution and clustering of circles.",
|
| 76 |
+
"packing_aspect_ratio_of_centers_bbox": "Monitoring this metric can reveal if the packing is becoming more balanced (closer to 1.0) or if it's developing a preferred elongation. A deviation from 1.0 might suggest that the algorithm is struggling to utilize the square boundaries effectively, or it could be an emergent property of a specific packing strategy. If the primary score plateaus, but this metric moves closer to 1.0, it could indicate structural refinement.",
|
| 77 |
+
"avg_num_touching_neighbors": "A sustained increase in this metric, especially if the primary score is stagnant, would be a strong indicator of improved packing density and efficiency. It suggests that circles are being fit together more closely. If this metric is low and stagnating, it implies that there are significant gaps between circles that could be exploited for larger radii.",
|
| 78 |
+
"avg_quadrant_radii_std_dev": "This metric provides spatial insight into radii diversity. If this value is high, it means different regions of the packing are employing varied circle sizes. If it's low, it indicates a more uniform sizing strategy across the board. This can inform whether the evolution should be encouraged to explore more diverse sizes regionally or to converge on a more uniform size distribution if that proves more efficient for the overall sum of radii.",
|
| 79 |
+
"avg_distance_from_unit_center_normalized": "The average distance of all circle centers from the center of the unit square (0.5, 0.5), normalized by the maximum possible distance from the center (`sqrt(0.5)`). A value closer to 0 indicates a more central packing, while a value closer to 1 implies circles are pushed towards the corners or edges, utilizing the full extent of the square. Useful for assessing if the packing strategy prioritizes central arrangements or boundary-conforming ones.",
|
| 80 |
+
"avg_pairwise_center_distance": "The average Euclidean distance between all unique pairs of circle centers. A higher value suggests a more spread-out arrangement of circles, potentially indicating a less \"clustered\" or more uniform distribution of centers. This complements nearest-neighbor distance by considering all pairs and provides a global measure of center dispersion."
|
| 81 |
+
},
|
| 82 |
+
"timestamp": 1770497647.551893,
|
| 83 |
+
"generation": 102
|
| 84 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_102/search_replace.txt
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NAME>
|
| 2 |
+
central_angle_tune
|
| 3 |
+
</NAME>
|
| 4 |
+
|
| 5 |
+
<DESCRIPTION>
|
| 6 |
+
Reintroducing the `central_asymmetry_angle_deg` parameter from previous high-performing solutions. The current program hardcodes a 45-degree diagonal split for the two central circles. By replacing this with a parameterized angle, set to `44.5` degrees, we allow for a slight perturbation from perfect symmetry. This perturbation was previously observed to yield a slightly better packing by optimizing the central circle positions relative to the surrounding 24 grid circles, thus maximizing their collective radii.
|
| 7 |
+
</DESCRIPTION>
|
| 8 |
+
|
| 9 |
+
<DIFF>
|
| 10 |
+
<<<<<<< SEARCH
|
| 11 |
+
# 2. Place 2 circles in the central gap with a diagonal split.
|
| 12 |
+
# This placement, with a separation of 0.107, is adopted from previous high-scoring
|
| 13 |
+
# configurations. It breaks the axis-aligned symmetry of the vertical split,
|
| 14 |
+
# creating a more balanced set of distances to the 8 surrounding grid circles.
|
| 15 |
+
# This typically allows for a more efficient packing in the central region.
|
| 16 |
+
separation = 0.107
|
| 17 |
+
# Calculate the x and y offsets for a 45-degree diagonal placement.
|
| 18 |
+
delta = separation / (2 * np.sqrt(2))
|
| 19 |
+
|
| 20 |
+
center_point = 0.5
|
| 21 |
+
centers[k] = [center_point - delta, center_point - delta]
|
| 22 |
+
k += 1
|
| 23 |
+
centers[k] = [center_point + delta, center_point + delta]
|
| 24 |
+
k += 1
|
| 25 |
+
=======
|
| 26 |
+
# 2. Place 2 circles in the central gap using a parameterized asymmetric diagonal split.
|
| 27 |
+
# This placement, with a separation of 0.107 and an angle of 44.5 degrees,
|
| 28 |
+
# is adopted from previous high-scoring configurations. It breaks the axis-aligned
|
| 29 |
+
# symmetry to create a more balanced set of distances to the 8 surrounding grid circles,
|
| 30 |
+
# leading to more efficient packing in the central region.
|
| 31 |
+
central_separation_distance = 0.107
|
| 32 |
+
central_asymmetry_angle_deg = 44.5
|
| 33 |
+
|
| 34 |
+
# R_prime is the distance from the square's center (0.5, 0.5) to each central circle's center.
|
| 35 |
+
R_prime = central_separation_distance / 2.0
|
| 36 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 37 |
+
|
| 38 |
+
# Calculate x and y offsets (dx, dy) for the asymmetric diagonal placement.
|
| 39 |
+
dx = R_prime * math.cos(angle_rad)
|
| 40 |
+
dy = R_prime * math.sin(angle_rad)
|
| 41 |
+
|
| 42 |
+
center_point = 0.5
|
| 43 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 44 |
+
k += 1
|
| 45 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 46 |
+
k += 1
|
| 47 |
+
>>>>>>> REPLACE
|
| 48 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/edit.diff
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,189 +1,196 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 7 |
+
and linear programming for optimal radii. This version refines the central
|
| 8 |
+
circle placement based on geometric constraint analysis.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.optimize import linprog
|
| 13 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def construct_packing():
|
| 17 |
+
"""
|
| 18 |
+
Constructs a packing of 26 circles based on a 5x5 grid. The central two
|
| 19 |
+
circles are placed using a configuration derived from balancing geometric
|
| 20 |
+
constraints to maximize packing potential.
|
| 21 |
+
|
| 22 |
+
The strategy is based on the following analysis:
|
| 23 |
+
1. The `(5x5-1)` grid structure with `linspace(0.1, 0.9, 5)` is retained
|
| 24 |
+
as a proven high-performance base.
|
| 25 |
+
2. The key improvement is in placing the two central circles. Their radii are
|
| 26 |
+
limited by their distance to each other and to the 8 surrounding grid
|
| 27 |
+
circles. An optimal placement should balance these distances.
|
| 28 |
+
3. A previous symmetric configuration (angle=45 deg) showed that a
|
| 29 |
+
`central_separation_distance` of `0.1552` perfectly balanced the
|
| 30 |
+
distance between the central pair and their nearest grid neighbors.
|
| 31 |
+
4. However, the highest scores were achieved with a slight asymmetry
|
| 32 |
+
(angle=44.5 deg), which likely prevents geometric locking.
|
| 33 |
+
5. This version combines the best of both worlds: the geometrically optimal
|
| 34 |
+
separation distance (`0.1552`) with the performance-enhancing
|
| 35 |
+
asymmetric angle (`44.5` deg). The small pair offset from the previous
|
| 36 |
+
version is removed (set to 0) as it did not improve the score and
|
| 37 |
+
complicates the core geometry.
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
Tuple of (centers, radii)
|
| 41 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 42 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 43 |
+
"""
|
| 44 |
+
n = 26
|
| 45 |
+
centers = np.zeros((n, 2))
|
| 46 |
+
k = 0
|
| 47 |
+
|
| 48 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 49 |
+
# This regular grid provides a strong, stable base structure.
|
| 50 |
+
num_grid_divs = 5
|
| 51 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 52 |
+
|
| 53 |
+
for i in range(num_grid_divs):
|
| 54 |
+
for j in range(num_grid_divs):
|
| 55 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 56 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 57 |
+
continue
|
| 58 |
+
centers[k] = [coords[i], coords[j]]
|
| 59 |
+
k += 1
|
| 60 |
+
|
| 61 |
+
- # 2. Place 2 circles in the central gap using a geometrically balanced and
|
| 62 |
+
- # asymmetric configuration.
|
| 63 |
+
- central_separation_distance = 0.1552 # Optimal distance for constraint balancing
|
| 64 |
+
- central_pair_orientation_angle_deg = 44.5 # Proven asymmetry to break geometric locking
|
| 65 |
+
+ # 2. Place 2 circles in the central gap using an empirically tuned configuration with a new perturbation.
|
| 66 |
+
+ # Analysis of prior runs shows the highest score (2.52) was achieved with a
|
| 67 |
+
+ # smaller separation distance (0.125), an asymmetric angle, and a slight horizontal offset.
|
| 68 |
+
+ # This configuration reverts to those high-performing parameters and introduces a
|
| 69 |
+
+ # new vertical offset to further break symmetry and seek a better optimum.
|
| 70 |
+
|
| 71 |
+
- # Offsets for the center of the central pair are set to zero to focus on
|
| 72 |
+
- # the primary parameters of separation and orientation.
|
| 73 |
+
- central_pair_offset_x = 0.0
|
| 74 |
+
- central_pair_offset_y = 0.0
|
| 75 |
+
+ # Revert to the empirically superior separation distance. The value 0.125
|
| 76 |
+
+ # outperformed the "geometrically balanced" 0.1552.
|
| 77 |
+
+ central_separation_distance = 0.125
|
| 78 |
+
+ central_pair_orientation_angle_deg = 44.5 # Proven asymmetry from high-scoring runs.
|
| 79 |
+
+
|
| 80 |
+
+ # Re-introduce the horizontal offset and add a new vertical offset. This shifts
|
| 81 |
+
+ # the central pair's centroid slightly up and to the left, creating a more
|
| 82 |
+
+ # complex and potentially advantageous pressure dynamic on surrounding grid circles.
|
| 83 |
+
+ central_pair_offset_x = -0.0015
|
| 84 |
+
+ central_pair_offset_y = 0.0010
|
| 85 |
+
|
| 86 |
+
# R_prime is half the distance between the two central centers.
|
| 87 |
+
R_prime = central_separation_distance / 2.0
|
| 88 |
+
angle_rad = math.radians(central_pair_orientation_angle_deg)
|
| 89 |
+
|
| 90 |
+
# Calculate internal offsets (dx_internal, dy_internal) for the two circles relative to their pair's center.
|
| 91 |
+
dx_internal = R_prime * math.cos(angle_rad)
|
| 92 |
+
dy_internal = R_prime * math.sin(angle_rad)
|
| 93 |
+
|
| 94 |
+
# Calculate the actual center point of the pair
|
| 95 |
+
center_pair_x = 0.5 + central_pair_offset_x
|
| 96 |
+
center_pair_y = 0.5 + central_pair_offset_y
|
| 97 |
+
|
| 98 |
+
# Place the two central circles around their calculated pair center.
|
| 99 |
+
centers[k] = [center_pair_x - dx_internal, center_pair_y - dy_internal]
|
| 100 |
+
k += 1
|
| 101 |
+
centers[k] = [center_pair_x + dx_internal, center_pair_y + dy_internal]
|
| 102 |
+
k += 1
|
| 103 |
+
|
| 104 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 105 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 106 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 107 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 108 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 109 |
+
|
| 110 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 111 |
+
radii = compute_max_radii(centers)
|
| 112 |
+
|
| 113 |
+
return centers, radii
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def compute_max_radii(centers):
|
| 117 |
+
"""
|
| 118 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 119 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 120 |
+
subject to non-overlapping and boundary constraints.
|
| 121 |
+
This function is retained from the best performing solution due to its
|
| 122 |
+
mathematical optimality for fixed centers.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 129 |
+
"""
|
| 130 |
+
n = centers.shape[0]
|
| 131 |
+
|
| 132 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 133 |
+
# minimizing sum(-radii).
|
| 134 |
+
c = -np.ones(n)
|
| 135 |
+
|
| 136 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 137 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 138 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 139 |
+
constraints = []
|
| 140 |
+
b_vector = []
|
| 141 |
+
|
| 142 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 143 |
+
# These ensure circles stay within the unit square.
|
| 144 |
+
for i in range(n):
|
| 145 |
+
# r_i <= x_i
|
| 146 |
+
row = np.zeros(n)
|
| 147 |
+
row[i] = 1
|
| 148 |
+
constraints.append(row)
|
| 149 |
+
b_vector.append(centers[i, 0])
|
| 150 |
+
|
| 151 |
+
# r_i <= 1 - x_i
|
| 152 |
+
row = np.zeros(n)
|
| 153 |
+
row[i] = 1
|
| 154 |
+
constraints.append(row)
|
| 155 |
+
b_vector.append(1 - centers[i, 0])
|
| 156 |
+
|
| 157 |
+
# r_i <= y_i
|
| 158 |
+
row = np.zeros(n)
|
| 159 |
+
row[i] = 1
|
| 160 |
+
constraints.append(row)
|
| 161 |
+
b_vector.append(centers[i, 1])
|
| 162 |
+
|
| 163 |
+
# r_i <= 1 - y_i
|
| 164 |
+
row = np.zeros(n)
|
| 165 |
+
row[i] = 1
|
| 166 |
+
constraints.append(row)
|
| 167 |
+
b_vector.append(1 - centers[i, 1])
|
| 168 |
+
|
| 169 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 170 |
+
# These prevent circles from overlapping.
|
| 171 |
+
for i in range(n):
|
| 172 |
+
for j in range(i + 1, n):
|
| 173 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 174 |
+
row = np.zeros(n)
|
| 175 |
+
row[i] = 1
|
| 176 |
+
row[j] = 1
|
| 177 |
+
constraints.append(row)
|
| 178 |
+
b_vector.append(dist)
|
| 179 |
+
|
| 180 |
+
A_ub = np.array(constraints)
|
| 181 |
+
b_ub = np.array(b_vector)
|
| 182 |
+
|
| 183 |
+
# All radii must be non-negative.
|
| 184 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 185 |
+
bounds = [(0, None) for _ in range(n)]
|
| 186 |
+
|
| 187 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 188 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 189 |
+
|
| 190 |
+
if res.success:
|
| 191 |
+
# Return the optimal radii found by the solver.
|
| 192 |
+
return res.x
|
| 193 |
+
else:
|
| 194 |
+
# Fallback in case of solver failure.
|
| 195 |
+
# Returning zeros implies no valid radii could be determined.
|
| 196 |
+
print(f"LP solver failed: {res.message}")
|
| 197 |
+
return np.zeros(n)
|
| 198 |
+
# EVOLVE-BLOCK-END
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
# This part remains fixed (not evolved)
|
| 202 |
+
def run_packing():
|
| 203 |
+
"""Run the circle packing constructor for n=26"""
|
| 204 |
+
centers, radii = construct_packing()
|
| 205 |
+
# Calculate the sum of radii
|
| 206 |
+
sum_radii = np.sum(radii)
|
| 207 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/main.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 4 |
+
and linear programming for optimal radii. This version refines the central
|
| 5 |
+
circle placement based on geometric constraint analysis.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles based on a 5x5 grid. The central two
|
| 16 |
+
circles are placed using a configuration derived from balancing geometric
|
| 17 |
+
constraints to maximize packing potential.
|
| 18 |
+
|
| 19 |
+
The strategy is based on the following analysis:
|
| 20 |
+
1. The `(5x5-1)` grid structure with `linspace(0.1, 0.9, 5)` is retained
|
| 21 |
+
as a proven high-performance base.
|
| 22 |
+
2. The key improvement is in placing the two central circles. Their radii are
|
| 23 |
+
limited by their distance to each other and to the 8 surrounding grid
|
| 24 |
+
circles. An optimal placement should balance these distances.
|
| 25 |
+
3. A previous symmetric configuration (angle=45 deg) showed that a
|
| 26 |
+
`central_separation_distance` of `0.1552` perfectly balanced the
|
| 27 |
+
distance between the central pair and their nearest grid neighbors.
|
| 28 |
+
4. However, the highest scores were achieved with a slight asymmetry
|
| 29 |
+
(angle=44.5 deg), which likely prevents geometric locking.
|
| 30 |
+
5. This version combines the best of both worlds: the geometrically optimal
|
| 31 |
+
separation distance (`0.1552`) with the performance-enhancing
|
| 32 |
+
asymmetric angle (`44.5` deg). The small pair offset from the previous
|
| 33 |
+
version is removed (set to 0) as it did not improve the score and
|
| 34 |
+
complicates the core geometry.
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
Tuple of (centers, radii)
|
| 38 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 39 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 40 |
+
"""
|
| 41 |
+
n = 26
|
| 42 |
+
centers = np.zeros((n, 2))
|
| 43 |
+
k = 0
|
| 44 |
+
|
| 45 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 46 |
+
# This regular grid provides a strong, stable base structure.
|
| 47 |
+
num_grid_divs = 5
|
| 48 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 49 |
+
|
| 50 |
+
for i in range(num_grid_divs):
|
| 51 |
+
for j in range(num_grid_divs):
|
| 52 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 53 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 54 |
+
continue
|
| 55 |
+
centers[k] = [coords[i], coords[j]]
|
| 56 |
+
k += 1
|
| 57 |
+
|
| 58 |
+
# 2. Place 2 circles in the central gap using an empirically tuned configuration with a new perturbation.
|
| 59 |
+
# Analysis of prior runs shows the highest score (2.52) was achieved with a
|
| 60 |
+
# smaller separation distance (0.125), an asymmetric angle, and a slight horizontal offset.
|
| 61 |
+
# This configuration reverts to those high-performing parameters and introduces a
|
| 62 |
+
# new vertical offset to further break symmetry and seek a better optimum.
|
| 63 |
+
|
| 64 |
+
# Revert to the empirically superior separation distance. The value 0.125
|
| 65 |
+
# outperformed the "geometrically balanced" 0.1552.
|
| 66 |
+
central_separation_distance = 0.125
|
| 67 |
+
central_pair_orientation_angle_deg = 44.5 # Proven asymmetry from high-scoring runs.
|
| 68 |
+
|
| 69 |
+
# Re-introduce the horizontal offset and add a new vertical offset. This shifts
|
| 70 |
+
# the central pair's centroid slightly up and to the left, creating a more
|
| 71 |
+
# complex and potentially advantageous pressure dynamic on surrounding grid circles.
|
| 72 |
+
central_pair_offset_x = -0.0015
|
| 73 |
+
central_pair_offset_y = 0.0010
|
| 74 |
+
|
| 75 |
+
# R_prime is half the distance between the two central centers.
|
| 76 |
+
R_prime = central_separation_distance / 2.0
|
| 77 |
+
angle_rad = math.radians(central_pair_orientation_angle_deg)
|
| 78 |
+
|
| 79 |
+
# Calculate internal offsets (dx_internal, dy_internal) for the two circles relative to their pair's center.
|
| 80 |
+
dx_internal = R_prime * math.cos(angle_rad)
|
| 81 |
+
dy_internal = R_prime * math.sin(angle_rad)
|
| 82 |
+
|
| 83 |
+
# Calculate the actual center point of the pair
|
| 84 |
+
center_pair_x = 0.5 + central_pair_offset_x
|
| 85 |
+
center_pair_y = 0.5 + central_pair_offset_y
|
| 86 |
+
|
| 87 |
+
# Place the two central circles around their calculated pair center.
|
| 88 |
+
centers[k] = [center_pair_x - dx_internal, center_pair_y - dy_internal]
|
| 89 |
+
k += 1
|
| 90 |
+
centers[k] = [center_pair_x + dx_internal, center_pair_y + dy_internal]
|
| 91 |
+
k += 1
|
| 92 |
+
|
| 93 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 94 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 95 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 96 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 97 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 98 |
+
|
| 99 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 100 |
+
radii = compute_max_radii(centers)
|
| 101 |
+
|
| 102 |
+
return centers, radii
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def compute_max_radii(centers):
|
| 106 |
+
"""
|
| 107 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 108 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 109 |
+
subject to non-overlapping and boundary constraints.
|
| 110 |
+
This function is retained from the best performing solution due to its
|
| 111 |
+
mathematical optimality for fixed centers.
|
| 112 |
+
|
| 113 |
+
Args:
|
| 114 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 118 |
+
"""
|
| 119 |
+
n = centers.shape[0]
|
| 120 |
+
|
| 121 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 122 |
+
# minimizing sum(-radii).
|
| 123 |
+
c = -np.ones(n)
|
| 124 |
+
|
| 125 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 126 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 127 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 128 |
+
constraints = []
|
| 129 |
+
b_vector = []
|
| 130 |
+
|
| 131 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 132 |
+
# These ensure circles stay within the unit square.
|
| 133 |
+
for i in range(n):
|
| 134 |
+
# r_i <= x_i
|
| 135 |
+
row = np.zeros(n)
|
| 136 |
+
row[i] = 1
|
| 137 |
+
constraints.append(row)
|
| 138 |
+
b_vector.append(centers[i, 0])
|
| 139 |
+
|
| 140 |
+
# r_i <= 1 - x_i
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
constraints.append(row)
|
| 144 |
+
b_vector.append(1 - centers[i, 0])
|
| 145 |
+
|
| 146 |
+
# r_i <= y_i
|
| 147 |
+
row = np.zeros(n)
|
| 148 |
+
row[i] = 1
|
| 149 |
+
constraints.append(row)
|
| 150 |
+
b_vector.append(centers[i, 1])
|
| 151 |
+
|
| 152 |
+
# r_i <= 1 - y_i
|
| 153 |
+
row = np.zeros(n)
|
| 154 |
+
row[i] = 1
|
| 155 |
+
constraints.append(row)
|
| 156 |
+
b_vector.append(1 - centers[i, 1])
|
| 157 |
+
|
| 158 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 159 |
+
# These prevent circles from overlapping.
|
| 160 |
+
for i in range(n):
|
| 161 |
+
for j in range(i + 1, n):
|
| 162 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 163 |
+
row = np.zeros(n)
|
| 164 |
+
row[i] = 1
|
| 165 |
+
row[j] = 1
|
| 166 |
+
constraints.append(row)
|
| 167 |
+
b_vector.append(dist)
|
| 168 |
+
|
| 169 |
+
A_ub = np.array(constraints)
|
| 170 |
+
b_ub = np.array(b_vector)
|
| 171 |
+
|
| 172 |
+
# All radii must be non-negative.
|
| 173 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 174 |
+
bounds = [(0, None) for _ in range(n)]
|
| 175 |
+
|
| 176 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 177 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 178 |
+
|
| 179 |
+
if res.success:
|
| 180 |
+
# Return the optimal radii found by the solver.
|
| 181 |
+
return res.x
|
| 182 |
+
else:
|
| 183 |
+
# Fallback in case of solver failure.
|
| 184 |
+
# Returning zeros implies no valid radii could be determined.
|
| 185 |
+
print(f"LP solver failed: {res.message}")
|
| 186 |
+
return np.zeros(n)
|
| 187 |
+
# EVOLVE-BLOCK-END
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# This part remains fixed (not evolved)
|
| 191 |
+
def run_packing():
|
| 192 |
+
"""Run the circle packing constructor for n=26"""
|
| 193 |
+
centers, radii = construct_packing()
|
| 194 |
+
# Calculate the sum of radii
|
| 195 |
+
sum_radii = np.sum(radii)
|
| 196 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/original.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 4 |
+
and linear programming for optimal radii. This version refines the central
|
| 5 |
+
circle placement based on geometric constraint analysis.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles based on a 5x5 grid. The central two
|
| 16 |
+
circles are placed using a configuration derived from balancing geometric
|
| 17 |
+
constraints to maximize packing potential.
|
| 18 |
+
|
| 19 |
+
The strategy is based on the following analysis:
|
| 20 |
+
1. The `(5x5-1)` grid structure with `linspace(0.1, 0.9, 5)` is retained
|
| 21 |
+
as a proven high-performance base.
|
| 22 |
+
2. The key improvement is in placing the two central circles. Their radii are
|
| 23 |
+
limited by their distance to each other and to the 8 surrounding grid
|
| 24 |
+
circles. An optimal placement should balance these distances.
|
| 25 |
+
3. A previous symmetric configuration (angle=45 deg) showed that a
|
| 26 |
+
`central_separation_distance` of `0.1552` perfectly balanced the
|
| 27 |
+
distance between the central pair and their nearest grid neighbors.
|
| 28 |
+
4. However, the highest scores were achieved with a slight asymmetry
|
| 29 |
+
(angle=44.5 deg), which likely prevents geometric locking.
|
| 30 |
+
5. This version combines the best of both worlds: the geometrically optimal
|
| 31 |
+
separation distance (`0.1552`) with the performance-enhancing
|
| 32 |
+
asymmetric angle (`44.5` deg). The small pair offset from the previous
|
| 33 |
+
version is removed (set to 0) as it did not improve the score and
|
| 34 |
+
complicates the core geometry.
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
Tuple of (centers, radii)
|
| 38 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 39 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 40 |
+
"""
|
| 41 |
+
n = 26
|
| 42 |
+
centers = np.zeros((n, 2))
|
| 43 |
+
k = 0
|
| 44 |
+
|
| 45 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 46 |
+
# This regular grid provides a strong, stable base structure.
|
| 47 |
+
num_grid_divs = 5
|
| 48 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 49 |
+
|
| 50 |
+
for i in range(num_grid_divs):
|
| 51 |
+
for j in range(num_grid_divs):
|
| 52 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 53 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 54 |
+
continue
|
| 55 |
+
centers[k] = [coords[i], coords[j]]
|
| 56 |
+
k += 1
|
| 57 |
+
|
| 58 |
+
# 2. Place 2 circles in the central gap using a geometrically balanced and
|
| 59 |
+
# asymmetric configuration.
|
| 60 |
+
central_separation_distance = 0.1552 # Optimal distance for constraint balancing
|
| 61 |
+
central_pair_orientation_angle_deg = 44.5 # Proven asymmetry to break geometric locking
|
| 62 |
+
|
| 63 |
+
# Offsets for the center of the central pair are set to zero to focus on
|
| 64 |
+
# the primary parameters of separation and orientation.
|
| 65 |
+
central_pair_offset_x = 0.0
|
| 66 |
+
central_pair_offset_y = 0.0
|
| 67 |
+
|
| 68 |
+
# R_prime is half the distance between the two central centers.
|
| 69 |
+
R_prime = central_separation_distance / 2.0
|
| 70 |
+
angle_rad = math.radians(central_pair_orientation_angle_deg)
|
| 71 |
+
|
| 72 |
+
# Calculate internal offsets (dx_internal, dy_internal) for the two circles relative to their pair's center.
|
| 73 |
+
dx_internal = R_prime * math.cos(angle_rad)
|
| 74 |
+
dy_internal = R_prime * math.sin(angle_rad)
|
| 75 |
+
|
| 76 |
+
# Calculate the actual center point of the pair
|
| 77 |
+
center_pair_x = 0.5 + central_pair_offset_x
|
| 78 |
+
center_pair_y = 0.5 + central_pair_offset_y
|
| 79 |
+
|
| 80 |
+
# Place the two central circles around their calculated pair center.
|
| 81 |
+
centers[k] = [center_pair_x - dx_internal, center_pair_y - dy_internal]
|
| 82 |
+
k += 1
|
| 83 |
+
centers[k] = [center_pair_x + dx_internal, center_pair_y + dy_internal]
|
| 84 |
+
k += 1
|
| 85 |
+
|
| 86 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 87 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 88 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 89 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 90 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 91 |
+
|
| 92 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 93 |
+
radii = compute_max_radii(centers)
|
| 94 |
+
|
| 95 |
+
return centers, radii
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def compute_max_radii(centers):
|
| 99 |
+
"""
|
| 100 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 101 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 102 |
+
subject to non-overlapping and boundary constraints.
|
| 103 |
+
This function is retained from the best performing solution due to its
|
| 104 |
+
mathematical optimality for fixed centers.
|
| 105 |
+
|
| 106 |
+
Args:
|
| 107 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 111 |
+
"""
|
| 112 |
+
n = centers.shape[0]
|
| 113 |
+
|
| 114 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 115 |
+
# minimizing sum(-radii).
|
| 116 |
+
c = -np.ones(n)
|
| 117 |
+
|
| 118 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 119 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 120 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 121 |
+
constraints = []
|
| 122 |
+
b_vector = []
|
| 123 |
+
|
| 124 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 125 |
+
# These ensure circles stay within the unit square.
|
| 126 |
+
for i in range(n):
|
| 127 |
+
# r_i <= x_i
|
| 128 |
+
row = np.zeros(n)
|
| 129 |
+
row[i] = 1
|
| 130 |
+
constraints.append(row)
|
| 131 |
+
b_vector.append(centers[i, 0])
|
| 132 |
+
|
| 133 |
+
# r_i <= 1 - x_i
|
| 134 |
+
row = np.zeros(n)
|
| 135 |
+
row[i] = 1
|
| 136 |
+
constraints.append(row)
|
| 137 |
+
b_vector.append(1 - centers[i, 0])
|
| 138 |
+
|
| 139 |
+
# r_i <= y_i
|
| 140 |
+
row = np.zeros(n)
|
| 141 |
+
row[i] = 1
|
| 142 |
+
constraints.append(row)
|
| 143 |
+
b_vector.append(centers[i, 1])
|
| 144 |
+
|
| 145 |
+
# r_i <= 1 - y_i
|
| 146 |
+
row = np.zeros(n)
|
| 147 |
+
row[i] = 1
|
| 148 |
+
constraints.append(row)
|
| 149 |
+
b_vector.append(1 - centers[i, 1])
|
| 150 |
+
|
| 151 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 152 |
+
# These prevent circles from overlapping.
|
| 153 |
+
for i in range(n):
|
| 154 |
+
for j in range(i + 1, n):
|
| 155 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 156 |
+
row = np.zeros(n)
|
| 157 |
+
row[i] = 1
|
| 158 |
+
row[j] = 1
|
| 159 |
+
constraints.append(row)
|
| 160 |
+
b_vector.append(dist)
|
| 161 |
+
|
| 162 |
+
A_ub = np.array(constraints)
|
| 163 |
+
b_ub = np.array(b_vector)
|
| 164 |
+
|
| 165 |
+
# All radii must be non-negative.
|
| 166 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 167 |
+
bounds = [(0, None) for _ in range(n)]
|
| 168 |
+
|
| 169 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 170 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 171 |
+
|
| 172 |
+
if res.success:
|
| 173 |
+
# Return the optimal radii found by the solver.
|
| 174 |
+
return res.x
|
| 175 |
+
else:
|
| 176 |
+
# Fallback in case of solver failure.
|
| 177 |
+
# Returning zeros implies no valid radii could be determined.
|
| 178 |
+
print(f"LP solver failed: {res.message}")
|
| 179 |
+
return np.zeros(n)
|
| 180 |
+
# EVOLVE-BLOCK-END
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
# This part remains fixed (not evolved)
|
| 184 |
+
def run_packing():
|
| 185 |
+
"""Run the circle packing constructor for n=26"""
|
| 186 |
+
centers, radii = construct_packing()
|
| 187 |
+
# Calculate the sum of radii
|
| 188 |
+
sum_radii = np.sum(radii)
|
| 189 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_103/search_replace.txt
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Based on my analysis of the previous runs, the key to improving the sum of radii lies in the precise placement of the two central circles. The data clearly shows that a configuration with a separation distance of `0.125` and a slight asymmetry (`angle=44.5`, `offset_x=-0.0015`) achieved the peak score of 2.52, outperforming the current "geometrically balanced" approach which scores 2.51.
|
| 2 |
+
|
| 3 |
+
My proposed change is to revert to these empirically superior parameters and introduce a novel perturbation. By adding a small vertical offset (`central_pair_offset_y`) to the central pair's centroid, we break the last remaining axial symmetry. This shifts the pair's center of mass diagonally, creating a more complex pressure dynamic on the 8 surrounding grid circles. This should prevent any residual geometric locking and allow the linear programming solver to explore a new, potentially more efficient packing configuration that was previously inaccessible.
|
| 4 |
+
|
| 5 |
+
<NAME>
|
| 6 |
+
revert_to_empirical_params_with_y_offset
|
| 7 |
+
</NAME>
|
| 8 |
+
|
| 9 |
+
<DESCRIPTION>
|
| 10 |
+
This edit reverts the central circle configuration to the empirically superior parameters from the highest-scoring previous run and introduces a new perturbation.
|
| 11 |
+
|
| 12 |
+
1. **`central_separation_distance`**: Changed from `0.1552` back to `0.125`. The performance data shows that the smaller separation distance yielded a better score (2.52 vs 2.51), indicating that a tighter constraint between the central pair is more beneficial than balancing constraints with the grid circles.
|
| 13 |
+
2. **`central_pair_offset_x`**: Changed from `0.0` to `-0.0015`. This re-introduces the slight horizontal offset that was present in the top-performing configuration, which helps break symmetry.
|
| 14 |
+
3. **`central_pair_offset_y`**: Changed from `0.0` to `0.0010`. This introduces a new vertical offset, creating a diagonal shift (`-x`, `+y`) of the central pair's centroid. This novel perturbation breaks the final axis of symmetry, aiming to unlock a more globally optimal packing by altering the pressure on the surrounding grid circles in a more complex and advantageous way.
|
| 15 |
+
</DESCRIPTION>
|
| 16 |
+
|
| 17 |
+
<DIFF>
|
| 18 |
+
<<<<<<< SEARCH
|
| 19 |
+
# 2. Place 2 circles in the central gap using a geometrically balanced and
|
| 20 |
+
# asymmetric configuration.
|
| 21 |
+
central_separation_distance = 0.1552 # Optimal distance for constraint balancing
|
| 22 |
+
central_pair_orientation_angle_deg = 44.5 # Proven asymmetry to break geometric locking
|
| 23 |
+
|
| 24 |
+
# Offsets for the center of the central pair are set to zero to focus on
|
| 25 |
+
# the primary parameters of separation and orientation.
|
| 26 |
+
central_pair_offset_x = 0.0
|
| 27 |
+
central_pair_offset_y = 0.0
|
| 28 |
+
=======
|
| 29 |
+
# 2. Place 2 circles in the central gap using an empirically tuned configuration with a new perturbation.
|
| 30 |
+
# Analysis of prior runs shows the highest score (2.52) was achieved with a
|
| 31 |
+
# smaller separation distance (0.125), an asymmetric angle, and a slight horizontal offset.
|
| 32 |
+
# This configuration reverts to those high-performing parameters and introduces a
|
| 33 |
+
# new vertical offset to further break symmetry and seek a better optimum.
|
| 34 |
+
|
| 35 |
+
# Revert to the empirically superior separation distance. The value 0.125
|
| 36 |
+
# outperformed the "geometrically balanced" 0.1552.
|
| 37 |
+
central_separation_distance = 0.125
|
| 38 |
+
central_pair_orientation_angle_deg = 44.5 # Proven asymmetry from high-scoring runs.
|
| 39 |
+
|
| 40 |
+
# Re-introduce the horizontal offset and add a new vertical offset. This shifts
|
| 41 |
+
# the central pair's centroid slightly up and to the left, creating a more
|
| 42 |
+
# complex and potentially advantageous pressure dynamic on surrounding grid circles.
|
| 43 |
+
central_pair_offset_x = -0.0015
|
| 44 |
+
central_pair_offset_y = 0.0010
|
| 45 |
+
>>>>>>> REPLACE
|
| 46 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (5.66 kB). View file
|
|
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/edit.diff
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,174 +1,180 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
"""
|
| 6 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 7 |
+
and linear programming for optimal radii. This version introduces a diagonal
|
| 8 |
+
split for the central circles as a crossover from previous designs.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.optimize import linprog
|
| 13 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def construct_packing():
|
| 17 |
+
"""
|
| 18 |
+
Constructs a packing of 26 circles based on a 5x5 grid, with the two central
|
| 19 |
+
circles placed diagonally to break symmetry and improve packing efficiency.
|
| 20 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 21 |
+
|
| 22 |
+
This structure is a crossover between two approaches:
|
| 23 |
+
1. It uses the high-performing 5x5 grid (24 circles) from the inspiration script.
|
| 24 |
+
2. It modifies the central placement by rotating the two inner circles by 45 degrees.
|
| 25 |
+
This is a reasoned perturbation aimed at improving upon the previous best score.
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
Tuple of (centers, radii)
|
| 29 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 30 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 31 |
+
"""
|
| 32 |
+
n = 26
|
| 33 |
+
centers = np.zeros((n, 2))
|
| 34 |
+
k = 0
|
| 35 |
+
|
| 36 |
+
- # 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 37 |
+
- # This regular grid provides a strong, stable base structure.
|
| 38 |
+
+ # 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 39 |
+
+ # The grid is modified to give more space to the central region. By pushing the
|
| 40 |
+
+ # inner grid lines (originally at 0.3 and 0.7) slightly outwards to 0.29 and 0.71,
|
| 41 |
+
+ # we increase the size of the central cell. This should relax constraints on the
|
| 42 |
+
+ # two central circles, allowing for larger radii.
|
| 43 |
+
num_grid_divs = 5
|
| 44 |
+
- coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 45 |
+
+ inner_grid_offset = 0.01
|
| 46 |
+
+ coords = np.array([0.1, 0.3 - inner_grid_offset, 0.5, 0.7 + inner_grid_offset, 0.9])
|
| 47 |
+
|
| 48 |
+
for i in range(num_grid_divs):
|
| 49 |
+
for j in range(num_grid_divs):
|
| 50 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 51 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 52 |
+
continue
|
| 53 |
+
centers[k] = [coords[i], coords[j]]
|
| 54 |
+
k += 1
|
| 55 |
+
|
| 56 |
+
- # 2. Place 2 circles in the central gap with a perfectly symmetric diagonal split.
|
| 57 |
+
- # Analysis of previous runs shows that balancing the constraints on the central circles is key.
|
| 58 |
+
- # The two main constraints are the distance to the other central circle (d_cc) and
|
| 59 |
+
- # the distance to the nearest grid neighbors (d_cg).
|
| 60 |
+
- # An analytical model predicts that these two distances are equalized when the
|
| 61 |
+
- # separation is ~0.1552 and the angle is 45 degrees. This configuration
|
| 62 |
+
- # should remove local bottlenecks and allow the LP solver to find a more
|
| 63 |
+
- # globally optimal solution, leading to a higher sum of radii.
|
| 64 |
+
- central_separation_distance = 0.1552
|
| 65 |
+
- central_asymmetry_angle_deg = 45.0 # Use perfect 45-degree symmetry for the balanced configuration
|
| 66 |
+
+ # 2. Place 2 circles in the central gap, using empirically superior parameters.
|
| 67 |
+
+ # Previous runs have consistently shown that a separation of 0.125 and a slight
|
| 68 |
+
+ # asymmetry angle of 44.5 degrees yield a higher score (2.52) than the analytically
|
| 69 |
+
+ # "balanced" but symmetric configuration (2.51). This change reinstates these
|
| 70 |
+
+ # high-performing values and re-introduces a small horizontal offset to the pair's
|
| 71 |
+
+ # centroid, which was part of the best-known setup and breaks further symmetry.
|
| 72 |
+
+ central_separation_distance = 0.125
|
| 73 |
+
+ central_asymmetry_angle_deg = 44.5
|
| 74 |
+
+ central_pair_offset_x = -0.0015
|
| 75 |
+
|
| 76 |
+
# R_prime is half the distance between the two central centers.
|
| 77 |
+
R_prime = central_separation_distance / 2.0
|
| 78 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 79 |
+
|
| 80 |
+
- # Calculate offsets (dx, dy) from the square's center (0.5, 0.5).
|
| 81 |
+
+ # Calculate internal offsets (dx, dy) relative to the pair's center.
|
| 82 |
+
dx = R_prime * math.cos(angle_rad)
|
| 83 |
+
dy = R_prime * math.sin(angle_rad)
|
| 84 |
+
|
| 85 |
+
- center_point = 0.5
|
| 86 |
+
- centers[k] = [center_point - dx, center_point - dy]
|
| 87 |
+
+ # The center of the pair is shifted slightly off (0.5, 0.5).
|
| 88 |
+
+ center_pair_x = 0.5 + central_pair_offset_x
|
| 89 |
+
+ center_pair_y = 0.5
|
| 90 |
+
+
|
| 91 |
+
+ centers[k] = [center_pair_x - dx, center_pair_y - dy]
|
| 92 |
+
k += 1
|
| 93 |
+
- centers[k] = [center_point + dx, center_point + dy]
|
| 94 |
+
+ centers[k] = [center_pair_x + dx, center_pair_y + dy]
|
| 95 |
+
k += 1
|
| 96 |
+
|
| 97 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 98 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 99 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 100 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 101 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 102 |
+
|
| 103 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 104 |
+
radii = compute_max_radii(centers)
|
| 105 |
+
|
| 106 |
+
return centers, radii
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def compute_max_radii(centers):
|
| 110 |
+
"""
|
| 111 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 112 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 113 |
+
subject to non-overlapping and boundary constraints.
|
| 114 |
+
This function is retained from the best performing solution due to its
|
| 115 |
+
mathematical optimality for fixed centers.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 122 |
+
"""
|
| 123 |
+
n = centers.shape[0]
|
| 124 |
+
|
| 125 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 126 |
+
# minimizing sum(-radii).
|
| 127 |
+
c = -np.ones(n)
|
| 128 |
+
|
| 129 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 130 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 131 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 132 |
+
constraints = []
|
| 133 |
+
b_vector = []
|
| 134 |
+
|
| 135 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 136 |
+
# These ensure circles stay within the unit square.
|
| 137 |
+
for i in range(n):
|
| 138 |
+
# r_i <= x_i
|
| 139 |
+
row = np.zeros(n)
|
| 140 |
+
row[i] = 1
|
| 141 |
+
constraints.append(row)
|
| 142 |
+
b_vector.append(centers[i, 0])
|
| 143 |
+
|
| 144 |
+
# r_i <= 1 - x_i
|
| 145 |
+
row = np.zeros(n)
|
| 146 |
+
row[i] = 1
|
| 147 |
+
constraints.append(row)
|
| 148 |
+
b_vector.append(1 - centers[i, 0])
|
| 149 |
+
|
| 150 |
+
# r_i <= y_i
|
| 151 |
+
row = np.zeros(n)
|
| 152 |
+
row[i] = 1
|
| 153 |
+
constraints.append(row)
|
| 154 |
+
b_vector.append(centers[i, 1])
|
| 155 |
+
|
| 156 |
+
# r_i <= 1 - y_i
|
| 157 |
+
row = np.zeros(n)
|
| 158 |
+
row[i] = 1
|
| 159 |
+
constraints.append(row)
|
| 160 |
+
b_vector.append(1 - centers[i, 1])
|
| 161 |
+
|
| 162 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 163 |
+
# These prevent circles from overlapping.
|
| 164 |
+
for i in range(n):
|
| 165 |
+
for j in range(i + 1, n):
|
| 166 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 167 |
+
row = np.zeros(n)
|
| 168 |
+
row[i] = 1
|
| 169 |
+
row[j] = 1
|
| 170 |
+
constraints.append(row)
|
| 171 |
+
b_vector.append(dist)
|
| 172 |
+
|
| 173 |
+
A_ub = np.array(constraints)
|
| 174 |
+
b_ub = np.array(b_vector)
|
| 175 |
+
|
| 176 |
+
# All radii must be non-negative.
|
| 177 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 178 |
+
bounds = [(0, None) for _ in range(n)]
|
| 179 |
+
|
| 180 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 181 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 182 |
+
|
| 183 |
+
if res.success:
|
| 184 |
+
# Return the optimal radii found by the solver.
|
| 185 |
+
return res.x
|
| 186 |
+
else:
|
| 187 |
+
# Fallback in case of solver failure.
|
| 188 |
+
# Returning zeros implies no valid radii could be determined.
|
| 189 |
+
print(f"LP solver failed: {res.message}")
|
| 190 |
+
return np.zeros(n)
|
| 191 |
+
# EVOLVE-BLOCK-END
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# This part remains fixed (not evolved)
|
| 195 |
+
def run_packing():
|
| 196 |
+
"""Run the circle packing constructor for n=26"""
|
| 197 |
+
centers, radii = construct_packing()
|
| 198 |
+
# Calculate the sum of radii
|
| 199 |
+
sum_radii = np.sum(radii)
|
| 200 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/main.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 4 |
+
and linear programming for optimal radii. This version introduces a diagonal
|
| 5 |
+
split for the central circles as a crossover from previous designs.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles based on a 5x5 grid, with the two central
|
| 16 |
+
circles placed diagonally to break symmetry and improve packing efficiency.
|
| 17 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 18 |
+
|
| 19 |
+
This structure is a crossover between two approaches:
|
| 20 |
+
1. It uses the high-performing 5x5 grid (24 circles) from the inspiration script.
|
| 21 |
+
2. It modifies the central placement by rotating the two inner circles by 45 degrees.
|
| 22 |
+
This is a reasoned perturbation aimed at improving upon the previous best score.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
Tuple of (centers, radii)
|
| 26 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 27 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 28 |
+
"""
|
| 29 |
+
n = 26
|
| 30 |
+
centers = np.zeros((n, 2))
|
| 31 |
+
k = 0
|
| 32 |
+
|
| 33 |
+
# 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 34 |
+
# The grid is modified to give more space to the central region. By pushing the
|
| 35 |
+
# inner grid lines (originally at 0.3 and 0.7) slightly outwards to 0.29 and 0.71,
|
| 36 |
+
# we increase the size of the central cell. This should relax constraints on the
|
| 37 |
+
# two central circles, allowing for larger radii.
|
| 38 |
+
num_grid_divs = 5
|
| 39 |
+
inner_grid_offset = 0.01
|
| 40 |
+
coords = np.array([0.1, 0.3 - inner_grid_offset, 0.5, 0.7 + inner_grid_offset, 0.9])
|
| 41 |
+
|
| 42 |
+
for i in range(num_grid_divs):
|
| 43 |
+
for j in range(num_grid_divs):
|
| 44 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 45 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 46 |
+
continue
|
| 47 |
+
centers[k] = [coords[i], coords[j]]
|
| 48 |
+
k += 1
|
| 49 |
+
|
| 50 |
+
# 2. Place 2 circles in the central gap, using empirically superior parameters.
|
| 51 |
+
# Previous runs have consistently shown that a separation of 0.125 and a slight
|
| 52 |
+
# asymmetry angle of 44.5 degrees yield a higher score (2.52) than the analytically
|
| 53 |
+
# "balanced" but symmetric configuration (2.51). This change reinstates these
|
| 54 |
+
# high-performing values and re-introduces a small horizontal offset to the pair's
|
| 55 |
+
# centroid, which was part of the best-known setup and breaks further symmetry.
|
| 56 |
+
central_separation_distance = 0.125
|
| 57 |
+
central_asymmetry_angle_deg = 44.5
|
| 58 |
+
central_pair_offset_x = -0.0015
|
| 59 |
+
|
| 60 |
+
# R_prime is half the distance between the two central centers.
|
| 61 |
+
R_prime = central_separation_distance / 2.0
|
| 62 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 63 |
+
|
| 64 |
+
# Calculate internal offsets (dx, dy) relative to the pair's center.
|
| 65 |
+
dx = R_prime * math.cos(angle_rad)
|
| 66 |
+
dy = R_prime * math.sin(angle_rad)
|
| 67 |
+
|
| 68 |
+
# The center of the pair is shifted slightly off (0.5, 0.5).
|
| 69 |
+
center_pair_x = 0.5 + central_pair_offset_x
|
| 70 |
+
center_pair_y = 0.5
|
| 71 |
+
|
| 72 |
+
centers[k] = [center_pair_x - dx, center_pair_y - dy]
|
| 73 |
+
k += 1
|
| 74 |
+
centers[k] = [center_pair_x + dx, center_pair_y + dy]
|
| 75 |
+
k += 1
|
| 76 |
+
|
| 77 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 78 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 79 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 80 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 81 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 82 |
+
|
| 83 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 84 |
+
radii = compute_max_radii(centers)
|
| 85 |
+
|
| 86 |
+
return centers, radii
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def compute_max_radii(centers):
|
| 90 |
+
"""
|
| 91 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 92 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 93 |
+
subject to non-overlapping and boundary constraints.
|
| 94 |
+
This function is retained from the best performing solution due to its
|
| 95 |
+
mathematical optimality for fixed centers.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 102 |
+
"""
|
| 103 |
+
n = centers.shape[0]
|
| 104 |
+
|
| 105 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 106 |
+
# minimizing sum(-radii).
|
| 107 |
+
c = -np.ones(n)
|
| 108 |
+
|
| 109 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 110 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 111 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 112 |
+
constraints = []
|
| 113 |
+
b_vector = []
|
| 114 |
+
|
| 115 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 116 |
+
# These ensure circles stay within the unit square.
|
| 117 |
+
for i in range(n):
|
| 118 |
+
# r_i <= x_i
|
| 119 |
+
row = np.zeros(n)
|
| 120 |
+
row[i] = 1
|
| 121 |
+
constraints.append(row)
|
| 122 |
+
b_vector.append(centers[i, 0])
|
| 123 |
+
|
| 124 |
+
# r_i <= 1 - x_i
|
| 125 |
+
row = np.zeros(n)
|
| 126 |
+
row[i] = 1
|
| 127 |
+
constraints.append(row)
|
| 128 |
+
b_vector.append(1 - centers[i, 0])
|
| 129 |
+
|
| 130 |
+
# r_i <= y_i
|
| 131 |
+
row = np.zeros(n)
|
| 132 |
+
row[i] = 1
|
| 133 |
+
constraints.append(row)
|
| 134 |
+
b_vector.append(centers[i, 1])
|
| 135 |
+
|
| 136 |
+
# r_i <= 1 - y_i
|
| 137 |
+
row = np.zeros(n)
|
| 138 |
+
row[i] = 1
|
| 139 |
+
constraints.append(row)
|
| 140 |
+
b_vector.append(1 - centers[i, 1])
|
| 141 |
+
|
| 142 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 143 |
+
# These prevent circles from overlapping.
|
| 144 |
+
for i in range(n):
|
| 145 |
+
for j in range(i + 1, n):
|
| 146 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 147 |
+
row = np.zeros(n)
|
| 148 |
+
row[i] = 1
|
| 149 |
+
row[j] = 1
|
| 150 |
+
constraints.append(row)
|
| 151 |
+
b_vector.append(dist)
|
| 152 |
+
|
| 153 |
+
A_ub = np.array(constraints)
|
| 154 |
+
b_ub = np.array(b_vector)
|
| 155 |
+
|
| 156 |
+
# All radii must be non-negative.
|
| 157 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 158 |
+
bounds = [(0, None) for _ in range(n)]
|
| 159 |
+
|
| 160 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 161 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 162 |
+
|
| 163 |
+
if res.success:
|
| 164 |
+
# Return the optimal radii found by the solver.
|
| 165 |
+
return res.x
|
| 166 |
+
else:
|
| 167 |
+
# Fallback in case of solver failure.
|
| 168 |
+
# Returning zeros implies no valid radii could be determined.
|
| 169 |
+
print(f"LP solver failed: {res.message}")
|
| 170 |
+
return np.zeros(n)
|
| 171 |
+
# EVOLVE-BLOCK-END
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# This part remains fixed (not evolved)
|
| 175 |
+
def run_packing():
|
| 176 |
+
"""Run the circle packing constructor for n=26"""
|
| 177 |
+
centers, radii = construct_packing()
|
| 178 |
+
# Calculate the sum of radii
|
| 179 |
+
sum_radii = np.sum(radii)
|
| 180 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/original.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EVOLVE-BLOCK-START
|
| 2 |
+
"""
|
| 3 |
+
Constructor-based circle packing for n=26 circles using a modified 5x5 grid
|
| 4 |
+
and linear programming for optimal radii. This version introduces a diagonal
|
| 5 |
+
split for the central circles as a crossover from previous designs.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from scipy.optimize import linprog
|
| 10 |
+
import math # Added for math.radians, math.cos, math.sin
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def construct_packing():
|
| 14 |
+
"""
|
| 15 |
+
Constructs a packing of 26 circles based on a 5x5 grid, with the two central
|
| 16 |
+
circles placed diagonally to break symmetry and improve packing efficiency.
|
| 17 |
+
The radii are then optimized using a linear programming solver to maximize their sum.
|
| 18 |
+
|
| 19 |
+
This structure is a crossover between two approaches:
|
| 20 |
+
1. It uses the high-performing 5x5 grid (24 circles) from the inspiration script.
|
| 21 |
+
2. It modifies the central placement by rotating the two inner circles by 45 degrees.
|
| 22 |
+
This is a reasoned perturbation aimed at improving upon the previous best score.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
Tuple of (centers, radii)
|
| 26 |
+
centers: np.array of shape (26, 2) with (x, y) coordinates.
|
| 27 |
+
radii: np.array of shape (26) with radius of each circle.
|
| 28 |
+
"""
|
| 29 |
+
n = 26
|
| 30 |
+
centers = np.zeros((n, 2))
|
| 31 |
+
k = 0
|
| 32 |
+
|
| 33 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 34 |
+
# This regular grid provides a strong, stable base structure.
|
| 35 |
+
num_grid_divs = 5
|
| 36 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 37 |
+
|
| 38 |
+
for i in range(num_grid_divs):
|
| 39 |
+
for j in range(num_grid_divs):
|
| 40 |
+
# Skip the center of the 5x5 grid (index 2, 2) to make space.
|
| 41 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 42 |
+
continue
|
| 43 |
+
centers[k] = [coords[i], coords[j]]
|
| 44 |
+
k += 1
|
| 45 |
+
|
| 46 |
+
# 2. Place 2 circles in the central gap with a perfectly symmetric diagonal split.
|
| 47 |
+
# Analysis of previous runs shows that balancing the constraints on the central circles is key.
|
| 48 |
+
# The two main constraints are the distance to the other central circle (d_cc) and
|
| 49 |
+
# the distance to the nearest grid neighbors (d_cg).
|
| 50 |
+
# An analytical model predicts that these two distances are equalized when the
|
| 51 |
+
# separation is ~0.1552 and the angle is 45 degrees. This configuration
|
| 52 |
+
# should remove local bottlenecks and allow the LP solver to find a more
|
| 53 |
+
# globally optimal solution, leading to a higher sum of radii.
|
| 54 |
+
central_separation_distance = 0.1552
|
| 55 |
+
central_asymmetry_angle_deg = 45.0 # Use perfect 45-degree symmetry for the balanced configuration
|
| 56 |
+
|
| 57 |
+
# R_prime is half the distance between the two central centers.
|
| 58 |
+
R_prime = central_separation_distance / 2.0
|
| 59 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 60 |
+
|
| 61 |
+
# Calculate offsets (dx, dy) from the square's center (0.5, 0.5).
|
| 62 |
+
dx = R_prime * math.cos(angle_rad)
|
| 63 |
+
dy = R_prime * math.sin(angle_rad)
|
| 64 |
+
|
| 65 |
+
center_point = 0.5
|
| 66 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 67 |
+
k += 1
|
| 68 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 69 |
+
k += 1
|
| 70 |
+
|
| 71 |
+
# Clip centers to be strictly within (0,1) to avoid numerical issues
|
| 72 |
+
# at boundaries when calculating radii. A smaller epsilon (1e-8) allows centers
|
| 73 |
+
# to be placed even closer to the boundaries for marginal gains, especially
|
| 74 |
+
# for grid circles intended to touch the walls (e.g., at 0.1 or 0.9).
|
| 75 |
+
centers = np.clip(centers, 1e-8, 1 - 1e-8)
|
| 76 |
+
|
| 77 |
+
# For the given centers, compute the radii that maximize the sum using LP.
|
| 78 |
+
radii = compute_max_radii(centers)
|
| 79 |
+
|
| 80 |
+
return centers, radii
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def compute_max_radii(centers):
|
| 84 |
+
"""
|
| 85 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 86 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 87 |
+
subject to non-overlapping and boundary constraints.
|
| 88 |
+
This function is retained from the best performing solution due to its
|
| 89 |
+
mathematical optimality for fixed centers.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 96 |
+
"""
|
| 97 |
+
n = centers.shape[0]
|
| 98 |
+
|
| 99 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 100 |
+
# minimizing sum(-radii).
|
| 101 |
+
c = -np.ones(n)
|
| 102 |
+
|
| 103 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 104 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 105 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 106 |
+
constraints = []
|
| 107 |
+
b_vector = []
|
| 108 |
+
|
| 109 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 110 |
+
# These ensure circles stay within the unit square.
|
| 111 |
+
for i in range(n):
|
| 112 |
+
# r_i <= x_i
|
| 113 |
+
row = np.zeros(n)
|
| 114 |
+
row[i] = 1
|
| 115 |
+
constraints.append(row)
|
| 116 |
+
b_vector.append(centers[i, 0])
|
| 117 |
+
|
| 118 |
+
# r_i <= 1 - x_i
|
| 119 |
+
row = np.zeros(n)
|
| 120 |
+
row[i] = 1
|
| 121 |
+
constraints.append(row)
|
| 122 |
+
b_vector.append(1 - centers[i, 0])
|
| 123 |
+
|
| 124 |
+
# r_i <= y_i
|
| 125 |
+
row = np.zeros(n)
|
| 126 |
+
row[i] = 1
|
| 127 |
+
constraints.append(row)
|
| 128 |
+
b_vector.append(centers[i, 1])
|
| 129 |
+
|
| 130 |
+
# r_i <= 1 - y_i
|
| 131 |
+
row = np.zeros(n)
|
| 132 |
+
row[i] = 1
|
| 133 |
+
constraints.append(row)
|
| 134 |
+
b_vector.append(1 - centers[i, 1])
|
| 135 |
+
|
| 136 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 137 |
+
# These prevent circles from overlapping.
|
| 138 |
+
for i in range(n):
|
| 139 |
+
for j in range(i + 1, n):
|
| 140 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 141 |
+
row = np.zeros(n)
|
| 142 |
+
row[i] = 1
|
| 143 |
+
row[j] = 1
|
| 144 |
+
constraints.append(row)
|
| 145 |
+
b_vector.append(dist)
|
| 146 |
+
|
| 147 |
+
A_ub = np.array(constraints)
|
| 148 |
+
b_ub = np.array(b_vector)
|
| 149 |
+
|
| 150 |
+
# All radii must be non-negative.
|
| 151 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 152 |
+
bounds = [(0, None) for _ in range(n)]
|
| 153 |
+
|
| 154 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 155 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 156 |
+
|
| 157 |
+
if res.success:
|
| 158 |
+
# Return the optimal radii found by the solver.
|
| 159 |
+
return res.x
|
| 160 |
+
else:
|
| 161 |
+
# Fallback in case of solver failure.
|
| 162 |
+
# Returning zeros implies no valid radii could be determined.
|
| 163 |
+
print(f"LP solver failed: {res.message}")
|
| 164 |
+
return np.zeros(n)
|
| 165 |
+
# EVOLVE-BLOCK-END
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# This part remains fixed (not evolved)
|
| 169 |
+
def run_packing():
|
| 170 |
+
"""Run the circle packing constructor for n=26"""
|
| 171 |
+
centers, radii = construct_packing()
|
| 172 |
+
# Calculate the sum of radii
|
| 173 |
+
sum_radii = np.sum(radii)
|
| 174 |
+
return centers, radii, sum_radii
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/results/correct.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"correct": true,
|
| 3 |
+
"error": null
|
| 4 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/results/metrics.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"combined_score": 2.405,
|
| 3 |
+
"correct": true,
|
| 4 |
+
"primary": {
|
| 5 |
+
"combined_score": 2.405,
|
| 6 |
+
"public": {
|
| 7 |
+
"centers_str": " centers[0] = (0.1000, 0.1000)\n centers[1] = (0.1000, 0.2900)\n centers[2] = (0.1000, 0.5000)\n centers[3] = (0.1000, 0.7100)\n centers[4] = (0.1000, 0.9000)\n centers[5] = (0.2900, 0.1000)\n centers[6] = (0.2900, 0.2900)\n centers[7] = (0.2900, 0.5000)\n centers[8] = (0.2900, 0.7100)\n centers[9] = (0.2900, 0.9000)\n centers[10] = (0.5000, 0.1000)\n centers[11] = (0.5000, 0.2900)\n centers[12] = (0.5000, 0.7100)\n centers[13] = (0.5000, 0.9000)\n centers[14] = (0.7100, 0.1000)\n centers[15] = (0.7100, 0.2900)\n centers[16] = (0.7100, 0.5000)\n centers[17] = (0.7100, 0.7100)\n centers[18] = (0.7100, 0.9000)\n centers[19] = (0.9000, 0.1000)\n centers[20] = (0.9000, 0.2900)\n centers[21] = (0.9000, 0.5000)\n centers[22] = (0.9000, 0.7100)\n centers[23] = (0.9000, 0.9000)\n centers[24] = (0.4539, 0.4562)\n centers[25] = (0.5431, 0.5438)",
|
| 8 |
+
"num_circles": 26
|
| 9 |
+
},
|
| 10 |
+
"private": {
|
| 11 |
+
"reported_sum_of_radii": 2.405
|
| 12 |
+
},
|
| 13 |
+
"execution_time_mean": 0.05832532048225403,
|
| 14 |
+
"execution_time_std": 0.0,
|
| 15 |
+
"num_valid_runs": 1,
|
| 16 |
+
"num_invalid_runs": 0,
|
| 17 |
+
"all_validation_errors": [],
|
| 18 |
+
"correct": true,
|
| 19 |
+
"validation_error": null
|
| 20 |
+
},
|
| 21 |
+
"auxiliary": {
|
| 22 |
+
"max_circle_overlap_magnitude": 0.0,
|
| 23 |
+
"num_overlapping_pairs": 0,
|
| 24 |
+
"max_boundary_violation_magnitude": 0.0,
|
| 25 |
+
"num_boundary_violations": 0,
|
| 26 |
+
"total_area_covered": 0.713941707900842,
|
| 27 |
+
"packing_density": 0.713941707900842,
|
| 28 |
+
"empty_space_ratio": 0.286058292099158,
|
| 29 |
+
"avg_gap_between_circles": 0.34007118009566395,
|
| 30 |
+
"min_gap_between_circles": 0.0,
|
| 31 |
+
"num_circles": 26,
|
| 32 |
+
"avg_radius": 0.0925,
|
| 33 |
+
"std_dev_radius": 0.013576285579837874,
|
| 34 |
+
"min_radius": 0.05246265181582166,
|
| 35 |
+
"max_radius": 0.12,
|
| 36 |
+
"radii_coefficient_of_variation": 0.1467706549171662,
|
| 37 |
+
"num_unique_radii": 12,
|
| 38 |
+
"avg_distance_from_unit_center": 0.3703805008216262,
|
| 39 |
+
"avg_distance_from_unit_center_normalized": 0.523797127500483,
|
| 40 |
+
"max_distance_from_unit_center": 0.5656854249492381,
|
| 41 |
+
"center_x_std_dev": 0.2804514614049722,
|
| 42 |
+
"center_y_std_dev": 0.28044182699511816,
|
| 43 |
+
"avg_nearest_neighbor_distance_centers": 0.18216911491099536,
|
| 44 |
+
"center_quadrant_density_variance": 2.25,
|
| 45 |
+
"avg_min_distance_to_boundary": 0.09327365453496626,
|
| 46 |
+
"min_overall_distance_to_boundary": 0.0,
|
| 47 |
+
"avg_pairwise_center_distance": 0.525071180095664,
|
| 48 |
+
"normalized_score_per_circle": 0.0925,
|
| 49 |
+
"packing_aspect_ratio_of_centers_bbox": 1.0,
|
| 50 |
+
"avg_num_touching_neighbors": 2.076923076923077,
|
| 51 |
+
"avg_quadrant_radii_std_dev": 0.013482330513421369,
|
| 52 |
+
"primary_combined_score": 2.405
|
| 53 |
+
},
|
| 54 |
+
"auxiliary_descriptions": {
|
| 55 |
+
"total_area_covered": "By tracking this, we can see if increases in `sum(radii)` are also leading to proportional increases in actual area covered. If `sum(radii)` plateaus but `total_area_covered` continues to rise (or maintains a high level), it suggests a more efficient use of space by re-distributing radii or centers to fill gaps more effectively.",
|
| 56 |
+
"packing_density": "This is identical to `total_area_covered` because the unit square has an area of 1. It directly represents the percentage of the available area covered by circles. Higher is better, indicating more efficient use of space.",
|
| 57 |
+
"avg_min_distance_to_boundary": "The average of the minimum distances from the edge of each circle to any of the four boundaries of the unit square. Smaller values (closer to 0) indicate that circles are packed more tightly against the boundaries, which is often a characteristic of optimal packing solutions. Negative values would imply a violation (circle outside boundary) which the primary metric already checks. Useful for understanding how well the solution leverages the container edges.",
|
| 58 |
+
"min_overall_distance_to_boundary": "The single smallest distance from any circle's edge to any boundary. This metric highlights if at least one circle is positioned extremely close to a boundary, indicating a strategy to maximize space.",
|
| 59 |
+
"avg_radius": "The average radius of the 26 circles. This provides a central tendency for circle sizes, helping to understand if the solution favors larger, smaller, or uniformly sized circles.",
|
| 60 |
+
"std_dev_radius": "This metric is critical for identifying potential local optima. If `std_dev_radius` is very low and `sum(radii)` is stagnating, it might suggest the algorithm is stuck with uniform-sized circles. Encouraging greater `std_dev_radius` could lead to exploring more diverse solutions (e.g., using a few large circles and many small ones) that might break current plateaus. Conversely, if `std_dev_radius` is high, it shows exploration of diverse sizes is still active.",
|
| 61 |
+
"min_radius": "The smallest radius among the 26 circles. Useful for identifying if very small circles are being used to fill tiny gaps.",
|
| 62 |
+
"max_radius": "The largest radius among the 26 circles. Indicates the largest circle size successfully placed, which is often a key factor in maximizing total radius sum.",
|
| 63 |
+
"avg_nearest_neighbor_distance_centers": "This metric calculates the average Euclidean distance from each circle's center to the center of its closest neighboring circle. A lower value indicates a more tightly packed or clustered arrangement of circle centers, while a higher value suggests a more dispersed or sparse distribution. This is useful for identifying if the packing strategy is creating dense clusters or leaving significant gaps between groups of circles.",
|
| 64 |
+
"center_quadrant_density_variance": "This metric divides the unit square into four equal quadrants (2x2 grid) and computes the variance of the number of circle centers found within each quadrant. A lower variance indicates a more uniform distribution of circle centers across the entire packing area, suggesting a balanced utilization of space. A higher variance implies that circle centers are concentrated in certain quadrants, potentially indicating inefficient or uneven packing.",
|
| 65 |
+
"empty_space_ratio": "A decreasing trend in this metric, even with a stable `sum(radii)`, indicates the solution is becoming more efficient in terms of area utilization. If it's stagnant, it suggests wasted space.",
|
| 66 |
+
"avg_gap_between_circles": "The average positive distance between the edges of any two non-overlapping circles. A smaller average gap suggests a tighter packing configuration where circles are placed closer to each other without violating overlap constraints.",
|
| 67 |
+
"min_gap_between_circles": "The smallest positive distance between the edges of any two non-overlapping circles. A value near zero indicates that at least one pair of circles is almost touching, which is characteristic of optimal packing where circles are tightly constrained.",
|
| 68 |
+
"radii_coefficient_of_variation": "Changes in this metric can reveal if the solution is exploring new combinations of circle sizes. A sudden increase might suggest an attempt to break free from a local optimum by introducing more diverse sizes, while a decrease might indicate convergence towards a more uniform or specific size distribution.",
|
| 69 |
+
"avg_distance_from_unit_center": "The average Euclidean distance of all circle centers from the center of the unit square (0.5, 0.5). A lower value suggests circles are clustered more towards the center of the packing area, while a higher value indicates a more peripheral or spread-out distribution.",
|
| 70 |
+
"max_distance_from_unit_center": "The maximum Euclidean distance of any circle center from the center of the unit square (0.5, 0.5). This indicates how far out the outermost circle is placed, providing insight into the spread of the packing.",
|
| 71 |
+
"center_x_std_dev": "The standard deviation of the x-coordinates of all circle centers. A lower value indicates less spread horizontally.",
|
| 72 |
+
"center_y_std_dev": "The standard deviation of the y-coordinates of all circle centers. A lower value indicates less spread vertically. Together with `center_x_std_dev`, these metrics describe the spatial distribution and clustering of circles.",
|
| 73 |
+
"packing_aspect_ratio_of_centers_bbox": "Monitoring this metric can reveal if the packing is becoming more balanced (closer to 1.0) or if it's developing a preferred elongation. A deviation from 1.0 might suggest that the algorithm is struggling to utilize the square boundaries effectively, or it could be an emergent property of a specific packing strategy. If the primary score plateaus, but this metric moves closer to 1.0, it could indicate structural refinement.",
|
| 74 |
+
"avg_num_touching_neighbors": "A sustained increase in this metric, especially if the primary score is stagnant, would be a strong indicator of improved packing density and efficiency. It suggests that circles are being fit together more closely. If this metric is low and stagnating, it implies that there are significant gaps between circles that could be exploited for larger radii.",
|
| 75 |
+
"avg_quadrant_radii_std_dev": "This metric provides spatial insight into radii diversity. If this value is high, it means different regions of the packing are employing varied circle sizes. If it's low, it indicates a more uniform sizing strategy across the board. This can inform whether the evolution should be encouraged to explore more diverse sizes regionally or to converge on a more uniform size distribution if that proves more efficient for the overall sum of radii.",
|
| 76 |
+
"avg_distance_from_unit_center_normalized": "The average distance of all circle centers from the center of the unit square (0.5, 0.5), normalized by the maximum possible distance from the center (`sqrt(0.5)`). A value closer to 0 indicates a more central packing, while a value closer to 1 implies circles are pushed towards the corners or edges, utilizing the full extent of the square. Useful for assessing if the packing strategy prioritizes central arrangements or boundary-conforming ones.",
|
| 77 |
+
"avg_pairwise_center_distance": "The average Euclidean distance between all unique pairs of circle centers. A higher value suggests a more spread-out arrangement of circles, potentially indicating a less \"clustered\" or more uniform distribution of centers. This complements nearest-neighbor distance by considering all pairs and provides a global measure of center dispersion.",
|
| 78 |
+
"avg_min_boundary_distance": "As solutions converge, an efficient packing should optimally utilize the boundaries. A consistently low (approaching zero) `avg_min_boundary_distance` combined with a high primary score indicates a well-optimized solution that is effectively using the edges of the unit square. If this value is still relatively high, it might suggest opportunities to push circles closer to the container's perimeter, thereby creating more space internally for larger circles or more circles."
|
| 79 |
+
},
|
| 80 |
+
"timestamp": 1770497811.3328636,
|
| 81 |
+
"generation": 104
|
| 82 |
+
}
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_104/search_replace.txt
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
I will propose two independent changes. The first reverts the central circle parameters to the empirically superior configuration from past runs to recover the best-known score of 2.52. The second introduces a novel modification to the grid layout, designed to create more space in the crowded central area, which is a promising strategy for pushing beyond the current local optimum.
|
| 2 |
+
|
| 3 |
+
<NAME>
|
| 4 |
+
non_uniform_grid_spacing
|
| 5 |
+
</NAME>
|
| 6 |
+
<DESCRIPTION>
|
| 7 |
+
The current configuration uses a uniform 5x5 grid defined by `np.linspace(0.1, 0.9, 5)`, resulting in coordinates `[0.1, 0.3, 0.5, 0.7, 0.9]`. Geometric analysis shows that the central area is the most constrained part of the packing, limiting the radii of the two central circles and their eight neighbors.
|
| 8 |
+
|
| 9 |
+
This edit introduces a non-uniform grid to strategically create more space in the center. I modify the grid coordinates by pushing the inner grid lines (originally at 0.3 and 0.7) slightly outwards by `0.01` to `0.29` and `0.71` respectively. This expands the central cell from `0.4x0.4` to `0.42x0.42`. This targeted relaxation of central constraints is hypothesized to allow the two central circles and their neighbors to grow, leading to a larger overall sum of radii, even if it slightly compresses the space in the outer bands. This is a common strategy in packing problems: creating space where it is most valuable to unlock a better global solution.
|
| 10 |
+
</DESCRIPTION>
|
| 11 |
+
<DIFF>
|
| 12 |
+
<<<<<<< SEARCH
|
| 13 |
+
# 1. Place 24 circles in a 5x5 grid, skipping the central point.
|
| 14 |
+
# This regular grid provides a strong, stable base structure.
|
| 15 |
+
num_grid_divs = 5
|
| 16 |
+
coords = np.linspace(0.1, 0.9, num_grid_divs)
|
| 17 |
+
=======
|
| 18 |
+
# 1. Place 24 circles in a non-uniform 5x5 grid, skipping the central point.
|
| 19 |
+
# The grid is modified to give more space to the central region. By pushing the
|
| 20 |
+
# inner grid lines (originally at 0.3 and 0.7) slightly outwards to 0.29 and 0.71,
|
| 21 |
+
# we increase the size of the central cell. This should relax constraints on the
|
| 22 |
+
# two central circles, allowing for larger radii.
|
| 23 |
+
num_grid_divs = 5
|
| 24 |
+
inner_grid_offset = 0.01
|
| 25 |
+
coords = np.array([0.1, 0.3 - inner_grid_offset, 0.5, 0.7 + inner_grid_offset, 0.9])
|
| 26 |
+
>>>>>>> REPLACE
|
| 27 |
+
</DIFF>
|
| 28 |
+
<NAME>
|
| 29 |
+
revert_to_best_central_params
|
| 30 |
+
</NAME>
|
| 31 |
+
<DESCRIPTION>
|
| 32 |
+
The current program uses a `central_separation_distance` of `0.1552` and an `angle` of `45.0` degrees, based on an analytical model of balancing constraints. However, empirical results from previous runs consistently show that this configuration (score: 2.51) is outperformed by one with a smaller separation, slight asymmetry, and a small offset (`separation=0.125`, `angle=44.5`, `offset_x=-0.0015`), which achieved a score of 2.52.
|
| 33 |
+
|
| 34 |
+
This edit reverts the parameters for the two central circles to this empirically superior configuration. While analytical models are valuable, slight asymmetry often prevents "geometric locking" and allows the optimization solver to find a better overall arrangement. This change reinstates the best-known parameters to recover performance and provide a stronger baseline for future experiments, such as the non-uniform grid modification.
|
| 35 |
+
</DESCRIPTION>
|
| 36 |
+
<DIFF>
|
| 37 |
+
<<<<<<< SEARCH
|
| 38 |
+
# 2. Place 2 circles in the central gap with a perfectly symmetric diagonal split.
|
| 39 |
+
# Analysis of previous runs shows that balancing the constraints on the central circles is key.
|
| 40 |
+
# The two main constraints are the distance to the other central circle (d_cc) and
|
| 41 |
+
# the distance to the nearest grid neighbors (d_cg).
|
| 42 |
+
# An analytical model predicts that these two distances are equalized when the
|
| 43 |
+
# separation is ~0.1552 and the angle is 45 degrees. This configuration
|
| 44 |
+
# should remove local bottlenecks and allow the LP solver to find a more
|
| 45 |
+
# globally optimal solution, leading to a higher sum of radii.
|
| 46 |
+
central_separation_distance = 0.1552
|
| 47 |
+
central_asymmetry_angle_deg = 45.0 # Use perfect 45-degree symmetry for the balanced configuration
|
| 48 |
+
|
| 49 |
+
# R_prime is half the distance between the two central centers.
|
| 50 |
+
R_prime = central_separation_distance / 2.0
|
| 51 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 52 |
+
|
| 53 |
+
# Calculate offsets (dx, dy) from the square's center (0.5, 0.5).
|
| 54 |
+
dx = R_prime * math.cos(angle_rad)
|
| 55 |
+
dy = R_prime * math.sin(angle_rad)
|
| 56 |
+
|
| 57 |
+
center_point = 0.5
|
| 58 |
+
centers[k] = [center_point - dx, center_point - dy]
|
| 59 |
+
k += 1
|
| 60 |
+
centers[k] = [center_point + dx, center_point + dy]
|
| 61 |
+
=======
|
| 62 |
+
# 2. Place 2 circles in the central gap, using empirically superior parameters.
|
| 63 |
+
# Previous runs have consistently shown that a separation of 0.125 and a slight
|
| 64 |
+
# asymmetry angle of 44.5 degrees yield a higher score (2.52) than the analytically
|
| 65 |
+
# "balanced" but symmetric configuration (2.51). This change reinstates these
|
| 66 |
+
# high-performing values and re-introduces a small horizontal offset to the pair's
|
| 67 |
+
# centroid, which was part of the best-known setup and breaks further symmetry.
|
| 68 |
+
central_separation_distance = 0.125
|
| 69 |
+
central_asymmetry_angle_deg = 44.5
|
| 70 |
+
central_pair_offset_x = -0.0015
|
| 71 |
+
|
| 72 |
+
# R_prime is half the distance between the two central centers.
|
| 73 |
+
R_prime = central_separation_distance / 2.0
|
| 74 |
+
angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 75 |
+
|
| 76 |
+
# Calculate internal offsets (dx, dy) relative to the pair's center.
|
| 77 |
+
dx = R_prime * math.cos(angle_rad)
|
| 78 |
+
dy = R_prime * math.sin(angle_rad)
|
| 79 |
+
|
| 80 |
+
# The center of the pair is shifted slightly off (0.5, 0.5).
|
| 81 |
+
center_pair_x = 0.5 + central_pair_offset_x
|
| 82 |
+
center_pair_y = 0.5
|
| 83 |
+
|
| 84 |
+
centers[k] = [center_pair_x - dx, center_pair_y - dy]
|
| 85 |
+
k += 1
|
| 86 |
+
centers[k] = [center_pair_x + dx, center_pair_y + dy]
|
| 87 |
+
>>>>>>> REPLACE
|
| 88 |
+
</DIFF>
|
examples_deprecated/circle_packing/results/results_full_gen200_period20_20260207_182944/gen_106/edit.diff
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--- a/original.py
|
| 2 |
+
+++ b/original.py
|
| 3 |
+
@@ -1,205 +1,199 @@
|
| 4 |
+
# EVOLVE-BLOCK-START
|
| 5 |
+
import numpy as np
|
| 6 |
+
from scipy.optimize import linprog
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import math
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class CirclePackingConfig:
|
| 13 |
+
"""
|
| 14 |
+
Configuration parameters for the circle packing problem.
|
| 15 |
+
Encapsulates all tunable parameters for clarity and easy modification.
|
| 16 |
+
"""
|
| 17 |
+
n_circles: int = 26
|
| 18 |
+
grid_num_divs: int = 5
|
| 19 |
+
- grid_margin: float = 0.1
|
| 20 |
+
+ # Increase margin to allow perimeter circles to grow, at the cost of shrinking the inner grid.
|
| 21 |
+
+ grid_margin: float = 0.11
|
| 22 |
+
# Euclidean distance between the centers of the two central circles.
|
| 23 |
+
- # This value (0.107) is adopted from a previous high-scoring solution (2.51).
|
| 24 |
+
- central_separation_distance: float = 0.107
|
| 25 |
+
- # Offset of the first central circle from the (0.5, 0.5) center point.
|
| 26 |
+
- # Initial values derived from the previously best-performing central_asymmetry_angle_deg = 44.5
|
| 27 |
+
- central_circle1_x_offset: float = -0.038195
|
| 28 |
+
- central_circle1_y_offset: float = -0.037434
|
| 29 |
+
- # Angle in degrees defining the orientation from the first central circle to the second.
|
| 30 |
+
- # Initial value matches the previous central_asymmetry_angle_deg = 44.5
|
| 31 |
+
- central_pair_orientation_angle_deg: float = 44.5
|
| 32 |
+
+ # This is reduced to compensate for the smaller central hole caused by the increased grid_margin.
|
| 33 |
+
+ central_separation_distance: float = 0.104
|
| 34 |
+
+ # Angle in degrees for the asymmetric diagonal placement of central circles, centered at (0.5, 0.5).
|
| 35 |
+
+ # A value of 44.5 degrees breaks perfect symmetry and is retained from prior high-scoring runs.
|
| 36 |
+
+ central_asymmetry_angle_deg: float = 44.5
|
| 37 |
+
clip_epsilon: float = 1e-8
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _place_grid_circles(config: CirclePackingConfig) -> np.ndarray:
|
| 41 |
+
"""
|
| 42 |
+
Generates centers for 24 circles arranged in a 5x5 grid, skipping the center.
|
| 43 |
+
The grid with a 0.1 margin is a proven, high-performing base structure.
|
| 44 |
+
"""
|
| 45 |
+
num_grid_divs = config.grid_num_divs
|
| 46 |
+
m = config.grid_margin
|
| 47 |
+
coords = np.linspace(m, 1.0 - m, num_grid_divs)
|
| 48 |
+
|
| 49 |
+
grid_centers = []
|
| 50 |
+
for i in range(num_grid_divs):
|
| 51 |
+
for j in range(num_grid_divs):
|
| 52 |
+
# Skip the center of the 5x5 grid (index 2, 2)
|
| 53 |
+
if i == num_grid_divs // 2 and j == num_grid_divs // 2:
|
| 54 |
+
continue
|
| 55 |
+
grid_centers.append([coords[i], coords[j]])
|
| 56 |
+
return np.array(grid_centers)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _place_central_circles(config: CirclePackingConfig) -> np.ndarray:
|
| 60 |
+
"""
|
| 61 |
+
- Generates centers for 2 circles placed with a parameterized asymmetric diagonal split.
|
| 62 |
+
- This method allows for independent positioning of the first central circle and
|
| 63 |
+
- then defines the second central circle relative to the first, providing maximum flexibility.
|
| 64 |
+
+ Generates centers for 2 circles placed with a parameterized asymmetric diagonal split,
|
| 65 |
+
+ symmetrically around the square's center (0.5, 0.5). This provides a more intuitive
|
| 66 |
+
+ and stable parameterization than defining one circle relative to the other.
|
| 67 |
+
"""
|
| 68 |
+
central_separation_distance = config.central_separation_distance
|
| 69 |
+
- central_circle1_x_offset = config.central_circle1_x_offset
|
| 70 |
+
- central_circle1_y_offset = config.central_circle1_y_offset
|
| 71 |
+
- central_pair_orientation_angle_deg = config.central_pair_orientation_angle_deg
|
| 72 |
+
+ central_asymmetry_angle_deg = config.central_asymmetry_angle_deg
|
| 73 |
+
+
|
| 74 |
+
+ # R_prime is the distance from the center (0.5, 0.5) to each central circle's center.
|
| 75 |
+
+ R_prime = central_separation_distance / 2.0
|
| 76 |
+
+
|
| 77 |
+
+ # Convert the asymmetry angle from degrees to radians for trigonometric functions.
|
| 78 |
+
+ angle_rad = math.radians(central_asymmetry_angle_deg)
|
| 79 |
+
+
|
| 80 |
+
+ # Calculate x and y offsets (dx, dy) from the center (0.5, 0.5).
|
| 81 |
+
+ dx = R_prime * math.cos(angle_rad)
|
| 82 |
+
+ dy = R_prime * math.sin(angle_rad)
|
| 83 |
+
|
| 84 |
+
center_point = 0.5
|
| 85 |
+
-
|
| 86 |
+
- # Position the first central circle based on its offset from the unit square's center
|
| 87 |
+
- c1_x = center_point + central_circle1_x_offset
|
| 88 |
+
- c1_y = center_point + central_circle1_y_offset
|
| 89 |
+
-
|
| 90 |
+
- # Calculate the position of the second central circle relative to the first
|
| 91 |
+
- # based on the separation distance and orientation angle.
|
| 92 |
+
- angle_rad = math.radians(central_pair_orientation_angle_deg)
|
| 93 |
+
- c2_x = c1_x + central_separation_distance * math.cos(angle_rad)
|
| 94 |
+
- c2_y = c1_y + central_separation_distance * math.sin(angle_rad)
|
| 95 |
+
-
|
| 96 |
+
central_centers = np.array([
|
| 97 |
+
- [c1_x, c1_y],
|
| 98 |
+
- [c2_x, c2_y]
|
| 99 |
+
+ [center_point - dx, center_point - dy],
|
| 100 |
+
+ [center_point + dx, center_point + dy]
|
| 101 |
+
])
|
| 102 |
+
return central_centers
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def generate_centers(config: CirclePackingConfig) -> np.ndarray:
|
| 106 |
+
"""
|
| 107 |
+
Combines different placement strategies to generate all 26 circle centers.
|
| 108 |
+
"""
|
| 109 |
+
grid_centers = _place_grid_circles(config)
|
| 110 |
+
central_centers = _place_central_circles(config)
|
| 111 |
+
|
| 112 |
+
# Combine all centers into a single array.
|
| 113 |
+
all_centers = np.vstack((grid_centers, central_centers))
|
| 114 |
+
|
| 115 |
+
# Clip centers to be strictly within the unit square.
|
| 116 |
+
all_centers = np.clip(all_centers, config.clip_epsilon, 1 - config.clip_epsilon)
|
| 117 |
+
|
| 118 |
+
return all_centers
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def construct_packing():
|
| 122 |
+
"""
|
| 123 |
+
Constructs a packing of 26 circles by first generating centers using a
|
| 124 |
+
hierarchical approach and then optimizing their radii via linear programming.
|
| 125 |
+
This version refactors the code into a modular, configuration-driven design and
|
| 126 |
+
adopts the best-performing parameters from prior experiments.
|
| 127 |
+
"""
|
| 128 |
+
# Initialize configuration with empirically optimized parameters.
|
| 129 |
+
config = CirclePackingConfig()
|
| 130 |
+
|
| 131 |
+
# Generate all circle centers based on the configuration.
|
| 132 |
+
centers = generate_centers(config)
|
| 133 |
+
|
| 134 |
+
# Compute the optimal radii for these fixed centers.
|
| 135 |
+
radii = compute_max_radii(centers)
|
| 136 |
+
|
| 137 |
+
return centers, radii
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def compute_max_radii(centers):
|
| 141 |
+
"""
|
| 142 |
+
Computes the maximum possible radii for a given set of circle centers
|
| 143 |
+
by solving a linear programming problem. This maximizes the sum of radii
|
| 144 |
+
subject to non-overlapping and boundary constraints.
|
| 145 |
+
This function is retained from the best performing solution due to its
|
| 146 |
+
mathematical optimality for fixed centers.
|
| 147 |
+
|
| 148 |
+
Args:
|
| 149 |
+
centers: np.array of shape (n, 2) with (x, y) coordinates.
|
| 150 |
+
|
| 151 |
+
Returns:
|
| 152 |
+
np.ndarray: An array of shape (n) with the optimal radius for each circle.
|
| 153 |
+
"""
|
| 154 |
+
n = centers.shape[0]
|
| 155 |
+
|
| 156 |
+
# The objective is to maximize sum(radii), which is equivalent to
|
| 157 |
+
# minimizing sum(-radii).
|
| 158 |
+
c = -np.ones(n)
|
| 159 |
+
|
| 160 |
+
# Inequality constraints (A_ub * r <= b_ub) are built as a list of rows.
|
| 161 |
+
# 1. r_i + r_j <= dist(c_i, c_j) for each pair of circles (i, j)
|
| 162 |
+
# 2. r_i <= dist_to_wall for each circle i (4 constraints per circle)
|
| 163 |
+
constraints = []
|
| 164 |
+
b_vector = []
|
| 165 |
+
|
| 166 |
+
# Wall constraints: r_i <= x_i, r_i <= 1-x_i, r_i <= y_i, r_i <= 1-y_i
|
| 167 |
+
# These ensure circles stay within the unit square.
|
| 168 |
+
for i in range(n):
|
| 169 |
+
# r_i <= x_i
|
| 170 |
+
row = np.zeros(n)
|
| 171 |
+
row[i] = 1
|
| 172 |
+
constraints.append(row)
|
| 173 |
+
b_vector.append(centers[i, 0])
|
| 174 |
+
|
| 175 |
+
# r_i <= 1 - x_i
|
| 176 |
+
row = np.zeros(n)
|
| 177 |
+
row[i] = 1
|
| 178 |
+
constraints.append(row)
|
| 179 |
+
b_vector.append(1 - centers[i, 0])
|
| 180 |
+
|
| 181 |
+
# r_i <= y_i
|
| 182 |
+
row = np.zeros(n)
|
| 183 |
+
row[i] = 1
|
| 184 |
+
constraints.append(row)
|
| 185 |
+
b_vector.append(centers[i, 1])
|
| 186 |
+
|
| 187 |
+
# r_i <= 1 - y_i
|
| 188 |
+
row = np.zeros(n)
|
| 189 |
+
row[i] = 1
|
| 190 |
+
constraints.append(row)
|
| 191 |
+
b_vector.append(1 - centers[i, 1])
|
| 192 |
+
|
| 193 |
+
# Pair constraints: r_i + r_j <= d_ij
|
| 194 |
+
# These prevent circles from overlapping.
|
| 195 |
+
for i in range(n):
|
| 196 |
+
for j in range(i + 1, n):
|
| 197 |
+
dist = np.linalg.norm(centers[i] - centers[j])
|
| 198 |
+
row = np.zeros(n)
|
| 199 |
+
row[i] = 1
|
| 200 |
+
row[j] = 1
|
| 201 |
+
constraints.append(row)
|
| 202 |
+
b_vector.append(dist)
|
| 203 |
+
|
| 204 |
+
A_ub = np.array(constraints)
|
| 205 |
+
b_ub = np.array(b_vector)
|
| 206 |
+
|
| 207 |
+
# All radii must be non-negative.
|
| 208 |
+
# The (0, None) bounds indicate r_i >= 0.
|
| 209 |
+
bounds = [(0, None) for _ in range(n)]
|
| 210 |
+
|
| 211 |
+
# Solve the linear program using the 'highs' solver for performance.
|
| 212 |
+
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
|
| 213 |
+
|
| 214 |
+
if res.success:
|
| 215 |
+
# Return the optimal radii found by the solver.
|
| 216 |
+
return res.x
|
| 217 |
+
else:
|
| 218 |
+
# Fallback in case of solver failure.
|
| 219 |
+
# Returning zeros implies no valid radii could be determined.
|
| 220 |
+
print(f"LP solver failed: {res.message}")
|
| 221 |
+
return np.zeros(n)
|
| 222 |
+
# EVOLVE-BLOCK-END
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
# This part remains fixed (not evolved)
|
| 226 |
+
def run_packing():
|
| 227 |
+
"""Run the circle packing constructor for n=26"""
|
| 228 |
+
centers, radii = construct_packing()
|
| 229 |
+
# Calculate the sum of radii
|
| 230 |
+
sum_radii = np.sum(radii)
|
| 231 |
+
return centers, radii, sum_radii
|