# Brain Module Architecture
The Brain module integrates sensory input, memory systems, and locomotor control. Larvaworld provides two implementations: `DefaultBrain` (standard) and `NengoBrain` (neural network-based).
---
## Brain architecture overview
```{mermaid}
graph TD
%% Brain implementations
subgraph Brain_Impl ["Brain Implementations"]
BrainBase["Brain"
Base class]:::base
DefaultBrain["DefaultBrain"
Standard implementation]:::impl
NengoBrain["NengoBrain"
Nengo-based implementation]:::impl
end
BrainBase --> DefaultBrain
BrainBase --> NengoBrain
%% Modalities
subgraph Modalities
OlfMod["olfaction"
odor sensing]:::modality
TouchMod["touch"
mechanoreception/feeding]:::modality
ThermoMod["thermosensation"
temperature]:::modality
WindMod["windsensation"
wind/airflow]:::modality
end
%% Sensors
OlfSensor["Olfactor"
odor sensor]:::sensor
TouchSensor["Toucher"
contact/food sensor]:::sensor
ThermoSensor["Thermosensor"
temperature sensor]:::sensor
WindSensor["Windsensor"
wind sensor]:::sensor
%% Memory modules
RLmem["RLOlfMemory / RLTouchMemory"
reinforcement learning]:::memory
NullMem["No memory"]:::memory
%% Locomotor interface
subgraph Locomotor_System ["Locomotor System"]
Locomotor["Locomotor"
crawl/turn/feed]:::locomotor
A_in["A_in"
sensory drive]:::signal
MotorCmds["motor commands"
crawl/turn/feed rates]:::output
end
%% Wiring: Brain -> Modalities
DefaultBrain --> OlfMod
DefaultBrain --> TouchMod
DefaultBrain --> ThermoMod
DefaultBrain --> WindMod
%% Wiring: Modalities -> Sensors
OlfMod --> OlfSensor
TouchMod --> TouchSensor
ThermoMod --> ThermoSensor
WindMod --> WindSensor
%% Wiring: Memory attachment (example: olfaction)
OlfMod --> RLmem
TouchMod --> NullMem
ThermoMod --> NullMem
WindMod --> NullMem
%% Wiring: Modalities -> Locomotor
OlfMod --> A_in
TouchMod --> A_in
ThermoMod --> A_in
WindMod --> A_in
A_in --> Locomotor
Locomotor --> MotorCmds
%% Color definitions
classDef base fill:#2c3e50,stroke:#34495e,stroke-width:3px,color:#ffffff
classDef impl fill:#3498db,stroke:#2980b9,stroke-width:2px,color:#ffffff
classDef modality fill:#9b59b6,stroke:#8e44ad,stroke-width:2px,color:#ffffff
classDef sensor fill:#f39c12,stroke:#e67e22,stroke-width:2px,color:#ffffff
classDef signal fill:#e74c3c,stroke:#c0392b,stroke-width:2px,color:#ffffff
classDef memory fill:#e91e63,stroke:#c2185b,stroke-width:2px,color:#ffffff
classDef locomotor fill:#27ae60,stroke:#229954,stroke-width:2px,color:#ffffff
classDef output fill:#f1c40f,stroke:#f39c12,stroke-width:3px,color:#000000
```
---
## Sensory Modalities
The brain organizes sensors into **modalities**, each processing a specific sensory channel:
| Modality | Sensor Class | Signal | Memory | Purpose |
| ------------------- | -------------- | ----------- | -------- | -------------------- |
| **olfaction** | `Olfactor` | `A` (float) | Optional | Odor detection |
| **touch** | `Toucher` | `A` (float) | Optional | Contact/food sensing |
| **thermosensation** | `Thermosensor` | `A` (float) | Optional | Temperature sensing |
| **windsensation** | `Windsensor` | `A` (float) | Optional | Wind/airflow sensing |
### Modality Structure
```python
# This mirrors `Brain.modalities` (see `larvaworld/lib/model/modules/brain.py`)
modality = {
"sensor": self.olfactor, # Sensor instance (or None)
"func": self.sense_odors, # Processing function
"A": 0.0, # Sensory drive from this modality
"mem": None, # Optional memory module (per modality)
}
```
---
## Sensor Modules
### Olfactor
**Purpose**: Odor detection
**Key Attributes**:
- `gain_dict` / `gain`: Gain per odor ID (memory can update this per step)
- `X`: Current odor concentrations per odor ID
- `dX`: Perceived change per odor ID (depends on `perception`: `log`/`linear`/`null`)
- `output`: Integrated sensory drive (used as modality `A`)
**Processing**:
1. Query odorscape value layers at larva position (via `Brain.sense_odors`)
2. Compute `dX` from current/previous `X` based on `perception`
3. Integrate `output` using `gain[id] * dX[id]` with exponential decay (`decay_coef`)
**Code Location**: `/lib/model/modules/sensor.py` (class `Olfactor`)
---
### Toucher
**Purpose**: Tactile sensing (touch sensors around the body contour)
**Key Attributes**:
- `touch_sensors`: Indices of touch sensors on the body contour
- `gain_dict` / `gain`: Gain per touch sensor ID
- `X` / `dX` / `output`: Same Sensor state variables as above
**Processing**:
1. `Brain.init_sensors()` registers touch sensors on the agent body
2. `Brain.sense_food_multi()` converts touch sensor positions to a `dict` of `0/1` activations
3. `Toucher.step(input=...)` integrates these into a tactile drive `A`
**Code Location**: `/lib/model/modules/sensor.py` (class `Toucher`)
---
### Windsensor
**Purpose**: Wind/airflow detection
**Key Attributes**:
- `weights`: Wind response weights (passed from brain config)
- `gain_dict` / `gain`: Gain for the `"windsensor"` channel (default `{"windsensor": 1.0}`)
- `perception`: Fixed to `"null"` (direct transduction)
**Code Location**: `/lib/model/modules/sensor.py` (class `Windsensor`)
---
### Thermosensor
**Purpose**: Temperature sensing
**Key Attributes**:
- `gain_dict` / `gain`: Gains for warm/cool channels
- `X` / `dX` / `output`: Same Sensor state variables as above
**Code Location**: `/lib/model/modules/sensor.py` (class `Thermosensor`)
---
## Memory Modules
Memory modules **attach to sensory modalities** and modulate their gain through learning.
### RLmemory
**Algorithm**: Q-learning (reinforcement learning) via a Q-table over discretized sensory change states.
**Mechanism**:
- Uses `rewardSum` + `dx` to update the Q-table and choose gain actions from `gain_space`.
**Code Location**: `/lib/model/modules/memory.py` (`RLmemory` class)
---
### RemoteBrianModelMemory (MB memory)
**Algorithm**: Mushroom Body model (Hebbian learning)
**Mechanism**:
- KC-MBON synaptic plasticity
- Reward-modulated learning
**Code Location**: `/lib/model/modules/memory.py` (`RemoteBrianModelMemory` class)
---
## Locomotor Integration
The brain coordinates locomotor modules through the `Locomotor` class:
```{mermaid}
graph LR
BRAIN[Brain] --> LOCOMOTOR[Locomotor]
LOCOMOTOR --> CRAWLER[Crawler]
LOCOMOTOR --> TURNER[Turner]
LOCOMOTOR --> FEEDER[Feeder]
LOCOMOTOR --> INTERMITTER[Intermitter]
```
### Brain → Locomotor Flow
1. **Brain.sense()**: Update each enabled sensor module and its modality drive `A`
2. **Brain.step()**: Call `Locomotor.step(A_in=..., on_food=...)`
3. **Locomotor.step()**: Run crawler/turner/feeder (+ interference + intermitter state machine)
---
## DefaultBrain
**Implementation**: Rule-based sensorimotor control
**Step Sequence**:
```python
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)
```
---
## NengoBrain
**Implementation**: Spiking neural network (Nengo)
:::{warning}
`NengoBrain` is experimental. In the current codebase it may fail at runtime with newer `nengo` versions (e.g. `nengo.exceptions.ReadonlyError: probes is read-only`) when initializing internal probes.
If you hit this, use `DefaultBrain` models until `NengoBrain` is updated for the installed `nengo` API.
:::
**Architecture**:
- **Input**: Sensory neurons (olfactory, tactile, etc.)
- **Hidden**: Processing layers
- **Output**: Motor neurons (forward, turn)
**Step Sequence**:
```python
def step(self):
# See `larvaworld/lib/model/modules/nengobrain.py` for the real implementation.
# NengoBrain runs an internal Nengo Simulator and maps probe outputs to (lin, ang, feed_motion).
...
```
---
## Configuration
### Enable Specific Modalities
```python
from larvaworld.lib import reg
from larvaworld.lib.sim import ExpRun
from larvaworld.lib.util import AttrDict
exp_params = reg.conf.Exp.getID("dish").get_copy()
model_conf = reg.conf.Model.getID("navigator").get_copy()
# Disable modalities by disabling their sensor modules in the brain config
model_conf.brain.windsensor = None
model_conf.brain.thermosensor = None
exp_params.larva_groups = AttrDict(
{
"nav": AttrDict(
{
"model": model_conf,
"distribution": AttrDict({"shape": "circle", "mode": "uniform", "N": 5}),
}
)
}
)
run = ExpRun(
experiment="dish",
parameters=exp_params,
duration=0.5,
screen_kws={"show_display": False, "vis_mode": None},
store_data=False,
)
run.simulate()
```
### Attach Memory
```python
from larvaworld.lib import reg
from larvaworld.lib.model.modules.module_modes import moduleDB
model_conf = reg.conf.Model.getID("navigator").get_copy()
model_conf.brain.memory = moduleDB.memory_kws(
mode="RL", modality="olfaction", as_entry=False
)
```
### Use NengoBrain
```python
from larvaworld.lib import reg
# NengoBrain is used when the model's crawler module has mode "nengo".
# Built-in model IDs include many `nengo_*` variants:
nengo_ids = [m for m in reg.conf.Model.confIDs if m.startswith("nengo_")]
print(nengo_ids[:20])
```
---
## Related Documentation
- {doc}`larva_agent_architecture` - Complete agent structure
- {doc}`../concepts/module_interaction` - Runtime interactions
- {doc}`../working_with_larvaworld/single_experiments` - Olfactory learning example