ZLayout EDA Library v1.0.0
Advanced Electronic Design Automation Layout Library with Bilingual Documentation
Loading...
Searching...
No Matches
analysis.py
Go to the documentation of this file.
1"""
2Advanced geometric analysis algorithms for layout optimization.
3"""
4
5import math
6from typing import List, Tuple, Set, Dict, Optional, Union
7from .geometry import Point, Rectangle, Polygon
8from .spatial import QuadTree, SpatialIndex
9
10
12 """Result of edge intersection analysis."""
13
14 def __init__(self):
15 self.intersecting_pairs: List[Tuple[int, int]] = [] # (polygon_id1, polygon_id2)
16 self.intersection_points: List[Point] = []
18
19
21 """Result of narrow distance analysis."""
22
23 def __init__(self):
24 self.narrow_regions: List[Tuple[Point, Point, float]] = [] # (point1, point2, distance)
25 self.min_distance = float('inf')
26 self.max_distance = 0.0
28
29
31 """Result of sharp angle analysis."""
32
33 def __init__(self):
34 self.sharp_angles: List[Tuple[int, int, float]] = [] # (polygon_id, vertex_index, angle_degrees)
35 self.sharpest_angle = 180.0
36 self.average_angle = 90.0
37
38
39def line_intersection(p1: Point, p2: Point, p3: Point, p4: Point) -> Optional[Point]:
40 """
41 Find intersection point of two line segments.
42 Returns None if lines don't intersect.
43 """
44 # Line 1: p1 to p2
45 # Line 2: p3 to p4
46
47 denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x)
48
49 if abs(denom) < 1e-10: # Lines are parallel
50 return None
51
52 t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom
53 u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / denom
54
55 # Check if intersection is within both line segments
56 if 0 <= t <= 1 and 0 <= u <= 1:
57 x = p1.x + t * (p2.x - p1.x)
58 y = p1.y + t * (p2.y - p1.y)
59 return Point(x, y)
60
61 return None
62
63
64def segments_intersect(p1: Point, p2: Point, p3: Point, p4: Point) -> bool:
65 """
66 Fast check if two line segments intersect using orientation method.
67 This is more efficient than computing actual intersection point.
68 """
69 def orientation(p: Point, q: Point, r: Point) -> int:
70 """Find orientation of ordered triplet (p, q, r).
71 Returns:
72 0 -> p, q and r are colinear
73 1 -> Clockwise
74 2 -> Counterclockwise
75 """
76 val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
77 if abs(val) < 1e-10:
78 return 0
79 return 1 if val > 0 else 2
80
81 def on_segment(p: Point, q: Point, r: Point) -> bool:
82 """Check if point q lies on segment pr."""
83 return (q.x <= max(p.x, r.x) and q.x >= min(p.x, r.x) and
84 q.y <= max(p.y, r.y) and q.y >= min(p.y, r.y))
85
86 o1 = orientation(p1, p2, p3)
87 o2 = orientation(p1, p2, p4)
88 o3 = orientation(p3, p4, p1)
89 o4 = orientation(p3, p4, p2)
90
91 # General case
92 if o1 != o2 and o3 != o4:
93 return True
94
95 # Special cases for collinear points
96 if (o1 == 0 and on_segment(p1, p3, p2) or
97 o2 == 0 and on_segment(p1, p4, p2) or
98 o3 == 0 and on_segment(p3, p1, p4) or
99 o4 == 0 and on_segment(p3, p2, p4)):
100 return True
101
102 return False
103
104
106 """Analyzer for polygon geometric properties and relationships."""
107
108 def __init__(self, spatial_index: Optional[SpatialIndex] = None):
109 self.spatial_index = spatial_index
110 self.polygons: List[Polygon] = []
111 self.polygon_ids: List[int] = []
112
113 def add_polygon(self, polygon: Polygon) -> int:
114 """Add a polygon for analysis."""
115 if self.spatial_index:
116 poly_id = self.spatial_index.add_polygon(polygon)
117 else:
118 poly_id = len(self.polygons)
119 self.polygons.append(polygon)
120 self.polygon_ids.append(poly_id)
121 return poly_id
122
123 def find_sharp_angles(self, threshold_degrees: float = 30.0) -> SharpAngleResult:
124 """Find all sharp angles in all polygons."""
125 result = SharpAngleResult()
126
127 polygons_to_analyze = []
128 if self.spatial_index:
129 for obj_id, obj in self.spatial_index.objects.items():
130 if isinstance(obj, Polygon):
131 polygons_to_analyze.append((obj_id, obj))
132 else:
133 for i, polygon in enumerate(self.polygons):
134 polygons_to_analyze.append((i, polygon))
135
136 total_angles = 0
137 angle_sum = 0.0
138
139 for poly_id, polygon in polygons_to_analyze:
140 sharp_vertices = polygon.get_sharp_angles(threshold_degrees)
141
142 # Calculate actual angles for sharp vertices
143 for vertex_idx in sharp_vertices:
144 n = len(polygon.vertices)
145 prev_vertex = polygon.vertices[(vertex_idx - 1) % n]
146 curr_vertex = polygon.vertices[vertex_idx]
147 next_vertex = polygon.vertices[(vertex_idx + 1) % n]
148
149 # Calculate angle
150 v1 = Point(prev_vertex.x - curr_vertex.x, prev_vertex.y - curr_vertex.y)
151 v2 = Point(next_vertex.x - curr_vertex.x, next_vertex.y - curr_vertex.y)
152
153 dot_product = v1.x * v2.x + v1.y * v2.y
154 mag1 = math.sqrt(v1.x**2 + v1.y**2)
155 mag2 = math.sqrt(v2.x**2 + v2.y**2)
156
157 if mag1 > 1e-10 and mag2 > 1e-10:
158 cos_angle = dot_product / (mag1 * mag2)
159 cos_angle = max(-1.0, min(1.0, cos_angle))
160 angle_degrees = math.degrees(math.acos(cos_angle))
161
162 result.sharp_angles.append((poly_id, vertex_idx, angle_degrees))
163 result.sharpest_angle = min(result.sharpest_angle, angle_degrees)
164 angle_sum += angle_degrees
165 total_angles += 1
166
167 if total_angles > 0:
168 result.average_angle = angle_sum / total_angles
169
170 return result
171
172 def find_narrow_distances(self, threshold_distance: float = 1.0) -> NarrowDistanceResult:
173 """Find regions where polygon edges are too close together."""
174 result = NarrowDistanceResult()
175
176 polygons_to_analyze = []
177 if self.spatial_index:
178 for obj_id, obj in self.spatial_index.objects.items():
179 if isinstance(obj, Polygon):
180 polygons_to_analyze.append((obj_id, obj))
181 else:
182 for i, polygon in enumerate(self.polygons):
183 polygons_to_analyze.append((i, polygon))
184
185 distances = []
186
187 # Check distances between edges of different polygons
188 for i, (id1, poly1) in enumerate(polygons_to_analyze):
189 for j, (id2, poly2) in enumerate(polygons_to_analyze[i+1:], i+1):
190 # Check all edge pairs between the two polygons
191 for edge1 in poly1.edges:
192 for edge2 in poly2.edges:
193 dist = self._edge_to_edge_distance(edge1[0], edge1[1], edge2[0], edge2[1])
194 distances.append(dist)
195
196 if dist < threshold_distance:
197 # Find closest points on the edges
198 closest_points = self._closest_points_on_edges(
199 edge1[0], edge1[1], edge2[0], edge2[1]
200 )
201 result.narrow_regions.append((closest_points[0], closest_points[1], dist))
202
203 # Also check within same polygon (self-intersection prevention)
204 for poly_id, polygon in polygons_to_analyze:
205 edges = polygon.edges
206 for i, edge1 in enumerate(edges):
207 for j, edge2 in enumerate(edges[i+2:], i+2): # Skip adjacent edges
208 if j == len(edges) - 1 and i == 0: # Skip last-first edge pair
209 continue
210
211 dist = self._edge_to_edge_distance(edge1[0], edge1[1], edge2[0], edge2[1])
212 distances.append(dist)
213
214 if dist < threshold_distance:
215 closest_points = self._closest_points_on_edges(
216 edge1[0], edge1[1], edge2[0], edge2[1]
217 )
218 result.narrow_regions.append((closest_points[0], closest_points[1], dist))
219
220 if distances:
221 result.min_distance = min(distances)
222 result.max_distance = max(distances)
223 result.average_distance = sum(distances) / len(distances)
224
225 return result
226
227 def find_edge_intersections(self) -> EdgeIntersectionResult:
228 """Find all edge intersections using spatial indexing for efficiency."""
229 result = EdgeIntersectionResult()
230
231 if self.spatial_index:
232 # Use spatial indexing for efficiency
233 intersecting_pairs = self.spatial_index.find_intersecting_edges()
234
235 for id1, id2 in intersecting_pairs:
236 poly1 = self.spatial_index.objects[id1]
237 poly2 = self.spatial_index.objects[id2]
238
239 if isinstance(poly1, Polygon) and isinstance(poly2, Polygon):
240 intersections = self._find_polygon_edge_intersections(poly1, poly2)
241 if intersections:
242 result.intersecting_pairs.append((id1, id2))
243 result.intersection_points.extend(intersections)
244 result.total_intersections += len(intersections)
245 else:
246 # Brute force approach for small datasets
247 for i, poly1 in enumerate(self.polygons):
248 for j, poly2 in enumerate(self.polygons[i+1:], i+1):
249 intersections = self._find_polygon_edge_intersections(poly1, poly2)
250 if intersections:
251 result.intersecting_pairs.append((i, j))
252 result.intersection_points.extend(intersections)
253 result.total_intersections += len(intersections)
254
255 return result
256
257 def _find_polygon_edge_intersections(self, poly1: Polygon, poly2: Polygon) -> List[Point]:
258 """Find all intersection points between edges of two polygons."""
259 intersections = []
260
261 for edge1 in poly1.edges:
262 for edge2 in poly2.edges:
263 intersection = line_intersection(edge1[0], edge1[1], edge2[0], edge2[1])
264 if intersection:
265 intersections.append(intersection)
266
267 return intersections
268
269 def _edge_to_edge_distance(self, p1: Point, p2: Point, p3: Point, p4: Point) -> float:
270 """Calculate minimum distance between two line segments."""
271 # Try all point-to-line distances
272 distances = [
273 p1.distance_to_line(p3, p4),
274 p2.distance_to_line(p3, p4),
275 p3.distance_to_line(p1, p2),
276 p4.distance_to_line(p1, p2)
277 ]
278
279 # Also check endpoint-to-endpoint distances
280 distances.extend([
281 p1.distance_to(p3), p1.distance_to(p4),
282 p2.distance_to(p3), p2.distance_to(p4)
283 ])
284
285 return min(distances)
286
287 def _closest_points_on_edges(self, p1: Point, p2: Point, p3: Point, p4: Point) -> Tuple[Point, Point]:
288 """Find the closest points on two line segments."""
289 # For simplicity, return the endpoints that give minimum distance
290 min_dist = float('inf')
291 closest_pair = (p1, p3)
292
293 candidates = [
294 (p1, p3), (p1, p4), (p2, p3), (p2, p4)
295 ]
296
297 for pt1, pt2 in candidates:
298 dist = pt1.distance_to(pt2)
299 if dist < min_dist:
300 min_dist = dist
301 closest_pair = (pt1, pt2)
302
303 return closest_pair
304
305
307 """High-level geometry processing and optimization."""
308
309 def __init__(self, world_bounds: Rectangle):
310 self.spatial_index = SpatialIndex(world_bounds)
312
313 def add_component(self, geometry: Union[Rectangle, Polygon]) -> int:
314 """Add a geometric component to the processor."""
315 if isinstance(geometry, Rectangle):
316 return self.spatial_index.add_rectangle(geometry)
317 elif isinstance(geometry, Polygon):
318 return self.analyzer.add_polygon(geometry)
319 else:
320 raise ValueError("Unsupported geometry type")
321
322 def analyze_layout(self, sharp_angle_threshold: float = 30.0,
323 narrow_distance_threshold: float = 1.0) -> Dict:
324 """Perform comprehensive layout analysis."""
325 results = {}
326
327 # Analyze sharp angles
328 sharp_angles = self.analyzer.find_sharp_angles(sharp_angle_threshold)
329 results['sharp_angles'] = {
330 'count': len(sharp_angles.sharp_angles),
331 'sharpest': sharp_angles.sharpest_angle,
332 'average': sharp_angles.average_angle,
333 'details': sharp_angles.sharp_angles
334 }
335
336 # Analyze narrow distances
337 narrow_distances = self.analyzer.find_narrow_distances(narrow_distance_threshold)
338 results['narrow_distances'] = {
339 'count': len(narrow_distances.narrow_regions),
340 'minimum': narrow_distances.min_distance,
341 'maximum': narrow_distances.max_distance,
342 'average': narrow_distances.average_distance,
343 'details': narrow_distances.narrow_regions
344 }
345
346 # Analyze edge intersections
347 intersections = self.analyzer.find_edge_intersections()
348 results['intersections'] = {
349 'polygon_pairs': len(intersections.intersecting_pairs),
350 'total_points': intersections.total_intersections,
351 'pairs': intersections.intersecting_pairs,
352 'points': intersections.intersection_points
353 }
354
355 return results
356
357 def optimize_layout(self) -> Dict:
358 """Suggest layout optimizations based on analysis."""
359 analysis = self.analyze_layout()
360 suggestions = []
361
362 if analysis['sharp_angles']['count'] > 0:
363 suggestions.append(f"Found {analysis['sharp_angles']['count']} sharp angles. "
364 f"Consider rounding corners or adjusting geometry.")
365
366 if analysis['narrow_distances']['count'] > 0:
367 suggestions.append(f"Found {analysis['narrow_distances']['count']} narrow regions. "
368 f"Minimum distance: {analysis['narrow_distances']['minimum']:.3f}")
369
370 if analysis['intersections']['polygon_pairs'] > 0:
371 suggestions.append(f"Found {analysis['intersections']['polygon_pairs']} intersecting polygon pairs. "
372 f"Total intersection points: {analysis['intersections']['total_points']}")
373
374 return {
375 'analysis': analysis,
376 'suggestions': suggestions,
377 'optimization_score': self._calculate_optimization_score(analysis)
378 }
379
380 def _calculate_optimization_score(self, analysis: Dict) -> float:
381 """Calculate a score representing layout quality (0-100, higher is better)."""
382 score = 100.0
383
384 # Penalty for sharp angles
385 sharp_penalty = min(analysis['sharp_angles']['count'] * 5, 30)
386 score -= sharp_penalty
387
388 # Penalty for narrow distances
389 narrow_penalty = min(analysis['narrow_distances']['count'] * 10, 40)
390 score -= narrow_penalty
391
392 # Heavy penalty for intersections
393 intersection_penalty = min(analysis['intersections']['polygon_pairs'] * 20, 50)
394 score -= intersection_penalty
395
396 return max(0.0, score)
float _calculate_optimization_score(self, Dict analysis)
Definition analysis.py:380
int add_component(self, Union[Rectangle, Polygon] geometry)
Definition analysis.py:313
__init__(self, Rectangle world_bounds)
Definition analysis.py:309
Dict analyze_layout(self, float sharp_angle_threshold=30.0, float narrow_distance_threshold=1.0)
Definition analysis.py:323
SharpAngleResult find_sharp_angles(self, float threshold_degrees=30.0)
Definition analysis.py:123
float _edge_to_edge_distance(self, Point p1, Point p2, Point p3, Point p4)
Definition analysis.py:269
__init__(self, Optional[SpatialIndex] spatial_index=None)
Definition analysis.py:108
List[Point] _find_polygon_edge_intersections(self, Polygon poly1, Polygon poly2)
Definition analysis.py:257
int add_polygon(self, Polygon polygon)
Definition analysis.py:113
NarrowDistanceResult find_narrow_distances(self, float threshold_distance=1.0)
Definition analysis.py:172
Tuple[Point, Point] _closest_points_on_edges(self, Point p1, Point p2, Point p3, Point p4)
Definition analysis.py:287
EdgeIntersectionResult find_edge_intersections(self)
Definition analysis.py:227
2D point with high-precision coordinates and utility methods
Definition point.hpp:23
bool segments_intersect(Point p1, Point p2, Point p3, Point p4)
Definition analysis.py:64
Optional[Point] line_intersection(Point p1, Point p2, Point p3, Point p4)
Definition analysis.py:39