""" Comparison utilities for analytical vs simulation results. """ from typing import Dict, Tuple from dataclasses import dataclass from .jackson import JacksonAnalyzer, NetworkAnalytics from ..core.simulation import SimulationResults @dataclass class QueueComparison: """Comparison of analytical vs simulation for a single queue.""" queue_id: str # Analytical analytical_utilization: float analytical_avg_customers: float analytical_avg_time: float analytical_avg_wait: float # Simulation simulation_utilization: float simulation_avg_customers: float simulation_avg_time: float simulation_avg_wait: float # Differences (%) utilization_diff_percent: float avg_customers_diff_percent: float avg_time_diff_percent: float avg_wait_diff_percent: float @dataclass class NetworkComparison: """Complete comparison of analytical vs simulation results.""" is_stable: bool coordinator: QueueComparison servers: Dict[str, QueueComparison] # System-wide analytical_total_L: float analytical_total_W: float simulation_total_L: float simulation_total_W: float total_L_diff_percent: float total_W_diff_percent: float def calculate_percentage_difference(analytical: float, simulation: float) -> float: """ Calculate percentage difference: ((simulation - analytical) / analytical) * 100 Args: analytical: Analytical value simulation: Simulation value Returns: Percentage difference """ if analytical == 0: return 0.0 if simulation == 0 else 100.0 return ((simulation - analytical) / analytical) * 100.0 def compare_queue( queue_id: str, analytical: 'QueueAnalytics', simulation: Dict ) -> QueueComparison: """ Compare analytical vs simulation results for a single queue. Args: queue_id: Queue identifier analytical: QueueAnalytics from Jackson's theorem simulation: Simulation statistics dict Returns: QueueComparison with all metrics """ # Handle unstable queues if not analytical.is_stable: analytical_L = float('inf') analytical_W = float('inf') analytical_Wq = float('inf') else: analytical_L = analytical.average_customers analytical_W = analytical.average_time analytical_Wq = analytical.average_wait_time simulation_rho = simulation['utilization'] simulation_W = simulation['average_system_time'] simulation_Wq = simulation['average_wait_time'] # Estimate L from simulation using Little's Law: L = λ * W simulation_L = analytical.arrival_rate * simulation_W if analytical.arrival_rate > 0 else 0 return QueueComparison( queue_id=queue_id, analytical_utilization=analytical.utilization, analytical_avg_customers=analytical_L, analytical_avg_time=analytical_W, analytical_avg_wait=analytical_Wq, simulation_utilization=simulation_rho, simulation_avg_customers=simulation_L, simulation_avg_time=simulation_W, simulation_avg_wait=simulation_Wq, utilization_diff_percent=calculate_percentage_difference( analytical.utilization, simulation_rho ), avg_customers_diff_percent=calculate_percentage_difference( analytical_L, simulation_L ), avg_time_diff_percent=calculate_percentage_difference( analytical_W, simulation_W ), avg_wait_diff_percent=calculate_percentage_difference( analytical_Wq, simulation_Wq ) ) def compare_results( analytical: NetworkAnalytics, simulation: SimulationResults ) -> NetworkComparison: """ Compare complete analytical vs simulation results. Args: analytical: NetworkAnalytics from Jackson's theorem simulation: SimulationResults from simulation Returns: NetworkComparison with all metrics """ # Compare coordinator coordinator_comp = compare_queue( "coordinator", analytical.coordinator, simulation.coordinator_stats ) # Compare servers server_comps = {} for server_id, server_analytical in analytical.servers.items(): server_simulation = simulation.server_stats[server_id] server_comps[server_id] = compare_queue( server_id, server_analytical, server_simulation ) # System-wide comparison simulation_total_W = simulation.average_system_time # Estimate total L using Little's Law: L = λ₀ × W # IMPORTANT: Use external arrival rate λ₀, NOT coordinator effective rate γ₀ external_lambda = analytical.external_arrival_rate simulation_total_L = external_lambda * simulation_total_W if external_lambda > 0 else 0 return NetworkComparison( is_stable=analytical.is_stable, coordinator=coordinator_comp, servers=server_comps, analytical_total_L=analytical.total_average_customers, analytical_total_W=analytical.total_average_time, simulation_total_L=simulation_total_L, simulation_total_W=simulation_total_W, total_L_diff_percent=calculate_percentage_difference( analytical.total_average_customers, simulation_total_L ), total_W_diff_percent=calculate_percentage_difference( analytical.total_average_time, simulation_total_W ) ) def print_comparison(comparison: NetworkComparison) -> None: """Print a formatted comparison report.""" print("=" * 80) print("ANALYTICAL VS SIMULATION COMPARISON") print("=" * 80) print(f"\n🌐 System-Wide Metrics:") print(f" {'Metric':<20} {'Analytical':<15} {'Simulation':<15} {'Diff %':<10}") print(" " + "-" * 60) print(f" {'Total L (customers)':<20} {comparison.analytical_total_L:<15.4f} " f"{comparison.simulation_total_L:<15.4f} {comparison.total_L_diff_percent:>9.2f}%") print(f" {'Total W (time)':<20} {comparison.analytical_total_W:<15.4f} " f"{comparison.simulation_total_W:<15.4f} {comparison.total_W_diff_percent:>9.2f}%") print(f"\n📊 Coordinator Queue:") coord = comparison.coordinator print(f" {'Metric':<20} {'Analytical':<15} {'Simulation':<15} {'Diff %':<10}") print(" " + "-" * 60) print(f" {'Utilization (ρ)':<20} {coord.analytical_utilization:<15.4f} " f"{coord.simulation_utilization:<15.4f} {coord.utilization_diff_percent:>9.2f}%") print(f" {'Avg customers (L)':<20} {coord.analytical_avg_customers:<15.4f} " f"{coord.simulation_avg_customers:<15.4f} {coord.avg_customers_diff_percent:>9.2f}%") print(f" {'Avg time (W)':<20} {coord.analytical_avg_time:<15.4f} " f"{coord.simulation_avg_time:<15.4f} {coord.avg_time_diff_percent:>9.2f}%") print(f" {'Avg wait (Wq)':<20} {coord.analytical_avg_wait:<15.4f} " f"{coord.simulation_avg_wait:<15.4f} {coord.avg_wait_diff_percent:>9.2f}%") for server_id, server_comp in comparison.servers.items(): print(f"\n📊 {server_id}:") print(f" {'Metric':<20} {'Analytical':<15} {'Simulation':<15} {'Diff %':<10}") print(" " + "-" * 60) print(f" {'Utilization (ρ)':<20} {server_comp.analytical_utilization:<15.4f} " f"{server_comp.simulation_utilization:<15.4f} {server_comp.utilization_diff_percent:>9.2f}%") print(f" {'Avg customers (L)':<20} {server_comp.analytical_avg_customers:<15.4f} " f"{server_comp.simulation_avg_customers:<15.4f} {server_comp.avg_customers_diff_percent:>9.2f}%") print(f" {'Avg time (W)':<20} {server_comp.analytical_avg_time:<15.4f} " f"{server_comp.simulation_avg_time:<15.4f} {server_comp.avg_time_diff_percent:>9.2f}%") print(f" {'Avg wait (Wq)':<20} {server_comp.analytical_avg_wait:<15.4f} " f"{server_comp.simulation_avg_wait:<15.4f} {server_comp.avg_wait_diff_percent:>9.2f}%") print("\n" + "=" * 80) print("✅ Comparison complete") print("=" * 80)