Files
2025-11-30 08:30:10 +08:00

8.4 KiB

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
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.

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.

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.

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.

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.

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.

event = env.event()
event.succeed(value='Success!')

fail(exception)

Marks event as failed with an exception.

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.

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.

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.

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

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

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

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