View on GitHub

Visualization Module API

The visualization module provides graph rendering capabilities using Graphviz.

Module: graph_renderer.py

Generates visual representations of circuit DAGs.

Functions

visualize_gate_graph(dag_filename, output_filename=None)

Create and save a graph visualization from a DAG JSON file.

Parameters:

Returns:

Example:

from opentestability.visualization.graph_renderer import visualize_gate_graph

# Default output name
png_path = visualize_gate_graph("circuit_dag.json")
# Creates: data/graphs/circuit_dag_graph.png

# Custom output name
png_path = visualize_gate_graph("circuit_dag.json", "my_circuit.png")
# Creates: data/graphs/my_circuit.png

CLI Usage:

python3 -m opentestability.visualization.graph_renderer circuit_dag.json

load_dag(dag_filename)

Load DAG data from JSON file.

Parameters:

Returns:

Raises:

Example:

from opentestability.visualization.graph_renderer import load_dag

edges, labels, pi, po = load_dag("circuit_dag.json")
print(f"Graph has {len(edges)} edges")
print(f"Inputs: {pi}")
print(f"Outputs: {po}")

create_graph_visualization(edges, labels, primary_inputs, primary_outputs)

Create a styled Graphviz graph from DAG data.

Parameters:

Returns:

Example:

from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

edges, labels, pi, po = load_dag("circuit_dag.json")
graph = create_graph_visualization(edges, labels, pi, po)

# Customize further
graph.graph_attr['rankdir'] = 'TB'  # Top to bottom layout
graph.layout(prog='dot')
graph.draw('custom_output.png')

format_gate_label(label)

Format gate labels for better visualization.

Parameters:

Returns:

Example:

from opentestability.visualization.graph_renderer import format_gate_label

label = format_gate_label("very_long_signal_name (AND2X1)")
# Returns truncated version for display

render_graph_with_custom_style(dag_filename, style_config=None)

Render graph with custom styling options.

Parameters:

Returns:

Default Style Config:

{
    'input_color': '#aec7e8',      # Light blue
    'output_color': '#98df8a',     # Light green
    'gate_color': '#ffbb78',       # Light orange
    'wire_color': '#d3d3d3',       # Light gray
    'edge_color': '#000000',       # Black
    'font_name': 'Arial',
    'font_size': 10,
    'node_shape': 'box',
    'rankdir': 'LR'                # Left to right
}

Example:

from opentestability.visualization.graph_renderer import render_graph_with_custom_style

# Custom dark theme
dark_style = {
    'input_color': '#4a90e2',
    'output_color': '#50c878',
    'gate_color': '#ff6b6b',
    'wire_color': '#cccccc',
    'edge_color': '#333333',
    'font_name': 'Courier',
    'font_size': 12,
    'rankdir': 'TB'  # Top to bottom
}

render_graph_with_custom_style("circuit_dag.json", dark_style)

Visual Styling

Node Types and Colors

The visualization uses different colors for different node types:

Node Type Default Color Description
Primary Input Light Blue (#aec7e8) Circuit inputs
Primary Output Light Green (#98df8a) Circuit outputs
Gate Light Orange (#ffbb78) Logic gates
Wire Light Gray (#d3d3d3) Intermediate signals

Layout Algorithms

Graphviz supports multiple layout engines via the prog parameter:

Engine Best For Characteristics
dot Hierarchical graphs Directed, top-down
neato Undirected graphs Spring model
fdp Large graphs Force-directed
sfdp Very large graphs Scalable force-directed
circo Circular layout Radial
twopi Radial layout Tree structure

Example:

from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

edges, labels, pi, po = load_dag("circuit_dag.json")
graph = create_graph_visualization(edges, labels, pi, po)

# Try different layouts
for layout in ['dot', 'neato', 'fdp']:
    graph.layout(prog=layout)
    graph.draw(f'circuit_{layout}.png')

Advanced Usage

Custom Node Attributes

import pygraphviz as pgv
from opentestability.visualization.graph_renderer import load_dag

edges, labels, pi, po = load_dag("circuit_dag.json")

# Create custom graph
G = pgv.AGraph(directed=True, strict=False)
G.graph_attr['rankdir'] = 'LR'
G.graph_attr['dpi'] = '300'  # High resolution

# Add nodes with custom attributes
for node, label in labels.items():
    if node in pi:
        G.add_node(node, label=label, shape='ellipse', 
                   fillcolor='lightblue', style='filled')
    elif node in po:
        G.add_node(node, label=label, shape='ellipse',
                   fillcolor='lightgreen', style='filled')
    else:
        G.add_node(node, label=label, shape='box',
                   fillcolor='wheat', style='filled')

# Add edges with weights
for source, target in edges:
    G.add_edge(source, target, color='gray', penwidth=2)

G.layout(prog='dot')
G.draw('custom_circuit.png')

Highlighting Critical Paths

from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

# Load graph
edges, labels, pi, po = load_dag("circuit_dag.json")
graph = create_graph_visualization(edges, labels, pi, po)

# Highlight specific paths (e.g., from SCOAP analysis)
critical_signals = ['n15', 'n23', 'n45']
for node in critical_signals:
    n = graph.get_node(node)
    n.attr['fillcolor'] = 'red'
    n.attr['style'] = 'filled'

graph.layout(prog='dot')
graph.draw('critical_path.png')

Subgraph Visualization

from opentestability.visualization.graph_renderer import load_dag
import pygraphviz as pgv

edges, labels, pi, po = load_dag("large_circuit_dag.json")

# Extract subgraph around a specific node
center_node = 'n25'
depth = 2

# BFS to find nearby nodes
from collections import deque
visited = set()
queue = deque([(center_node, 0)])
subgraph_nodes = set()

while queue:
    node, d = queue.popleft()
    if d <= depth and node not in visited:
        visited.add(node)
        subgraph_nodes.add(node)
        # Add neighbors
        for src, tgt in edges:
            if src == node:
                queue.append((tgt, d + 1))

# Create subgraph
G = pgv.AGraph(directed=True)
for node in subgraph_nodes:
    G.add_node(node, label=labels.get(node, node))
    
for src, tgt in edges:
    if src in subgraph_nodes and tgt in subgraph_nodes:
        G.add_edge(src, tgt)

G.layout(prog='dot')
G.draw(f'subgraph_{center_node}.png')

Output Formats

Supported Formats

Graphviz supports multiple output formats:

from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

edges, labels, pi, po = load_dag("circuit_dag.json")
graph = create_graph_visualization(edges, labels, pi, po)
graph.layout(prog='dot')

# Different output formats
graph.draw('circuit.png')    # PNG (default)
graph.draw('circuit.pdf')    # PDF (vector)
graph.draw('circuit.svg')    # SVG (vector)
graph.draw('circuit.ps')     # PostScript
graph.draw('circuit.dot')    # DOT source

Resolution Control

# High-resolution output for publications
graph.graph_attr['dpi'] = '300'
graph.draw('high_res_circuit.png')

# Lower resolution for quick previews
graph.graph_attr['dpi'] = '72'
graph.draw('preview_circuit.png')

Integration Examples

With SCOAP Results

import json
from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

# Load DAG
edges, labels, pi, po = load_dag("circuit_dag.json")

# Load SCOAP results
with open("data/results/scoap_results.json", 'r') as f:
    scoap = json.load(f)

# Create graph
graph = create_graph_visualization(edges, labels, pi, po)

# Color nodes by controllability (CC0 + CC1)
for node in labels:
    if node in scoap:
        cc = scoap[node]['CC0'] + scoap[node]['CC1']
        # Red = hard to control, Green = easy to control
        if cc > 10:
            color = 'red'
        elif cc > 5:
            color = 'yellow'
        else:
            color = 'lightgreen'
        
        n = graph.get_node(node)
        n.attr['fillcolor'] = color
        n.attr['style'] = 'filled'

graph.layout(prog='dot')
graph.draw('scoap_colored_circuit.png')

With Reconvergence Analysis

import json
from opentestability.visualization.graph_renderer import (
    load_dag, create_graph_visualization
)

# Load data
edges, labels, pi, po = load_dag("circuit_dag.json")
with open("data/reconvergence_output/circuit_reconv.json", 'r') as f:
    reconv = json.load(f)

# Create graph
graph = create_graph_visualization(edges, labels, pi, po)

# Highlight reconvergence points
reconv_points = set()
for rc in reconv:
    reconv_points.add(rc['fanout_point'])
    reconv_points.add(rc['reconverge_point'])

for node in reconv_points:
    n = graph.get_node(node)
    n.attr['fillcolor'] = 'orange'
    n.attr['style'] = 'filled'
    n.attr['penwidth'] = 3

graph.layout(prog='dot')
graph.draw('reconvergence_highlighted.png')

Performance

Large Graphs

For circuits with 1000+ nodes:

Optimization Tips

  1. Use subgraph visualization for exploration
  2. Reduce node labels for large graphs
  3. Use vector formats (SVG/PDF) for scalability
  4. Adjust DPI based on usage (72 for screen, 300 for print)
# For large circuits, simplify labels
def simplify_label(label):
    # Remove gate types for cleaner view
    return label.split('(')[0]

for node in graph.nodes():
    n = graph.get_node(node)
    n.attr['label'] = simplify_label(n.attr['label'])

Dependencies

Required Packages

System Requirements


Troubleshooting

Common Issues

ImportError: pygraphviz

# Install system graphviz first, then:
pip install --upgrade pygraphviz

Layout engine not found

# Check available programs
import pygraphviz as pgv
print(pgv.AGraph().layout_programs())

Memory issues with large graphs

# Use sfdp for large graphs
graph.layout(prog='sfdp')

# Or reduce graph size with subgraph extraction