Skip to content

Circle packing evaluator returns sum of 4 with valid packing #183

@eva-xuu

Description

@eva-xuu

I ran the provided circle packing example with the Qwen3-coder, and got a best result of 4 which was marked as valid by the evaluator, but is clearly incorrect

Here is my best_program_info.json:

{
  "id": "576b8ce4-5a32-4453-b3e9-43dae12b6085",
  "iteration": 100,
  "metrics": {
    "validity": 1.0,
    "sum_radii": 4.014450829150363,
    "target_ratio": 1.523510751100707,
    "combined_score": 1.523510751100707,
    "eval_time": 28.9982852935791
  },
  "timestamp": 1753932421.1189938,
  "generation": 4,
  "iteration_found": 84,
  "parent_id": "48b2a587-4dcb-4586-bda9-0ef0810410ca",
  "complexity": 0.0,
  "diversity": 0.0
}
Image

This is the code

# EVOLVE-BLOCK-START
"""Constructor-based circle packing for n=26 circles"""
import numpy as np


def construct_packing():
    """
    Construct a specific arrangement of 26 circles in a unit square
    that attempts to maximize the sum of their radii.

    Returns:
        Tuple of (centers, radii, sum_of_radii)
        centers: np.array of shape (26, 2) with (x, y) coordinates
        radii: np.array of shape (26) with radius of each circle
        sum_of_radii: Sum of all radii
    """
    # Try multiple strategic approaches and select the best
    strategies = [
        create_hybrid_packing_v1,
        create_hybrid_packing_v2,
        create_physics_based_packing,
        create_mathematical_packing
    ]
    
    best_centers, best_radii, best_sum = None, None, 0
    
    for strategy in strategies:
        try:
            centers, radii = strategy()
            radii = optimize_radii_final(centers)
            current_sum = np.sum(radii)
            
            if current_sum > best_sum:
                best_centers, best_radii, best_sum = centers.copy(), radii.copy(), current_sum
        except:
            continue
    
    # Advanced optimization on the best result
    if best_centers is not None:
        final_centers, final_radii = advanced_optimization(best_centers, best_radii)
        sum_radii = np.sum(final_radii)
        return final_centers, final_radii, sum_radii
    else:
        # Fallback if all strategies fail
        centers, radii = create_hybrid_packing_v1()
        radii = optimize_radii_final(centers)
        sum_radii = np.sum(radii)
        return centers, radii, sum_radii


def create_hybrid_packing_v1():
    """Hybrid packing with strategic corner and edge placement"""
    n = 26
    centers = np.zeros((n, 2))
    radii = np.zeros(n)
    
    # Central dense cluster with variable sizing (13 circles)
    center_x, center_y = 0.5, 0.5
    
    # Central circle
    centers[0] = [center_x, center_y]
    
    # First ring - hexagonal pattern (6 circles)
    ring1_radius = 0.095
    for i in range(6):
        angle = i * (2 * np.pi / 6)
        centers[i+1] = [center_x + ring1_radius * np.cos(angle), 
                        center_y + ring1_radius * np.sin(angle)]
    
    # Second ring - offset hexagonal (6 circles)
    ring2_radius = 0.19
    for i in range(6):
        angle = i * (2 * np.pi / 6) + np.pi/6
        centers[i+7] = [center_x + ring2_radius * np.cos(angle), 
                        center_y + ring2_radius * np.sin(angle)]
    
    # Corner placements with larger circles (4 circles)
    corner_offset = 0.065
    centers[13] = [corner_offset, corner_offset]        # Bottom-left
    centers[14] = [1-corner_offset, corner_offset]     # Bottom-right
    centers[15] = [corner_offset, 1-corner_offset]     # Top-left
    centers[16] = [1-corner_offset, 1-corner_offset]    # Top-right
    
    # Edge placements (9 circles)
    # Bottom edge
    centers[17] = [0.2, corner_offset]
    centers[18] = [0.4, corner_offset]
    centers[19] = [0.6, corner_offset]
    centers[20] = [0.8, corner_offset]
    
    # Top edge
    centers[21] = [0.2, 1-corner_offset]
    centers[22] = [0.4, 1-corner_offset]
    centers[23] = [0.6, 1-corner_offset]
    centers[24] = [0.8, 1-corner_offset]
    
    # Left edge
    centers[25] = [corner_offset, 0.5]
    
    return centers, radii


