Advanced Step Events and Movement Patterns
This tutorial covers how to create custom step events and design realistic movement patterns for your simulations. Step events control when, where, and how agents move and interact during each simulation day.
We recommend that along with reading this, you should look at the tutorial `disease_spread_mobility.ipynb`` to see how StepEvent looks like in action
Understanding Step Events
Step events are the building blocks of agent behavior in simcronomicon. They define:
When agents perform activities (event sequence)
Where agents go (place types and distances)
How agents choose destinations (probability functions)
What agents do when they arrive (interaction functions)
Basic Step Event Creation
import simcronomicon as scon
# Simple end-of-day event (SEND_HOME)
# This event exists by default at the end of step_events list without your input.
sleep_event = scon.StepEvent(
name="end_day",
folk_action=scon.FolkSEIR.sleep
)
# Basic movement event (DISPERSE)
work_event = scon.StepEvent(
name="work_commute",
folk_action=scon.FolkSEIR.interact,
event_type=scon.EventType.DISPERSE,
max_distance=15000, # 15km max travel
place_types=['workplace']
)
Important: Automatic End-of-Day Event
You never need to manually add a sleep/end-of-day event to your step events list.
The simcronomicon framework automatically appends an “end_day” event to your step events sequence. This event:
Calls the sleep() method for all agents
Sends all agents back to their home addresses
Resets agent energy for the next day
Handles end-of-day status transitions
# DON'T do this - the end_day event is added automatically!
step_events = [
scon.StepEvent("work", scon.FolkSEIR.interact, ...),
scon.StepEvent("shopping", scon.FolkSEIR.interact, ...),
# scon.StepEvent("end_day", scon.FolkSEIR.sleep) # ← NOT NEEDED!
]
# DO this - just define your activity events
step_events = [
scon.StepEvent("work", scon.FolkSEIR.interact, ...),
scon.StepEvent("shopping", scon.FolkSEIR.interact, ...)
# End-of-day automatically added by the model
]
This automatic behavior ensures that:
Agents always return home at day’s end
Energy is properly reset for the next simulation day
You can focus on defining meaningful daily activities
The simulation maintains proper day/night cycles
Event Types Explained
- SEND_HOME Events
All agents return directly to their home addresses
No distance limitations or place type filtering
Used for: end-of-day, emergency evacuations, curfews
- DISPERSE Events
Agents move to locations within specified constraints
Enables agent interactions at destinations
Used for: work, shopping, social activities
Built-in Probability Functions
We have 2 built-in functions to simulate agent movement patterns:
Log-Normal Mobility Models realistic human travel patterns based on research literature. Best for: - Work commutes and regular activities - Shopping and errands - Healthcare visits - Any activity with preferred typical distances
# Log-normal mobility with intuitive parameters
shopping_event = scon.StepEvent(
name="shopping",
folk_action=scon.FolkSEIR.interact,
event_type=scon.EventType.DISPERSE,
max_distance=8000,
place_types=['commercial'],
probability_func=lambda distances, agent: scon.log_normal_mobility(
distances, agent, median_distance=2000, sigma=1.2)
)
Energy-Dependent Exponential Mobility Models agent movement based on current energy levels. Best for: - Social activities after work - Leisure activities - Any energy-dependent behavior
# Energy-dependent mobility with distance scaling
social_event = scon.StepEvent(
name="evening_social",
folk_action=scon.FolkSEIR.interact,
event_type=scon.EventType.DISPERSE,
max_distance=15000,
place_types=['commercial', 'entertainment'],
probability_func=lambda distances, agent: scon.energy_exponential_mobility(
distances, agent, distance_scale=2000)
)
Parameter Guidelines:
Log-Normal Mobility: - median_distance: 400m (local), 1100m (neighborhood), 3000m (city-wide), 8000m (regional) - sigma: 0.8 (consistent), 1.0 (moderate), 1.5 (variable)
Energy Exponential Mobility: - distance_scale: 200 (very local), 1000 (moderate), 3000 (wide range)
Creating Custom Probability Functions
But maybe you might want to use other types of function to define the probability of an agent going somewhere that is dependent with the distances. You can define them yourselves!
Your probability function must:
Accept exactly 2 non-default arguments: (distances, agent)
Return probabilities between 0 and 1
Probabilities should sum to 1. This means you must normalize the probabilities!
Handle numpy arrays for distances
Be robust to edge cases (empty arrays, zero distances)
Here is an example of how you can define your own simple probability function:
def distance_preference_mobility(distances, agent, preference="nearby"):
import numpy as np
distances = np.array(distances)
if preference == "nearby":
# Exponential decay - prefer closer locations
probs = np.exp(-distances / 2000) # 2km characteristic distance
elif preference == "far":
# Prefer moderate to far distances
probs = distances / np.max(distances) if len(distances) > 1 else [1.0]
else:
# Uniform - all distances equally likely
probs = np.ones_like(distances)
# Use custom function
exploration_event = scon.StepEvent(
name="exploration",
folk_action=scon.FolkSEIR.interact,
event_type=scon.EventType.DISPERSE,
max_distance=20000,
place_types=['commercial', 'religious', 'education'],
probability_func=lambda dists: distance_preference(dists, "far")
)
Agent-Dependent Probability Functions
The power of the 2-parameter system is enabling agent-specific behavior. For example, if you have an SEIR model, you can make assumption about agent’s mobility dependence with their status:
def status_based_mobility(distances, agent):
"""
Movement patterns that depend on agent health status.
"""
import numpy as np
distances = np.array(distances)
# Quarantined agents cannot move (handled elsewhere)
# Sick agents prefer shorter distances
if hasattr(agent, 'status'):
if agent.status == 'I': # Infectious - stay closer to home
probs = np.exp(-distances / 1000) # 1km characteristic distance
elif agent.status == 'R': # Recovered - normal mobility
probs = np.exp(-distances / 3000) # 3km characteristic distance
else: # Susceptible - slightly more adventurous
probs = np.exp(-distances / 4000) # 4km characteristic distance
else:
# Default behavior for other statuses
probs = np.exp(-distances / 2000)
return probs / probs.sum() if probs.sum() > 0 else np.ones_like(probs) / len(probs)
Complete Example: Daily Routine
# Define a realistic daily schedule with varied movement patterns
def create_daily_events():
return [
# Morning commute - log-normal for realistic work travel
scon.StepEvent(
"morning_commute",
scon.FolkSEIR.interact,
scon.EventType.DISPERSE,
max_distance=20000,
place_types=['workplace', 'education'],
probability_func=lambda distances, agent: scon.log_normal_mobility(
distances, agent, median_distance=5000, sigma=1.0)
),
# Lunch break - energy-dependent for tired workers
scon.StepEvent(
"lunch_break",
scon.FolkSEIR.interact,
scon.EventType.DISPERSE,
max_distance=3000,
place_types=['commercial'],
probability_func=lambda distances, agent: scon.energy_exponential_mobility(
distances, agent, distance_scale=800)
),
# Evening activities - custom preference function
scon.StepEvent(
"evening_social",
scon.FolkSEIR.interact,
scon.EventType.DISPERSE,
max_distance=15000,
place_types=['commercial', 'religious', 'entertainment'],
probability_func=lambda distances, agent: distance_preference_mobility(
distances, agent, "far")
),
]
# Use in simulation
step_events = create_daily_events()
model = scon.SEIRModel(model_params, step_events)
Tips for Effective Step Events
- Event Timing
Order events logically (commute → work → lunch → home)
Consider realistic time constraints for each activity
- Distance Constraints
Match max_distance to activity type (nearby shopping vs. long commutes)
Consider transportation modes in your model area
- Place Type Selection
Be specific: [‘workplace’] vs. [‘commercial’, ‘workplace’]
Ensure your town has the required place types
- Probability Function Parameters
Log-normal median_distance: Set to typical travel distance for the activity
Log-normal sigma: Lower for consistent behavior, higher for varied patterns
Energy exponential distance_scale: Lower for local activities, higher for wide-range movement
Test with sample distances before using in simulation
Parameter Testing Example
# Test your probability functions with sample data
import numpy as np
class TestAgent:
def __init__(self, energy=5, max_energy=10):
self.energy = energy
self.max_energy = max_energy
test_distances = np.array([100, 500, 1000, 2000, 5000])
test_agent = TestAgent()
# Test log-normal mobility
log_probs = scon.log_normal_mobility(test_distances, test_agent,
median_distance=1500, sigma=1.0)
print(f"Log-normal probabilities: {log_probs}")
# Test energy exponential mobility
energy_probs = scon.energy_exponential_mobility(test_distances, test_agent,
distance_scale=1000)
print(f"Energy exponential probabilities: {energy_probs}")
Debugging Step Events
# Test your probability function
test_distances = [100, 500, 1000, 5000, 10000]
test_probs = distance_preference(test_distances, "nearby")
print(f"Distances: {test_distances}")
print(f"Probabilities: {test_probs}")
print(f"Sum: {sum(test_probs)}") # Should be close to 1.0
# Validate step events before simulation
events = create_daily_events()
for event in events:
print(f"Event: {event.name}")
print(f" Type: {event.event_type}")
print(f" Max distance: {event.max_distance}m")
print(f" Place types: {event.place_types}")
Next Steps
Experiment with different probability functions for the same activity
Create event sequences that reflect real-world daily patterns
Combine step events with advanced model features (vaccination, quarantine)
Consider seasonal or policy-driven changes to movement patterns
For more complex scenarios, see the SEIQRDV advanced features tutorial and the full API documentation.