""" Simpy-based Event-Driven Simulator for LEACH and LEACH-C Protocols. This module wraps the LEACH and LEACH-C protocols in a discrete event simulation framework using Simpy, allowing for fine-grained control over node movements, cluster head elections, and communication events. Key Features: - Event-driven architecture using Simpy's Environment - Discrete time steps for each protocol round - Node mobility as separate events - Metrics collection at defined intervals """ import simpy from typing import List, Dict from node import Node from config import ENABLE_MOBILITY class EventDrivenNetworkSimulator: """ Lightweight event-driven simulator using Simpy framework. Uses discrete events for protocol rounds and mobility. Simpler than full concurrent process model - each round is one event with defined substeps. Args: protocol: Protocol instance (LEACH or LEACHC) nodes: List of Node objects round_duration: Simulated time per round (default 1.0) """ def __init__(self, protocol, nodes: List[Node], round_duration: float = 1.0): self.env = simpy.Environment() self.protocol = protocol self.nodes = nodes self.round_duration = round_duration self.events_log = [] def _log_event(self, event_type: str, round_num: int = 0, details: Dict = None): """Log a discrete event (DRY: single logging method).""" self.events_log.append({ 'time': self.env.now, 'event': event_type, 'round': round_num, **(details or {}) }) def _execute_round_event(self, round_num: int): """ Execute one round as a discrete event. Substeps: elect CHs → form clusters → communicate → mobility """ self.protocol.run_round() alive_count = sum(1 for n in self.nodes if n.is_alive) self._log_event('ROUND_COMPLETE', round_num, { 'alive_nodes': alive_count, 'avg_energy': sum(n.energy for n in self.nodes) / len(self.nodes) if self.nodes else 0 }) return alive_count > 0 def simulation_process(self, num_rounds: int): """Simpy process: Execute all rounds as discrete events.""" for round_num in range(num_rounds): yield self.env.timeout(self.round_duration) if not self._execute_round_event(round_num): break # All nodes dead def run_simulation(self, num_rounds: int) -> Dict: """Run the event-driven simulation.""" self.env.process(self.simulation_process(num_rounds)) self.env.run() return self.protocol.get_metrics(num_rounds) def get_events_log(self) -> List[Dict]: """Get the event log.""" return self.events_log if __name__ == "__main__": """ Demo: Event-driven simulation with Simpy. Shows how discrete events are managed by Simpy framework. """ import random from leach import LEACH from config import FIELD_WIDTH, FIELD_HEIGHT, INITIAL_ENERGY print("=" * 70) print("SIMPY EVENT-DRIVEN SIMULATOR DEMONSTRATION") print("=" * 70) # Create test nodes random.seed(42) test_nodes = [] for i in range(20): # Small network for demo x = random.uniform(0, FIELD_WIDTH) y = random.uniform(0, FIELD_HEIGHT) test_nodes.append(Node(i, x, y, INITIAL_ENERGY)) # Create Simpy-based simulator protocol = LEACH(test_nodes, probability_ch=0.05, packet_size=2000) simpy_sim = EventDrivenNetworkSimulator(protocol, test_nodes) print("\nInitializing event-driven simulator with Simpy...") print(f"Initial nodes: {len(test_nodes)}") print("Running 50 rounds with discrete event model...") metrics = simpy_sim.run_simulation(num_rounds=50) # Display results print(f"\nSimulation completed at time {simpy_sim.env.now}s") print(f"Total discrete events logged: {len(simpy_sim.events_log)}") print(f"Final alive nodes: {metrics['final_alive_nodes']}") print(f"First Dead Node (FDN): {metrics['first_dead_node_round']}") print(f"First Muted Round (FMR): {metrics['first_muted_round']}") print(f"DLBI: {metrics['dlbi']:.4f}") print(f"RSPI: {metrics['rspi']:.4f}")