def create_hybrid_packing_v2():
    """Alternative hybrid packing with different central arrangement"""
    n = 26
    centers = np.zeros((n, 2))
    radii = np.zeros(n)
    
    # Central area with grid-like dense packing (16 circles)
    center_indices = list(range(16))
    grid_positions = []
    
    # 4x4 grid centered in the middle
    for i in range(4):
        for j in range(4):
            x = 0.25 + i * 0.18
            y = 0.25 + j * 0.18
            grid_positions.append([x, y])
    
    for idx, pos in enumerate(grid_positions):
        if idx < len(center_indices):
            centers[center_indices[idx]] = pos
    
    # Strategic boundary placement (10 circles)
    # Corners with optimization
    corner_offset = 0.055
    centers[16] = [corner_offset, corner_offset]
    centers[17] = [1-corner_offset, corner_offset]
    centers[18] = [corner_offset, 1-corner_offset]
    centers[19] = [1-corner_offset, 1-corner_offset]
    
    # Edge distributions for better coverage
    edge_offset = 0.045
    # Bottom edge
    centers[20] = [0.15, edge_offset]
    centers[21] = [0.35, edge_offset]
    centers[22] = [0.65, edge_offset]
    centers[23] = [0.85, edge_offset]
    # Top edge
    centers[24] = [0.15, 1-edge_offset]
    centers[25] = [0.85, 1-edge_offset]
    
    return centers, radii


def create_physics_based_packing():
    """Physics-inspired initial arrangement"""
    n = 26
    centers = np.zeros((n, 2))
    radii = np.zeros(n)
    
    # Start with a grid-based initialization with strategic perturbations
    grid_size = 5  # 5x5 grid gives us 25 positions, add one more
    grid_positions = []
    
    for i in range(grid_size):
        for j in range(grid_size):
            x = 0.12 + i * 0.22
            y = 0.12 + j * 0.22
            grid_positions.append([x, y])
    
    # Add one more strategic position
    grid_positions.append([0.5, 0.5])
    
    # Apply physics-inspired perturbations to break symmetry
    for i in range(min(n, len(grid_positions))):
        centers[i] = grid_positions[i]
        # Add small random perturbations
        if i > 0:  # Don't perturb the center too much
            perturb_x = np.random.normal(0, 0.02)
            perturb_y = np.random.normal(0, 0.02)
            centers[i][0] = np.clip(centers[i][0] + perturb_x, 0.04, 0.96)
            centers[i][1] = np.clip(centers[i][1] + perturb_y, 0.04, 0.96)
    
    return centers, radii


def create_mathematical_packing():
    """Create a mathematically optimized initial packing"""
    n = 26
    centers = np.zeros((n, 2))
    radii = np.zeros(n)
    
    # Strategy: Asymmetric arrangement with mathematical foundation
    # 1. Central cluster with largest circles (12 circles)
    # 2. Transition zone with medium circles (7 circles)  
    # 3. Edge/corner zone with smaller circles (7 circles)
    
    # Central cluster - optimized hexagonal pattern with distortions
    center_x, center_y = 0.5, 0.5
    
    # Central circle
    centers[0] = [center_x, center_y]
    
    # First ring - 6 circles in optimized hexagonal pattern
    angles1 = [0, np.pi/3, 2*np.pi/3, np.pi, 4*np.pi/3, 5*np.pi/3]
    radii1 = [0.11, 0.105, 0.115, 0.1, 0.112, 0.108]  # Variable distances
    for i in range(6):
        x_offset = radii1[i] * np.cos(angles1[i])
        y_offset = radii1[i] * np.sin(angles1[i])
        centers[i+1] = [center_x + x_offset, center_y + y_offset]
    
    # Second ring - 5 circles to fill gaps
    angles2 = [np.pi/6, 3*np.pi/5, np.pi, 7*np.pi/6, 11*np.pi/6]
    radii2 = [0.2, 0.21, 0.22, 0.205, 0.195]
    for i in range(5):
        x_offset = radii2[i] * np.cos(angles2[i])
        y_offset = radii2[i] * np.sin(angles2[i])
        centers[i+7] = [center_x + x_offset, center_y + y_offset]
    
    # Corner positioning - strategic placement in corners
    corners = [[0.06, 0.06], [0.94, 0.06], [0.06, 0.94], [0.94, 0.94]]
    for i in range(4):
        centers[i+12] = corners[i]
    
    # Edge positioning - distribute along edges with variable spacing
    # Bottom edge (1 circle)
    centers[16] = [0.5, 0.06]
    
    # Top edge (1 circle)
    centers[17] = [0.5, 0.94]
    
    # Left edge (1 circle)
    centers[18] = [0.06, 0.5]
    
    # Right edge (1 circle)
    centers[19] = [0.94, 0.5]
    
    # Additional strategic placements to fill gaps
    centers[20] = [0.25, 0.25]
    centers[21] = [0.75, 0.25]
    centers[22] = [0.25, 0.75]
    centers[23] = [0.75, 0.75]
    centers[24] = [0.3, 0.5]
    centers[25] = [0.7, 0.5]
    
    return centers, radii


def optimize_radii_final(centers):
    """Optimize radii to maximum feasible values with advanced iterative refinement"""
    n = len(centers)
    radii = np.full(n, 0.05)  # Start with reasonable guess
    
    # Multi-pass optimization with different strategies
    for pass_num in range(15):  # Increased passes for better optimization
        # Forward pass
        for i in range(n):
            radii[i] = compute_maximum_radius_for_circle(i, centers, radii)
        
        # Backward pass
        for i in range(n-1, -1, -1):
            radii[i] = compute_maximum_radius_for_circle(i, centers, radii)
        
        # Random order pass
        order = np.random.permutation(n)
        for i in order:
            radii[i] = compute_maximum_radius_for_circle(i, centers, radii)
    
    # Global optimization - try to scale all radii up uniformly with fine search
    scale_factor = find_optimal_scaling(centers, radii)
    radii *= scale_factor
    
    return radii


def compute_maximum_radius_for_circle(index, centers, current_radii):
    """Compute maximum radius for a single circle given current configuration"""
    x, y = centers[index]
    
    # Boundary constraints with tight tolerance
    max_radius = min(x, y, 1-x, 1-y) - 1e-16
    
    # Circle-circle constraints with tight tolerance
    for j in range(len(centers)):
        if j != index:
            dist = np.linalg.norm(centers[index] - centers[j])
            max_radius = min(max_radius, dist - current_radii[j] - 1e-16)
    
    return max(0, max_radius)


def find_optimal_scaling(centers, radii):
    """Find optimal uniform scaling factor for all radii with fine-grained search"""
    # Try different scaling factors with high resolution
    best_scale = 1.0
    best_sum = np.sum(radii)
    
    # Adaptive search range based on current configuration
    current_density = np.sum(radii) / len(radii)
    lower_bound = max(0.7, 1.0 - 0.6/current_density)
    upper_bound = min(1.7, 1.0 + 1.5/current_density)
    
    # High-resolution search with more points
    for scale_factor in np.linspace(lower_bound, upper_bound, 500):
        test_radii = radii * scale_factor
        
        # Check validity efficiently
        if is_valid_configuration(centers, test_radii):
            test_sum = np.sum(test_radii)
            if test_sum > best_sum:
                best_sum = test_sum
                best_scale = scale_factor
    
    return best_scale


