Source code for physdes.cts.clk_tree3d_vis

"""
Clock Tree Visualization Module

This module provides functions to visualize clock trees generated by the DME algorithm
using SVG format for clear, scalable graphics.
"""

import doctest
from typing import Any, Callable, Dict, List, Optional, Tuple

from physdes.cts.dme_algorithm import Sink
from physdes.point import Point


[docs] class ClockTree3dVisualizer: """Visualizes clock trees in SVG format"""
[docs] def __init__( self, margin: int = 50, node_radius: int = 8, wire_width: int = 2, sink_color: str = "#4CAF50", internal_color: str = "#2196F3", root_color: str = "#F44336", wire_color: str = "#666666", text_color: str = "#333333", ): """ Initialize the visualizer with styling parameters Args: margin: Margin around the drawing node_radius: Radius of node circles wire_width: Width of wire lines sink_color: Color for sink nodes internal_color: Color for internal nodes root_color: Color for root node wire_color: Color for wires text_color: Color for text labels Examples: >>> viz = ClockTree3dVisualizer(margin=10, node_radius=5) >>> viz.margin 10 >>> viz.node_radius 5 """ self.margin = margin self.node_radius = node_radius self.wire_width = wire_width self.sink_color = sink_color self.internal_color = internal_color self.root_color = root_color self.wire_color = wire_color self.text_color = text_color
[docs] def visualize_tree3d( self, root: Any, sinks: List[Any], filename: str = "clock_tree3d.svg", width: int = 800, height: int = 600, analysis: Optional[Dict[str, Any]] = None, ) -> str: """ Create an SVG visualization of the clock tree3d Args: root: Root node of the clock tree3d sinks: List of original sink objects filename: Output filename width: SVG width height: SVG height analysis: Optional analysis results from DME algorithm Returns: SVG string content Examples: >>> from physdes.cts.dme_algorithm import TreeNode, Sink >>> from physdes.point import Point >>> s1 = TreeNode(name="s1", position=Point(Point(10, 0), 20)) >>> s2 = TreeNode(name="s2", position=Point(Point(30, 0), 40)) >>> root = TreeNode(name="n1", position=Point(Point(20, 0), 30), left=s1, right=s2) >>> s1.parent = root >>> s2.parent = root >>> sinks = [Sink("s1", Point(Point(10, 0), 20)), Sink("s2", Point(Point(30, 0), 40))] >>> viz = ClockTree3dVisualizer() >>> svg = viz.visualize_tree3d(root, sinks, filename="", width=200, height=200) >>> '<circle cx="75.0" cy="75.0"' in svg True >>> '<circle cx="125.0" cy="125.0"' in svg True >>> '<circle cx="100.0" cy="100.0"' in svg True """ # Collect all nodes and calculate bounds all_nodes = self._collect_all_nodes(root) min_x, min_y, max_x, max_y = self._calculate_bounds(all_nodes, sinks) # Scale coordinates to fit SVG canvas scale_x = (width - 2 * self.margin) / (max_x - min_x) if max_x > min_x else 1 scale_y = (height - 2 * self.margin) / (max_y - min_y) if max_y > min_y else 1 scale = min(scale_x, scale_y) # Maintain aspect ratio def scale_coord(x_coord: float, y_coord: float) -> tuple[float, float]: scaled_x = (x_coord - min_x) * scale + self.margin scaled_y = (y_coord - min_y) * scale + self.margin return scaled_x, scaled_y # Create SVG content svg_content = [ f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">', "<style>", " .node-label { font: 10px sans-serif; fill: #333; }", " .delay-label { font: 8px sans-serif; fill: #666; }", " .wire-label { font: 9px sans-serif; fill: #444; }", " .analysis-label { font: 12px sans-serif; fill: #333; }", "</style>", '<rect width="100%" height="100%" fill="white"/>', '<g class="clock-tree3d">', ] # Draw wires first (so they appear behind nodes) svg_content.extend(self._draw_wires3d(root, scale_coord)) # Draw nodes svg_content.extend(self._draw_nodes(root, sinks, scale_coord)) # Add analysis information if provided if analysis: svg_content.extend(self._create_analysis_box(analysis, width)) # Close SVG svg_content.extend(["</g>", "</svg>"]) svg_string = "\n".join(svg_content) # Save to file if filename: with open(filename, "w") as f: f.write(svg_string) print(f"Clock tree3d visualization saved to {filename}") return svg_string
def _collect_all_nodes(self, root: Any) -> List[Any]: """Collect all nodes in the tree3d""" nodes = [] def collect(node: Any) -> None: if node: nodes.append(node) collect(node.left) collect(node.right) collect(root) return nodes def _calculate_bounds( self, nodes: List, sinks: List ) -> Tuple[float, float, float, float]: """Calculate the bounding box of all nodes and sinks""" all_points = [] # Add tree3d nodes for node in nodes: all_points.append((node.position.xcoord.xcoord, node.position.ycoord)) # Add original sinks (in case some are not in the tree3d) for sink in sinks: all_points.append((sink.position.xcoord.xcoord, sink.position.ycoord)) if not all_points: return 0, 0, 100, 100 min_x = min(x_coord for x_coord, y_coord in all_points) max_x = max(x_coord for x_coord, y_coord in all_points) min_y = min(y_coord for x_coord, y_coord in all_points) max_y = max(y_coord for x_coord, y_coord in all_points) # Add some padding padding = max((max_x - min_x) * 0.1, (max_y - min_y) * 0.1, 10) return (min_x - padding, min_y - padding, max_x + padding, max_y + padding) def _draw_wires3d( self, root: Any, scale_coord: Callable[[Any, Any], Tuple[float, float]] ) -> List[str]: """Draw all wires in the clock tree3d""" svg_elements = [] def draw_wires_recursive(node: Any) -> None: if not node: return if node.parent: # Draw wire from parent to current node x_start, y_start = scale_coord( node.parent.position.xcoord.xcoord, node.parent.position.ycoord ) x_end, y_end = scale_coord( node.position.xcoord.xcoord, node.position.ycoord ) svg_elements.append( f'<line x1="{x_start}" y1="{y_start}" x2="{x_end}" y2="{y_end}" stroke="{self.wire_color}" stroke-width="{self.wire_width}" stroke-linecap="round"/>' ) # Add wire length label mid_x = (x_start + x_end) / 2 mid_y = (y_start + y_end) / 2 if hasattr(node, "wire_length") and node.wire_length > 0: svg_elements.append( f'<text x="{mid_x}" y="{mid_y - 5}" class="wire-label" text-anchor="middle">{node.wire_length:.1f}</text>' ) draw_wires_recursive(node.left) draw_wires_recursive(node.right) draw_wires_recursive(root) return svg_elements def _draw_nodes( self, root: Any, sinks: List[Any], scale_coord: Callable[[Any, Any], Tuple[float, float]], ) -> List[str]: """Draw all nodes in the clock tree3d""" svg_elements = [] sink_positions = { (sink.position.xcoord.xcoord, sink.position.ycoord) for sink in sinks } def draw_nodes_recursive(node: Any, depth: int = 0) -> None: if not node: return x_pos, y_pos = scale_coord( node.position.xcoord.xcoord, node.position.ycoord ) # Determine node type and color is_sink = ( node.position.xcoord.xcoord, node.position.ycoord, ) in sink_positions is_root = node.parent is None if is_root: color = self.root_color radius = self.node_radius + 2 elif is_sink: color = self.sink_color radius = self.node_radius else: color = self.internal_color radius = self.node_radius - 2 # Draw node circle svg_elements.append( f'<circle cx="{x_pos}" cy="{y_pos}" r="{radius}" fill="{color}" stroke="#333" stroke-width="1"/>' ) # Draw node label label_y_offset = -radius - 5 svg_elements.append( f'<text x="{x_pos}" y="{y_pos + label_y_offset}" class="node-label" text-anchor="middle">{node.name}</text>' ) # Draw delay information if available if hasattr(node, "delay"): delay_y_offset = radius + 12 svg_elements.append( f'<text x="{x_pos}" y="{y_pos + delay_y_offset}" class="delay-label" text-anchor="middle">d:{node.delay:.1f}</text>' ) # Draw capacitance information for sinks if is_sink and hasattr(node, "capacitance"): cap_y_offset = radius + 22 svg_elements.append( f'<text x="{x_pos}" y="{y_pos + cap_y_offset}" class="delay-label" text-anchor="middle">c:{node.capacitance:.1f}</text>' ) draw_nodes_recursive(node.left, depth + 1) draw_nodes_recursive(node.right, depth + 1) draw_nodes_recursive(root) return svg_elements def _create_analysis_box( self, analysis: Dict[str, Any], svg_width: int ) -> List[str]: """Create analysis information box""" delay_model = analysis.get("delay_model", "Unknown") analysis_text = [ "Clock Tree Analysis", f"Delay Model: {delay_model}", f"Max Delay: {analysis.get('max_delay', 0):.3f}", f"Min Delay: {analysis.get('min_delay', 0):.3f}", f"Skew: {analysis.get('skew', 0):.3f}", f"Total Wirelength: {analysis.get('total_wirelength', 0):.1f}", f"Sinks: {len(analysis.get('sink_delays', []))}", ] analysis_box = [ '<g class="analysis-info">', '<rect x="10" y="10" width="220" height="140" fill="white" stroke="#ccc" stroke-width="1" rx="5"/>', '<rect x="10" y="10" width="220" height="20" fill="#f0f0f0" stroke="#ccc" stroke-width="1" rx="5"/>', '<text x="20" y="25" font-family="sans-serif" font-size="12" font-weight="bold" fill="#333">Clock Tree Analysis</text>', '<text x="20" y="45" font-family="monospace" font-size="11" fill="#333">', ] for idx, text in enumerate(analysis_text[1:]): # Skip title line analysis_box.append( f'<tspan x="20" y="{45 + (idx + 1) * 16}">{text}</tspan>' ) analysis_box.append("</text>") analysis_box.append("</g>") return analysis_box
[docs] def create_interactive_svg( root: Any, sinks: List[Any], analysis: Optional[Dict[str, Any]], filename: str = "clock_tree3d_interactive.svg", width: int = 1000, height: int = 700, ) -> str: """ Create an interactive SVG with additional information and styling Args: root: Root node of clock tree3d sinks: List of sink objects analysis: Skew analysis results filename: Output filename width: SVG width height: SVG height Returns: SVG string content """ visualizer = ClockTree3dVisualizer( margin=60, node_radius=10, wire_width=3, sink_color="#2E7D32", internal_color="#1565C0", root_color="#C62828", wire_color="#455A64", text_color="#263238", ) svg_content = visualizer.visualize_tree3d( root, sinks, filename, width, height, analysis ) return svg_content
[docs] def create_comparison_visualization( trees_data: List[Dict[str, Any]], filename: str = "clock_tree3d_comparison.svg", width: int = 1200, height: int = 800, ) -> str: """ Create a comparison visualization of multiple clock trees Args: trees_data: List of dictionaries containing: - 'tree': root node - 'sinks': list of sinks - 'analysis': analysis results - 'title': descriptive title filename: Output filename width: SVG width height: SVG height Returns: SVG string content Examples: >>> from physdes.cts.dme_algorithm import TreeNode, Sink >>> from physdes.point import Point >>> tree1 = TreeNode("root1", Point(Point(50,0), 50)) >>> tree2 = TreeNode("root2", Point(Point(150,0), 50)) >>> sinks = [Sink("s1", Point(Point(10,0), 20)), Sink("s2", Point(Point(30,0), 40))] >>> analysis = {"skew": 0.1, "max_delay": 5.0, "total_wirelength": 100} >>> data = [ ... {"tree": tree1, "sinks": sinks, "analysis": analysis, "title": "Linear Model"}, ... {"tree": tree2, "sinks": sinks, "analysis": analysis, "title": "Elmore Model"} ... ] >>> svg = create_comparison_visualization(data, "", 800, 400) >>> "Linear Model" in svg True """ if not trees_data: raise ValueError("No tree data provided for comparison") num_trees = len(trees_data) cols = min(2, num_trees) # Maximum 2 columns rows = (num_trees + cols - 1) // cols sub_width = width // cols sub_height = height // rows svg_content = [ f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">', "<style>", " .node-label { font: 8px sans-serif; fill: #333; }", " .delay-label { font: 7px sans-serif; fill: #666; }", " .title { font: 14px sans-serif; fill: #333; font-weight: bold; }", " .comparison-label { font: 10px sans-serif; fill: #333; }", "</style>", '<rect width="100%" height="100%" fill="white"/>', ] visualizer = ClockTree3dVisualizer( margin=40, node_radius=6, wire_width=2, sink_color="#4CAF50", internal_color="#2196F3", root_color="#F44336", ) for i, tree_data in enumerate(trees_data): row = i // cols col = i % cols offset_x = col * sub_width offset_y = row * sub_height # Add title for this subplot svg_content.append( f'<text x="{offset_x + sub_width // 2}" y="{offset_y + 20}" class="title" text-anchor="middle">{tree_data.get("title", f"Tree {i + 1}")}</text>' ) # Create a temporary SVG for this tree temp_svg = visualizer.visualize_tree3d( tree_data["tree"], tree_data["sinks"], "", # No filename for temporary use sub_width - 20, sub_height - 40, tree_data.get("analysis"), ) # Extract the main content (between <g class="clock-tree3d"> and </g>) lines = temp_svg.split("\n") start_idx = -1 end_idx = -1 for line_idx, line in enumerate(lines): if '<g class="clock-tree3d">' in line: start_idx = line_idx elif ( start_idx != -1 and "</g>" in line and "clock-tree3d" not in lines[line_idx - 1] ): end_idx = line_idx break if start_idx == -1 or end_idx == -1: raise ValueError("Could not parse temporary SVG content") tree_content = lines[start_idx + 1 : end_idx] # Skip <g> and </g> # Remove the background rect if present if ( tree_content and '<rect width="100%" height="100%" fill="white"/>' in tree_content[0] ): tree_content = tree_content[1:] # Add transformed group svg_content.append( f'<g transform="translate({offset_x + 10}, {offset_y + 40})">' ) svg_content.extend(tree_content) svg_content.append("</g>") svg_content.append("</g>") # why? svg_content.append("</svg>") svg_string = "\n".join(svg_content) if filename: with open(filename, "w") as f: f.write(svg_string) print(f"Comparison visualization saved to {filename}") return svg_string
[docs] def create_delay_model_comparison3d( linear_tree_data: Dict[str, Any], elmore_tree_data: Dict[str, Any], filename: str = "delay_model_comparison3d.svg", ) -> str: """ Create a specialized comparison between linear and Elmore delay models Args: linear_tree_data: Data for linear delay model tree elmore_tree_data: Data for Elmore delay model tree filename: Output filename Returns: SVG string content """ linear_tree_data["title"] = "Linear Delay Model" elmore_tree_data["title"] = "Elmore Delay Model" return create_comparison_visualization( [linear_tree_data, elmore_tree_data], filename, width=1200, height=600 )
# Example usage function
[docs] def visualize_example_tree3d() -> Tuple[str, str, str]: """Example function demonstrating clock tree3d visualization with different delay models""" from physdes.cts.dme_algorithm import ( DMEAlgorithm, ElmoreDelayCalculator, LinearDelayCalculator, ) # Generate example clock tree3d with both delay models example_sinks = [ Sink("s1", Point(Point(-100, 0), 40), 1.0), Sink("s2", Point(Point(-60, 0), 60), 1.0), Sink("s3", Point(Point(0, 0), 40), 1.0), Sink("s4", Point(Point(20, 0), 20), 1.0), Sink("s5", Point(Point(-20, 0), -20), 1.0), Sink("s6", Point(Point(-30, 0), -50), 1.0), Sink("s7", Point(Point(-100, 0), -40), 1.0), Sink("s8", Point(Point(-100, 0), 0), 1.0), ] print("=== Generating Clock Trees with Different Delay Models ===") source = Point(Point(40, 0), -10) # Linear delay model linear_calc = LinearDelayCalculator(delay_per_unit=0.5, capacitance_per_unit=0.2) dme_linear = DMEAlgorithm(example_sinks, linear_calc, source) clock_tree_linear = dme_linear.build_clock_tree() analysis_linear = dme_linear.analyze_skew(clock_tree_linear) # Elmore delay model elmore_calc = ElmoreDelayCalculator(unit_resistance=0.1, unit_capacitance=0.2) dme_elmore = DMEAlgorithm(example_sinks, elmore_calc, source) clock_tree_elmore = dme_elmore.build_clock_tree() analysis_elmore = dme_elmore.analyze_skew(clock_tree_elmore) # Create individual visualizations visualizer = ClockTree3dVisualizer() # Linear model visualization linear_svg = visualizer.visualize_tree3d( clock_tree_linear, example_sinks, "linear_model_clock_tree3d.svg", analysis=analysis_linear, ) # Elmore model visualization elmore_svg = visualizer.visualize_tree3d( clock_tree_elmore, example_sinks, "elmore_model_clock_tree3d.svg", analysis=analysis_elmore, ) # Comparison visualization linear_data = { "tree": clock_tree_linear, "sinks": example_sinks, "analysis": analysis_linear, "title": "Linear Delay Model", } elmore_data = { "tree": clock_tree_elmore, "sinks": example_sinks, "analysis": analysis_elmore, "title": "Elmore Delay Model", } comparison_svg = create_delay_model_comparison3d(linear_data, elmore_data) print("Visualizations created:") print("- linear_model_clock_tree3d.svg: Linear delay model") print("- elmore_model_clock_tree3d.svg: Elmore delay model") print("- delay_model_comparison3d.svg: Side-by-side comparison") return linear_svg, elmore_svg, comparison_svg
if __name__ == "__main__": # Run example visualization linear_svg, elmore_svg, comparison_svg = visualize_example_tree3d() doctest.testmod()