Skip to content

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

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions