File size: 7,380 Bytes
1ca9dbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env python3
"""
Plot circle packing results from metrics.json file
"""
import json
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
import re
from pathlib import Path

def parse_centers_from_string(centers_str):
    """Parse center coordinates from the centers_str field"""
    centers = []
    # Pattern to match: centers[i] = (x, y)
    pattern = r'centers\[\d+\] = \(([0-9.]+), ([0-9.]+)\)'
    matches = re.findall(pattern, centers_str)
    
    for x, y in matches:
        centers.append((float(x), float(y)))
    
    return np.array(centers)

def calculate_radii_from_centers(centers, target_sum):
    """
    Estimate radii assuming roughly equal radii for all circles
    This is just for visualization - actual radii would come from the solution
    """
    n = len(centers)
    # Start with uniform distribution
    uniform_radius = target_sum / n
    
    # Calculate actual minimum distances and adjust
    radii = []
    for i in range(n):
        # Calculate minimum distance to boundaries
        x, y = centers[i]
        min_boundary_dist = min(x, y, 1.0 - x, 1.0 - y)
        
        # Calculate minimum distance to other circles (divided by 2 for radius)
        min_circle_dist = float('inf')
        for j in range(n):
            if i != j:
                dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
                min_circle_dist = min(min_circle_dist, dist)
        
        # Estimate radius as smaller of boundary constraint and half of nearest circle
        estimated_r = min(min_boundary_dist, min_circle_dist / 2.0, uniform_radius * 1.5)
        radii.append(estimated_r)
    
    # Scale to match target sum
    radii = np.array(radii)
    radii = radii * (target_sum / np.sum(radii))
    
    return radii

