Configure behavior for objects#
Each object in the simulation can be assigned a behavior independently to simulate different scenarios. The behavior of the object can be configured by specifying the behavior parameters in the YAML configuration file.
IR-SIM supports two types of behavior configuration:
behavior: Controls individual object movement (e.g.,dash,rvo)group_behavior: Coordinates behavior for all objects in a group (e.g.,orca)
Behavior Configuration Parameters#
Currently, there are two built-in individual behaviors: dash and rvo. By default, the moving objects’ behavior is static. You can set the behavior of the object to dash or rvo in the YAML configuration file. The dash behavior is a simple behavior that moves the object from its initial position to the goal position directly. The rvo behavior is a dynamic collision avoidance algorithm for multiple agents.
The example of RVO behavior is shown below:
import irsim
env = irsim.make()
for i in range(1000):
env.step()
env.render(0.05)
if env.done():
break
env.end()
world:
height: 10 # the height of the world
width: 10 # the width of the world
robot:
- number: 10
distribution: {name: 'circle', radius: 4.0, center: [5, 5]}
kinematics: {name: 'diff'}
shape:
- {name: 'circle', radius: 0.2}
behavior: {name: 'rvo', vxmax: 1.5, vymax: 1.5, acce: 1.0, factor: 1.0}
vel_min: [-3, -3.0]
vel_max: [3, 3.0]
color: ['royalblue', 'red', 'green', 'orange', 'purple', 'yellow', 'cyan', 'magenta', 'lime', 'pink', 'brown']
arrive_mode: position
goal_threshold: 0.15
plot:
show_trail: true
show_goal: true
trail_fill: true
trail_alpha: 0.2
show_trajectory: false
Important Behavior Parameters Explained#
Common Parameters:
name: Behavior type ('dash','rvo', or custom name)wander: Random goal generation after reaching current goal (default:False)target_roles: Filter objects for behavior ('all','robot','obstacle')
RVO-specific Parameters:
vxmax/vymax: Maximum velocities in x/y directions (default:1.5)acce: Maximum acceleration (default:1.0)factor: Collision penalty weight (default:1.0, higher = more conservative)mode: Algorithm variant -'rvo'(default),'hrvo', or'vo'neighbor_threshold: Detection range for nearby objects (default:3.0meters)
Dash-specific Parameters:
angle_tolerance: Orientation alignment tolerance fordiff/acker(default:0.1radians)
Full list of behavior parameters can be found in the YAML Configuration.
Group Behavior#
While behavior controls individual object movement, group_behavior enables coordinated behavior for all objects within the same group. Group behavior computes actions for all members in a single step, making it more efficient than individual behaviors for coordinated scenarios.
Behavior vs Group Behavior#
Feature |
|
|
|---|---|---|
Scope |
Individual object |
All objects in a group |
Computation |
Per-object each step |
All members at once |
Use case |
Simple navigation |
Coordinated multi-agent |
Available |
|
|
ORCA (Optimal Reciprocal Collision Avoidance)#
ORCA is a classical built-in group-level collision avoidance algorithm that computes optimal velocities for multiple agents simultaneously. It ensures smooth, collision-free navigation even with hundreds of agents.
Note
ORCA requires the pyrvo library, which is a python binding for the ORCA C++ algorithm. Install it using:
pip install pyrvo
Basic ORCA Configuration#
import irsim
env = irsim.make()
while True:
env.step()
env.render()
if env.done():
break
env.end()
world:
height: 290
width: 290
step_time: 0.1
sample_time: 0.7
offset: [-140, -140]
robot:
- number: 200
kinematics: {name: 'omni'}
shape: {name: 'circle', radius: 1.0}
distribution: {name: 'circle', radius: 140}
color: 'g'
goal_threshold: 0.1
vel_max: [3, 3]
vel_min: [-3, -3]
plot:
show_trajectory: True
show_goal: True
keep_traj_length: 50
group_behavior:
name: 'orca'
neighborDist: 15.0
maxNeighbors: 10
timeHorizon: 20.0
timeHorizonObst: 10.0
safe_radius: 0.5
maxSpeed: 2.0
ORCA Parameters Explained#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
- |
Must be |
|
|
|
Maximum distance to consider other agents as neighbors |
|
|
|
Maximum number of neighbors to consider for collision avoidance |
|
|
|
Time horizon for agent-agent collision avoidance (seconds) |
|
|
|
Time horizon for agent-obstacle collision avoidance (seconds) |
|
|
|
Additional safety margin added to agent radius |
|
|
|
Maximum speed override (uses robot’s |
|
|
|
Generate random goals when current goal is reached |
|
|
- |
Lower bounds for random goal generation |
|
|
- |
Upper bounds for random goal generation |
ORCA with Random Wandering#
For continuous simulation where agents keep moving after reaching their goals:
import irsim
env = irsim.make()
for i in range(1000):
env.step()
env.render()
if env.done():
break
env.end()
world:
height: 290
width: 290
step_time: 0.1
sample_time: 0.7
offset: [-140, -140]
robot:
- number: 100
kinematics: {name: 'omni'}
shape: {name: 'circle', radius: 1.0}
distribution: {name: 'random', range_low: [-100, -100, -3.14], range_high: [100, 100, 3.14]}
color: 'g'
goal_threshold: 0.1
vel_max: [3, 3]
vel_min: [-3, -3]
plot:
show_trajectory: True
show_goal: True
keep_traj_length: 50
group_behavior:
name: 'orca'
neighborDist: 15.0
maxNeighbors: 10
timeHorizon: 20.0
timeHorizonObst: 10.0
safe_radius: 0.5
maxSpeed: 2.0
wander: True
range_low: [-100, -100, -3.14]
range_high: [100, 100, 3.14]
Tip
Use group_behavior in YAML for most cases as it handles initialization automatically. Use the manual approach only when you need custom ORCA logic or integration with external planners.
Custom Group Behavior#
You can create custom group behaviors for coordinated multi-agent control. IR-SIM provides two registration patterns:
@register_group_behavior: Function-based (called every step)@register_group_behavior_class: Class-based (initialized once, then called every step)
Function-based vs Class-based Behaviors#
Aspect |
Function-based |
Class-based |
|---|---|---|
Decorator |
|
|
Initialization |
None (stateless) |
Once at start |
State |
No persistent state |
Can maintain state between steps |
Use case |
Simple calculations |
Complex algorithms with initialization |
Example |
Simple formation control |
ORCA (requires simulator setup) |
Custom Group Behavior Example (Class-based)#
Here’s an example of a custom group behavior that implements simple formation control:
# custom_group_methods.py
from typing import Any
import numpy as np
from irsim.lib.behavior.behavior_registry import register_group_behavior_class
from irsim.world.object_base import ObjectBase
@register_group_behavior_class("omni", "formation")
def init_formation_behavior(members: list[ObjectBase], **kwargs: Any):
"""Registered initializer returning a class-based handler."""
return FormationGroupBehavior(members, **kwargs)
class FormationGroupBehavior:
"""
Class-based formation group behavior.
Maintains formation shape while moving toward goals.
"""
def __init__(
self,
members: list[ObjectBase],
formation_gain: float = 1.0,
goal_gain: float = 0.5,
**kwargs: Any,
) -> None:
"""
Initialize formation behavior.
Args:
members: List of group members
formation_gain: Weight for formation keeping
goal_gain: Weight for goal reaching
"""
self._formation_gain = formation_gain
self._goal_gain = goal_gain
# Calculate initial formation offsets (relative positions)
if members:
centroid = np.mean([m.state[:2, 0] for m in members], axis=0)
self._offsets = [m.state[:2, 0] - centroid for m in members]
else:
self._offsets = []
def __call__(
self, members: list[ObjectBase], **kwargs: Any
) -> list[np.ndarray]:
"""
Generate velocities for all members to maintain formation.
Args:
members: Current group members
**kwargs: Additional parameters
Returns:
List of velocity arrays for each member
"""
if not members:
return []
velocities = []
# Calculate current centroid
centroid = np.mean([m.state[:2, 0] for m in members], axis=0)
# Calculate target centroid (average of all goals)
goals = [m.goal[:2, 0] for m in members if m.goal is not None]
if goals:
target_centroid = np.mean(goals, axis=0)
else:
target_centroid = centroid
for i, member in enumerate(members):
# Desired position in formation
desired_pos = centroid + self._offsets[i] if i < len(self._offsets) else member.state[:2, 0]
# Formation keeping velocity
formation_vel = self._formation_gain * (desired_pos - member.state[:2, 0])
# Goal reaching velocity
goal_vel = self._goal_gain * (target_centroid - centroid)
# Combined velocity
vel = formation_vel + goal_vel
# Clip to max speed
speed = np.linalg.norm(vel)
if speed > member.max_speed:
vel = vel / speed * member.max_speed
velocities.append(np.c_[vel])
return velocities
Using Custom Group Behavior#
import irsim
env = irsim.make()
env.load_behavior("custom_group_methods")
for i in range(500):
env.step()
env.render()
if env.done():
break
env.end()
world:
height: 20
width: 20
robot:
- number: 5
kinematics: {name: 'omni'}
shape: {name: 'circle', radius: 0.3}
distribution: {name: 'circle', radius: 3, center: [10, 10]}
color: 'blue'
goal: [15, 15]
vel_max: [2, 2]
vel_min: [-2, -2]
group_behavior:
name: 'formation'
formation_gain: 1.0
goal_gain: 0.5
Function-based Group Behavior Example#
For simpler behaviors without initialization requirements:
# custom_group_methods.py
from typing import Any
import numpy as np
from irsim.lib.behavior.behavior_registry import register_group_behavior
from irsim.world.object_base import ObjectBase
@register_group_behavior("omni", "swarm")
def beh_omni_swarm(
members: list[ObjectBase], **kwargs: Any
) -> list[np.ndarray]:
"""
Simple swarm behavior - all members move toward group centroid.
Args:
members: List of group members
**kwargs: Additional parameters (cohesion_gain, goal_gain)
Returns:
List of velocity arrays for each member
"""
if not members:
return []
cohesion_gain = kwargs.get("cohesion_gain", 0.5)
goal_gain = kwargs.get("goal_gain", 1.0)
# Calculate centroid
centroid = np.mean([m.state[:2, 0] for m in members], axis=0)
velocities = []
for member in members:
# Cohesion: move toward centroid
cohesion_vel = cohesion_gain * (centroid - member.state[:2, 0])
# Goal: move toward individual goal
if member.goal is not None:
goal_vel = goal_gain * (member.goal[:2, 0] - member.state[:2, 0])
else:
goal_vel = np.zeros(2)
vel = cohesion_vel + goal_vel
# Clip to max speed
speed = np.linalg.norm(vel)
if speed > member.max_speed:
vel = vel / speed * member.max_speed
velocities.append(np.c_[vel])
return velocities
Important
Use
@register_group_behavior_classwhen your behavior needs initialization (e.g., setting up external libraries, computing initial states)Use
@register_group_behaviorfor simple stateless behaviorsThe first argument is the kinematics type (
'omni','diff','acker')The second argument is the behavior name used in YAML
group_behavior: {name: 'your_name'}
Advanced Configuration for Custom Behavior#
Custom Behavior Function#
If you want to create a custom behavior for the object, you should first define your own behavior in a custom python script (e.g. custom_behavior_methods.py) as shown below:
from irsim.lib import register_behavior
from irsim.util.util import relative_position, WrapToPi
import numpy as np
@register_behavior("diff", "dash_custom")
def beh_diff_dash(ego_object, objects, **kwargs):
state = ego_object.state
goal = ego_object.goal
goal_threshold = ego_object.goal_threshold
_, max_vel = ego_object.get_vel_range()
angle_tolerance = kwargs.get("angle_tolerance", 0.1)
behavior_vel = DiffDash2(state, goal, max_vel, goal_threshold, angle_tolerance)
return behavior_vel
def DiffDash2(state, goal, max_vel, goal_threshold=0.1, angle_tolerance=0.2):
distance, radian = relative_position(state, goal)
if distance < goal_threshold:
return np.zeros((2, 1))
diff_radian = WrapToPi(radian - state[2, 0])
linear = max_vel[0, 0] * np.cos(diff_radian)
if abs(diff_radian) < angle_tolerance:
angular = 0
else:
angular = max_vel[1, 0] * np.sign(diff_radian)
return np.array([[linear], [angular]])
The ego_object is the object that you want to control, and the objects is the list of all objects in the simulation. The custom behavior function should return the velocity of the object. You can obtain the state, goal, and other properties from the objects. DiffDash2 is the custom behavior function that calculates the dash velocity of the object.
Important
You must use the @register_behavior decorator to register the custom behavior. The first argument of the decorator is the name of the kinematics (diff, acker, omni), and the second argument is the name of the behavior used in YAML file.
Register Behavior#
The python script and YAML to run the simulation with the custom behavior is shown below:
import irsim
env = irsim.make()
env.load_behavior("custom_behavior_methods")
for i in range(1000):
env.step()
env.render(0.01)
if env.done():
break
env.end(5)
load_behavior function loads the custom behavior methods from the python script. custom_behavior_methods is the name of the python script that contains the custom behavior function.
Note
Please place the script with the name custom_behavior_methods in the same directory as the main python script.
Now, you can use the custom behavior dash_custom in the YAML configuration file as shown below.
world:
height: 10 # the height of the world
width: 10 # the width of the world
robot:
- number: 10
distribution: {name: 'circle', radius: 4.0, center: [5, 5]}
kinematics: {name: 'diff'}
shape:
- {name: 'circle', radius: 0.2}
behavior: {name: 'dash_custom'}
vel_min: [-3, -3.0]
vel_max: [3, 3.0]
color: ['royalblue', 'red', 'green', 'orange', 'purple', 'yellow', 'cyan', 'magenta', 'lime', 'pink', 'brown']
arrive_mode: position
goal_threshold: 0.15
plot:
show_trail: true
show_goal: true
trail_fill: true
trail_alpha: 0.2
show_trajectory: false
Custom Behavior Class (Stateful)#
For behaviors that require initialization or need to maintain state between steps, use the @register_behavior_class decorator. This is useful when:
Your behavior needs one-time setup (e.g., loading models, initializing planners)
You need to track history or state across simulation steps
You want to cache computations for efficiency
Function-based vs Class-based Individual Behaviors#
Aspect |
|
|
|---|---|---|
Execution |
Called every step |
|
State |
Stateless |
Can maintain internal state |
Setup |
None |
One-time initialization in |
Use case |
Simple reactive behaviors |
Complex planners, learning-based control |
Class-based Behavior Example#
# custom_behavior_methods.py
from typing import Any
import numpy as np
from irsim.lib.behavior.behavior_registry import register_behavior_class
@register_behavior_class("diff", "smooth_dash")
def init_smooth_dash(object_info, **kwargs):
"""Registered initializer returning a class-based handler."""
return SmoothDashBehavior(object_info, **kwargs)
class SmoothDashBehavior:
"""
Class-based smooth dash behavior with velocity smoothing.
Maintains previous velocity for smooth acceleration.
"""
def __init__(self, object_info, smoothing: float = 0.3, **kwargs):
"""
Initialize smooth dash behavior.
Args:
object_info: Object information from ObjectBase
smoothing: Smoothing factor (0-1), higher = smoother
"""
self._smoothing = smoothing
self._prev_vel = np.zeros((2, 1))
self._object_info = object_info
def __call__(
self,
ego_object,
external_objects: list,
**kwargs: Any,
) -> np.ndarray:
"""
Generate smoothed velocity toward goal.
Args:
ego_object: The controlled object
external_objects: Other objects in environment
**kwargs: Additional parameters
Returns:
Smoothed velocity array (2x1)
"""
if ego_object.goal is None:
return np.zeros((2, 1))
# Calculate desired velocity (simple dash)
state = ego_object.state
goal = ego_object.goal
_, max_vel = ego_object.get_vel_range()
diff = goal[:2, 0] - state[:2, 0]
distance = np.linalg.norm(diff)
if distance < ego_object.goal_threshold:
target_vel = np.zeros((2, 1))
else:
# Convert to diff drive velocities
angle_to_goal = np.arctan2(diff[1], diff[0])
diff_angle = angle_to_goal - state[2, 0]
diff_angle = np.arctan2(np.sin(diff_angle), np.cos(diff_angle))
linear = max_vel[0, 0] * np.cos(diff_angle)
angular = max_vel[1, 0] * np.sign(diff_angle) if abs(diff_angle) > 0.1 else 0
target_vel = np.array([[linear], [angular]])
# Apply smoothing (exponential moving average)
smoothed_vel = (
self._smoothing * self._prev_vel +
(1 - self._smoothing) * target_vel
)
self._prev_vel = smoothed_vel
return smoothed_vel
Using Class-based Behavior#
import irsim
env = irsim.make()
env.load_behavior("custom_behavior_methods")
for i in range(500):
env.step()
env.render(0.05)
if env.done():
break
env.end()
world:
height: 10
width: 10
robot:
- kinematics: {name: 'diff'}
shape: {name: 'circle', radius: 0.2}
state: [2, 2, 0]
goal: [8, 8]
behavior:
name: 'smooth_dash'
smoothing: 0.5 # Custom smoothing parameter
vel_max: [2, 2]
vel_min: [-2, -2]
Behavior Registration Summary#
IR-SIM provides four behavior registration decorators:
Decorator |
Scope |
Type |
Use Case |
|---|---|---|---|
|
Individual |
Function |
Simple per-object behaviors |
|
Individual |
Class |
Stateful per-object behaviors |
|
Group |
Function |
Simple multi-agent coordination |
|
Group |
Class |
Complex multi-agent algorithms (e.g., ORCA) |
All decorators take two arguments:
Kinematics type:
'omni','diff', or'acker'Behavior name: The name used in YAML configuration
Tip
Choose the right decorator based on your needs:
Start with function-based (
@register_behavioror@register_group_behavior) for simple behaviorsUse class-based when you need initialization, state, or complex algorithms
Use group behaviors when actions depend on multiple agents together