← Back to TopologyHandler documentation
TopologyHandler - Source Code¶
File: Distributed_Design_Optimizer/postprocess/handlers/TopologyHandler.py
# Copyright (C) The DistributedDesignOptimizer Contributors
# Licensed under the GNU General Public License v3.0. See LICENSE file for details.
#
# Additional Request:
# We kindly request that any modifications or changes to the source code be
# shared with us by submitting them as pull requests to our GitHub repository.
# This request is not legally binding but is made in the spirit of
# collaboration and open-source development.
"""Topology analysis handler — orchestrates analysis and emits results.
Static analyses delegate to _static_graph_builder.
Dynamic analyses delegate to _dynamic_builder.
"""
import logging
from collections import deque
import networkx as nx
from PySide6.QtCore import QObject, Signal
from ..models.AnalysisConfig import AnalysisConfig
from ..workers.TopologyWorker import TopologyWorker
from ._topology_dispatch import resolve as _resolve_topology
logger = logging.getLogger(__name__)
class TopologyHandler(QObject):
"""Runs topology analyses and produces native rendering data."""
graph_ready = Signal(dict, str, dict)
chart_ready = Signal(object, str, dict)
html_ready = Signal(str, str, dict)
analysis_error = Signal(str)
def __init__(self, parent: QObject | None = None) -> None:
"""Initialize the topology handler.
Args:
parent: Optional parent QObject for Qt ownership.
"""
super().__init__(parent)
self._worker: TopologyWorker | None = None
def run_analysis(self, dill_data: deque, config: AnalysisConfig, filename: str) -> None:
"""Run the specified topology analysis in a background thread.
Args:
dill_data: Deque of iteration data loaded from a dill history file.
config: Analysis configuration specifying type and mode.
filename: Name of the source dill file for labeling.
"""
try:
analysis = config.analysis_type
mode = config.mode
context_data = self._extract_context(analysis, mode, dill_data)
context_data["filename"] = filename
label = f"{analysis}_{mode}"
build_fn, output_type = _resolve_topology(analysis, mode)
args = (dill_data, analysis, filename)
self.cancel()
self._worker = TopologyWorker(build_fn, args, label, context_data, output_type)
self._worker.graph_ready.connect(self.graph_ready.emit)
self._worker.chart_ready.connect(self.chart_ready.emit)
self._worker.html_ready.connect(self.html_ready.emit)
self._worker.error.connect(self.analysis_error.emit)
self._worker.start()
except Exception as e:
logger.error(f"Topology analysis setup failed: {e}", exc_info=True)
self.analysis_error.emit(str(e))
def cancel(self) -> None:
"""Cancel any running topology analysis worker."""
if self._worker is not None and self._worker.isRunning():
self._worker.requestInterruption()
self._worker.wait(2000)
self._worker = None
def validate_coordinator_file(self, metadata: dict) -> bool:
"""Check if the entry is a coordinator-type dill file.
Args:
metadata: File metadata dictionary from the file registry.
Returns:
True if the file is a coordinator dill file, False otherwise.
"""
return metadata.get("is_coordinator", False)
def _extract_context(self, analysis_type: str, mode: str, dill_data: deque) -> dict:
"""Build a context summary dict for the ChatHandler."""
context: dict = {
"analysis_type": analysis_type,
"mode": mode,
"n_iterations": len(dill_data),
}
if dill_data:
last = dill_data[-1] if isinstance(dill_data, (deque, list)) else dill_data
if isinstance(last, dict):
master = last.get("MasterGraph")
if master is not None:
if isinstance(master, (nx.Graph, nx.DiGraph, nx.MultiDiGraph)):
context["n_nodes"] = master.number_of_nodes()
context["n_edges"] = master.number_of_edges()
cluster = last.get("ClusterAnalysis")
if cluster:
context["cluster_info"] = str(cluster)[:500]
centrality = last.get("CentralityMeasures")
if centrality:
context["centrality_info"] = str(centrality)[:500]
return context