def plot_circle_packing(metrics_file):
    """Plot circle packing from metrics.json file"""
    # Load metrics
    with open(metrics_file, 'r') as f:
        data = json.load(f)
    
    # Extract information
    combined_score = data['combined_score']
    is_correct = data['correct']
    generation = data.get('generation', 'N/A')
    num_circles = data['primary']['public']['num_circles']
    centers_str = data['primary']['public']['centers_str']
    
    # Try to load actual radii from extra.npz
    extra_file = Path(metrics_file).parent / 'extra.npz'
    if extra_file.exists():
        extra_data = np.load(extra_file)
        centers = extra_data['centers']
        radii = extra_data['radii']
        print(f"Loaded {len(centers)} circles with actual radii from extra.npz")
        print(f"Sum of radii: {np.sum(radii):.6f} (target: {combined_score:.6f})")
    else:
        # Fallback: Parse centers from string and estimate radii
        centers = parse_centers_from_string(centers_str)
        print(f"Parsed {len(centers)} circle centers")
        radii = calculate_radii_from_centers(centers, combined_score)
        print(f"Estimated sum of radii: {np.sum(radii):.6f} (target: {combined_score:.6f})")
        print("WARNING: Using estimated radii. Actual radii not found in extra.npz")
    
    # Create figure with multiple subplots
    fig = plt.figure(figsize=(16, 6))
    
    # Main packing plot
    ax1 = plt.subplot(1, 3, 1)
    ax1.set_xlim(-0.05, 1.05)
    ax1.set_ylim(-0.05, 1.05)
    ax1.set_aspect('equal')
    ax1.set_title(f'Circle Packing (Generation {generation})', fontsize=14, fontweight='bold')
    ax1.set_xlabel('X')
    ax1.set_ylabel('Y')
    
    # Draw unit square
    square = patches.Rectangle((0, 0), 1, 1, linewidth=2, edgecolor='black', facecolor='none')
    ax1.add_patch(square)
    
    # Draw circles with color based on radius
    colors = plt.cm.viridis(radii / np.max(radii))
    for i, (center, radius) in enumerate(zip(centers, radii)):
        circle = patches.Circle(center, radius, linewidth=1, 
                              edgecolor='black', facecolor=colors[i], alpha=0.6)
        ax1.add_patch(circle)
        # Add circle number
        ax1.text(center[0], center[1], str(i), ha='center', va='center', 
                fontsize=6, fontweight='bold')
    
    # Add grid
    ax1.grid(True, alpha=0.3, linestyle='--')
    
    # Radii distribution plot
    ax2 = plt.subplot(1, 3, 2)
    ax2.hist(radii, bins=15, color='steelblue', edgecolor='black', alpha=0.7)
    ax2.set_xlabel('Radius')
    ax2.set_ylabel('Frequency')
    ax2.set_title('Radius Distribution', fontsize=12, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    
    # Statistics text
    ax2.axvline(np.mean(radii), color='red', linestyle='--', linewidth=2, label=f'Mean: {np.mean(radii):.4f}')
    ax2.axvline(np.median(radii), color='green', linestyle='--', linewidth=2, label=f'Median: {np.median(radii):.4f}')
    ax2.legend()
    
    # Metrics summary
    ax3 = plt.subplot(1, 3, 3)
    ax3.axis('off')
    
    # Calculate some additional metrics for display
    total_area = np.sum(np.pi * radii ** 2)
    packing_efficiency = total_area / 1.0  # Unit square area = 1
    
    # Check for overlaps (approximate)
    overlaps = 0
    min_gap = float('inf')
    for i in range(len(centers)):
        for j in range(i+1, len(centers)):
            dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2))
            gap = dist - (radii[i] + radii[j])
            if gap < 0:
                overlaps += 1
            elif gap < min_gap:
                min_gap = gap
    
    # Check boundary violations
    boundary_violations = 0
    for i in range(len(centers)):
        x, y = centers[i]
        r = radii[i]
        if x - r < 0 or x + r > 1 or y - r < 0 or y + r > 1:
            boundary_violations += 1
    
    metrics_text = f"""
CIRCLE PACKING METRICS
{'='*40}

Primary Metrics:
  • Sum of Radii: {combined_score:.6f}
  • Number of Circles: {num_circles}
  • Valid Solution: {'✓ Yes' if is_correct else '✗ No'}
  • Generation: {generation}

Radius Statistics:
  • Mean Radius: {np.mean(radii):.6f}
  • Std Dev: {np.std(radii):.6f}
  • Min Radius: {np.min(radii):.6f}
  • Max Radius: {np.max(radii):.6f}
  • Median Radius: {np.median(radii):.6f}

Packing Efficiency:
  • Total Circle Area: {total_area:.6f}
  • Area Ratio: {packing_efficiency:.2%}
  • Avg Area per Circle: {total_area/num_circles:.6f}

Validation (Approximate):
  • Overlaps Detected: {overlaps}
  • Boundary Violations: {boundary_violations}
  • Min Gap (non-touching): {min_gap:.6f}

Spatial Distribution:
  • Center of Mass: ({np.mean(centers[:, 0]):.4f}, {np.mean(centers[:, 1]):.4f})
  • X-spread (std): {np.std(centers[:, 0]):.4f}
  • Y-spread (std): {np.std(centers[:, 1]):.4f}
    """
    
    ax3.text(0.1, 0.95, metrics_text, transform=ax3.transAxes, 
            fontsize=10, verticalalignment='top', fontfamily='monospace',
            bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))
    
    plt.tight_layout()
    
    # Save figure
    output_dir = Path(metrics_file).parent
    output_file = output_dir / 'circle_packing_visualization.png'
    plt.savefig(output_file, dpi=150, bbox_inches='tight')
    print(f"\nVisualization saved to: {output_file}")
    
    plt.show()

if __name__ == '__main__':
    metrics_file = '/home/tengxiao/pj/ShinkaEvolve/examples/circle_packing/results/results_full_gen200_period10_20260206_062935/best/results/metrics.json'
    plot_circle_packing(metrics_file)