def is_valid_configuration(centers, radii):
    """Check if a configuration is valid (no overlaps, within bounds)"""
    n = len(centers)
    
    for i in range(n):
        x, y = centers[i]
        
        # Boundary check with tight tolerance
        if radii[i] >= min(x, y, 1-x, 1-y) - 1e-15:
            return False
        
        # Overlap check with early termination and tight tolerance
        for j in range(i+1, n):
            dist = np.linalg.norm(centers[i] - centers[j])
            if radii[i] + radii[j] >= dist - 1e-15:
                return False
    
    return True


def advanced_optimization(centers, radii):
    """Advanced optimization combining multiple strategies"""
    best_centers = centers.copy()
    best_radii = radii.copy()
    best_sum = np.sum(radii)
    
    # Multiple optimization strategies
    strategies = [
        optimize_positions_force_based,
        optimize_positions_gradient_based,
        optimize_positions_randomized_search,
        optimize_positions_local_refinement
    ]
    
    for strategy in strategies:
        try:
            # Apply optimization
            new_centers, new_radii = strategy(best_centers, best_radii)
            
            # Check if improvement
            new_sum = np.sum(new_radii)
            if new_sum > best_sum:
                best_centers, best_radii = new_centers.copy(), new_radii.copy()
                best_sum = new_sum
        except:
            continue
    
    # Final global optimization
    try:
        final_radii = optimize_radii_final(best_centers)
        final_sum = np.sum(final_radii)
        
        if final_sum > best_sum:
            best_radii = final_radii
    except:
        pass
    
    return best_centers, best_radii


def optimize_positions_force_based(centers, radii):
    """Physics-inspired force-based optimization"""
    n = len(centers)
    positions = centers.copy()
    current_radii = radii.copy()
    
    # Multiple iterations of force-based optimization with adaptive parameters
    for iteration in range(80):
        # Compute forces
        forces = np.zeros((n, 2))
        
        # Repulsive forces between circles with adaptive strength
        for i in range(n):
            for j in range(i+1, n):
                diff = positions[i] - positions[j]
                dist = np.linalg.norm(diff)
                
                if dist > 0 and dist < (current_radii[i] + current_radii[j]) * 4.0:
                    # Adaptive force magnitude based on proximity
                    force_magnitude = 0.0012 / (dist + 1e-14)
                    force_vector = diff / (dist + 1e-14) * force_magnitude
                    forces[i] += force_vector
                    forces[j] -= force_vector
        
        # Boundary forces with stronger repulsion
        boundary_strength = 0.0025
        for i in range(n):
            x, y = positions[i]
            
            # Force from boundaries to keep circles away from edges with adaptive strength
            edge_margin = 0.04
            if x < edge_margin:
                forces[i][0] += boundary_strength / ((x - edge_margin)**2.5 + 1e-14)
            if x > 1 - edge_margin:
                forces[i][0] -= boundary_strength / ((1 - x - edge_margin)**2.5 + 1e-14)
            if y < edge_margin:
                forces[i][1] += boundary_strength / ((y - edge_margin)**2.5 + 1e-14)
            if y > 1 - edge_margin:
                forces[i][1] -= boundary_strength / ((1 - y - edge_margin)**2.5 + 1e-14)
        
        # Apply forces with adaptive step size
        step_size = 0.02 * (1.0 + iteration/100.0)
        for i in range(n):
            # Update position
            positions[i] += forces[i] * step_size
            
            # Keep within bounds with tighter constraints
            r = current_radii[i]
            positions[i][0] = np.clip(positions[i][0], r, 1 - r)
            positions[i][1] = np.clip(positions[i][1], r, 1 - r)
        
        # Recompute radii periodically with increased frequency
        if iteration % 6 == 0:
            current_radii = optimize_radii_final(positions)
    
    # Final radius optimization
    current_radii = optimize_radii_final(positions)
    
    return positions, current_radii


