Skip to content

Commit 2c6298f

Browse files
committed
init
1 parent 10a4ad3 commit 2c6298f

File tree

4 files changed

+475
-0
lines changed

4 files changed

+475
-0
lines changed

examples/circle_packing/README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Circle Packing Example
2+
3+
This example attempts to replicate one of the results from the AlphaEvolve paper (Section B.12): packing circles inside a unit square to maximize the sum of their radii.
4+
5+
## Problem Description
6+
7+
Given a positive integer n, the problem is to pack n disjoint circles inside a unit square so as to maximize the sum of their radii. The circles must:
8+
- Lie entirely within the unit square [0,1] × [0,1]
9+
- Not overlap with each other
10+
11+
This is a well-studied problem in computational geometry with applications in various fields including material science, facility location, and computer graphics.
12+
13+
## AlphaEvolve Results
14+
15+
According to the paper, AlphaEvolve found new constructions improving the state of the art:
16+
- For n = 26, improved from 2.634 to 2.635
17+
- For n = 32, improved from 2.936 to 2.937
18+
19+
## Running the Example
20+
21+
```bash
22+
python openevolve-run.py examples/circle_packing/initial_program.py examples/circle_packing/evaluator.py --config examples/circle_packing/config.yaml --iterations 100
23+
```
24+
25+
## Evaluation Metrics
26+
27+
The evaluator calculates several metrics:
28+
- `sum_radii_26`: Sum of radii for n=26
29+
- `sum_radii_32`: Sum of radii for n=32
30+
- `target_ratio_26`: Ratio of achieved sum to target (2.635) for n=26
31+
- `target_ratio_32`: Ratio of achieved sum to target (2.937) for n=32
32+
- `validity`: 1.0 if solutions for both n=26 and n=32 are valid, 0.0 otherwise
33+
- `avg_target_ratio`: Average of target ratios
34+
- `combined_score`: avg_target_ratio * validity (main fitness metric)
35+
36+
## Expected Results
37+
38+
A successful run should find packing arrangements with sums approaching or exceeding the values reported in the AlphaEvolve paper:
39+
- n=26: 2.635
40+
- n=32: 2.937
41+
42+
## Visualization
43+
44+
You can visualize the best solution by adding a visualization function to the best program:
45+
46+
```python
47+
def visualize(centers, radii):
48+
import matplotlib.pyplot as plt
49+
from matplotlib.patches import Circle
50+
51+
fig, ax = plt.subplots(figsize=(8, 8))
52+
53+
# Draw unit square
54+
ax.set_xlim(0, 1)
55+
ax.set_ylim(0, 1)
56+
ax.set_aspect('equal')
57+
ax.grid(True)
58+
59+
# Draw circles
60+
for i, (center, radius) in enumerate(zip(centers, radii)):
61+
circle = Circle(center, radius, alpha=0.5)
62+
ax.add_patch(circle)
63+
ax.text(center[0], center[1], str(i), ha='center', va='center')
64+
65+
plt.title(f"Circle Packing (n={len(centers)}, sum={sum(radii):.6f})")
66+
plt.show()
67+
68+
# Example usage:
69+
# centers, radii, sum_radii = run_packing(26)
70+
# visualize(centers, radii)
71+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Configuration for circle packing example
2+
max_iterations: 100
3+
checkpoint_interval: 10
4+
log_level: "INFO"
5+
6+
# LLM configuration
7+
llm:
8+
# primary_model: "gemini-2.0-flash-lite"
9+
primary_model: "llama3.1-8b"
10+
primary_model_weight: 0.8
11+
# secondary_model: "gemini-2.0-flash"
12+
secondary_model: "llama-4-scout-17b-16e-instruct"
13+
secondary_model_weight: 0.2
14+
# api_base: "https://generativelanguage.googleapis.com/v1beta/openai/"
15+
api_base: "https://api.cerebras.ai/v1"
16+
temperature: 0.7
17+
top_p: 0.95
18+
max_tokens: 4096
19+
20+
# Prompt configuration
21+
prompt:
22+
system_message: "You are an expert programmer specializing in optimization algorithms and computational geometry. Your task is to improve a circle packing algorithm to maximize the sum of radii when packing n circles in a unit square without overlaps. The AlphaEvolve paper achieved a sum of 2.635 for n=26 and 2.937 for n=32. Focus on finding better optimization strategies to reach or exceed these values."
23+
num_top_programs: 3
24+
use_template_stochasticity: true
25+
26+
# Database configuration
27+
database:
28+
population_size: 50
29+
archive_size: 20
30+
num_islands: 3
31+
elite_selection_ratio: 0.2
32+
exploitation_ratio: 0.7
33+
34+
# Evaluator configuration
35+
evaluator:
36+
timeout: 60
37+
cascade_evaluation: true
38+
cascade_thresholds: [0.5, 0.75]
39+
parallel_evaluations: 4
40+
use_llm_feedback: false
41+
42+
# Evolution settings
43+
diff_based_evolution: true
44+
allow_full_rewrites: false
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
Evaluator for circle packing example
3+
"""
4+
5+
import importlib.util
6+
import numpy as np
7+
import time
8+
import concurrent.futures
9+
import threading
10+
import traceback
11+
import sys
12+
13+
14+
def run_with_timeout(func, args=(), kwargs={}, timeout_seconds=30):
15+
"""
16+
Run a function with a timeout using concurrent.futures
17+
18+
Args:
19+
func: Function to run
20+
args: Arguments to pass to the function
21+
kwargs: Keyword arguments to pass to the function
22+
timeout_seconds: Timeout in seconds
23+
24+
Returns:
25+
Result of the function or raises TimeoutError
26+
"""
27+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
28+
future = executor.submit(func, *args, **kwargs)
29+
try:
30+
return future.result(timeout=timeout_seconds)
31+
except concurrent.futures.TimeoutError:
32+
raise TimeoutError(
33+
f"Function {func.__name__} timed out after {timeout_seconds} seconds"
34+
)
35+
36+
37+
def validate_packing(centers, radii):
38+
"""
39+
Validate that circles don't overlap and are inside the unit square
40+
41+
Args:
42+
centers: np.array of shape (n, 2) with (x, y) coordinates
43+
radii: np.array of shape (n) with radius of each circle
44+
45+
Returns:
46+
True if valid, False otherwise
47+
"""
48+
n = centers.shape[0]
49+
50+
# Check if circles are inside the unit square
51+
for i in range(n):
52+
x, y = centers[i]
53+
r = radii[i]
54+
if x - r < -1e-6 or x + r > 1 + 1e-6 or y - r < -1e-6 or y + r > 1 + 1e-6:
55+
print(f"Circle {i} at ({x}, {y}) with radius {r} is outside the unit square")
56+
return False
57+
58+
# Check for overlaps
59+
for i in range(n):
60+
for j in range(i + 1, n):
61+
dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
62+
if dist < radii[i] + radii[j] - 1e-6: # Allow for tiny numerical errors
63+
print(f"Circles {i} and {j} overlap: dist={dist}, r1+r2={radii[i]+radii[j]}")
64+
return False
65+
66+
return True
67+
68+
69+
def evaluate(program_path):
70+
"""
71+
Evaluate the program by running it for n=26 and n=32 and checking the sum of radii
72+
73+
Args:
74+
program_path: Path to the program file
75+
76+
Returns:
77+
Dictionary of metrics
78+
"""
79+
# Target values from the paper
80+
TARGETS = {26: 2.635, 32: 2.937} # AlphaEvolve result for n=26 # AlphaEvolve result for n=32
81+
82+
try:
83+
# Load the program
84+
spec = importlib.util.spec_from_file_location("program", program_path)
85+
program = importlib.util.module_from_spec(spec)
86+
spec.loader.exec_module(program)
87+
88+
# Check if the required function exists
89+
if not hasattr(program, "run_packing"):
90+
print(f"Error: program does not have 'run_packing' function")
91+
return {"sum_radii": 0.0, "validity": 0.0, "combined_score": 0.0}
92+
93+
# Run for two different n values
94+
results = {}
95+
96+
for n in [26, 32]:
97+
try:
98+
start_time = time.time()
99+
100+
# Run packing with timeout
101+
centers, radii, sum_radii = run_with_timeout(
102+
program.run_packing, args=(n,), timeout_seconds=30
103+
)
104+
105+
end_time = time.time()
106+
107+
# Ensure centers and radii are numpy arrays
108+
if not isinstance(centers, np.ndarray):
109+
centers = np.array(centers)
110+
if not isinstance(radii, np.ndarray):
111+
radii = np.array(radii)
112+
113+
# Validate solution
114+
valid = validate_packing(centers, radii)
115+
116+
# Check shape and size
117+
shape_valid = centers.shape == (n, 2) and radii.shape == (n,)
118+
if not shape_valid:
119+
print(
120+
f"Invalid shapes: centers={centers.shape}, radii={radii.shape}, expected ({n}, 2) and ({n},)"
121+
)
122+
valid = False
123+
124+
# Recalculate sum to verify
125+
actual_sum = np.sum(radii) if valid else 0.0
126+
127+
# Make sure sum_radii matches the actual sum
128+
if abs(actual_sum - sum_radii) > 1e-6:
129+
print(
130+
f"Warning: Reported sum {sum_radii} doesn't match calculated sum {actual_sum}"
131+
)
132+
133+
target = TARGETS[n]
134+
135+
# Store results
136+
results[n] = {
137+
"valid": valid,
138+
"sum_radii": actual_sum,
139+
"time": end_time - start_time,
140+
"target_ratio": actual_sum / target if valid else 0.0,
141+
}
142+
143+
print(
144+
f"n={n}: valid={valid}, sum_radii={actual_sum:.6f}, target={target}, ratio={actual_sum/target if valid else 0:.6f}"
145+
)
146+
147+
except TimeoutError as e:
148+
print(f"Timeout running for n={n}: {str(e)}")
149+
results[n] = {
150+
"valid": False,
151+
"sum_radii": 0.0,
152+
"time": 30.0, # timeout value
153+
"target_ratio": 0.0,
154+
}
155+
except Exception as e:
156+
print(f"Error running for n={n}: {str(e)}")
157+
traceback.print_exc()
158+
results[n] = {"valid": False, "sum_radii": 0.0, "time": 0.0, "target_ratio": 0.0}
159+
160+
# Calculate combined metrics
161+
avg_ratio = (results[26]["target_ratio"] + results[32]["target_ratio"]) / 2
162+
validity = 1.0 if results[26]["valid"] and results[32]["valid"] else 0.0
163+
164+
# Return metrics - higher values are better
165+
return {
166+
"sum_radii_26": float(results[26]["sum_radii"]),
167+
"sum_radii_32": float(results[32]["sum_radii"]),
168+
"target_ratio_26": float(results[26]["target_ratio"]),
169+
"target_ratio_32": float(results[32]["target_ratio"]),
170+
"validity": float(validity),
171+
"avg_target_ratio": float(avg_ratio),
172+
"combined_score": float(avg_ratio * validity),
173+
}
174+
175+
except Exception as e:
176+
print(f"Evaluation failed completely: {str(e)}")
177+
traceback.print_exc()
178+
return {
179+
"sum_radii_26": 0.0,
180+
"sum_radii_32": 0.0,
181+
"target_ratio_26": 0.0,
182+
"target_ratio_32": 0.0,
183+
"validity": 0.0,
184+
"avg_target_ratio": 0.0,
185+
"combined_score": 0.0,
186+
}
187+
188+
189+
# Stage-based evaluation for cascade evaluation
190+
def evaluate_stage1(program_path):
191+
"""
192+
First stage evaluation - quick validation check with only n=26
193+
"""
194+
try:
195+
# Load the program
196+
spec = importlib.util.spec_from_file_location("program", program_path)
197+
program = importlib.util.module_from_spec(spec)
198+
spec.loader.exec_module(program)
199+
200+
# Check if the required function exists
201+
if not hasattr(program, "run_packing"):
202+
print(f"Error: program does not have 'run_packing' function")
203+
return {"validity": 0.0, "error": "Missing run_packing function"}
204+
205+
try:
206+
# Run with a lower iteration count for quicker checking
207+
centers, radii, sum_radii = run_with_timeout(
208+
program.run_packing, args=(26,), timeout_seconds=10
209+
)
210+
211+
# Ensure centers and radii are numpy arrays
212+
if not isinstance(centers, np.ndarray):
213+
centers = np.array(centers)
214+
if not isinstance(radii, np.ndarray):
215+
radii = np.array(radii)
216+
217+
# Validate solution (shapes and constraints)
218+
shape_valid = centers.shape == (26, 2) and radii.shape == (26,)
219+
if not shape_valid:
220+
print(f"Invalid shapes: centers={centers.shape}, radii={radii.shape}")
221+
return {"validity": 0.0, "error": "Invalid shapes"}
222+
223+
valid = validate_packing(centers, radii)
224+
225+
# Calculate sum
226+
actual_sum = np.sum(radii) if valid else 0.0
227+
228+
# Target from paper
229+
target = 2.635
230+
231+
# Return evaluation metrics
232+
return {
233+
"validity": 1.0 if valid else 0.0,
234+
"sum_radii": float(actual_sum),
235+
"target_ratio": float(actual_sum / target if valid else 0.0),
236+
}
237+
238+
except TimeoutError as e:
239+
print(f"Stage 1 evaluation timed out: {e}")
240+
return {"validity": 0.0, "error": "Timeout"}
241+
except Exception as e:
242+
print(f"Stage 1 evaluation failed: {e}")
243+
print(traceback.format_exc())
244+
return {"validity": 0.0, "error": str(e)}
245+
246+
except Exception as e:
247+
print(f"Stage 1 evaluation failed completely: {e}")
248+
print(traceback.format_exc())
249+
return {"validity": 0.0, "error": str(e)}
250+
251+
252+
def evaluate_stage2(program_path):
253+
"""
254+
Second stage evaluation - full evaluation with n=26 and n=32
255+
"""
256+
# Full evaluation as in the main evaluate function
257+
return evaluate(program_path)

0 commit comments

Comments
 (0)