Module Interaction
This page describes how Larvaworld’s modules interact at runtime during simulation execution. Understanding these interactions is crucial for extending the platform or debugging behavior.
High-Level Interaction Flow
sequenceDiagram
participant User as User
participant CLI as CLI
participant SimEngine as SimEngine
participant LarvaAgent as LarvaAgent
participant Brain as Brain
participant Crawler as Crawler
participant Feeder as Feeder
participant Sensor as Sensor
participant Environment as Environment
User->>CLI: Run simulation command
CLI->>SimEngine: Initialize simulation
SimEngine->>Environment: Create arena
SimEngine->>LarvaAgent: Create larva agents
loop Simulation Loop
SimEngine->>LarvaAgent: Update timestep
LarvaAgent->>Sensor: Read environment
Sensor->>Environment: Get sensory input
Environment-->>Sensor: Return sensory data
Sensor-->>LarvaAgent: Sensory feedback
LarvaAgent->>Brain: Process sensory input
Brain->>Crawler: Generate crawling commands
Brain->>Feeder: Generate feeding commands
Crawler-->>LarvaAgent: Movement commands
Feeder-->>LarvaAgent: Feeding commands
LarvaAgent->>Environment: Execute actions
Environment-->>LarvaAgent: Action results
LarvaAgent-->>SimEngine: Update state
SimEngine->>SimEngine: Record data
end
SimEngine-->>CLI: Simulation complete
CLI-->>User: Results available
Detailed Phase-by-Phase Breakdown
Phase 1: Initialization
Sequence:
User Command: User runs
larvaworld Exp chemotaxis -N 20CLI Parsing:
argparser.pyparses argumentsSimEngine Setup:
ExpRun.__init__()configures the run object (parameters, runtime, screen manager)Environment Creation: during
ExpRun.setup(),BaseRun.build_env(...)builds arena, sources, and sensory landscapesAgent Creation: during
ExpRun.setup(),ExpRun.build_agents(...)placesLarvaSimagents (N=20)Module Initialization: each
LarvaSiminitializes its brain/locomotor/sensors from the selected model configuration
Code Path:
# cli/main.py
main() → SimModeParser.parse_args() → SimModeParser.configure()
# sim/single_run.py
ExpRun.simulate() → ABModel.run() → ExpRun.setup()
ExpRun.setup() → BaseRun.build_env() → ExpRun.build_agents()
# sim/base_run.py
BaseRun.build_env() → envs.Arena/Border/FoodGrid + create_odor_layers()
# model/agents/_larva_sim.py
LarvaSim.__init__() → LarvaMotile.__init__() → build_brain() → DefaultBrain()
Phase 2: Simulation Loop
The core execution loop runs for Nsteps timesteps (computed as duration * 60 / dt, where duration is in minutes). For dt=0.1s, Nsteps ≈ duration * 600.
2.1 Timestep Update
SimEngine → LarvaAgent: “Update to step t”
Code:
# sim/ABM_model.py (ABModel.run)
self.sim_setup(steps, seed)
while self.running:
self.sim_step() # BaseRun.sim_step: step_env → step → screen_manager.step → update
2.2 Sensory Input
LarvaAgent → Sensors → Environment
Sequence:
Larva queries sensors
Sensors read environment state (odor, food, obstacles)
Environment returns sensory data
Sensors process and return to larva
Code (excerpt):
# model/agents/_larva.py (LarvaMotile.step)
self.cum_dur += m.dt
self.sense() # placeholder; subclasses/robots can override
pos = self.olfactor_pos
if m.space.accessible_sources:
self.food_detected = m.space.accessible_sources[self]
elif self.brain.locomotor.feeder or self.brain.toucher:
self.food_detected = util.sense_food(
pos, sources=m.sources, grid=m.food_grid, radius=self.radius
)
self.resolve_carrying(self.food_detected)
lin, ang, self.feeder_motion = self.brain.step(
pos, length=self.length, on_food=self.on_food
)
Olfactory Example:
# model/modules/sensor.py (Olfactor)
from larvaworld.lib.model.modules.sensor import Olfactor
olf = Olfactor(decay_coef=0.15, perception="log")
olf.step({"odor_A": 0.6, "odor_B": 0.2})
print("First odor:", olf.first_odor_concentration)
2.3 Neural Processing
LarvaAgent → Brain: “Process sensory input”
Brain Responsibilities:
Integrate multi-sensory information
Update memory (reinforcement learning, gain adaptation)
Generate locomotor commands
Code:
# model/modules/brain.py (DefaultBrain.step)
def step(self, pos, on_food: bool = False, **kwargs):
# 1. Sense environment and update neural drive
self.sense(pos=pos, reward=on_food)
# 2. Compute locomotor commands (crawler/turner/feeder)
return self.locomotor.step(A_in=self.A_in, on_food=on_food, **kwargs)
2.4 Motor Command Generation
Brain → Crawler/Turner/Feeder: “Generate actions”
Locomotor Coordination:
# model/modules/locomotor.py (Locomotor.step)
def step(self, A_in=0, length=1, on_food=False):
C, F, T, If = self.crawler, self.feeder, self.turner, self.interference
if If:
If.cur_attenuation = 1
if F:
F.step()
if F.active and If:
If.check_module(F, "Feeder")
if C:
lin = C.step() * length
if C.active and If:
If.check_module(C, "Crawler")
else:
lin = 0
# Run/pause/feed state machine
self.step_intermitter(
stride_completed=self.stride_completed,
feed_motion=self.feed_motion,
on_food=on_food,
)
# Turning with optional crawl–turn interference
if T:
if If:
cur_att_in, cur_att_out = If.apply_attenuation(If.cur_attenuation)
else:
cur_att_in, cur_att_out = 1, 1
ang = T.step(A_in=A_in * cur_att_in) * cur_att_out
else:
ang = 0
return lin, ang, self.feed_motion
2.5 Action Execution
LarvaAgent → Environment: “Execute actions”
Physics Update:
# model/agents/_larva.py (LarvaMotile.step)
lin, ang, self.feeder_motion = self.brain.step(
pos, length=self.length, on_food=self.on_food
)
self.prepare_motion(lin=lin, ang=ang)
Feeding Action:
# model/agents/_larva.py (LarvaMotile.feed)
def feed(self, source, motion):
def get_max_V_bite():
return self.brain.locomotor.feeder.V_bite * self.V * 1000
if motion and source is not None:
grid = self.model.food_grid
a_max = get_max_V_bite()
if grid:
V = -grid.add_cell_value(source, -a_max)
else:
V = source.subtract_amount(a_max)
return V
return 0
DEB Update (energetics):
# model/agents/_larva.py (LarvaMotile.run_energetics)
def run_energetics(self, V_eaten):
self.deb.run_check(dt=self.model.dt, X_V=V_eaten)
self.length = self.deb.Lw * 10 / 1000
self.mass = self.deb.Ww
self.V = self.deb.V
2.6 State Recording
SimEngine: Record data
Data Collection:
# sim/base_run.py (BaseRun.set_collectors)
self.collectors = reg.par.get_reporters(cs=cs, agents=self.agents)
# sim/single_run.py (update/end)
self.agents.nest_record(self.collectors["step"]) # per-step variables
...
self.agents.nest_record(self.collectors["end"]) # endpoint variables
# sim/single_run.py (simulate)
self.data_collection = LarvaDatasetCollection.from_agentpy_output(self.output)
self.datasets = self.data_collection.datasets
Phase 3: Finalization
Sequence:
SimEngine: Simulation loop completes
Data Processing: Convert raw data to
LarvaDatasetStorage: Save to HDF5
Visualization: Generate plots (optional)
CLI: Return control to user
Code:
# sim/single_run.py (ExpRun.simulate)
def simulate(self, **kwargs):
self.run(**kwargs) # AgentPy run loop
if getattr(self, "aborted", False):
self.datasets = []
return self.datasets
# Collect into datasets
self.data_collection = LarvaDatasetCollection.from_agentpy_output(self.output)
self.datasets = self.data_collection.datasets
# Optional enrichment and storage
if self.p.enrichment:
for d in self.datasets:
d.enrich(**self.p.enrichment, is_last=False)
if self.store_data and not getattr(self, "aborted", False):
self.store()
Module Dependencies
Larva Agent Dependencies
LarvaSim
├── LarvaMotile (parent)
│ ├── Brain
│ │ ├── Olfactor (sensor)
│ │ ├── Toucher (sensor)
│ │ ├── Memory (learning)
│ │ └── Locomotor
│ │ ├── Crawler
│ │ ├── Turner
│ │ ├── Feeder
│ │ ├── Intermitter
│ │ └── Interference
│ ├── DEB (energetics)
│ └── Body (morphology)
└── BaseController (parent)
└── Visualization methods
Environment Dependencies
Environment (BaseRun.build_env)
├── Space/Arena (geometry + collision detection)
├── FoodGrid (food sources; optional)
├── Odor layers (GaussianValueLayer / DiffusionValueLayer; optional)
├── Thermoscape (thermal gradients; optional)
└── Windscape (wind fields; optional)
Communication Patterns
1. Sensor → Environment (Pull)
Pattern: Sensors pull data from environment on each timestep.
# model/modules/brain.py (Brain.sense_odors)
odor_input = {id: layer.get_value(pos) for id, layer in self.agent.model.odor_layers.items()}
A_olf = self.olfactor.step(odor_input)
2. Brain → Locomotor (Command)
Pattern: Brain commands locomotor modules.
# model/modules/brain.py (DefaultBrain.step)
def step(self, pos, on_food=False, **kwargs):
self.sense(pos=pos, reward=on_food)
return self.locomotor.step(A_in=self.A_in, on_food=on_food, **kwargs)
3. Locomotor → Agent (Update)
Pattern: Locomotor modules update agent state.
# model/agents/_larva.py (LarvaMotile.step)
lin, ang, self.feeder_motion = self.brain.step(
pos, length=self.length, on_food=self.on_food
)
self.prepare_motion(lin=lin, ang=ang) # applies lin/ang to body pose
4. Agent → Environment (Modify)
Pattern: Agents modify environment state (feeding, collisions).
# model/agents/_larva.py (LarvaMotile.feed)
if motion and source is not None:
a_max = self.brain.locomotor.feeder.V_bite * self.V * 1000
grid = self.model.food_grid
V = -grid.add_cell_value(source, -a_max) if grid else source.subtract_amount(a_max)
5. SimEngine → Agent (Broadcast)
Pattern: SimEngine broadcasts timestep update to all agents.
# sim/single_run.py (ExpRun.step)
self.agents.step() # AgentPy: steps all agents synchronously
Extending the Platform
Adding a New Sensor
Steps:
Create a subclass of an existing sensor (e.g.,
Olfactor,Thermosensor) in your own module.Register it as a new
modeinBrainModuleDB.BrainModuleModes.Select that mode in a model configuration (e.g.,
mm.brain.olfactor.mode = "custom").Run a simulation using that model configuration.
Example:
from larvaworld.lib import reg
from larvaworld.lib.model.modules.module_modes import BrainModuleDB
from larvaworld.lib.model.modules.sensor import Olfactor
class MyOlfactor(Olfactor):
def update(self):
super().update()
self.output *= 0.5 # example custom processing
# Register a new sensor mode (global for the current Python session)
BrainModuleDB.BrainModuleModes["olfactor"]["custom"] = MyOlfactor
# Select the new mode in a model configuration
mm = reg.conf.Model.getID("explorer").get_copy()
mm.brain.olfactor.mode = "custom"
Adding a New Behavioral Module
Steps:
Create a subclass of the appropriate module type (e.g.,
Crawler,Turner,Feeder).Register it as a new
modeinBrainModuleDB.BrainModuleModes.Select that mode in a model configuration (e.g.,
mm.brain.turner.mode = "custom").Run a simulation using that model configuration.
Example:
from larvaworld.lib import reg
from larvaworld.lib.model.modules.module_modes import BrainModuleDB
from larvaworld.lib.model.modules.turner import ConstantTurner
class MyTurner(ConstantTurner):
def update(self):
super().update()
self.output *= 0.0 # example: disable turning
BrainModuleDB.BrainModuleModes["turner"]["custom"] = MyTurner
mm = reg.conf.Model.getID("explorer").get_copy()
mm.brain.turner.mode = "custom"
For detailed tutorial, see Launch a virtual experiment.