mirror of
https://github.com/bbenchoff/OrthoRoute.git
synced 2026-01-11 14:01:27 +00:00
329 lines
13 KiB
Python
329 lines
13 KiB
Python
"""
|
|
PathFinder Live Statistics Widget
|
|
Shows real-time routing progress, timing, and vital statistics
|
|
"""
|
|
|
|
import time
|
|
from typing import Dict, Any, Optional
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar,
|
|
QGroupBox, QGridLayout, QLCDNumber, QFrame
|
|
)
|
|
from PyQt6.QtCore import QTimer, pyqtSignal
|
|
from PyQt6.QtGui import QFont, QColor, QPalette
|
|
|
|
class PathFinderStatsWidget(QWidget):
|
|
"""Live statistics widget for PathFinder routing"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.start_time = None
|
|
self.current_iteration = 0
|
|
self.total_nets = 0
|
|
self.successful_routes = 0
|
|
self.failed_routes = 0
|
|
self.congestion_count = 0
|
|
self.overuse_history = [] # Track last 3 iterations: [(iter, overuse_sum, overuse_edges), ...]
|
|
|
|
self.setup_ui()
|
|
|
|
# Timer for live updates
|
|
self.update_timer = QTimer()
|
|
self.update_timer.timeout.connect(self.update_elapsed_time)
|
|
self.update_timer.setInterval(100) # Update every 100ms
|
|
|
|
def setup_ui(self):
|
|
"""Setup the statistics UI"""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
# Title
|
|
title_font = QFont()
|
|
title_font.setBold(True)
|
|
title_font.setPointSize(10)
|
|
|
|
title = QLabel("PathFinder Live Statistics")
|
|
title.setFont(title_font)
|
|
layout.addWidget(title)
|
|
|
|
# Progress Section
|
|
progress_group = self.create_progress_section()
|
|
layout.addWidget(progress_group)
|
|
|
|
# Timing Section
|
|
timing_group = self.create_timing_section()
|
|
layout.addWidget(timing_group)
|
|
|
|
# Routing Stats Section
|
|
routing_group = self.create_routing_stats_section()
|
|
layout.addWidget(routing_group)
|
|
|
|
# Congestion Section
|
|
congestion_group = self.create_congestion_section()
|
|
layout.addWidget(congestion_group)
|
|
|
|
layout.addStretch()
|
|
|
|
def create_progress_section(self) -> QGroupBox:
|
|
"""Create PathFinder iteration progress section"""
|
|
group = QGroupBox("PathFinder Progress")
|
|
layout = QVBoxLayout(group)
|
|
|
|
# Current iteration
|
|
iter_layout = QHBoxLayout()
|
|
iter_layout.addWidget(QLabel("Iteration:"))
|
|
self.iteration_label = QLabel("0 / 50")
|
|
iter_layout.addWidget(self.iteration_label)
|
|
iter_layout.addStretch()
|
|
layout.addLayout(iter_layout)
|
|
|
|
# Progress bar
|
|
self.iteration_progress = QProgressBar()
|
|
self.iteration_progress.setMaximum(50) # Default max iterations
|
|
self.iteration_progress.setValue(0)
|
|
layout.addWidget(self.iteration_progress)
|
|
|
|
# Overuse history display (last 3 iterations in table format)
|
|
overuse_font = QFont("Courier New", 9) # Monospace for table alignment
|
|
self.overuse_label = QLabel("Waiting for routing...")
|
|
self.overuse_label.setFont(overuse_font)
|
|
self.overuse_label.setStyleSheet("QLabel { color: #333; background-color: #f0f0f0; padding: 5px; border: 1px solid #ccc; }")
|
|
self.overuse_label.setWordWrap(True)
|
|
layout.addWidget(self.overuse_label)
|
|
|
|
return group
|
|
|
|
def create_timing_section(self) -> QGroupBox:
|
|
"""Create timing statistics section"""
|
|
group = QGroupBox("Timing")
|
|
layout = QGridLayout(group)
|
|
|
|
# Elapsed time with large LCD display
|
|
layout.addWidget(QLabel("Elapsed:"), 0, 0)
|
|
self.elapsed_lcd = QLCDNumber(8)
|
|
self.elapsed_lcd.setSegmentStyle(QLCDNumber.SegmentStyle.Flat)
|
|
self.elapsed_lcd.setDigitCount(8)
|
|
self.elapsed_lcd.display("00:00.00")
|
|
layout.addWidget(self.elapsed_lcd, 0, 1, 1, 2)
|
|
|
|
# Iteration time
|
|
layout.addWidget(QLabel("Iter Time:"), 1, 0)
|
|
self.iteration_time_label = QLabel("0.00s")
|
|
layout.addWidget(self.iteration_time_label, 1, 1)
|
|
|
|
# Estimated total
|
|
layout.addWidget(QLabel("Est. Total:"), 1, 2)
|
|
self.estimated_total_label = QLabel("--:--")
|
|
layout.addWidget(self.estimated_total_label, 1, 3)
|
|
|
|
return group
|
|
|
|
def create_routing_stats_section(self) -> QGroupBox:
|
|
"""Create routing success statistics"""
|
|
group = QGroupBox("Routing Statistics")
|
|
layout = QGridLayout(group)
|
|
|
|
# Total nets
|
|
layout.addWidget(QLabel("Total Nets:"), 0, 0)
|
|
self.total_nets_label = QLabel("0")
|
|
layout.addWidget(self.total_nets_label, 0, 1)
|
|
|
|
# Successful routes
|
|
layout.addWidget(QLabel("Routed:"), 1, 0)
|
|
self.success_label = QLabel("0")
|
|
self.success_label.setStyleSheet("QLabel { color: green; font-weight: bold; }")
|
|
layout.addWidget(self.success_label, 1, 1)
|
|
|
|
# Failed routes
|
|
layout.addWidget(QLabel("Failed:"), 1, 2)
|
|
self.failed_label = QLabel("0")
|
|
self.failed_label.setStyleSheet("QLabel { color: red; }")
|
|
layout.addWidget(self.failed_label, 1, 3)
|
|
|
|
# Success rate
|
|
layout.addWidget(QLabel("Success Rate:"), 2, 0)
|
|
self.success_rate_label = QLabel("0.0%")
|
|
layout.addWidget(self.success_rate_label, 2, 1)
|
|
|
|
# Routes per second
|
|
layout.addWidget(QLabel("Routes/sec:"), 2, 2)
|
|
self.routes_per_sec_label = QLabel("0.0")
|
|
layout.addWidget(self.routes_per_sec_label, 2, 3)
|
|
|
|
return group
|
|
|
|
def create_congestion_section(self) -> QGroupBox:
|
|
"""Create congestion statistics section"""
|
|
group = QGroupBox("Congestion Analysis")
|
|
layout = QGridLayout(group)
|
|
|
|
# Congested edges
|
|
layout.addWidget(QLabel("Congested Edges:"), 0, 0)
|
|
self.congestion_count_label = QLabel("0")
|
|
layout.addWidget(self.congestion_count_label, 0, 1)
|
|
|
|
# Congestion progress bar
|
|
layout.addWidget(QLabel("Congestion Level:"), 1, 0)
|
|
self.congestion_progress = QProgressBar()
|
|
self.congestion_progress.setMaximum(100)
|
|
self.congestion_progress.setValue(0)
|
|
layout.addWidget(self.congestion_progress, 1, 1, 1, 3)
|
|
|
|
# Congestion trend
|
|
layout.addWidget(QLabel("Trend:"), 2, 0)
|
|
self.congestion_trend_label = QLabel("Stable")
|
|
layout.addWidget(self.congestion_trend_label, 2, 1)
|
|
|
|
return group
|
|
|
|
def start_routing(self, total_nets: int, max_iterations: int = 50):
|
|
"""Start routing session"""
|
|
self.start_time = time.time()
|
|
self.total_nets = total_nets
|
|
self.current_iteration = 0
|
|
self.successful_routes = 0
|
|
self.failed_routes = 0
|
|
self.congestion_count = 0
|
|
|
|
# Update UI
|
|
self.total_nets_label.setText(str(total_nets))
|
|
self.iteration_progress.setMaximum(max_iterations)
|
|
self.iteration_progress.setValue(0)
|
|
self.iteration_label.setText(f"0 / {max_iterations}")
|
|
self.overuse_history = [] # Reset overuse history
|
|
self.overuse_label.setText("Initializing routing...")
|
|
|
|
# Start timer
|
|
self.update_timer.start()
|
|
|
|
def update_iteration(self, iteration: int, max_iterations: int, overuse_sum: int = 0, overuse_edges: int = 0):
|
|
"""Update current iteration progress with overuse data"""
|
|
self.current_iteration = iteration
|
|
self.iteration_progress.setValue(iteration)
|
|
self.iteration_label.setText(f"{iteration} / {max_iterations}")
|
|
|
|
# Track overuse history (keep last 3 iterations)
|
|
self.overuse_history.append((iteration, overuse_sum, overuse_edges))
|
|
if len(self.overuse_history) > 3:
|
|
self.overuse_history.pop(0)
|
|
|
|
# Build table display for last 3 iterations
|
|
if self.overuse_history:
|
|
lines = []
|
|
lines.append("┌─────────┬──────────┬────────┐")
|
|
lines.append("│ Iter │ Overuse │ Edges │")
|
|
lines.append("├─────────┼──────────┼────────┤")
|
|
|
|
for iter_num, over_sum, over_edges in self.overuse_history:
|
|
# Format with proper spacing for alignment
|
|
iter_str = f"{iter_num:>5}"
|
|
over_str = f"{over_sum:>8,}"
|
|
edges_str = f"{over_edges:>6,}"
|
|
lines.append(f"│ {iter_str} │ {over_str} │ {edges_str} │")
|
|
|
|
lines.append("└─────────┴──────────┴────────┘")
|
|
|
|
table_text = "\n".join(lines)
|
|
self.overuse_label.setText(table_text)
|
|
|
|
def update_routing_stats(self, successful: int, failed: int):
|
|
"""Update routing success statistics"""
|
|
self.successful_routes = successful
|
|
self.failed_routes = failed
|
|
|
|
self.success_label.setText(str(successful))
|
|
self.failed_label.setText(str(failed))
|
|
|
|
# Calculate success rate
|
|
total_attempted = successful + failed
|
|
if total_attempted > 0:
|
|
success_rate = (successful / total_attempted) * 100
|
|
self.success_rate_label.setText(f"{success_rate:.1f}%")
|
|
else:
|
|
self.success_rate_label.setText("0.0%")
|
|
|
|
# Calculate routes per second
|
|
if self.start_time:
|
|
elapsed = time.time() - self.start_time
|
|
if elapsed > 0:
|
|
routes_per_sec = total_attempted / elapsed
|
|
self.routes_per_sec_label.setText(f"{routes_per_sec:.1f}")
|
|
|
|
def update_congestion(self, congested_edges: int, total_edges: int, trend: str = "Stable"):
|
|
"""Update congestion statistics"""
|
|
self.congestion_count = congested_edges
|
|
self.congestion_count_label.setText(str(congested_edges))
|
|
|
|
# Update congestion progress bar
|
|
if total_edges > 0:
|
|
congestion_percent = min(100, (congested_edges / total_edges) * 100)
|
|
self.congestion_progress.setValue(int(congestion_percent))
|
|
|
|
# Color code based on congestion level
|
|
if congestion_percent > 75:
|
|
self.congestion_progress.setStyleSheet("QProgressBar::chunk { background-color: red; }")
|
|
elif congestion_percent > 50:
|
|
self.congestion_progress.setStyleSheet("QProgressBar::chunk { background-color: orange; }")
|
|
elif congestion_percent > 25:
|
|
self.congestion_progress.setStyleSheet("QProgressBar::chunk { background-color: yellow; }")
|
|
else:
|
|
self.congestion_progress.setStyleSheet("QProgressBar::chunk { background-color: green; }")
|
|
|
|
self.congestion_trend_label.setText(trend)
|
|
|
|
def update_elapsed_time(self):
|
|
"""Update elapsed time display"""
|
|
if self.start_time:
|
|
elapsed = time.time() - self.start_time
|
|
minutes = int(elapsed // 60)
|
|
seconds = elapsed % 60
|
|
time_str = f"{minutes:02d}:{seconds:05.2f}"
|
|
self.elapsed_lcd.display(time_str)
|
|
|
|
def finish_routing(self, success: bool, final_message: str = ""):
|
|
"""Finish routing session"""
|
|
self.update_timer.stop()
|
|
|
|
# Keep the overuse table visible, just update iteration label
|
|
if success:
|
|
self.iteration_label.setText(self.iteration_label.text() + " ✓")
|
|
else:
|
|
self.iteration_label.setText(self.iteration_label.text() + " ✗")
|
|
|
|
def reset(self):
|
|
"""Reset all statistics"""
|
|
self.update_timer.stop()
|
|
self.start_time = None
|
|
self.current_iteration = 0
|
|
self.successful_routes = 0
|
|
self.failed_routes = 0
|
|
self.congestion_count = 0
|
|
|
|
# Reset UI
|
|
self.iteration_label.setText("0 / 50")
|
|
self.iteration_progress.setValue(0)
|
|
self.overuse_history = []
|
|
self.overuse_label.setText("Waiting for routing...")
|
|
self.elapsed_lcd.display("00:00.00")
|
|
self.success_label.setText("0")
|
|
self.failed_label.setText("0")
|
|
self.success_rate_label.setText("0.0%")
|
|
self.routes_per_sec_label.setText("0.0")
|
|
self.congestion_count_label.setText("0")
|
|
self.congestion_progress.setValue(0)
|
|
self.congestion_trend_label.setText("Stable")
|
|
|
|
def get_statistics_summary(self) -> Dict[str, Any]:
|
|
"""Get current statistics as dictionary"""
|
|
elapsed = time.time() - self.start_time if self.start_time else 0
|
|
|
|
return {
|
|
'elapsed_time': elapsed,
|
|
'current_iteration': self.current_iteration,
|
|
'total_nets': self.total_nets,
|
|
'successful_routes': self.successful_routes,
|
|
'failed_routes': self.failed_routes,
|
|
'success_rate': self.successful_routes / max(1, self.successful_routes + self.failed_routes),
|
|
'routes_per_second': (self.successful_routes + self.failed_routes) / max(0.1, elapsed),
|
|
'congested_edges': self.congestion_count
|
|
} |