""" Analysis and Visualization for Static vs Dynamic Simulation Comparison. This module compares results between static (no mobility) and dynamic (with mobility) network simulations, generating comparison metrics and visualizations. """ import json import csv from pathlib import Path from typing import Dict, List, Tuple import matplotlib.pyplot as plt import numpy as np class StaticDynamicAnalyzer: """ Compare static and dynamic simulation results. Loads JSON results from both modes and generates: - Comparison tables - Impact metrics (percentage differences) - Visualization graphs """ def __init__(self, dynamic_file: str, static_file: str): """ Initialize analyzer with result files. Args: dynamic_file: Path to dynamic simulation JSON results static_file: Path to static simulation JSON results """ self.dynamic_results = self._load_json(dynamic_file) self.static_results = self._load_json(static_file) self.comparison_data = {} @staticmethod def _load_json(filepath: str) -> Dict: """Load and return JSON results (DRY: single loading method).""" try: with open(filepath, 'r') as f: return json.load(f) except FileNotFoundError: print(f"Warning: {filepath} not found") return {} def _extract_metric(self, results: Dict, scenario: str, protocol: str, metric: str): """Extract a single metric (DRY pattern).""" try: return results[scenario][protocol]['metrics'].get(metric) except (KeyError, TypeError): return None def compute_comparison(self) -> Dict: """ Compute static vs dynamic comparison for all scenarios. Returns: Dict: Comparison data with impact percentages """ metrics_to_compare = [ 'first_dead_node_round', 'first_muted_round', 'dlbi', 'rspi', 'final_alive_nodes' ] comparison = {} # Get all scenario names from dynamic results for scenario in self.dynamic_results.keys(): comparison[scenario] = {'LEACH': {}, 'LEACH-C': {}} for protocol in ['LEACH', 'LEACH-C']: for metric in metrics_to_compare: dyn = self._extract_metric(self.dynamic_results, scenario, protocol, metric) stat = self._extract_metric(self.static_results, scenario, protocol, metric) # Compute impact (percentage difference) if isinstance(dyn, (int, float)) and isinstance(stat, (int, float)): if stat != 0: impact = ((dyn - stat) / stat) * 100 else: impact = 0 else: impact = None comparison[scenario][protocol][metric] = { 'dynamic': dyn, 'static': stat, 'impact_pct': impact } self.comparison_data = comparison return comparison def generate_csv_report(self, output_file: str = "comparison_static_dynamic.csv"): """ Generate CSV report of static vs dynamic comparison. Args: output_file: Output CSV filename """ with open(output_file, 'w', newline='') as f: writer = csv.writer(f) writer.writerow([ 'Scenario', 'Protocol', 'Metric', 'Dynamic', 'Static', 'Impact(%)' ]) for scenario, protocols in self.comparison_data.items(): for protocol, metrics in protocols.items(): for metric, values in metrics.items(): writer.writerow([ scenario, protocol, metric, values.get('dynamic', 'N/A'), values.get('static', 'N/A'), f"{values.get('impact_pct', 'N/A'):.2f}" if values.get('impact_pct') is not None else 'N/A' ]) print(f"CSV report generated: {output_file}") def plot_comparison(self, metric: str = 'first_dead_node_round', output_file: str = None): """ Generate comparison bar chart for a metric. Args: metric: Metric to compare output_file: Output PNG filename (optional) """ scenarios = list(self.comparison_data.keys()) leach_dynamic = [] leach_static = [] leachc_dynamic = [] leachc_static = [] for scenario in scenarios: leach_dyn = self.comparison_data[scenario]['LEACH'][metric]['dynamic'] leach_stat = self.comparison_data[scenario]['LEACH'][metric]['static'] leachc_dyn = self.comparison_data[scenario]['LEACH-C'][metric]['dynamic'] leachc_stat = self.comparison_data[scenario]['LEACH-C'][metric]['static'] # Handle None values leach_dynamic.append(leach_dyn if leach_dyn is not None else 0) leach_static.append(leach_stat if leach_stat is not None else 0) leachc_dynamic.append(leachc_dyn if leachc_dyn is not None else 0) leachc_static.append(leachc_stat if leachc_stat is not None else 0) x = np.arange(len(scenarios)) width = 0.2 fig, ax = plt.subplots(figsize=(12, 6)) ax.bar(x - 1.5*width, leach_dynamic, width, label='LEACH Dynamic') ax.bar(x - 0.5*width, leach_static, width, label='LEACH Static') ax.bar(x + 0.5*width, leachc_dynamic, width, label='LEACH-C Dynamic') ax.bar(x + 1.5*width, leachc_static, width, label='LEACH-C Static') ax.set_xlabel('Scenario') ax.set_ylabel(metric.replace('_', ' ').title()) ax.set_title(f'Static vs Dynamic Comparison: {metric}') ax.set_xticks(x) ax.set_xticklabels(scenarios, rotation=45, ha='right') ax.legend() ax.grid(axis='y', alpha=0.3) plt.tight_layout() if output_file is None: output_file = f"comparison_{metric}.png" plt.savefig(output_file, dpi=300) print(f"Graph saved: {output_file}") plt.close() def print_summary(self): """Print a summary of static vs dynamic comparison.""" print("\n" + "="*80) print("STATIC VS DYNAMIC COMPARISON SUMMARY") print("="*80) for scenario in self.comparison_data.keys(): print(f"\n{scenario}:") print("-" * 80) for protocol in ['LEACH', 'LEACH-C']: metrics = self.comparison_data[scenario][protocol] print(f"\n {protocol}:") for metric, values in metrics.items(): dyn = values['dynamic'] stat = values['static'] impact = values['impact_pct'] if isinstance(dyn, (int, float)) and isinstance(stat, (int, float)): print(f" {metric:30s}: Dynamic={dyn:10.2f}, Static={stat:10.2f}, Impact={impact:+.2f}%") else: print(f" {metric:30s}: Dynamic={dyn}, Static={stat}") def main(): """Run static vs dynamic analysis.""" analyzer = StaticDynamicAnalyzer( "results/simulation_results_dynamic.json", "results/simulation_results_static.json" ) # Compute comparison analyzer.compute_comparison() # Generate reports analyzer.generate_csv_report("results/comparison_static_dynamic.csv") analyzer.print_summary() # Generate visualizations for key metrics for metric in ['first_dead_node_round', 'first_muted_round', 'dlbi']: analyzer.plot_comparison( metric, output_file=f"results/comparison_{metric}.png" ) if __name__ == "__main__": main()