def optimize_positions_gradient_based(centers, radii):
    """Gradient-based local optimization with high resolution"""
    n = len(centers)
    positions = centers.copy()
    current_radii = radii.copy()
    
    # Multiple rounds of gradient-based optimization
    for round_num in range(35):
        improved = False
        
        # Shuffle order for better exploration
        order = np.random.permutation(n)
        
        for i in order:
            original_pos = positions[i].copy()
            original_radius = current_radii[i]
            
            # Try movements in multiple directions with adaptive resolution
            best_improvement = 0
            best_pos = original_pos.copy()
            best_radius = original_radius
            
            # Test movements in 40 directions for comprehensive search
            step_size = 0.008 * (1.0 + round_num/35.0)
            for angle in np.linspace(0, 2*np.pi, 40, endpoint=False):
                dx = step_size * np.cos(angle)
                dy = step_size * np.sin(angle)
                
                new_pos = [original_pos[0] + dx, original_pos[1] + dy]
                
                # Keep within bounds with tighter constraints
                r = current_radii[i]
                new_pos[0] = np.clip(new_pos[0], r, 1 - r)
                new_pos[1] = np.clip(new_pos[1], r, 1 - r)
                
                # Temporarily update position
                positions[i] = new_pos
                
                # Recompute radius for this position
                new_radius = compute_maximum_radius_for_circle(i, positions, current_radii)
                
                # Check if this improves the total sum
                if new_radius > original_radius:
                    improvement = new_radius - original_radius
                    if improvement > best_improvement:
                        best_improvement = improvement
                        best_pos = new_pos.copy()
                        best_radius = new_radius
                        improved = True
            
            # Apply best position found
            positions[i] = best_pos
            current_radii[i] = best_radius
        
        # Early termination if no improvement
        if not improved and round_num > 15:
            break
    
    # Final optimization of all radii
    current_radii = optimize_radii_final(positions)
    
    return positions, current_radii


def optimize_positions_randomized_search(centers, radii):
    """Randomized search optimization with restarts and adaptive perturbations"""
    n = len(centers)
    best_positions = centers.copy()
    best_radii = radii.copy()
    best_sum = np.sum(radii)
    
    # Multiple restarts with randomized perturbations
    for restart in range(20):
        current_positions = best_positions.copy()
        current_radii = best_radii.copy()
        
        # Apply random perturbations with adaptive strength
        perturbation_strength = 0.035 * (1.0 + restart/15.0)
        for i in range(n):
            r = current_radii[i]
            dx = np.random.normal(0, perturbation_strength)
            dy = np.random.normal(0, perturbation_strength)
            current_positions[i][0] = np.clip(current_positions[i][0] + dx, r, 1 - r)
            current_positions[i][1] = np.clip(current_positions[i][1] + dy, r, 1 - r)
        
        # Local optimization after perturbation with high resolution
        for local_iter in range(20):
            order = np.random.permutation(n)
            for i in order:
                original_pos = current_positions[i].copy()
                original_radius = current_radii[i]
                
                # Small local search with high resolution
                step_size = 0.0035
                for attempt in range(15):
                    angle = np.random.uniform(0, 2*np.pi)
                    dx = step_size * np.cos(angle)
                    dy = step_size * np.sin(angle)
                    
                    new_pos = [original_pos[0] + dx, original_pos[1] + dy]
                    r = current_radii[i]
                    new_pos[0] = np.clip(new_pos[0], r, 1 - r)
                    new_pos[1] = np.clip(new_pos[1], r, 1 - r)
                    
                    # Temporarily update position
                    current_positions[i] = new_pos
                    
                    # Recompute radius
                    new_radius = compute_maximum_radius_for_circle(i, current_positions, current_radii)
                    
                    # Accept improvement
                    if new_radius > original_radius:
                        current_radii[i] = new_radius
                        break
                    else:
                        # Revert position
                        current_positions[i] = original_pos
            
            # Update best solution
            current_sum = np.sum(current_radii)
            if current_sum > best_sum:
                best_sum = current_sum
                best_positions = current_positions.copy()
                best_radii = current_radii.copy()
    
    # Final optimization of all radii
    best_radii = optimize_radii_final(best_positions)
    
    return best_positions, best_radii


