| schedule | every 6h |
|---|---|
| timeout-minutes | 40 |
Improve the circle packing constructor in .autoloop/programs/circle_packing/code/initial_program.py to maximize the sum of radii of 26 non-overlapping circles packed inside a unit square.
This is a classic computational geometry optimization problem. The AlphaEvolve paper achieved a sum of radii of 2.635 for n=26, which serves as the target. The initial implementation uses a naive concentric-ring layout (1 center + 8 inner ring + 16 outer ring) with greedy radius computation, achieving a much lower score.
Each iteration should improve the construct_packing() and compute_max_radii() functions inside the # EVOLVE-BLOCK-START / # EVOLVE-BLOCK-END markers. The algorithm must return exactly 26 circles, all within the unit square [0,1]x[0,1], with no overlaps.
Key geometric insights from the config's system message:
- Circle packings often follow hexagonal patterns in the densest regions
- Maximum density for infinite circle packing is pi/(2*sqrt(3)) ~ 0.9069
- Edge effects make square container packing harder than infinite packing
- Circles can be placed in layers or shells when confined to a square
- Similar radius circles often form regular patterns, while varied radii allow better space utilization
- Perfect symmetry may not yield the optimal packing due to edge effects
- Consider scipy.optimize.minimize with SLSQP for numerical refinement of positions
The evaluation validates the packing (no overlaps, all inside square, correct shapes) then computes:
- sum_radii: The total sum of all 26 radii
- target_ratio:
sum_radii / 2.635(how close to AlphaEvolve's result) - combined_score:
target_ratio * validity(0 if any constraint is violated)
The metric is combined_score. Higher is better. A score of 1.0 means matching AlphaEvolve's result.
Only modify these files:
.autoloop/programs/circle_packing/code/initial_program.py-- the packing constructor (only code betweenEVOLVE-BLOCK-STARTandEVOLVE-BLOCK-END)
Do NOT modify:
.autoloop/programs/circle_packing/code/evaluator.py-- the evaluation script.autoloop/programs/circle_packing/code/config.yaml-- configuration for OpenEvolve.autoloop/programs/circle_packing/code/requirements.txt-- dependencies- The
run_packing,visualizefunctions outside the evolve block ininitial_program.py
pip install -q numpy scipy matplotlib && python3 -c "
import importlib.util, numpy as np, json
spec = importlib.util.spec_from_file_location('program', '.autoloop/programs/circle_packing/code/initial_program.py')
program = importlib.util.module_from_spec(spec)
spec.loader.exec_module(program)
TARGET = 2.635
try:
centers, radii, reported_sum = program.run_packing()
centers, radii = np.array(centers), np.array(radii)
# Validate shapes
if centers.shape != (26, 2) or radii.shape != (26,):
print(json.dumps({'combined_score': 0.0, 'error': 'invalid shapes'}))
exit()
# Validate no NaN
if np.isnan(centers).any() or np.isnan(radii).any():
print(json.dumps({'combined_score': 0.0, 'error': 'NaN values'}))
exit()
# Validate non-negative radii
if (radii < 0).any():
print(json.dumps({'combined_score': 0.0, 'error': 'negative radii'}))
exit()
# Validate inside unit square
valid = True
for i in range(26):
x, y, r = centers[i][0], centers[i][1], radii[i]
if x-r < -1e-6 or x+r > 1+1e-6 or y-r < -1e-6 or y+r > 1+1e-6:
valid = False
break
# Validate no overlaps
if valid:
for i in range(26):
for j in range(i+1, 26):
dist = np.sqrt(np.sum((centers[i]-centers[j])**2))
if dist < radii[i]+radii[j]-1e-6:
valid = False
break
if not valid:
break
sum_radii = float(np.sum(radii)) if valid else 0.0
ratio = sum_radii / TARGET if valid else 0.0
score = ratio if valid else 0.0
print(json.dumps({
'combined_score': round(score, 6),
'sum_radii': round(sum_radii, 6),
'target_ratio': round(ratio, 6),
'validity': 1.0 if valid else 0.0,
'target': TARGET,
'initial_score': 0.0
}))
except Exception as e:
print(json.dumps({'combined_score': 0.0, 'error': str(e)}))
"The metric is combined_score from the JSON output. Higher is better.
A score of 1.0 means matching AlphaEvolve's result of sum_radii = 2.635.
This section guides how the autoloop agent should approach proposing changes across iterations. The ideas are adapted from OpenEvolve's evolutionary programming framework.
At the start of each iteration, read .autoloop/programs/circle_packing/code/config.yaml and use its values to drive the evolution strategy. Do not assume hard-coded values -- always read the config file fresh, as the user may tune parameters between runs.
The key config sections and how to use them:
database.population_size: Maximum number of candidate solutions to track. Scale down proportionally for autoloop.database.archive_size: Number of elite solutions to always preserve. Scale similarly.database.num_islands: Number of conceptual sub-populations. Each island represents a different packing strategy.database.exploitation_ratio: Probability of choosing exploitation (refining top solutions) vs exploration.database.elite_selection_ratio: Fraction of population considered "elite" for parent selection.evaluator.cascade_evaluationandevaluator.cascade_thresholds: Multi-stage evaluation gates.diff_based_evolution: If false (as in this config), full rewrites are preferred over diffs. This is because constructor functions benefit from holistic redesigns rather than incremental tweaks.allow_full_rewrites: If true, the agent should feel free to completely rewrite the constructor each iteration.prompt.system_message: Contains domain expertise and geometric insights. Read for inspiration.prompt.num_top_programs: How many top solutions to review for inspiration.
In repo memory, maintain a population of distinct packing solutions (sized proportionally to database.population_size from the config), each with:
- code: The full evolve block content
- combined_score: The evaluation metric
- sum_radii: The actual sum of radii achieved
- packing_strategy: A short label (e.g., "concentric_rings", "hexagonal_grid", "optimized_grid", "scipy_slsqp", "hybrid_construct_optimize")
- code_complexity: Approximate line count of the evolve block
- generation: Which iteration produced it
- parent_id: Which population member it was derived from (null for the initial program)
- island: Which conceptual island this solution belongs to
Store this in repo memory under autoloop/circle_packing as a population list alongside the existing history and rejected_approaches.
Maintain an elite archive sized proportionally to database.archive_size and database.elite_selection_ratio. Elite solutions are always preserved.
Read database.num_islands from the config and organize the population into that many conceptual islands, each representing a different packing strategy. For example, with 4 islands:
- Island 0: Pure constructive approaches (grid layouts, hexagonal patterns, layered shells)
- Island 1: Optimization-based approaches (scipy.optimize with SLSQP/COBYLA, gradient-based refinement)
- Island 2: Hybrid approaches (construct initial layout, then optimize positions/radii numerically)
- Island 3: Novel / experimental approaches (physics-based simulation, evolutionary within the constructor, greedy insertion)
When proposing a change:
- Most iterations evolve within an island (refine the same strategy family)
- Periodically (every ~5 iterations), attempt a migration: take the best technique from one island and incorporate it into a solution on another
Each iteration, read database.exploitation_ratio from the config and choose a strategy:
- Exploitation (per config ratio): Take the current best-scoring solution and refine it. Adjust circle positions slightly, tune optimization parameters, improve the initial layout fed to an optimizer.
- Exploration (most of the remainder): Try a fundamentally different packing strategy. If the population is all grid-based, try optimization-based. If all optimization, try a novel geometric construction.
- Weighted random (small remainder): Pick any population member (weighted by score) and make a moderate change.
Record which strategy was used for each iteration in the history.
Important: Note that diff_based_evolution: false in this config. Unlike the other examples, this problem benefits from full rewrites of the constructor function. Small diffs to circle positions are unlikely to find good packings -- the geometry needs to be rethought holistically.
Read evaluator.cascade_evaluation and evaluator.cascade_thresholds from the config. If enabled, apply a mental pre-check:
- Stage 1 (first threshold): Will this approach produce a valid packing at all? Will 26 circles fit without overlaps? If the geometric layout is clearly invalid, skip it.
- Stage 2 (second threshold): After implementation, is the score competitive with the current best? If not, reject and record why.
Read prompt.num_top_programs from the config. Draw inspiration from top and diverse population members:
- Always review the best-scoring solutions
- Review diverse solutions from different islands
- For circle packing specifically, look at: What geometric pattern does each use? What optimization method? What's the radii distribution (many similar vs few large + many small)?
- Explicitly note: "Inspired by population member X which uses [pattern/optimization]."
Circle packing has domain-specific considerations:
- Validity is mandatory: Any overlap or boundary violation means score = 0. Always validate mentally before proposing.
- Constructor vs optimizer: The evolve block should produce a deterministic result. You can use scipy.optimize inside the constructor, but the output must be reproducible.
- Exact count: Must return exactly 26 circles. No more, no less.
- Numerical precision: Use tolerances (1e-6) for overlap and boundary checks. Slightly shrink radii if needed to ensure validity.
- Target awareness: AlphaEvolve achieved 2.635. Any solution above 2.5 is strong. Above 2.6 is excellent.
- Radii distribution matters: The optimal packing likely has a mix of circle sizes, not all equal. Larger circles in the center, smaller ones filling gaps.
Before implementing a proposed change, check against rejected_approaches and existing population:
- If the approach is essentially the same as a rejected attempt, do not repeat it
- If nearly identical to an existing population member, try something more distinct
- Focus on meaningful differences: different geometric layouts, different optimization methods, different radii distributions
Track solutions along two feature dimensions:
- packing_strategy: The broad category (grid, hexagonal, optimized, hybrid, novel)
- code_complexity: Simple (< 40 lines), medium (40-100 lines), complex (> 100 lines)
When the population is full, a new solution only enters if it fills an empty niche or beats the worst member in its feature cell.
Before proposing each change, explicitly reason through:
- Config review: "Read config.yaml. exploitation_ratio=X, num_islands=Y, cascade_thresholds=Z, diff_based_evolution=W."
- Population state: "Current population has N members across M islands. Best sum_radii is X. Strategies represented: [list]. Under-explored niches: [list]."
- Strategy choice: "This iteration I will use [exploitation/exploration/weighted/migration]. Reason: [...]."
- Parent selection: "Starting from population member [id] (score: X, strategy: Y, island: Z)."
- Inspirations: "Drawing ideas from member [id] which uses [geometric pattern]."
- Validity pre-check: "This layout should be valid because [reasoning about no overlaps, boundary containment]."
- Novelty check: "This differs from rejected attempt [N] because [specific difference]."
- Proposed change: "I will [specific change] because [reasoning]."
If the last 3 consecutive iterations were rejected (no improvement):
- Switch strategy: if you've been refining positions, try a completely different geometric layout
- If pure construction is plateauing, add numerical optimization (scipy.optimize)
- Consider combining the best geometric layout with the best optimization method from different population members
If the last 5 consecutive iterations were rejected:
- The optimization may have plateaued
- Try a radically different approach from an unexplored island
- Re-read
prompt.system_messagefrom the config for geometric insights not yet exploited - Consider whether the current radii distribution is suboptimal (e.g., try fewer equal-sized circles with more small gap-fillers)