AgentTorch Configuration API Tutorial
This tutorial will guide you through using AgentTorch's Configuration API to set up simulations. We'll use a simple movement simulation as our primary example and then explore advanced features.
Basic Usage: Movement Simulation
Let's create a simulation where agents move randomly within a bounded space. The full code is available here. This example demonstrates the core concepts of AgentTorch's configuration system.
Project Structure
agent_torch/examples/models/movement/
├── __init__.py
├── substeps/
│ ├── __init__.py
│ ├── random_move.py
│ └── update_position.py
└── yamls/
└── config.yaml
Setting Up the Configuration
First, import the necessary builders:
from agent_torch.config import (
ConfigBuilder,
StateBuilder,
AgentBuilder,
PropertyBuilder,
EnvironmentBuilder,
SubstepBuilder,
PolicyBuilder,
TransitionBuilder
)
1. Simulation Metadata
Define the basic simulation parameters (note that all these are REQUIRED PARAMETERS):
metadata = {
"num_agents": 1000,
"num_episodes": 10,
"num_steps_per_episode": 20,
"num_substeps_per_step": 1,
"device": "cpu",
"calibration": False # if learnable parameters will be optimized externally [more details in gradient-based calibration tutorial]
}
config.set_metadata(metadata)
2. State Configuration
Configure the simulation state including agents and environment:
# Build state
state_builder = StateBuilder()
# Add agent with position property
agent_builder = AgentBuilder("citizens", metadata["num_agents"])
position = PropertyBuilder("position")\
.set_dtype("float")\
.set_shape([metadata["num_agents"], 2])\
.set_value([0.0, 0.0])
agent_builder.add_property(position)
state_builder.add_agent("citizens", agent_builder)
# Add environment bounds
env_builder = EnvironmentBuilder()
bounds = PropertyBuilder("bounds")\
.set_dtype("float")\
.set_shape([2])\
.set_value([100.0, 100.0])
env_builder.add_variable(bounds)
state_builder.set_environment(env_builder)
# Set state in config
config.set_state(state_builder.to_dict())
3. Substep Configuration
Define the simulation substeps with their policies and transitions:
# Create movement substep
movement = SubstepBuilder("Movement", "Agent movement simulation")
movement.add_active_agent("citizens")
movement.config["observation"] = {"citizens": None}
# Add movement policy
policy = PolicyBuilder()
step_size = PropertyBuilder.create_argument(
name="Step size parameter",
value=1.0,
learnable=True
).config
policy.add_policy(
"move",
"RandomMove",
{"position": "agents/citizens/position"},
["direction"],
{"step_size": step_size}
)
movement.set_policy("citizens", policy)
# Add movement transition
transition = TransitionBuilder()
bounds_param = PropertyBuilder.create_argument(
name="Environment bounds",
value=[100.0, 100.0],
shape=[2],
learnable=True
).config
transition.add_transition(
"update_position",
"UpdatePosition",
{"position": "agents/citizens/position"},
["position"],
{"bounds": bounds_param}
)
movement.set_transition(transition)
# Add substep to config and save
config.add_substep("0", movement)
config.save_yaml("models/movement/yamls/config.yaml")
4. Manual Substep Implementation
The substeps can be implemented manually as Python classes. Here's an example of the movement policy:
@Registry.register_substep("move", "policy")
class RandomMove(SubstepAction):
def forward(self, state: Dict[str, Any], observations) -> Dict[str, Any]:
positions = get_var(state, self.input_variables["position"])
num_agents = positions.shape[0]
# Get step size from learnable arguments
self.step_size = self.learnable_args["step_size"]
# Generate random angles and directions
angles = torch.rand(num_agents) * 2 * torch.pi
direction = torch.stack([
torch.cos(angles) * self.step_size,
torch.sin(angles) * self.step_size
], dim=1)
# Return using output variable name from config
outputs = {}
outputs[self.output_variables[0]] = direction
return outputs
5. Setting Up Module Structure
The substeps need to be properly imported and registered. This is done through __init__.py
files:
# models/movement/substeps/__init__.py
"""Substep implementations for movement simulation."""
from .random_move import *
from .update_position import *
# models/movement/__init__.py
"""Movement simulation model."""
from agent_torch.core import Registry
from agent_torch.core.helpers import *
from .substeps import *
# Create and populate registry as a module-level variable
registry = Registry()
These files ensure that: 1. All substep implementations are imported when the movement module is imported 2. The registry is created at the module level 3. Substeps are automatically registered via their decorators when imported
6. Running the Simulation
The movement simulation can be run using:
# run_movement_sim.py
from agent_torch.populations import sample2
from agent_torch.examples.models import movement
from agent_torch.core.environment import envs
def run_movement_simulation():
"""Run the movement simulation."""
# Create simulation runner
runner = envs.create(
model=movement,
population=sample2
)
# Get simulation parameters
sim_steps = runner.config["simulation_metadata"]["num_steps_per_episode"]
num_episodes = runner.config["simulation_metadata"]["num_episodes"]
# Run episodes
for episode in range(num_episodes):
if episode > 0:
runner.reset()
runner.step(sim_steps)
# Print statistics
positions = runner.state["agents"]["citizens"]["position"]
print(f"Episode {episode + 1} - Average position: {positions.mean(dim=0)}")
if __name__ == "__main__":
runner = run_movement_simulation()
To run the simulation:
python -m agent_torch.examples.run_movement_sim
Advanced Usage: Automatic Substep Generation
Instead of writing substeps manually, AgentTorch provides SubstepBuilderWithImpl
to automatically generate implementation templates. Let's see how we could have used it for our movement example:
from agent_torch.config import SubstepBuilderWithImpl
# Create substep with implementation generation
movement_substep = SubstepBuilderWithImpl(
name="Movement",
description="Agent movement simulation",
output_dir="models/movement/substeps"
)
movement_substep.add_active_agent("citizens")
movement_substep.config["observation"] = {"citizens": None}
# Add same policy and transition configurations
policy = PolicyBuilder()
policy.add_policy(
"move",
"RandomMove",
{"position": "agents/citizens/position"},
["direction"],
{"step_size": step_size}
)
movement_substep.add_policy("citizens", policy)
transition = TransitionBuilder()
transition.add_transition(
"update_position",
"UpdatePosition",
{"position": "agents/citizens/position"},
["position"],
{"bounds": bounds_param}
)
movement_substep.set_transition(transition)
# Generate implementation files
generated_files = movement_substep.generate_implementations()
This will generate template files for both random_move.py
and update_position.py
with:
- Proper imports and class structure
- Registry decorators
- Input/output variable handling
- TODO sections for implementation logic
You would then only need to fill in the forward logic in the TODO sections.
More Examples
For a more complex example, check out the COVID-19 simulation in agent_torch/examples/models/covid/
. This example demonstrates:
- Multiple substeps with complex interactions
- Custom observation and reward functions
- Network-based agent interactions
- Advanced use of learnable parameters
You can run it with:
python -m agent_torch.examples.run_covid_sim
Best Practices
-
Metadata: Always include required fields like
num_agents
,num_episodes
,num_steps_per_episode
, andnum_substeps_per_step
. -
PropertyBuilder Arguments: Use
PropertyBuilder.create_argument()
for learnable parameters, and access them in substeps viaself.learnable_args
orself.arguments
(if not learnable). -
Output Variables: Always use
self.output_variables[index]
as dictionary keys when returning from substep forward methods to ensure consistency with the configuration. -
Observation Structure: When no observation is needed, use
{"citizens": None}
instead of an empty dict. -
Implementation Generation: Use
SubstepBuilderWithImpl
for complex simulations to automatically generate consistent substep templates. This is especially helpful when you have many substeps or want to ensure consistent structure across your codebase.