Launch a virtual experiment
This tutorial demonstrates how to develop your own custom behavioral modules.
Let’s walk through the steps to create two custom behavioral module implementations: a custom olfactor and a custom thermosensor.
First define all required imports:
%load_ext param.ipython
from param.ipython import ParamPager
import params.IPython
import larvaworld as lw
from larvaworld.lib import reg, sim
from larvaworld.lib.reg.generators import ExpConf
# Tutorial safety switches (avoid GUI/media by default)
RUN_CUSTOM_MODULE_DEMO = False
RUN_GUI_DEMO = False
SAVE_MEDIA = False
MEDIA_DIR = "videos"
lw.VERBOSE = 1
# Interfaces we're about to customize
from larvaworld.lib.model.modules.sensor import Olfactor, Thermosensor
from larvaworld.lib.model.modules.turner import Turner
from larvaworld.lib.model.modules.module_modes import BrainModuleDB
import random
Behavioral modules in larvaworld are the building blocks used to run and control an individual agents. Larvaworld tries to be as modular as possible to allow you to write your own custom modules and plug them in.
In essence a behavioral module is a Python class that maps some input state to some output state. The output state of one module may then be fed as input state to some other behavioral module. To see how computed states of modules flow through the modular architecture please refer to our technical paper.
The basic steps to implement a custom module are:
create a new Python class
inherit from the appropriate larvaworld module base class (ie. Olfactor)
implement a ´update()´ function
compute some new output state - potentially using sensory input via ´self.input´
store the computed output state in ´self.output´ property
In the example below we will implement 2 custom modules:
Custom Olfactor
This will read the currently sensed odor value from the sensor. The output state will be the current absolute odor value multiplied by some random number
We will use that class as implementation for the olfactor sensor.
Custom Behavior Module
This will not make use of any input. The output state will be computed as a simple random variable.
We will use that module as implementation for the Thermosensor.
class CustomOlfactor(Olfactor):
def __init__(self, **kwargs):
self.last_osn_activity = None
print("**** CustomOlfactor ****")
print(kwargs)
super().__init__(**kwargs)
def update(self):
agent_id = (
self.brain.agent.unique_id if self.brain is not None else self.agent_id
)
sim_id = self.brain.agent.model.id if self.brain is not None else self.sim_id
# self.X.values() provides an array of all odor types, where the index is the odor_id
# lets read the currently sensed values for the first odor:
olfactory_input = {
"odor_id": 0,
# absolute concentration of 1st odor
"concentration_mmol": self.input.values()[0],
# change in concentration of 1st odor
"concentration_change_mmol": self.first_odor_concentration_change,
}
# set the output value of this custom Olfaction module
# that gets passed as input to other larvaworld modules
self.output = olfactory_input["concentration_mmol"] * random.random()
print(
f"LocalOlfactor output: {self.output} sim_id: {sim_id} agent_id: {agent_id}"
)
class CustomBehaviorModule(Thermosensor):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def update(self):
agent_id = (
self.brain.agent.unique_id if self.brain is not None else self.agent_id
)
sim_id = self.brain.agent.model.id if self.brain is not None else self.sim_id
# ignore input and use some randomness
self.output = random.random()
print(
f"CustomBehaviorModule output: {self.output} sim_id: {sim_id} agent_id: {agent_id}"
)
Now that we have the implementations for our custom modules we need to instruct larvaworld to use these for the respective behavioral modules instead of larvaworld’s default implementations
# overwrite mode 'osn' to use our custom CustomOlfactor class
BrainModuleDB.BrainModuleModes.olfactor.osn = CustomOlfactor
# instruct larvaworld to use our CustomBehaviorModule class as the Thermosensor
BrainModuleDB.BrainModuleModes.thermosensor.custom = CustomBehaviorModule
# for a custom turner implementation you would use:
# BrainModuleDB.BrainModuleModes.turner.custom = CustomTurnerModule
That’s almost it - all that’s left is to configure an experiment to run.
And instruct larvaworld to use the custom implementations when creating larva agent instances.
This will be done by adjusting the model configuration.
# load predefined experiment
expID = "chemorbit"
exp_conf = reg.conf.Exp.getID(expID)
# explore some experiment settings
print(
f"Experiment odor intensity: {exp_conf.env_params.food_params.source_units.Source.odor}"
)
# customize odor intensity of above source
exp_conf.env_params.food_params.source_units.Source.odor.spread = 0.01
print(
f"Adjusted odor intensity: {exp_conf.env_params.food_params.source_units.Source.odor}"
)
larva_group = exp_conf.larva_groups
# Print the entire larva group config
print("Larva group configuration:")
print(larva_group)
print(f"Available larva group IDs/names: {larva_group.keylist}")
# get the "name" of the first larva group
# this defaults to the larva agent model ID used by this group if none is provided
larva_group_id = larva_group.keylist[0]
# larva_group_id and model ID are both the same thing if no custom larva_group name is provided
print(larva_group_id, larva_group[larva_group_id].model)
# access the model_id that is used by larva agent instances created by this group:
print(f"model_id of group #1: {larva_group[larva_group_id].model}")
# change the model type to use by this larva group:
exp_conf.larva_groups[larva_group_id].model = "Levy_navigator"
Fetch and update the model configuration and set the behavioral module mode’s to use our custom implementation
# retrieve and print the model configuration of a given model ID
mm = reg.conf.Model.getID(larva_group[larva_group_id].model)
print(f"larva agent model config: {mm}")
# set the model's module implementations to 'custom' to activate our custom implementations:
mm.brain.olfactor.mode = "osn"
mm.brain.thermosensor.mode = "custom"
# to activate the custom turner you'd use:
# mm.brain.turner.mode = 'custom'
Finally run the simulation
# Launch a simulation run of the customized experiment
run_id = "my-custom-modules-run"
screen_kws = {}
if RUN_GUI_DEMO or SAVE_MEDIA:
screen_kws = {
"vis_mode": "video",
"show_display": RUN_GUI_DEMO,
"save_video": SAVE_MEDIA,
"fps": 20,
"video_file": f"larva-sim-{run_id}",
"media_dir": MEDIA_DIR,
}
erun = sim.ExpRun(
experiment=expID,
modelIDs=["navigator", "Levy_navigator"],
screen_kws=screen_kws,
N=2,
duration=0.5, # minutes
)
if RUN_CUSTOM_MODULE_DEMO:
erun.simulate()
print(f"Run_id: {run_id} completed")
# erun.analyze()