# SimPy Events System This guide covers the event system in SimPy, which forms the foundation of discrete-event simulation. ## Event Basics Events are the core mechanism for controlling simulation flow. Processes yield events and resume when those events are triggered. ### Event Lifecycle Events progress through three states: 1. **Not triggered** - Initial state as memory objects 2. **Triggered** - Scheduled in event queue; `triggered` property is `True` 3. **Processed** - Removed from queue with callbacks executed; `processed` property is `True` ```python import simpy env = simpy.Environment() # Create an event event = env.event() print(f'Triggered: {event.triggered}, Processed: {event.processed}') # Both False # Trigger the event event.succeed(value='Event result') print(f'Triggered: {event.triggered}, Processed: {event.processed}') # True, False # Run to process the event env.run() print(f'Triggered: {event.triggered}, Processed: {event.processed}') # True, True print(f'Value: {event.value}') # 'Event result' ``` ## Core Event Types ### Timeout Controls time progression in simulations. Most common event type. ```python import simpy def process(env): print(f'Starting at {env.now}') yield env.timeout(5) print(f'Resumed at {env.now}') # Timeout with value result = yield env.timeout(3, value='Done') print(f'Result: {result} at {env.now}') env = simpy.Environment() env.process(process(env)) env.run() ``` **Usage:** - `env.timeout(delay)` - Wait for specified time - `env.timeout(delay, value=val)` - Wait and return value ### Process Events Processes themselves are events, allowing processes to wait for other processes to complete. ```python import simpy def worker(env, name, duration): print(f'{name} starting at {env.now}') yield env.timeout(duration) print(f'{name} finished at {env.now}') return f'{name} result' def coordinator(env): # Start worker processes worker1 = env.process(worker(env, 'Worker 1', 5)) worker2 = env.process(worker(env, 'Worker 2', 3)) # Wait for worker1 to complete result = yield worker1 print(f'Coordinator received: {result}') # Wait for worker2 result = yield worker2 print(f'Coordinator received: {result}') env = simpy.Environment() env.process(coordinator(env)) env.run() ``` ### Event Generic event that can be manually triggered. ```python import simpy def waiter(env, event): print(f'Waiting for event at {env.now}') value = yield event print(f'Event received with value: {value} at {env.now}') def triggerer(env, event): yield env.timeout(5) print(f'Triggering event at {env.now}') event.succeed(value='Hello!') env = simpy.Environment() event = env.event() env.process(waiter(env, event)) env.process(triggerer(env, event)) env.run() ``` ## Composite Events ### AllOf - Wait for Multiple Events Triggers when all specified events have occurred. ```python import simpy def process(env): # Start multiple tasks task1 = env.timeout(3, value='Task 1 done') task2 = env.timeout(5, value='Task 2 done') task3 = env.timeout(4, value='Task 3 done') # Wait for all to complete results = yield simpy.AllOf(env, [task1, task2, task3]) print(f'All tasks completed at {env.now}') print(f'Results: {results}') # Alternative syntax using & operator task4 = env.timeout(2) task5 = env.timeout(3) yield task4 & task5 print(f'Tasks 4 and 5 completed at {env.now}') env = simpy.Environment() env.process(process(env)) env.run() ``` **Returns:** Dictionary mapping events to their values **Use cases:** - Parallel task completion - Barrier synchronization - Waiting for multiple resources ### AnyOf - Wait for Any Event Triggers when at least one specified event has occurred. ```python import simpy def process(env): # Start multiple tasks with different durations fast_task = env.timeout(2, value='Fast') slow_task = env.timeout(10, value='Slow') # Wait for first to complete results = yield simpy.AnyOf(env, [fast_task, slow_task]) print(f'First task completed at {env.now}') print(f'Results: {results}') # Alternative syntax using | operator task1 = env.timeout(5) task2 = env.timeout(3) yield task1 | task2 print(f'One of the tasks completed at {env.now}') env = simpy.Environment() env.process(process(env)) env.run() ``` **Returns:** Dictionary with completed events and their values **Use cases:** - Racing conditions - Timeout mechanisms - First-to-respond scenarios ## Event Triggering Methods Events can be triggered in three ways: ### succeed(value=None) Marks event as successful. ```python event = env.event() event.succeed(value='Success!') ``` ### fail(exception) Marks event as failed with an exception. ```python def process(env): event = env.event() event.fail(ValueError('Something went wrong')) try: yield event except ValueError as e: print(f'Caught exception: {e}') env = simpy.Environment() env.process(process(env)) env.run() ``` ### trigger(event) Copies another event's outcome. ```python event1 = env.event() event1.succeed(value='Original') event2 = env.event() event2.trigger(event1) # event2 now has same outcome as event1 ``` ## Callbacks Attach functions to execute when events are triggered. ```python import simpy def callback(event): print(f'Callback executed! Event value: {event.value}') def process(env): event = env.timeout(5, value='Done') event.callbacks.append(callback) yield event env = simpy.Environment() env.process(process(env)) env.run() ``` **Note:** Yielding an event from a process automatically adds the process's resume method as a callback. ## Event Sharing Multiple processes can wait for the same event. ```python import simpy def waiter(env, name, event): print(f'{name} waiting at {env.now}') value = yield event print(f'{name} resumed with {value} at {env.now}') def trigger_event(env, event): yield env.timeout(5) event.succeed(value='Go!') env = simpy.Environment() shared_event = env.event() env.process(waiter(env, 'Process 1', shared_event)) env.process(waiter(env, 'Process 2', shared_event)) env.process(waiter(env, 'Process 3', shared_event)) env.process(trigger_event(env, shared_event)) env.run() ``` **Use cases:** - Broadcasting signals - Barrier synchronization - Coordinated process resumption ## Advanced Event Patterns ### Timeout with Cancellation ```python import simpy def process_with_timeout(env): work = env.timeout(10, value='Work complete') timeout = env.timeout(5, value='Timeout!') # Race between work and timeout result = yield work | timeout if work in result: print(f'Work completed: {result[work]}') else: print(f'Timed out: {result[timeout]}') env = simpy.Environment() env.process(process_with_timeout(env)) env.run() ``` ### Event Chaining ```python import simpy def event_chain(env): # Create chain of dependent events event1 = env.event() event2 = env.event() event3 = env.event() def trigger_sequence(env): yield env.timeout(2) event1.succeed(value='Step 1') yield env.timeout(2) event2.succeed(value='Step 2') yield env.timeout(2) event3.succeed(value='Step 3') env.process(trigger_sequence(env)) # Wait for sequence val1 = yield event1 print(f'{val1} at {env.now}') val2 = yield event2 print(f'{val2} at {env.now}') val3 = yield event3 print(f'{val3} at {env.now}') env = simpy.Environment() env.process(event_chain(env)) env.run() ``` ### Conditional Events ```python import simpy def conditional_process(env): temperature = 20 if temperature > 25: yield env.timeout(5) # Cooling required print('System cooled') else: yield env.timeout(1) # No cooling needed print('Temperature acceptable') env = simpy.Environment() env.process(conditional_process(env)) env.run() ``` ## Best Practices 1. **Always yield events**: Processes must yield events to pause execution 2. **Don't trigger events multiple times**: Events can only be triggered once 3. **Handle failures**: Use try-except when yielding events that might fail 4. **Composite events for parallelism**: Use AllOf/AnyOf for concurrent operations 5. **Shared events for broadcasting**: Multiple processes can yield the same event 6. **Event values for data passing**: Use event values to pass results between processes