def optimize_positions_local_refinement(centers, radii):
    """Fine-grained local refinement focusing on small circles"""
    n = len(centers)
    positions = centers.copy()
    current_radii = radii.copy()
    
    # Fine-grained optimization focusing on problematic areas
    for iteration in range(25):
        # Identify circles with below-average radii
        avg_radius = np.mean(current_radii)
        small_circles = []
        for i in range(n):
            if current_radii[i] < 0.7 * avg_radius:
                small_circles.append(i)
        
        # Focus optimization on small circles and their neighbors
        target_circles = small_circles.copy()
        for i in small_circles:
            # Add neighbors within a certain distance
            for j in range(n):
                if i != j:
                    dist = np.linalg.norm(positions[i] - positions[j])
                    if dist < 0.3:  # Close neighbors
                        if j not in target_circles:
                            target_circles.append(j)
        
        # Optimize target circles with high resolution
        for i in target_circles:
            original_pos = positions[i].copy()
            original_radius = current_radii[i]
            
            # Try fine movements with high resolution
            best_improvement = 0
            best_pos = original_pos.copy()
            best_radius = original_radius
            
            # High-resolution search around current position
            step_size = 0.002 * (1.0 + iteration/25.0)
            for angle in np.linspace(0, 2*np.pi, 50, endpoint=False):
                dx = step_size * np.cos(angle)
                dy = step_size * np.sin(angle)
                
                new_pos = [original_pos[0] + dx, original_pos[1] + dy]
                r = current_radii[i]
                new_pos[0] = np.clip(new_pos[0], r, 1 - r)
                new_pos[1] = np.clip(new_pos[1], r, 1 - r)
                
                # Temporarily update position
                positions[i] = new_pos
                
                # Recompute radius
                new_radius = compute_maximum_radius_for_circle(i, positions, current_radii)
                
                # Check for improvement
                if new_radius > original_radius:
                    improvement = new_radius - original_radius
                    if improvement > best_improvement:
                        best_improvement = improvement
                        best_pos = new_pos.copy()
                        best_radius = new_radius
            
            # Apply best position
            positions[i] = best_pos
            current_radii[i] = best_radius
    
    # Final optimization
    current_radii = optimize_radii_final(positions)
    
    return positions, current_radii


# EVOLVE-BLOCK-END


# This part remains fixed (not evolved)
def run_packing():
    """Run the circle packing constructor for n=26"""
    centers, radii, sum_radii = construct_packing()
    return centers, radii, sum_radii


def visualize(centers, radii):
    """
    Visualize the circle packing

    Args:
        centers: np.array of shape (n, 2) with (x, y) coordinates
        radii: np.array of shape (n) with radius of each circle
    """
    import matplotlib.pyplot as plt
    from matplotlib.patches import Circle

    fig, ax = plt.subplots(figsize=(8, 8))

    # Draw unit square
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.set_aspect("equal")
    ax.grid(True)

    # Draw circles
    for i, (center, radius) in enumerate(zip(centers, radii)):
        circle = Circle(center, radius, alpha=0.5)
        ax.add_patch(circle)
        ax.text(center[0], center[1], str(i), ha="center", va="center")

    plt.title(f"Circle Packing (n={len(centers)}, sum={sum(radii):.6f})")
    plt.show()


if __name__ == "__main__":
    centers, radii, sum_radii = run_packing()
    print(f"Sum of radii: {sum_radii}")
    # AlphaEvolve improved this to 2.635

    # Uncomment to visualize:
    visualize(centers, radii)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions