Behavior Modulations

Behavior Modulations#

This tutorial shows how to introduce a BehaviorModulation to modulate a behavior.

Behavior modulations are a more modular alternative to subclassing behaviors. Let’s say that you want to modify the parameters of the behavior dynamically, like

class MyBehavior(BehaviorParent):

    def compute_cmd_internal(self, time_step: float, frame: core.Frame) -> core.Twist2:
        # modify params
        return super().compute_cmd_internal(time_step, frame)

Then, instead of a subclass, you can define a modulation like

class MyModulation(core.BehaviorModulation):

    def pre(self, behavior: Behavior, time_step: float) -> None:
        # modify params of `behavior`
        ...

and add it to any behavior:

behavior = BehaviorParent()
behavior.add_modulation(MyModulation())

The result will be the same as MyModulation.pre will be executed just before the evaluation of BehaviorParent.compute_cmd. Similarly, MyModulation.pre will execute after the evaluation of BehaviorParent.compute_cmd and may modify the computed command.

Let us look a simple example where we make the agents slow down when crowded.

[1]:
from navground import core, sim
import numpy as np

class SlowdownWhenCrowded(core.BehaviorModulation, name="Slowdown"):

    _max_crowdedness = 20.0

    @property
    @core.register(20.0, "Minimal crowdedness to almost stop")
    def max_crowdedness(self) -> float:
        return self._max_crowdedness

    @max_crowdedness.setter
    def max_crowdedness(self, value: float) -> None:
        return max(0.1, self._max_crowdedness)

    def pre(self, behavior: core.Behavior, time_step: float) -> None:
        crowdedness = sum(1 / np.linalg.norm(n.position - behavior.position) for n in behavior.environment_state.neighbors)
        self._default_optimal_speed = behavior.optimal_speed
        behavior.optimal_speed *= 1 - np.clip(0, 0.99, crowdedness / self.max_crowdedness)

    def post(self, behavior: core.Behavior, time_step: float, cmd: core.Twist2) -> core.Twist2:
        behavior.optimal_speed = self._default_optimal_speed
        return cmd

Like behaviors, modulations are also registered and can be instatiated from YAML. Let us try it in a scenario where half of the agents (gold) are using the modulation while the other half not.

[2]:
scenario = sim.load_scenario("""
type: Cross
agent_margin: 0.1
side: 6
target_margin: 0.1
tolerance: 0.5
groups:
  -
    type: thymio
    number: 20
    radius: 0.08
    control_period: 0.1
    speed_tolerance: 0.02
    kinematics:
      type: 2WDiff
      wheel_axis: 0.094
      max_speed: 0.166
    behavior:
      type: HL
      optimal_speed: 0.12
      horizon: 5.0
      safety_margin: 0.02
      modulations:
         - type: Slowdown
           enabled: [true, false]
           max_crowdedness: 30.0
    color: [gold, cyan]
    state_estimation:
      type: Bounded
      range: 5.0
""")

[3]:
from navground.sim.ui.video import display_video
world = sim.World()
scenario.init_world(world)

display_video(world, time_step=0.1, duration=60, factor=20, width=1200, display_width=600)
[3]: