Last active
December 9, 2025 12:10
-
-
Save mdmitry1/e68e5572f0b2e541e65d38a5f07d70ba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3.12 | |
| def visualize_quadtree(quadtree, points, query_rect=None, found_points=None, | |
| filename="quadtree", title="Quadtree Visualization"): | |
| """Visualize the quadtree structure with points.""" | |
| fig, ax = plt.subplots(figsize=(12, 12)) | |
| # Draw all quadtree boundaries | |
| boundaries = quadtree.get_all_boundaries() | |
| for boundary in boundaries: | |
| x = boundary.x - boundary.width/2 | |
| y = boundary.y - boundary.height/2 | |
| rect = patches.Rectangle((x, y), boundary.width, boundary.height, | |
| linewidth=1, edgecolor='gray', | |
| facecolor='none', alpha=0.5) | |
| ax.add_patch(rect) | |
| # Draw all points | |
| for point in points: | |
| ax.plot(point.x, point.y, 'bo', markersize=4, alpha=0.6) | |
| # Draw query range if provided | |
| if query_rect: | |
| x = query_rect.x - query_rect.width/2 | |
| y = query_rect.y - query_rect.height/2 | |
| query_patch = patches.Rectangle((x, y), query_rect.width, query_rect.height, | |
| linewidth=2, edgecolor='red', | |
| facecolor='red', alpha=0.1) | |
| ax.add_patch(query_patch) | |
| # Highlight found points | |
| if found_points: | |
| for point in found_points: | |
| ax.plot(point.x, point.y, 'ro', markersize=8, alpha=0.8) | |
| # Set bounds | |
| boundary = quadtree.boundary | |
| margin = boundary.width * 0.05 | |
| ax.set_xlim(boundary.x - boundary.width/2 - margin, | |
| boundary.x + boundary.width/2 + margin) | |
| ax.set_ylim(boundary.y - boundary.height/2 - margin, | |
| boundary.y + boundary.height/2 + margin) | |
| ax.set_aspect('equal') | |
| ax.set_title(title, fontsize=16, fontweight='bold') | |
| ax.set_xlabel('X', fontsize=12) | |
| ax.set_ylabel('Y', fontsize=12) | |
| ax.grid(True, alpha=0.3) | |
| # Add info | |
| info_text = f"Points: {len(points)}\nQuadrants: {quadtree.count_nodes()}" | |
| if found_points: | |
| info_text += f"\nFound: {len(found_points)}" | |
| ax.text(0.02, 0.98, info_text, transform=ax.transAxes, | |
| verticalalignment='top', bbox=dict(boxstyle='round', | |
| facecolor='wheat', alpha=0.5), fontsize=10) | |
| plt.tight_layout() | |
| filepath = os.path.join(OUTPUT_DIR, f"{filename}.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| return filepath | |
| def example_6_rectangles_collision(): | |
| """Example 6: Collision detection with rectangles (circuit components).""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 6: Rectangle Collision Detection (Circuit Components)") | |
| print("="*70) | |
| print("\nScenario: Detecting overlapping circuit components") | |
| print("Use case: Design Rule Checking, component placement validation") | |
| # Create quadtree for circuit board | |
| boundary = Rectangle(500, 500, 1000, 1000) | |
| qt = Quadtree(boundary, capacity=6) | |
| # Create circuit components as rectangles | |
| random.seed(200) | |
| components = [] | |
| print("\nPlacing circuit components...") | |
| # Add various sized components | |
| component_types = [ | |
| ("Resistor", 20, 8), | |
| ("Capacitor", 15, 15), | |
| ("IC", 40, 40), | |
| ("Transistor", 12, 12), | |
| ("Connector", 30, 10) | |
| ] | |
| for i in range(80): | |
| comp_type, width, height = random.choice(component_types) | |
| x = random.uniform(50, 950 - width) | |
| y = random.uniform(50, 950 - height) | |
| rect = Rect(x, y, width, height, data=f"{comp_type}_{i}") | |
| components.append(rect) | |
| qt.insert_rect(rect) | |
| print(f"Placed {len(components)} components") | |
| print(f"Quadtree nodes: {qt.count_nodes()}") | |
| # Visualize all components | |
| filepath = visualize_quadtree_rects(qt, components, | |
| filename="6a_circuit_components_rects", | |
| title="Circuit Board with Component Rectangles") | |
| print(f"✓ Saved: {filepath}") | |
| # Query: Find components in a specific region | |
| query_region = Rectangle(400, 600, 250, 250) | |
| found = qt.query_rects(query_region) | |
| print(f"\nCollision detection query:") | |
| print(f" Region: centered at (400, 600), size 250×250") | |
| print(f" Found {len(found)} intersecting components") | |
| filepath = visualize_quadtree_rects(qt, components, query_region, found, | |
| filename="6b_collision_detection", | |
| title="Collision Detection - Green region finds overlapping components") | |
| print(f"✓ Saved: {filepath}") | |
| print("\nApplications:") | |
| print(" • Design Rule Checking: Ensure minimum spacing") | |
| print(" • Placement validation: Detect overlaps before manufacturing") | |
| print(" • Routing: Find components to connect") | |
| print(" • Interference analysis: Check EM compatibility") | |
| def example_7_polygon_operations(): | |
| """Example 7: Polygon operations - realistic semiconductor masks.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 7: Polygon Operations (Semiconductor Masks)") | |
| print("="*70) | |
| print("\nScenario: Managing polygon masks for photolithography") | |
| print("Use case: Mask layer operations, design rule checking") | |
| # Create quadtree | |
| boundary = Rectangle(5000, 5000, 10000, 10000) # 10µm x 10µm chip | |
| qt = Quadtree(boundary, capacity=8) | |
| # Create realistic polygon patterns (metal traces, vias, pads) | |
| random.seed(300) | |
| polygons = [] | |
| print("\nCreating semiconductor mask polygons...") | |
| # Metal traces (long thin rectangles) | |
| for i in range(30): | |
| x = random.uniform(100, 9800) | |
| y = random.uniform(100, 9800) | |
| if random.random() < 0.5: | |
| # Horizontal trace | |
| width = random.uniform(200, 800) | |
| height = random.uniform(5, 15) | |
| else: | |
| # Vertical trace | |
| width = random.uniform(5, 15) | |
| height = random.uniform(200, 800) | |
| rect = Rect(x, y, width, height, data=f"metal_trace_{i}") | |
| polygons.append(rect) | |
| qt.insert_rect(rect) | |
| # Contact pads (squares) | |
| for i in range(40): | |
| x = random.uniform(100, 9900) | |
| y = random.uniform(100, 9900) | |
| size = random.uniform(30, 80) | |
| rect = Rect(x, y, size, size, data=f"contact_pad_{i}") | |
| polygons.append(rect) | |
| qt.insert_rect(rect) | |
| # Vias (small squares) | |
| for i in range(50): | |
| x = random.uniform(100, 9990) | |
| y = random.uniform(100, 9990) | |
| size = random.uniform(3, 10) | |
| rect = Rect(x, y, size, size, data=f"via_{i}") | |
| polygons.append(rect) | |
| qt.insert_rect(rect) | |
| print(f"Created {len(polygons)} polygons:") | |
| print(f" - 30 metal traces") | |
| print(f" - 40 contact pads") | |
| print(f" - 50 vias") | |
| print(f"Quadtree nodes: {qt.count_nodes()}") | |
| # Visualize full layout | |
| filepath = visualize_quadtree_rects(qt, polygons, | |
| filename="7a_semiconductor_masks", | |
| title="Semiconductor Mask Layout (Metal layer)") | |
| print(f"✓ Saved: {filepath}") | |
| # Design rule check: Find polygons too close to a critical trace | |
| critical_trace = Rectangle(3000, 5000, 600, 600) | |
| nearby = qt.query_rects(critical_trace) | |
| print(f"\nDesign Rule Check:") | |
| print(f" Checking proximity to critical region") | |
| print(f" Found {len(nearby)} polygons within check region") | |
| print(f" Application: Ensure minimum spacing rules") | |
| filepath = visualize_quadtree_rects(qt, polygons, critical_trace, nearby, | |
| filename="7b_design_rule_check", | |
| title="Design Rule Check - Finding polygons near critical trace") | |
| print(f"✓ Saved: {filepath}") | |
| # Benchmark: Compare with brute force | |
| start = time.time() | |
| for _ in range(100): | |
| found_qt = qt.query_rects(critical_trace) | |
| qt_time = (time.time() - start) / 100 | |
| # Brute force check | |
| critical_as_rect = Rect( | |
| critical_trace.x - critical_trace.width/2, | |
| critical_trace.y - critical_trace.height/2, | |
| critical_trace.width, | |
| critical_trace.height | |
| ) | |
| start = time.time() | |
| for _ in range(100): | |
| found_bf = [p for p in polygons if p.intersects(critical_as_rect)] | |
| bf_time = (time.time() - start) / 100 | |
| print(f"\nPerformance:") | |
| print(f" Quadtree: {qt_time*1000:.3f} ms") | |
| print(f" Brute force: {bf_time*1000:.3f} ms") | |
| print(f" Speedup: {bf_time/qt_time:.1f}x") | |
| print("\nSemiconductor applications:") | |
| print(" • Mask layer collision detection") | |
| print(" • Design rule verification (spacing, width)") | |
| print(" • Polygon boolean operations (union, intersection)") | |
| print(" • Optical proximity correction (OPC)") | |
| print(" • Fast spatial queries for multi-million polygon designs") | |
| """ | |
| Quadtree Data Structure Examples | |
| Demonstrates spatial indexing for circuit design and polygon operations | |
| Quadtrees are hierarchical spatial data structures that recursively | |
| partition 2D space into four quadrants. Essential for: | |
| - Fast spatial queries (range search, nearest neighbor) | |
| - Collision detection | |
| - Circuit layout optimization | |
| - Polygon intersection detection | |
| Install: pip install pyquadtree2 | |
| Or we'll implement from scratch if not available | |
| """ | |
| import random | |
| import time | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as patches | |
| from collections import namedtuple | |
| import os | |
| # Create output directory | |
| OUTPUT_DIR = "quadtree_illustrations" | |
| if not os.path.exists(OUTPUT_DIR): | |
| os.makedirs(OUTPUT_DIR) | |
| # Try to import pyquadtree2, fall back to custom implementation | |
| try: | |
| from pyquadtree2 import Index as QuadtreeIndex | |
| HAS_PYQUADTREE = True | |
| except ImportError: | |
| HAS_PYQUADTREE = False | |
| print("Note: pyquadtree2 not installed. Using custom implementation.") | |
| print("To install: pip install pyquadtree2") | |
| # Custom Quadtree Implementation (educational) | |
| class Point: | |
| """Represents a 2D point.""" | |
| def __init__(self, x, y, data=None): | |
| self.x = x | |
| self.y = y | |
| self.data = data | |
| def __repr__(self): | |
| return f"Point({self.x}, {self.y})" | |
| class Rect: | |
| """Represents a rectangle (for circuit components).""" | |
| def __init__(self, x, y, width, height, data=None): | |
| self.x = x # Bottom-left x | |
| self.y = y # Bottom-left y | |
| self.width = width | |
| self.height = height | |
| self.data = data | |
| @property | |
| def center(self): | |
| """Get center point of rectangle.""" | |
| return Point(self.x + self.width/2, self.y + self.height/2) | |
| def intersects(self, other): | |
| """Check if this rectangle intersects another.""" | |
| return not (self.x + self.width < other.x or | |
| other.x + other.width < self.x or | |
| self.y + self.height < other.y or | |
| other.y + other.height < self.y) | |
| def __repr__(self): | |
| return f"Rect({self.x}, {self.y}, {self.width}x{self.height})" | |
| class Rectangle: | |
| """Represents a bounding box for quadtree boundaries.""" | |
| def __init__(self, x, y, width, height): | |
| self.x = x # Center x | |
| self.y = y # Center y | |
| self.width = width | |
| self.height = height | |
| def contains(self, point): | |
| """Check if point is inside rectangle.""" | |
| return (self.x - self.width/2 <= point.x <= self.x + self.width/2 and | |
| self.y - self.height/2 <= point.y <= self.y + self.height/2) | |
| def intersects(self, range_rect): | |
| """Check if this rectangle intersects with another.""" | |
| return not (range_rect.x - range_rect.width/2 > self.x + self.width/2 or | |
| range_rect.x + range_rect.width/2 < self.x - self.width/2 or | |
| range_rect.y - range_rect.height/2 > self.y + self.height/2 or | |
| range_rect.y + range_rect.height/2 < self.y - self.height/2) | |
| def intersects_rect(self, rect): | |
| """Check if this boundary intersects with a Rect object.""" | |
| rect_left = rect.x | |
| rect_right = rect.x + rect.width | |
| rect_bottom = rect.y | |
| rect_top = rect.y + rect.height | |
| bound_left = self.x - self.width/2 | |
| bound_right = self.x + self.width/2 | |
| bound_bottom = self.y - self.height/2 | |
| bound_top = self.y + self.height/2 | |
| return not (rect_right < bound_left or | |
| rect_left > bound_right or | |
| rect_top < bound_bottom or | |
| rect_bottom > bound_top) | |
| def __repr__(self): | |
| return f"Rect({self.x}, {self.y}, {self.width}x{self.height})" | |
| class Quadtree: | |
| """ | |
| Custom Quadtree implementation for educational purposes. | |
| A quadtree recursively divides 2D space into 4 quadrants: | |
| - NW (northwest) - top-left | |
| - NE (northeast) - top-right | |
| - SW (southwest) - bottom-left | |
| - SE (southeast) - bottom-right | |
| Can store both points and rectangles. | |
| """ | |
| def __init__(self, boundary, capacity=4): | |
| """ | |
| Args: | |
| boundary: Rectangle defining the spatial bounds | |
| capacity: Max items before subdivision | |
| """ | |
| self.boundary = boundary | |
| self.capacity = capacity | |
| self.points = [] | |
| self.rectangles = [] # Store rectangle objects | |
| self.divided = False | |
| # Child quadrants (created on subdivision) | |
| self.northwest = None | |
| self.northeast = None | |
| self.southwest = None | |
| self.southeast = None | |
| def subdivide(self): | |
| """Divide this quadrant into 4 sub-quadrants.""" | |
| x = self.boundary.x | |
| y = self.boundary.y | |
| w = self.boundary.width / 2 | |
| h = self.boundary.height / 2 | |
| # Create 4 child quadrants | |
| nw_boundary = Rectangle(x - w/2, y + h/2, w, h) | |
| self.northwest = Quadtree(nw_boundary, self.capacity) | |
| ne_boundary = Rectangle(x + w/2, y + h/2, w, h) | |
| self.northeast = Quadtree(ne_boundary, self.capacity) | |
| sw_boundary = Rectangle(x - w/2, y - h/2, w, h) | |
| self.southwest = Quadtree(sw_boundary, self.capacity) | |
| se_boundary = Rectangle(x + w/2, y - h/2, w, h) | |
| self.southeast = Quadtree(se_boundary, self.capacity) | |
| self.divided = True | |
| def insert(self, point): | |
| """Insert a point into the quadtree.""" | |
| # Ignore if point is outside boundary | |
| if not self.boundary.contains(point): | |
| return False | |
| # If capacity not reached, add here | |
| if len(self.points) < self.capacity: | |
| self.points.append(point) | |
| return True | |
| # Otherwise, subdivide and redistribute | |
| if not self.divided: | |
| self.subdivide() | |
| # Try to insert into children | |
| if self.northwest.insert(point): | |
| return True | |
| if self.northeast.insert(point): | |
| return True | |
| if self.southwest.insert(point): | |
| return True | |
| if self.southeast.insert(point): | |
| return True | |
| return False | |
| def insert_rect(self, rect): | |
| """ | |
| Insert a rectangle into the quadtree. | |
| Uses rectangle's center point for placement. | |
| """ | |
| center = rect.center | |
| # Check if center is in this boundary | |
| if not self.boundary.contains(center): | |
| return False | |
| # If capacity not reached, add here | |
| if len(self.rectangles) < self.capacity: | |
| self.rectangles.append(rect) | |
| return True | |
| # Otherwise, subdivide and redistribute | |
| if not self.divided: | |
| self.subdivide() | |
| # Try to insert into children | |
| if self.northwest.insert_rect(rect): | |
| return True | |
| if self.northeast.insert_rect(rect): | |
| return True | |
| if self.southwest.insert_rect(rect): | |
| return True | |
| if self.southeast.insert_rect(rect): | |
| return True | |
| return False | |
| def query(self, range_rect, found=None): | |
| """ | |
| Find all points within a rectangular range. | |
| This is the key operation that makes quadtrees fast! | |
| """ | |
| if found is None: | |
| found = [] | |
| # If range doesn't intersect boundary, return early | |
| if not self.boundary.intersects(range_rect): | |
| return found | |
| # Check points in this quadrant | |
| for point in self.points: | |
| if range_rect.contains(point): | |
| found.append(point) | |
| # Recursively search children if divided | |
| if self.divided: | |
| self.northwest.query(range_rect, found) | |
| self.northeast.query(range_rect, found) | |
| self.southwest.query(range_rect, found) | |
| self.southeast.query(range_rect, found) | |
| return found | |
| def query_rects(self, range_rect, found=None): | |
| """ | |
| Find all rectangles that intersect with the query rectangle. | |
| Critical for collision detection in circuit design. | |
| """ | |
| if found is None: | |
| found = [] | |
| # If range doesn't intersect boundary, return early | |
| if not self.boundary.intersects_rect(Rect( | |
| range_rect.x - range_rect.width/2, | |
| range_rect.y - range_rect.height/2, | |
| range_rect.width, | |
| range_rect.height)): | |
| return found | |
| # Check rectangles in this quadrant | |
| query_as_rect = Rect( | |
| range_rect.x - range_rect.width/2, | |
| range_rect.y - range_rect.height/2, | |
| range_rect.width, | |
| range_rect.height | |
| ) | |
| for rect in self.rectangles: | |
| if rect.intersects(query_as_rect): | |
| found.append(rect) | |
| # Recursively search children if divided | |
| if self.divided: | |
| self.northwest.query_rects(range_rect, found) | |
| self.northeast.query_rects(range_rect, found) | |
| self.southwest.query_rects(range_rect, found) | |
| self.southeast.query_rects(range_rect, found) | |
| return found | |
| def count_nodes(self): | |
| """Count total number of nodes (for visualization).""" | |
| count = 1 | |
| if self.divided: | |
| count += self.northwest.count_nodes() | |
| count += self.northeast.count_nodes() | |
| count += self.southwest.count_nodes() | |
| count += self.southeast.count_nodes() | |
| return count | |
| def get_all_boundaries(self): | |
| """Get all quadrant boundaries for visualization.""" | |
| boundaries = [self.boundary] | |
| if self.divided: | |
| boundaries.extend(self.northwest.get_all_boundaries()) | |
| boundaries.extend(self.northeast.get_all_boundaries()) | |
| boundaries.extend(self.southwest.get_all_boundaries()) | |
| boundaries.extend(self.southeast.get_all_boundaries()) | |
| return boundaries | |
| def visualize_quadtree_rects(quadtree, rects, query_rect=None, found_rects=None, | |
| filename="quadtree_rects", title="Quadtree with Rectangles"): | |
| """Visualize quadtree with rectangle objects (circuit components).""" | |
| fig, ax = plt.subplots(figsize=(12, 12)) | |
| # Draw all quadtree boundaries | |
| boundaries = quadtree.get_all_boundaries() | |
| for boundary in boundaries: | |
| x = boundary.x - boundary.width/2 | |
| y = boundary.y - boundary.height/2 | |
| rect = patches.Rectangle((x, y), boundary.width, boundary.height, | |
| linewidth=1, edgecolor='gray', | |
| facecolor='none', alpha=0.5) | |
| ax.add_patch(rect) | |
| # Draw all component rectangles | |
| for rect in rects: | |
| is_found = found_rects and rect in found_rects | |
| color = 'red' if is_found else 'blue' | |
| alpha = 0.6 if is_found else 0.3 | |
| linewidth = 2 if is_found else 1 | |
| rect_patch = patches.Rectangle((rect.x, rect.y), rect.width, rect.height, | |
| linewidth=linewidth, edgecolor=color, | |
| facecolor=color, alpha=alpha) | |
| ax.add_patch(rect_patch) | |
| # Draw center point | |
| center = rect.center | |
| ax.plot(center.x, center.y, 'ko', markersize=3, alpha=0.5) | |
| # Draw query range if provided | |
| if query_rect: | |
| x = query_rect.x - query_rect.width/2 | |
| y = query_rect.y - query_rect.height/2 | |
| query_patch = patches.Rectangle((x, y), query_rect.width, query_rect.height, | |
| linewidth=3, edgecolor='green', | |
| facecolor='green', alpha=0.1, | |
| linestyle='--') | |
| ax.add_patch(query_patch) | |
| # Set bounds | |
| boundary = quadtree.boundary | |
| margin = boundary.width * 0.05 | |
| ax.set_xlim(boundary.x - boundary.width/2 - margin, | |
| boundary.x + boundary.width/2 + margin) | |
| ax.set_ylim(boundary.y - boundary.height/2 - margin, | |
| boundary.y + boundary.height/2 + margin) | |
| ax.set_aspect('equal') | |
| ax.set_title(title, fontsize=16, fontweight='bold') | |
| ax.set_xlabel('X (nm)', fontsize=12) | |
| ax.set_ylabel('Y (nm)', fontsize=12) | |
| ax.grid(True, alpha=0.3) | |
| # Add info | |
| info_text = f"Components: {len(rects)}\nQuadrants: {quadtree.count_nodes()}" | |
| if found_rects: | |
| info_text += f"\nIntersecting: {len(found_rects)}" | |
| ax.text(0.02, 0.98, info_text, transform=ax.transAxes, | |
| verticalalignment='top', bbox=dict(boxstyle='round', | |
| facecolor='wheat', alpha=0.5), fontsize=10) | |
| # Legend | |
| blue_patch = patches.Patch(color='blue', alpha=0.3, label='Components') | |
| if found_rects: | |
| red_patch = patches.Patch(color='red', alpha=0.6, label='Found (intersecting)') | |
| green_patch = patches.Patch(color='green', alpha=0.1, label='Query region') | |
| ax.legend(handles=[blue_patch, red_patch, green_patch], loc='upper right') | |
| else: | |
| ax.legend(handles=[blue_patch], loc='upper right') | |
| plt.tight_layout() | |
| filepath = os.path.join(OUTPUT_DIR, f"{filename}.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| return filepath | |
| def example_1_basic_quadtree(): | |
| """Example 1: Basic quadtree construction and visualization.""" | |
| print("="*70) | |
| print("EXAMPLE 1: Basic Quadtree Construction") | |
| print("="*70) | |
| # Create a quadtree covering 0-100 in both x and y | |
| boundary = Rectangle(50, 50, 100, 100) | |
| qt = Quadtree(boundary, capacity=4) | |
| # Insert some points | |
| print("\nInserting points...") | |
| random.seed(42) | |
| points = [] | |
| for i in range(50): | |
| x = random.uniform(0, 100) | |
| y = random.uniform(0, 100) | |
| p = Point(x, y, data=f"point_{i}") | |
| points.append(p) | |
| qt.insert(p) | |
| print(f"Inserted {len(points)} points") | |
| print(f"Quadtree has {qt.count_nodes()} nodes (quadrants)") | |
| # Visualize | |
| filepath = visualize_quadtree(qt, points, | |
| filename="1_basic_quadtree", | |
| title="Basic Quadtree (capacity=4, 50 points)") | |
| print(f"✓ Saved: {filepath}") | |
| print("\nHow it works:") | |
| print(" 1. Start with one large quadrant covering entire space") | |
| print(" 2. Insert points until capacity (4) is reached") | |
| print(" 3. Subdivide into 4 child quadrants (NW, NE, SW, SE)") | |
| print(" 4. Recursively insert remaining points into children") | |
| print(" 5. Dense areas have more subdivisions (adaptive)") | |
| def example_2_range_query(): | |
| """Example 2: Range query - the killer feature of quadtrees.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 2: Range Query (Fast Spatial Search)") | |
| print("="*70) | |
| # Create quadtree with many points | |
| boundary = Rectangle(50, 50, 100, 100) | |
| qt = Quadtree(boundary, capacity=4) | |
| random.seed(42) | |
| points = [] | |
| print("\nInserting 500 points...") | |
| for i in range(500): | |
| x = random.uniform(0, 100) | |
| y = random.uniform(0, 100) | |
| p = Point(x, y, data=f"point_{i}") | |
| points.append(p) | |
| qt.insert(p) | |
| print(f"Quadtree has {qt.count_nodes()} nodes") | |
| # Define query range | |
| query_rect = Rectangle(30, 40, 20, 20) | |
| # Perform query with timing | |
| start = time.time() | |
| found = qt.query(query_rect) | |
| query_time = time.time() - start | |
| print(f"\nRange query: Rectangle centered at (30, 40), size 20×20") | |
| print(f"Found {len(found)} points in {query_time*1000:.3f} ms") | |
| # Compare with brute force | |
| start = time.time() | |
| brute_force = [p for p in points if query_rect.contains(p)] | |
| brute_time = time.time() - start | |
| print(f"Brute force: {len(brute_force)} points in {brute_time*1000:.3f} ms") | |
| print(f"Speedup: {brute_time/query_time:.1f}x faster with quadtree") | |
| # Visualize | |
| filepath = visualize_quadtree(qt, points, query_rect, found, | |
| filename="2_range_query", | |
| title="Range Query: Red box = query, Red dots = found points") | |
| print(f"✓ Saved: {filepath}") | |
| print("\nWhy quadtrees are fast:") | |
| print(" • Brute force: Check ALL 500 points = O(n)") | |
| print(" • Quadtree: Only check relevant quadrants = O(log n)") | |
| print(" • Key insight: Skip entire quadrants that don't intersect query") | |
| def example_3_circuit_design(): | |
| """Example 3: Circuit design application - component placement.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 3: Circuit Component Placement") | |
| print("="*70) | |
| print("\nScenario: Finding components near a selected region") | |
| print("Use case: Design Rule Checking (DRC), proximity analysis") | |
| # Create quadtree for circuit board (1000×1000 units) | |
| boundary = Rectangle(500, 500, 1000, 1000) | |
| qt = Quadtree(boundary, capacity=8) | |
| # Simulate circuit components | |
| random.seed(100) | |
| components = [] | |
| # Add clustered components (realistic circuit layout) | |
| clusters = [ | |
| (200, 200, 50), # Cluster center and radius | |
| (400, 600, 60), | |
| (700, 300, 40), | |
| (800, 800, 55) | |
| ] | |
| component_id = 0 | |
| for cx, cy, radius in clusters: | |
| for _ in range(30): | |
| x = cx + random.gauss(0, radius) | |
| y = cy + random.gauss(0, radius) | |
| x = max(0, min(1000, x)) | |
| y = max(0, min(1000, y)) | |
| p = Point(x, y, data=f"IC_{component_id}") | |
| components.append(p) | |
| qt.insert(p) | |
| component_id += 1 | |
| print(f"Placed {len(components)} components in {len(clusters)} clusters") | |
| print(f"Quadtree structure: {qt.count_nodes()} nodes") | |
| # Query: Find components near center for routing | |
| query_rect = Rectangle(400, 600, 150, 150) | |
| found = qt.query(query_rect) | |
| print(f"\nQuery: Components near (400, 600)") | |
| print(f"Found {len(found)} components in proximity") | |
| print(f"Application: Check spacing rules, route connections") | |
| filepath = visualize_quadtree(qt, components, query_rect, found, | |
| filename="3_circuit_components", | |
| title="Circuit Component Placement - DRC proximity check") | |
| print(f"✓ Saved: {filepath}") | |
| print("\nCircuit design applications:") | |
| print(" • Design Rule Checking (DRC): Find nearby components") | |
| print(" • Collision detection: Prevent component overlap") | |
| print(" • Routing: Find components to connect") | |
| print(" • Proximity analysis: Identify interference risks") | |
| def example_4_performance_comparison(): | |
| """Example 4: Performance scaling comparison.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 4: Performance Scaling Analysis") | |
| print("="*70) | |
| print("\nComparing quadtree vs brute force for different data sizes...") | |
| sizes = [100, 500, 1000, 2000, 5000] | |
| qt_times = [] | |
| bf_times = [] | |
| for n in sizes: | |
| # Build quadtree | |
| boundary = Rectangle(500, 500, 1000, 1000) | |
| qt = Quadtree(boundary, capacity=8) | |
| random.seed(42) | |
| points = [] | |
| for i in range(n): | |
| x = random.uniform(0, 1000) | |
| y = random.uniform(0, 1000) | |
| p = Point(x, y) | |
| points.append(p) | |
| qt.insert(p) | |
| # Query range | |
| query_rect = Rectangle(500, 500, 200, 200) | |
| # Time quadtree query | |
| start = time.time() | |
| for _ in range(100): # Average over 100 queries | |
| found_qt = qt.query(query_rect) | |
| qt_time = (time.time() - start) / 100 | |
| qt_times.append(qt_time * 1000) # Convert to ms | |
| # Time brute force | |
| start = time.time() | |
| for _ in range(100): | |
| found_bf = [p for p in points if query_rect.contains(p)] | |
| bf_time = (time.time() - start) / 100 | |
| bf_times.append(bf_time * 1000) | |
| speedup = bf_time / qt_time | |
| print(f" n={n:>5}: Quadtree={qt_time*1000:>6.3f}ms, " | |
| f"BruteForce={bf_time*1000:>6.3f}ms, Speedup={speedup:>5.1f}x") | |
| # Plot results | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| ax.plot(sizes, qt_times, 'b-o', label='Quadtree', linewidth=2, markersize=8) | |
| ax.plot(sizes, bf_times, 'r-s', label='Brute Force', linewidth=2, markersize=8) | |
| ax.set_xlabel('Number of Points', fontsize=12, fontweight='bold') | |
| ax.set_ylabel('Query Time (ms)', fontsize=12, fontweight='bold') | |
| ax.set_title('Quadtree vs Brute Force Performance', fontsize=14, fontweight='bold') | |
| ax.legend(fontsize=11) | |
| ax.grid(True, alpha=0.3) | |
| ax.set_yscale('log') | |
| filepath = os.path.join(OUTPUT_DIR, "4_performance_comparison.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| print(f"\n✓ Saved: {filepath}") | |
| print("\nKey insights:") | |
| print(" • Quadtree: O(log n) - scales logarithmically") | |
| print(" • Brute force: O(n) - scales linearly") | |
| print(" • Speedup increases with data size") | |
| print(" • Critical for real-time applications with millions of points") | |
| def example_5_adaptive_subdivision(): | |
| """Example 5: Demonstrate adaptive subdivision.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 5: Adaptive Subdivision") | |
| print("="*70) | |
| print("\nDemonstrating how quadtrees adapt to data distribution...") | |
| # Create two datasets: uniform and clustered | |
| boundary = Rectangle(50, 50, 100, 100) | |
| # Dataset 1: Uniform distribution | |
| qt_uniform = Quadtree(boundary, capacity=4) | |
| random.seed(42) | |
| uniform_points = [] | |
| for i in range(100): | |
| x = random.uniform(0, 100) | |
| y = random.uniform(0, 100) | |
| p = Point(x, y) | |
| uniform_points.append(p) | |
| qt_uniform.insert(p) | |
| filepath1 = visualize_quadtree(qt_uniform, uniform_points, | |
| filename="5a_uniform_distribution", | |
| title="Uniform Distribution - Even subdivision") | |
| print(f"✓ Uniform: {qt_uniform.count_nodes()} nodes") | |
| print(f" Saved: {filepath1}") | |
| # Dataset 2: Clustered distribution | |
| qt_clustered = Quadtree(boundary, capacity=4) | |
| clustered_points = [] | |
| # Dense cluster in one corner | |
| for i in range(80): | |
| x = random.gauss(25, 8) | |
| y = random.gauss(25, 8) | |
| x = max(0, min(100, x)) | |
| y = max(0, min(100, y)) | |
| p = Point(x, y) | |
| clustered_points.append(p) | |
| qt_clustered.insert(p) | |
| # Sparse elsewhere | |
| for i in range(20): | |
| x = random.uniform(50, 100) | |
| y = random.uniform(50, 100) | |
| p = Point(x, y) | |
| clustered_points.append(p) | |
| qt_clustered.insert(p) | |
| filepath2 = visualize_quadtree(qt_clustered, clustered_points, | |
| filename="5b_clustered_distribution", | |
| title="Clustered Distribution - Adaptive subdivision") | |
| print(f"✓ Clustered: {qt_clustered.count_nodes()} nodes") | |
| print(f" Saved: {filepath2}") | |
| print("\nAdaptive subdivision benefits:") | |
| print(" • Dense regions get more subdivision (finer granularity)") | |
| print(" • Sparse regions stay coarse (less overhead)") | |
| print(" • Automatic load balancing based on data distribution") | |
| print(" • Critical for real-world circuits (non-uniform layouts)") | |
| def main(): | |
| """Run all examples.""" | |
| print("\n" + "█"*70) | |
| print("QUADTREE DATA STRUCTURES FOR SPATIAL INDEXING") | |
| print("█"*70) | |
| print("\nWHAT IS A QUADTREE?") | |
| print("A hierarchical data structure that recursively partitions 2D space") | |
| print("into four quadrants (NW, NE, SW, SE). Each node can subdivide when") | |
| print("it reaches capacity, creating adaptive resolution.") | |
| print("\nKEY OPERATIONS:") | |
| print(" • Insert: Add point to appropriate quadrant - O(log n)") | |
| print(" • Query: Find all points in a rectangle - O(log n) average") | |
| print(" • Range search: Only check intersecting quadrants") | |
| print("\nAPPLICATIONS IN SEMICONDUCTOR DESIGN:") | |
| print(" • Design Rule Checking (DRC)") | |
| print(" • Collision detection in layout") | |
| print(" • Component proximity analysis") | |
| print(" • Spatial indexing for polygon operations") | |
| print(" • Fast nearest-neighbor search") | |
| print(f"\n✓ All visualizations will be saved to: {OUTPUT_DIR}/") | |
| print("="*70) | |
| # Run examples | |
| example_1_basic_quadtree() | |
| example_2_range_query() | |
| example_3_circuit_design() | |
| example_4_performance_comparison() | |
| example_5_adaptive_subdivision() | |
| example_6_rectangles_collision() | |
| example_7_polygon_operations() | |
| print("\n" + "="*70) | |
| print("INTERVIEW KEY POINTS") | |
| print("="*70) | |
| print("\nQuadtree vs Other Structures:") | |
| print(" • vs Array: O(log n) query vs O(n)") | |
| print(" • vs Grid: Adaptive resolution vs fixed") | |
| print(" • vs R-tree: Simpler, better for points; R-tree better for rectangles") | |
| print(" • vs K-d tree: Quadtree better for 2D, K-d tree for higher dimensions") | |
| print("\nWhen to use Quadtrees:") | |
| print(" ✓ 2D spatial queries (range, nearest neighbor)") | |
| print(" ✓ Dynamic data (frequent insertions)") | |
| print(" ✓ Non-uniform data distribution") | |
| print(" ✓ Real-time collision detection") | |
| print("\nLimitations:") | |
| print(" ✗ Higher memory overhead than arrays") | |
| print(" ✗ Worst-case still O(n) for pathological distributions") | |
| print(" ✗ Not ideal for 3D+ (use octrees or k-d trees)") | |
| print(f"\n✓ All visualizations saved to: {os.path.abspath(OUTPUT_DIR)}/") | |
| print("="*70) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3.12 | |
| """ | |
| R-tree vs Quadtree: Algorithmic Comparison | |
| Understanding the fundamental differences between these spatial data structures | |
| is critical for semiconductor design applications. | |
| """ | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as patches | |
| import random | |
| import os | |
| OUTPUT_DIR = "rtree_vs_quadtree_comparison" | |
| if not os.path.exists(OUTPUT_DIR): | |
| os.makedirs(OUTPUT_DIR) | |
| def visualize_comparison(): | |
| """Create side-by-side comparison of R-tree and Quadtree.""" | |
| print("="*70) | |
| print("R-TREE VS QUADTREE: ALGORITHMIC DIFFERENCES") | |
| print("="*70) | |
| # Generate same set of rectangles for both | |
| random.seed(42) | |
| rectangles = [] | |
| for i in range(12): | |
| x = random.uniform(10, 80) | |
| y = random.uniform(10, 80) | |
| w = random.uniform(5, 15) | |
| h = random.uniform(5, 15) | |
| rectangles.append((x, y, w, h, f"R{i}")) | |
| fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9)) | |
| # LEFT: Quadtree approach | |
| ax1.set_title("QUADTREE\nSpace-driven partitioning", | |
| fontsize=14, fontweight='bold') | |
| ax1.set_xlim(0, 100) | |
| ax1.set_ylim(0, 100) | |
| ax1.set_aspect('equal') | |
| ax1.grid(True, alpha=0.3) | |
| # Draw quadtree divisions (fixed, space-based) | |
| ax1.axhline(50, color='red', linewidth=2, linestyle='-', alpha=0.7) | |
| ax1.axvline(50, color='red', linewidth=2, linestyle='-', alpha=0.7) | |
| ax1.axhline(25, xmax=0.5, color='orange', linewidth=1.5, linestyle='-', alpha=0.6) | |
| ax1.axvline(25, ymax=0.5, color='orange', linewidth=1.5, linestyle='-', alpha=0.6) | |
| ax1.axhline(75, xmin=0.5, color='orange', linewidth=1.5, linestyle='-', alpha=0.6) | |
| ax1.axvline(75, ymin=0.5, color='orange', linewidth=1.5, linestyle='-', alpha=0.6) | |
| # Label quadrants | |
| ax1.text(25, 75, 'NW', ha='center', va='center', fontsize=16, | |
| bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.3)) | |
| ax1.text(75, 75, 'NE', ha='center', va='center', fontsize=16, | |
| bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.3)) | |
| ax1.text(25, 25, 'SW', ha='center', va='center', fontsize=16, | |
| bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.3)) | |
| ax1.text(75, 25, 'SE', ha='center', va='center', fontsize=16, | |
| bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.3)) | |
| # Draw rectangles on quadtree | |
| for x, y, w, h, label in rectangles: | |
| rect = patches.Rectangle((x, y), w, h, linewidth=2, | |
| edgecolor='blue', facecolor='lightblue', alpha=0.6) | |
| ax1.add_patch(rect) | |
| ax1.plot(x + w/2, y + h/2, 'ko', markersize=4) | |
| # RIGHT: R-tree approach | |
| ax2.set_title("R-TREE\nData-driven bounding boxes", | |
| fontsize=14, fontweight='bold') | |
| ax2.set_xlim(0, 100) | |
| ax2.set_ylim(0, 100) | |
| ax2.set_aspect('equal') | |
| ax2.grid(True, alpha=0.3) | |
| # Manually group rectangles into R-tree bounding boxes (simulated) | |
| # Group 1: bottom-left cluster | |
| group1_rects = [(x, y, w, h, l) for x, y, w, h, l in rectangles if x < 40 and y < 40] | |
| if group1_rects: | |
| min_x = min(r[0] for r in group1_rects) | |
| min_y = min(r[1] for r in group1_rects) | |
| max_x = max(r[0] + r[2] for r in group1_rects) | |
| max_y = max(r[1] + r[3] for r in group1_rects) | |
| bbox1 = patches.Rectangle((min_x-2, min_y-2), max_x-min_x+4, max_y-min_y+4, | |
| linewidth=3, edgecolor='red', facecolor='none', | |
| linestyle='--', alpha=0.8) | |
| ax2.add_patch(bbox1) | |
| ax2.text((min_x+max_x)/2, max_y+5, 'MBR 1', ha='center', fontsize=12, | |
| fontweight='bold', color='red') | |
| # Group 2: top cluster | |
| group2_rects = [(x, y, w, h, l) for x, y, w, h, l in rectangles if y > 50] | |
| if group2_rects: | |
| min_x = min(r[0] for r in group2_rects) | |
| min_y = min(r[1] for r in group2_rects) | |
| max_x = max(r[0] + r[2] for r in group2_rects) | |
| max_y = max(r[1] + r[3] for r in group2_rects) | |
| bbox2 = patches.Rectangle((min_x-2, min_y-2), max_x-min_x+4, max_y-min_y+4, | |
| linewidth=3, edgecolor='green', facecolor='none', | |
| linestyle='--', alpha=0.8) | |
| ax2.add_patch(bbox2) | |
| ax2.text((min_x+max_x)/2, max_y+5, 'MBR 2', ha='center', fontsize=12, | |
| fontweight='bold', color='green') | |
| # Group 3: remaining | |
| group3_rects = [(x, y, w, h, l) for x, y, w, h, l in rectangles | |
| if not (x < 40 and y < 40) and not (y > 50)] | |
| if group3_rects: | |
| min_x = min(r[0] for r in group3_rects) | |
| min_y = min(r[1] for r in group3_rects) | |
| max_x = max(r[0] + r[2] for r in group3_rects) | |
| max_y = max(r[1] + r[3] for r in group3_rects) | |
| bbox3 = patches.Rectangle((min_x-2, min_y-2), max_x-min_x+4, max_y-min_y+4, | |
| linewidth=3, edgecolor='purple', facecolor='none', | |
| linestyle='--', alpha=0.8) | |
| ax2.add_patch(bbox3) | |
| ax2.text((min_x+max_x)/2, min_y-5, 'MBR 3', ha='center', fontsize=12, | |
| fontweight='bold', color='purple') | |
| # Draw rectangles on R-tree | |
| for x, y, w, h, label in rectangles: | |
| rect = patches.Rectangle((x, y), w, h, linewidth=2, | |
| edgecolor='blue', facecolor='lightblue', alpha=0.6) | |
| ax2.add_patch(rect) | |
| plt.tight_layout() | |
| filepath = os.path.join(OUTPUT_DIR, "1_visual_comparison.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| print(f"\n✓ Saved: {filepath}") | |
| return filepath | |
| def create_detailed_comparison(): | |
| """Create detailed algorithmic comparison diagram.""" | |
| fig = plt.figure(figsize=(16, 12)) | |
| # Title | |
| fig.suptitle("Algorithmic Differences: R-tree vs Quadtree", | |
| fontsize=18, fontweight='bold', y=0.98) | |
| # Create grid for comparison | |
| gs = fig.add_gridspec(4, 2, hspace=0.4, wspace=0.3) | |
| # PARTITIONING STRATEGY | |
| ax1 = fig.add_subplot(gs[0, 0]) | |
| ax1.set_title("Quadtree: Fixed Space Division", fontweight='bold') | |
| ax1.set_xlim(0, 100) | |
| ax1.set_ylim(0, 100) | |
| ax1.set_aspect('equal') | |
| ax1.axhline(50, color='red', linewidth=2) | |
| ax1.axvline(50, color='red', linewidth=2) | |
| ax1.text(50, 95, "Always divides space\ninto 4 equal quadrants", | |
| ha='center', fontsize=10, bbox=dict(boxstyle='round', facecolor='wheat')) | |
| ax1.axis('off') | |
| ax2 = fig.add_subplot(gs[0, 1]) | |
| ax2.set_title("R-tree: Data-driven Grouping", fontweight='bold') | |
| ax2.set_xlim(0, 100) | |
| ax2.set_ylim(0, 100) | |
| ax2.set_aspect('equal') | |
| # Draw overlapping bounding boxes | |
| rect1 = patches.Rectangle((10, 10), 40, 40, linewidth=2, | |
| edgecolor='red', facecolor='red', alpha=0.2) | |
| rect2 = patches.Rectangle((35, 35), 50, 50, linewidth=2, | |
| edgecolor='blue', facecolor='blue', alpha=0.2) | |
| ax2.add_patch(rect1) | |
| ax2.add_patch(rect2) | |
| ax2.text(50, 95, "Groups objects by proximity\n(can overlap!)", | |
| ha='center', fontsize=10, bbox=dict(boxstyle='round', facecolor='wheat')) | |
| ax2.axis('off') | |
| # NODE STRUCTURE | |
| ax3 = fig.add_subplot(gs[1, 0]) | |
| ax3.text(0.5, 0.9, "Quadtree Node Structure", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax3.transAxes) | |
| structure_text = """ | |
| • Fixed: Always 4 children (NW, NE, SW, SE) | |
| • Boundary: Center point + width/height | |
| • Split criterion: Count > capacity | |
| • Empty quadrants: Still exist in tree | |
| • Example: | |
| Node { | |
| boundary: (50, 50, 100×100) | |
| children: [NW, NE, SW, SE] | |
| points: [...] | |
| } | |
| """ | |
| ax3.text(0.1, 0.75, structure_text, ha='left', va='top', | |
| fontsize=9, family='monospace', transform=ax3.transAxes) | |
| ax3.axis('off') | |
| ax4 = fig.add_subplot(gs[1, 1]) | |
| ax4.text(0.5, 0.9, "R-tree Node Structure", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax4.transAxes) | |
| structure_text = """ | |
| • Variable: m to M children (e.g., 2-8) | |
| • MBR: Minimum Bounding Rectangle | |
| • Split criterion: Overflow + minimize area | |
| • No empty nodes | |
| • Example: | |
| Node { | |
| mbr: (x1, y1, x2, y2) | |
| children: [MBR1, MBR2, ..., MBRn] | |
| entries: [...] | |
| } | |
| """ | |
| ax4.text(0.1, 0.75, structure_text, ha='left', va='top', | |
| fontsize=9, family='monospace', transform=ax4.transAxes) | |
| ax4.axis('off') | |
| # INSERTION ALGORITHM | |
| ax5 = fig.add_subplot(gs[2, 0]) | |
| ax5.text(0.5, 0.95, "Quadtree Insertion", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax5.transAxes) | |
| algo_text = """ | |
| 1. Check if point in boundary | |
| 2. If capacity not full: add here | |
| 3. Else: subdivide into 4 quadrants | |
| 4. Recursively insert into children | |
| 5. Based on spatial position only | |
| Key: Deterministic - same point | |
| always goes to same quadrant | |
| """ | |
| ax5.text(0.05, 0.8, algo_text, ha='left', va='top', | |
| fontsize=9, transform=ax5.transAxes) | |
| ax5.axis('off') | |
| ax6 = fig.add_subplot(gs[2, 1]) | |
| ax6.text(0.5, 0.95, "R-tree Insertion", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax6.transAxes) | |
| algo_text = """ | |
| 1. Choose subtree (minimize area increase) | |
| 2. If node full: split node | |
| 3. Choose split: minimize overlap & area | |
| 4. Adjust MBRs up the tree | |
| 5. Based on grouping heuristics | |
| Key: Non-deterministic - depends on | |
| insertion order and split strategy | |
| """ | |
| ax6.text(0.05, 0.8, algo_text, ha='left', va='top', | |
| fontsize=9, transform=ax6.transAxes) | |
| ax6.axis('off') | |
| # QUERY ALGORITHM | |
| ax7 = fig.add_subplot(gs[3, 0]) | |
| ax7.text(0.5, 0.95, "Quadtree Query", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax7.transAxes) | |
| query_text = """ | |
| Query: Find all objects in range R | |
| 1. If boundary doesn't intersect R: STOP | |
| 2. Check all objects in this node | |
| 3. If subdivided: | |
| - Recursively query NW | |
| - Recursively query NE | |
| - Recursively query SW | |
| - Recursively query SE | |
| Prunes: Entire quadrants | |
| """ | |
| ax7.text(0.05, 0.8, query_text, ha='left', va='top', | |
| fontsize=9, transform=ax7.transAxes) | |
| ax7.axis('off') | |
| ax8 = fig.add_subplot(gs[3, 1]) | |
| ax8.text(0.5, 0.95, "R-tree Query", ha='center', va='top', | |
| fontsize=12, fontweight='bold', transform=ax8.transAxes) | |
| query_text = """ | |
| Query: Find all objects in range R | |
| 1. If MBR doesn't intersect R: STOP | |
| 2. If leaf: check all entries | |
| 3. If internal: | |
| - For each child MBR: | |
| - If intersects R: recurse | |
| Prunes: Minimum bounding rectangles | |
| (can be more/less efficient | |
| depending on data) | |
| """ | |
| ax8.text(0.05, 0.8, query_text, ha='left', va='top', | |
| fontsize=9, transform=ax8.transAxes) | |
| ax8.axis('off') | |
| filepath = os.path.join(OUTPUT_DIR, "2_algorithmic_details.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| print(f"✓ Saved: {filepath}") | |
| def create_use_case_comparison(): | |
| """Create use-case comparison chart.""" | |
| fig, ax = plt.subplots(figsize=(14, 10)) | |
| ax.axis('off') | |
| # Title | |
| ax.text(0.5, 0.95, "When to Use: R-tree vs Quadtree", | |
| ha='center', fontsize=18, fontweight='bold', transform=ax.transAxes) | |
| # Create comparison table | |
| categories = [ | |
| ("Data Type", "Points", "Rectangles/Polygons"), | |
| ("Dimensionality", "2D only", "2D, 3D, N-D"), | |
| ("Distribution", "Any (adaptive)", "Clustered data (optimal)"), | |
| ("Overlap", "No overlap in structure", "MBRs can overlap"), | |
| ("Memory", "Higher (empty nodes)", "Lower (no empty nodes)"), | |
| ("Balancing", "Automatic (space-based)", "Requires rebalancing"), | |
| ("Query Speed", "Fast for uniform data", "Fast for clustered data"), | |
| ("Insert Complexity", "O(log n)", "O(log n) avg, O(n) worst"), | |
| ("Implementation", "Simpler", "More complex"), | |
| ] | |
| y_start = 0.85 | |
| y_step = 0.08 | |
| # Header | |
| ax.text(0.25, y_start + 0.02, "QUADTREE", ha='center', fontsize=14, | |
| fontweight='bold', color='blue', transform=ax.transAxes) | |
| ax.text(0.50, y_start + 0.02, "Aspect", ha='center', fontsize=14, | |
| fontweight='bold', transform=ax.transAxes) | |
| ax.text(0.75, y_start + 0.02, "R-TREE", ha='center', fontsize=14, | |
| fontweight='bold', color='red', transform=ax.transAxes) | |
| # Draw rows | |
| for i, (category, quad, rtree) in enumerate(categories): | |
| y = y_start - (i + 1) * y_step | |
| # Background | |
| if i % 2 == 0: | |
| rect = patches.Rectangle((0.05, y - 0.025), 0.9, y_step * 0.9, | |
| facecolor='lightgray', alpha=0.3, | |
| transform=ax.transAxes) | |
| ax.add_patch(rect) | |
| # Category | |
| ax.text(0.50, y, category, ha='center', fontsize=11, fontweight='bold', | |
| transform=ax.transAxes) | |
| # Quadtree | |
| ax.text(0.25, y, quad, ha='center', fontsize=10, color='blue', | |
| transform=ax.transAxes) | |
| # R-tree | |
| ax.text(0.75, y, rtree, ha='center', fontsize=10, color='red', | |
| transform=ax.transAxes) | |
| # Use case recommendations | |
| y_bottom = y_start - len(categories) * y_step - 0.08 | |
| ax.text(0.5, y_bottom, "SEMICONDUCTOR DESIGN RECOMMENDATIONS", | |
| ha='center', fontsize=14, fontweight='bold', | |
| bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5), | |
| transform=ax.transAxes) | |
| quad_cases = """ | |
| QUADTREE: | |
| ✓ Point data (component centers, vias) | |
| ✓ Uniform grid layouts | |
| ✓ 2D chip designs | |
| ✓ Fast prototyping (simpler code) | |
| ✓ Teaching/learning spatial structures | |
| ✓ Memory is not critical | |
| """ | |
| rtree_cases = """ | |
| R-TREE: | |
| ✓ Rectangle/polygon data (masks, traces) | |
| ✓ Clustered layouts (realistic circuits) | |
| ✓ 3D structures (multi-layer routing) | |
| ✓ Production systems (optimized) | |
| ✓ Large-scale polygon operations | |
| ✓ Minimize memory footprint | |
| """ | |
| ax.text(0.25, y_bottom - 0.15, quad_cases, ha='center', va='top', fontsize=10, | |
| family='monospace', bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.5), | |
| transform=ax.transAxes) | |
| ax.text(0.75, y_bottom - 0.15, rtree_cases, ha='center', va='top', fontsize=10, | |
| family='monospace', bbox=dict(boxstyle='round', facecolor='lightcoral', alpha=0.5), | |
| transform=ax.transAxes) | |
| filepath = os.path.join(OUTPUT_DIR, "3_use_case_comparison.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| print(f"✓ Saved: {filepath}") | |
| def main(): | |
| """Generate all comparison visualizations.""" | |
| print("\n" + "█"*70) | |
| print("R-TREE VS QUADTREE: COMPLETE ALGORITHMIC COMPARISON") | |
| print("█"*70) | |
| print("\n" + "="*70) | |
| print("KEY ALGORITHMIC DIFFERENCES") | |
| print("="*70) | |
| print("\n1. PARTITIONING STRATEGY") | |
| print(" Quadtree: SPACE-DRIVEN") | |
| print(" • Divides space into 4 equal quadrants") | |
| print(" • Fixed, regular subdivision") | |
| print(" • Based on spatial coordinates") | |
| print("\n R-tree: DATA-DRIVEN") | |
| print(" • Groups objects into Minimum Bounding Rectangles (MBRs)") | |
| print(" • Variable number of children per node (m to M)") | |
| print(" • Based on object proximity and clustering") | |
| print("\n2. NODE STRUCTURE") | |
| print(" Quadtree: Always 4 children (or leaf)") | |
| print(" • Fixed branching factor = 4") | |
| print(" • Empty quadrants still exist") | |
| print(" • Boundary defined by center + size") | |
| print("\n R-tree: Variable children (e.g., 2-8)") | |
| print(" • Branching factor: m ≤ children ≤ M") | |
| print(" • No empty nodes") | |
| print(" • Each node stores its MBR") | |
| print("\n3. OVERLAP") | |
| print(" Quadtree: NO OVERLAP") | |
| print(" • Quadrants never overlap") | |
| print(" • Each point belongs to exactly one quadrant") | |
| print("\n R-tree: CAN OVERLAP") | |
| print(" • MBRs can overlap (inevitable with rectangles)") | |
| print(" • Objects may be checked multiple times in queries") | |
| print("\n4. INSERTION") | |
| print(" Quadtree:") | |
| print(" • Find quadrant containing point") | |
| print(" • If full, subdivide into 4") | |
| print(" • Deterministic (same point → same location)") | |
| print("\n R-tree:") | |
| print(" • Choose subtree that needs least enlargement") | |
| print(" • If full, split node (complex heuristics)") | |
| print(" • Non-deterministic (depends on insert order)") | |
| print("\n5. COMPLEXITY") | |
| print(" Quadtree:") | |
| print(" • Insert: O(log₄ n) = O(log n)") | |
| print(" • Query: O(log n) average, O(n) worst-case") | |
| print(" • Simple to implement") | |
| print("\n R-tree:") | |
| print(" • Insert: O(log_M n) average, O(n) worst-case") | |
| print(" • Query: O(log n) average for good data") | |
| print(" • Complex splitting algorithms") | |
| print("\n" + "="*70) | |
| print("GENERATING VISUALIZATIONS...") | |
| print("="*70) | |
| visualize_comparison() | |
| create_detailed_comparison() | |
| create_use_case_comparison() | |
| print("\n" + "="*70) | |
| print("INTERVIEW KEY POINTS") | |
| print("="*70) | |
| print("\nQuadtree:") | |
| print(" ✓ Pro: Simpler, faster for points, good for uniform data") | |
| print(" ✓ Pro: Adaptive subdivision, guaranteed O(log n) depth") | |
| print(" ✗ Con: Only 2D, wastes space on empty quadrants") | |
| print(" ✗ Con: Poor for rectangles (stores by center point)") | |
| print("\nR-tree:") | |
| print(" ✓ Pro: Optimal for rectangles/polygons, works in N dimensions") | |
| print(" ✓ Pro: No wasted space, better for clustered data") | |
| print(" ✗ Con: More complex, can have overlap, may need rebalancing") | |
| print(" ✗ Con: Insert order affects structure quality") | |
| print("\nFor Semiconductor EDA:") | |
| print(" • Use Quadtree for: Component placement, via locations, DRC (points)") | |
| print(" • Use R-tree for: Polygon masks, routing layers, large designs") | |
| print(" • Hybrid: Quadtree for quick prototyping, R-tree for production") | |
| print(f"\n✓ All visualizations saved to: {os.path.abspath(OUTPUT_DIR)}/") | |
| print("="*70) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3.12 | |
| from quads import QuadTree, Point, visualize, BoundingBox | |
| points = [ Point(50, 50), Point(200, 70), | |
| Point(100, 180), Point(220, 220), | |
| Point(30, 150) ] | |
| width, height = 512, 512 | |
| tree = QuadTree((0, 0), width, height) | |
| [tree.insert(p) for p in points] | |
| visualize(tree) | |
| # You can also perform queries, e.g., finding points within a region | |
| bb = BoundingBox(min_x=40, min_y=40, max_x=120, max_y=120) | |
| found_points = tree.within_bb(bb) | |
| print(f"\nPoints found in region {bb}:") | |
| for p in found_points: | |
| print(f" {p.x}, {p.y}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3.12 | |
| """ | |
| Scanline Algorithm for Rectangle Overlap Detection | |
| The scanline algorithm is a sweep-line technique that efficiently finds | |
| overlapping rectangles in O(n log n) time, much better than the naive O(n²). | |
| Critical for semiconductor design: | |
| - Design Rule Checking (DRC) | |
| - Mask layer validation | |
| - Component overlap detection | |
| """ | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as patches | |
| from dataclasses import dataclass | |
| from typing import List, Set, Tuple | |
| import random | |
| import os | |
| OUTPUT_DIR = "scanline_results" | |
| if not os.path.exists(OUTPUT_DIR): | |
| os.makedirs(OUTPUT_DIR) | |
| @dataclass | |
| class Rectangle: | |
| """Rectangle representation.""" | |
| id: int | |
| x1: float # Left | |
| y1: float # Bottom | |
| x2: float # Right | |
| y2: float # Top | |
| name: str = "" | |
| def __post_init__(self): | |
| if not self.name: | |
| self.name = f"R{self.id}" | |
| def overlaps(self, other: 'Rectangle') -> bool: | |
| """Check if two rectangles overlap.""" | |
| return not (self.x2 <= other.x1 or # self is completely to the left | |
| self.x1 >= other.x2 or # self is completely to the right | |
| self.y2 <= other.y1 or # self is completely below | |
| self.y1 >= other.y2) # self is completely above | |
| def __repr__(self): | |
| return f"{self.name}[({self.x1},{self.y1})-({self.x2},{self.y2})]" | |
| @dataclass | |
| class Event: | |
| """Sweep line event.""" | |
| x: float # X-coordinate of event | |
| rect_id: int # Rectangle ID | |
| is_start: bool # True if entering rectangle, False if leaving | |
| y1: float # Bottom y | |
| y2: float # Top y | |
| def __lt__(self, other): | |
| """For sorting events by x-coordinate.""" | |
| if self.x != other.x: | |
| return self.x < other.x | |
| # If same x, process end events before start events | |
| # This prevents false positives for touching rectangles | |
| return not self.is_start and other.is_start | |
| class IntervalTree: | |
| """ | |
| Simple interval tree for 1D overlap detection. | |
| Used to find overlapping y-intervals when scanline is at position x. | |
| """ | |
| def __init__(self): | |
| self.intervals = [] # List of (y1, y2, rect_id) | |
| def insert(self, y1: float, y2: float, rect_id: int): | |
| """Insert a y-interval.""" | |
| self.intervals.append((y1, y2, rect_id)) | |
| def remove(self, rect_id: int): | |
| """Remove all intervals for a rectangle.""" | |
| self.intervals = [(y1, y2, rid) for y1, y2, rid in self.intervals | |
| if rid != rect_id] | |
| def find_overlaps(self, y1: float, y2: float, rect_id: int) -> List[int]: | |
| """Find all intervals that overlap with [y1, y2].""" | |
| overlaps = [] | |
| for interval_y1, interval_y2, interval_id in self.intervals: | |
| if interval_id != rect_id: # Don't compare with self | |
| # Check if intervals overlap | |
| if not (y2 <= interval_y1 or y1 >= interval_y2): | |
| overlaps.append(interval_id) | |
| return overlaps | |
| def scanline_find_overlaps(rectangles: List[Rectangle]) -> Set[Tuple[int, int]]: | |
| """ | |
| Use scanline algorithm to find all overlapping rectangle pairs. | |
| Algorithm: | |
| 1. Create events for each rectangle (start and end) | |
| 2. Sort events by x-coordinate | |
| 3. Sweep from left to right: | |
| - When entering a rectangle: check y-overlap with active rectangles | |
| - Add rectangle to active set | |
| - When leaving: remove from active set | |
| Returns: | |
| Set of overlapping pairs (id1, id2) where id1 < id2 | |
| """ | |
| print("\n" + "="*70) | |
| print("SCANLINE ALGORITHM: Finding Overlapping Rectangles") | |
| print("="*70) | |
| # Step 1: Create events | |
| events = [] | |
| for rect in rectangles: | |
| # Start event (entering rectangle) | |
| events.append(Event(rect.x1, rect.id, True, rect.y1, rect.y2)) | |
| # End event (leaving rectangle) | |
| events.append(Event(rect.x2, rect.id, False, rect.y1, rect.y2)) | |
| # Step 2: Sort events by x-coordinate | |
| events.sort() | |
| print(f"\nProcessing {len(rectangles)} rectangles...") | |
| print(f"Created {len(events)} events (2 per rectangle)") | |
| # Step 3: Sweep through events | |
| active_intervals = IntervalTree() # Currently active rectangles | |
| overlaps = set() # Store overlapping pairs | |
| print("\nSweeping left to right:") | |
| current_x = None | |
| event_count = 0 | |
| for event in events: | |
| if current_x is None or event.x != current_x: | |
| if current_x is not None: | |
| event_count += 1 | |
| current_x = event.x | |
| if event.is_start: | |
| # Entering a rectangle: check for y-overlaps with active rectangles | |
| overlapping_ids = active_intervals.find_overlaps( | |
| event.y1, event.y2, event.rect_id | |
| ) | |
| # Record overlaps | |
| for other_id in overlapping_ids: | |
| pair = tuple(sorted([event.rect_id, other_id])) | |
| overlaps.add(pair) | |
| if len(overlaps) <= 5: # Print first few | |
| rect1 = rectangles[event.rect_id] | |
| rect2 = rectangles[other_id] | |
| print(f" Overlap found: {rect1.name} ↔ {rect2.name}") | |
| # Add to active set | |
| active_intervals.insert(event.y1, event.y2, event.rect_id) | |
| else: | |
| # Leaving a rectangle: remove from active set | |
| active_intervals.remove(event.rect_id) | |
| if len(overlaps) > 5: | |
| print(f" ... and {len(overlaps) - 5} more overlaps") | |
| print(f"\n✓ Found {len(overlaps)} overlapping pairs") | |
| print(f"✓ Processed {event_count} distinct x-coordinates") | |
| return overlaps | |
| def visualize_results(rectangles: List[Rectangle], | |
| overlapping_pairs: Set[Tuple[int, int]], | |
| filename: str = "scanline_overlap_detection", | |
| title: str = "Scanline Overlap Detection"): | |
| """Visualize rectangles with overlaps highlighted in red.""" | |
| # Determine which rectangles are involved in overlaps | |
| overlapping_ids = set() | |
| for id1, id2 in overlapping_pairs: | |
| overlapping_ids.add(id1) | |
| overlapping_ids.add(id2) | |
| fig, ax = plt.subplots(figsize=(14, 12)) | |
| # Draw all rectangles | |
| for rect in rectangles: | |
| is_overlapping = rect.id in overlapping_ids | |
| color = 'red' if is_overlapping else 'blue' | |
| alpha = 0.5 if is_overlapping else 0.3 | |
| linewidth = 2 if is_overlapping else 1 | |
| rect_patch = patches.Rectangle( | |
| (rect.x1, rect.y1), | |
| rect.x2 - rect.x1, | |
| rect.y2 - rect.y1, | |
| linewidth=linewidth, | |
| edgecolor=color, | |
| facecolor=color, | |
| alpha=alpha, | |
| label=rect.name if is_overlapping else None | |
| ) | |
| ax.add_patch(rect_patch) | |
| # Add label | |
| center_x = (rect.x1 + rect.x2) / 2 | |
| center_y = (rect.y1 + rect.y2) / 2 | |
| ax.text(center_x, center_y, rect.name, | |
| ha='center', va='center', | |
| fontsize=8, fontweight='bold', | |
| color='white' if is_overlapping else 'black') | |
| # Set plot properties | |
| all_x = [r.x1 for r in rectangles] + [r.x2 for r in rectangles] | |
| all_y = [r.y1 for r in rectangles] + [r.y2 for r in rectangles] | |
| margin_x = (max(all_x) - min(all_x)) * 0.1 | |
| margin_y = (max(all_y) - min(all_y)) * 0.1 | |
| ax.set_xlim(min(all_x) - margin_x, max(all_x) + margin_x) | |
| ax.set_ylim(min(all_y) - margin_y, max(all_y) + margin_y) | |
| ax.set_aspect('equal') | |
| ax.grid(True, alpha=0.3) | |
| ax.set_xlabel('X (nm)', fontsize=12, fontweight='bold') | |
| ax.set_ylabel('Y (nm)', fontsize=12, fontweight='bold') | |
| ax.set_title(title, fontsize=16, fontweight='bold', pad=20) | |
| # Add legend | |
| blue_patch = patches.Patch(color='blue', alpha=0.3, label='No overlap') | |
| red_patch = patches.Patch(color='red', alpha=0.5, label='Overlapping') | |
| ax.legend(handles=[blue_patch, red_patch], loc='upper right', fontsize=11) | |
| # Add statistics box | |
| stats_text = f"Total rectangles: {len(rectangles)}\n" | |
| stats_text += f"Overlapping: {len(overlapping_ids)}\n" | |
| stats_text += f"Overlap pairs: {len(overlapping_pairs)}\n" | |
| stats_text += f"Algorithm: O(n log n)" | |
| ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, | |
| verticalalignment='top', | |
| bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8), | |
| fontsize=10, family='monospace') | |
| plt.tight_layout() | |
| filepath = os.path.join(OUTPUT_DIR, f"{filename}.png") | |
| plt.savefig(filepath, dpi=150, bbox_inches='tight') | |
| plt.close() | |
| print(f"\n✓ Visualization saved: {filepath}") | |
| return filepath | |
| def example_1_simple_case(): | |
| """Example 1: Simple case with obvious overlaps.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 1: Simple Case") | |
| print("="*70) | |
| rectangles = [ | |
| Rectangle(0, 10, 10, 40, 30, "A"), # id, x1, y1, x2, y2, name | |
| Rectangle(1, 20, 20, 50, 40, "B"), # Overlaps with A | |
| Rectangle(2, 45, 15, 70, 35, "C"), # Overlaps with B | |
| Rectangle(3, 80, 10, 100, 30, "D"), # No overlap | |
| Rectangle(4, 15, 50, 35, 70, "E"), # No overlap | |
| Rectangle(5, 55, 50, 75, 70, "F"), # No overlap | |
| ] | |
| print("\nRectangles:") | |
| for rect in rectangles: | |
| print(f" {rect}") | |
| overlaps = scanline_find_overlaps(rectangles) | |
| print("\nOverlapping pairs:") | |
| for id1, id2 in sorted(overlaps): | |
| print(f" {rectangles[id1].name} ↔ {rectangles[id2].name}") | |
| visualize_results(rectangles, overlaps, | |
| "1_simple_case", | |
| "Example 1: Simple Overlap Detection") | |
| def example_2_circuit_components(): | |
| """Example 2: Realistic circuit component layout.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 2: Circuit Component Layout") | |
| print("="*70) | |
| print("\nScenario: Detecting overlapping components in PCB layout") | |
| random.seed(42) | |
| rectangles = [] | |
| # Create realistic component layout | |
| component_types = [ | |
| ("IC", 40, 40), | |
| ("Resistor", 20, 8), | |
| ("Capacitor", 15, 15), | |
| ("Connector", 30, 10), | |
| ] | |
| for i in range(30): | |
| comp_type, w, h = random.choice(component_types) | |
| x1 = random.uniform(0, 400) | |
| y1 = random.uniform(0, 300) | |
| rectangles.append(Rectangle(i, x1, y1, x1 + w, y1 + h, f"{comp_type}{i}")) | |
| print(f"\nGenerated {len(rectangles)} components") | |
| overlaps = scanline_find_overlaps(rectangles) | |
| if overlaps: | |
| print(f"\n⚠️ WARNING: {len(overlaps)} overlapping pairs detected!") | |
| print("These would cause manufacturing issues:") | |
| for id1, id2 in list(overlaps)[:5]: | |
| print(f" • {rectangles[id1].name} overlaps {rectangles[id2].name}") | |
| if len(overlaps) > 5: | |
| print(f" ... and {len(overlaps) - 5} more") | |
| else: | |
| print("\n✓ No overlaps detected - layout is valid") | |
| visualize_results(rectangles, overlaps, | |
| "2_circuit_layout", | |
| "Example 2: Circuit Component Overlap Detection (DRC)") | |
| def example_3_semiconductor_masks(): | |
| """Example 3: Semiconductor mask layer validation.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 3: Semiconductor Mask Layer Validation") | |
| print("="*70) | |
| print("\nScenario: Validating metal trace layer for short circuits") | |
| rectangles = [] | |
| # Metal traces (horizontal and vertical) | |
| # Horizontal traces | |
| rectangles.append(Rectangle(0, 10, 100, 200, 110, "Trace_H1")) | |
| rectangles.append(Rectangle(1, 50, 150, 250, 160, "Trace_H2")) | |
| rectangles.append(Rectangle(2, 100, 200, 300, 210, "Trace_H3")) | |
| # Vertical traces | |
| rectangles.append(Rectangle(3, 150, 50, 160, 250, "Trace_V1")) | |
| rectangles.append(Rectangle(4, 220, 80, 230, 220, "Trace_V2")) | |
| # Contact pads | |
| rectangles.append(Rectangle(5, 140, 90, 170, 120, "Pad1")) # Overlaps V1 | |
| rectangles.append(Rectangle(6, 210, 140, 240, 170, "Pad2")) # Overlaps H2 and V2 | |
| rectangles.append(Rectangle(7, 280, 190, 310, 220, "Pad3")) # Overlaps H3 | |
| # Vias | |
| rectangles.append(Rectangle(8, 155, 105, 160, 110, "Via1")) # Inside Pad1 | |
| rectangles.append(Rectangle(9, 225, 155, 230, 160, "Via2")) # Inside Pad2 | |
| print(f"\nMask layer contains:") | |
| print(f" - {len([r for r in rectangles if 'Trace' in r.name])} traces") | |
| print(f" - {len([r for r in rectangles if 'Pad' in r.name])} contact pads") | |
| print(f" - {len([r for r in rectangles if 'Via' in r.name])} vias") | |
| overlaps = scanline_find_overlaps(rectangles) | |
| print(f"\nValidation results:") | |
| if overlaps: | |
| print(f" ⚠️ Found {len(overlaps)} potential issues:") | |
| # Categorize overlaps | |
| intentional = 0 | |
| issues = 0 | |
| for id1, id2 in overlaps: | |
| r1, r2 = rectangles[id1], rectangles[id2] | |
| # Pad-Via overlaps are intentional (connections) | |
| if ('Pad' in r1.name and 'Via' in r2.name) or \ | |
| ('Via' in r1.name and 'Pad' in r2.name): | |
| intentional += 1 | |
| else: | |
| issues += 1 | |
| print(f" ✗ SHORT CIRCUIT: {r1.name} ↔ {r2.name}") | |
| if intentional > 0: | |
| print(f" ✓ {intentional} intentional overlaps (pad-via connections)") | |
| if issues > 0: | |
| print(f"\n ❌ MASK INVALID: {issues} unintended overlaps detected") | |
| else: | |
| print(f"\n ✓ MASK VALID: All overlaps are intentional connections") | |
| else: | |
| print(" ⚠️ No overlaps found - check if connections are missing!") | |
| visualize_results(rectangles, overlaps, | |
| "3_semiconductor_masks", | |
| "Example 3: Semiconductor Mask Validation") | |
| def example_4_performance_benchmark(): | |
| """Example 4: Performance comparison with naive algorithm.""" | |
| print("\n" + "="*70) | |
| print("EXAMPLE 4: Performance Benchmark") | |
| print("="*70) | |
| import time | |
| print("\nComparing Scanline O(n log n) vs Naive O(n²)") | |
| sizes = [50, 100, 200, 500] | |
| print(f"\n{'N':<8} {'Scanline':<15} {'Naive':<15} {'Speedup':<10}") | |
| print("-" * 50) | |
| for n in sizes: | |
| # Generate random rectangles | |
| random.seed(42) | |
| rectangles = [] | |
| for i in range(n): | |
| x1 = random.uniform(0, 1000) | |
| y1 = random.uniform(0, 1000) | |
| w = random.uniform(10, 50) | |
| h = random.uniform(10, 50) | |
| rectangles.append(Rectangle(i, x1, y1, x1 + w, y1 + h)) | |
| # Time scanline algorithm | |
| start = time.time() | |
| overlaps_scanline = scanline_find_overlaps(rectangles) | |
| scanline_time = time.time() - start | |
| # Time naive algorithm (only for small n) | |
| if n <= 200: | |
| start = time.time() | |
| overlaps_naive = set() | |
| for i, r1 in enumerate(rectangles): | |
| for j, r2 in enumerate(rectangles[i+1:], i+1): | |
| if r1.overlaps(r2): | |
| overlaps_naive.add((i, j)) | |
| naive_time = time.time() - start | |
| speedup = naive_time / scanline_time | |
| else: | |
| naive_time = None | |
| speedup = "N/A (too slow)" | |
| print(f"{n:<8} {scanline_time*1000:<14.2f}ms ", end="") | |
| if naive_time: | |
| print(f"{naive_time*1000:<14.2f}ms {speedup:<10.1f}x") | |
| else: | |
| print(f"{'(skipped)':<14} {speedup}") | |
| print("\n✓ Scanline algorithm scales to large datasets efficiently") | |
| print(" For 500 rectangles: Scanline remains fast, Naive would take ~seconds") | |
| def main(): | |
| """Run all examples.""" | |
| print("\n" + "="*70) | |
| print("SCANLINE ALGORITHM FOR RECTANGLE OVERLAP DETECTION") | |
| print("="*70) | |
| print("\nALGORITHM OVERVIEW:") | |
| print("━" * 70) | |
| print("The scanline (sweep-line) algorithm is a computational geometry") | |
| print("technique that efficiently finds overlapping rectangles.") | |
| print() | |
| print("How it works:") | |
| print(" 1. Create start/end events for each rectangle's left/right edges") | |
| print(" 2. Sort events by x-coordinate") | |
| print(" 3. Sweep a vertical line from left to right:") | |
| print(" • When entering rectangle: check y-overlap with active rectangles") | |
| print(" • Add to active set") | |
| print(" • When leaving: remove from active set") | |
| print() | |
| print("Complexity: O(n log n) - much better than naive O(n²)") | |
| print() | |
| print("Applications in Semiconductor Design:") | |
| print(" • Design Rule Checking (DRC)") | |
| print(" • Mask layer validation") | |
| print(" • Short circuit detection") | |
| print(" • Component placement verification") | |
| print("━" * 70) | |
| # Run examples | |
| example_1_simple_case() | |
| example_2_circuit_components() | |
| example_3_semiconductor_masks() | |
| example_4_performance_benchmark() | |
| print("\n" + "="*70) | |
| print("SUMMARY") | |
| print("="*70) | |
| print("\n✓ Scanline algorithm efficiently detects all overlapping rectangles") | |
| print("✓ Red rectangles in visualizations indicate overlaps") | |
| print("✓ Critical for validating semiconductor mask layers") | |
| print("✓ O(n log n) complexity enables real-time checking on large designs") | |
| print(f"\n✓ All results saved to: {os.path.abspath(OUTPUT_DIR)}/") | |
| print("="*70) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment