Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ This project simulates how unpopular norms can dominate a society even when the
### [Humanitarian Aid Distribution Model](https://github.com/mesa/mesa-examples/tree/main/examples/humanitarian_aid_distribution)

This model simulates a humanitarian aid distribution scenario using a needs-based behavioral architecture. Beneficiaries have dynamic needs (water, food) and trucks distribute aid using a hybrid triage system.

### [Hierarchical Organization Model](https://github.com/mesa/mesa-examples/tree/main/examples/hierarchical_organization)

An agent-based model demonstrating a three-level organizational hierarchy — Employees, Departments, and an Organization. Shows how explicit bottom-up activation works in Mesa 3.x and how external shocks propagate through a structured system.

### [Rumor Mill Model](https://github.com/mesa/mesa-examples/tree/main/examples/rumor_mill)

A simple agent-based simulation showing how rumors spread through a population based on the spread chance and initial knowing percentage, implemented with the Mesa framework and adapted from NetLogo [Rumor mill](https://www.netlogoweb.org/launch#https://www.netlogoweb.org/assets/modelslib/Sample%20Models/Social%20Science/Rumor%20Mill.nlogox).
Expand Down
44 changes: 44 additions & 0 deletions examples/hierarchical_organization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Hierarchical Organization Model

**Mesa Version:** >=3.0 | **Visualization:** SolaraViz

---

## Overview

This model simulates how a three-level organization works — employees doing the actual work, departments managing them, and an organization setting policy for everyone.

```
EmployeeAgent → DepartmentAgent → OrganizationAgent
```

Each level responds to what's happening below it. Employees produce output based on their productivity and morale. Departments watch that output and adjust workloads accordingly. The organization looks at the overall picture and nudges morale up or down through policy.

The model also introduces random external shocks — sudden morale drops across all employees — to see whether the system can absorb and recover from disruptions.

---

## How to Run

```bash
pip install mesa[rec]
python -m solara run app.py
```

---

## Parameters

| Parameter | Default | Description |
|---|---|---|
| `num_departments` | 3 | Number of departments |
| `employees_per_department` | 5 | Employees per department |
| `shock_probability` | 0.05 | Probability of external shock per step |

---

## Mesa 3.x Notes

- No legacy schedulers — activation is explicit in `model.step()`
- `unique_id` is auto-assigned by Mesa 3.x via `super().__init__(model)`
- Relative imports used throughout for pytest compatibility
110 changes: 110 additions & 0 deletions examples/hierarchical_organization/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import random

from mesa import Agent


class EmployeeAgent(Agent):
"""
Base-level agent representing an employee.
Produces output based on productivity and morale.
"""

def __init__(self, model, department_id):
super().__init__(model)
# NOTE: In Mesa 3.x, unique_id is auto-assigned by super().__init__()
# Do NOT manually set self.unique_id — it conflicts with Mesa internals.
self.department_id = department_id

self.productivity = random.uniform(0.7, 1.3)
self.morale = random.uniform(0.7, 1.0)
self.workload = random.uniform(0.8, 1.0)

def step(self):
self.update_morale()
self.produce()

def update_morale(self):
# Workload stress
if self.workload > 1.2:
self.morale -= 0.03
else:
self.morale += 0.01

# Natural recovery
self.morale += 0.005

self.morale = max(0.1, min(self.morale, 2.0))

def produce(self):
output = self.productivity * self.morale
self.model.total_output += output


class DepartmentAgent(Agent):
"""
Mid-level meta-agent representing a Department.
Aggregates employee performance and adjusts workload.
"""

def __init__(self, model):
super().__init__(model)
# unique_id auto-assigned by Mesa 3.x
self.performance = 0.0

def step(self):
self.aggregate_performance()
self.adjust_workload()

def aggregate_performance(self):
employees = [
agent
for agent in self.model.employees
if agent.department_id == self.unique_id
]
self.performance = sum(agent.productivity * agent.morale for agent in employees)

def adjust_workload(self):
employees = [
agent
for agent in self.model.employees
if agent.department_id == self.unique_id
]
target = self.model.performance_threshold
gap = target - self.performance

for agent in employees:
agent.workload += 0.02 * gap
agent.workload = max(0.5, min(agent.workload, 2.0))


class OrganizationAgent(Agent):
"""
Top-level meta-agent representing the Organization.
Applies policy adjustments based on global performance.
"""

def __init__(self, model):
super().__init__(model)
# unique_id auto-assigned by Mesa 3.x
self.policy_factor = 1.0

def step(self):
self.evaluate_departments()

def evaluate_departments(self):
departments = self.model.departments
if not departments:
return

avg_performance = sum(d.performance for d in departments) / len(departments)

if avg_performance < self.model.performance_threshold:
self.policy_factor += 0.01
else:
self.policy_factor -= 0.005

self.policy_factor = max(0.9, min(self.policy_factor, 1.2))

# Additive morale boost/penalty based on policy
for employee in self.model.employees:
employee.morale += 0.02 * (self.policy_factor - 1.0)
46 changes: 46 additions & 0 deletions examples/hierarchical_organization/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from mesa.visualization import SolaraViz, make_plot_component

# Use relative import consistent with model.py
from .model import HierarchicalOrganizationModel

model_params = {
"num_departments": {
"type": "SliderInt",
"value": 3,
"min": 1,
"max": 8,
"step": 1,
"label": "Number of Departments",
},
"employees_per_department": {
"type": "SliderInt",
"value": 5,
"min": 1,
"max": 15,
"step": 1,
"label": "Employees per Department",
},
"shock_probability": {
"type": "SliderFloat",
"value": 0.05,
"min": 0.0,
"max": 0.5,
"step": 0.01,
"label": "Shock Probability",
},
}

# NOTE: SolaraViz instantiates the model itself using model_params.
# Do NOT instantiate the model here manually — pass the class instead.
model = HierarchicalOrganizationModel()

main_plot = make_plot_component(["Total Output", "Avg Department Performance"])

shock_plot = make_plot_component(["Shock Event"])

page = SolaraViz(
model,
components=[main_plot, shock_plot],
model_params=model_params,
name="Hierarchical Organization Model",
)
105 changes: 105 additions & 0 deletions examples/hierarchical_organization/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import random

from mesa import Model
from mesa.datacollection import DataCollector

# Use relative imports so pytest can find the module from repo root
from .agents import DepartmentAgent, EmployeeAgent, OrganizationAgent


class HierarchicalOrganizationModel(Model):
"""
Demonstrates explicit hierarchical activation in Mesa 3.x.

Three-level hierarchy:
EmployeeAgent → DepartmentAgent → OrganizationAgent

Activation order is explicit and deliberate:
1. All employees act first (produce output, update morale).
2. Departments aggregate and rebalance workload.
3. Organization evaluates globally and adjusts policy.

This avoids the legacy RandomActivation scheduler entirely,
which was removed in Mesa 3.x.
"""

def __init__(
self,
num_departments=3,
employees_per_department=5,
shock_probability=0.05,
):
super().__init__()

self.num_departments = num_departments
self.employees_per_department = employees_per_department
self.shock_probability = shock_probability

self.total_output = 0.0
self.shock_event = False

self.employees = []
self.departments = []

# Performance threshold scales with department size
self.performance_threshold = employees_per_department * 1.0

# Create organization (top-level) — Mesa 3.x auto-assigns unique_id
self.organization = OrganizationAgent(self)

# Create departments and employees
for _ in range(num_departments):
department = DepartmentAgent(self)
self.departments.append(department)

for _ in range(employees_per_department):
# Pass department's auto-assigned unique_id so employees
# know which department they belong to
employee = EmployeeAgent(self, department.unique_id)
self.employees.append(employee)

self.datacollector = DataCollector(
model_reporters={
"Total Output": lambda m: m.total_output,
"Avg Department Performance": lambda m: (
sum(d.performance for d in m.departments) / len(m.departments)
)
if m.departments
else 0,
"Shock Event": lambda m: int(m.shock_event),
}
)

self.running = True

def apply_external_shock(self):
"""
With probability `shock_probability`, applies a morale penalty
to all employees to simulate an external disruption (e.g. layoffs,
market downturn). This tests the system's resilience mechanisms.
"""
if random.random() < self.shock_probability:
self.shock_event = True
for employee in self.employees:
employee.morale -= random.uniform(0.05, 0.15)
# Clamp after shock to prevent going below floor
employee.morale = max(0.1, employee.morale)
else:
self.shock_event = False

def step(self):
self.total_output = 0.0

# 1. Apply random external shock
self.apply_external_shock()

# 2. Explicit bottom-up activation
for employee in self.employees:
employee.step()

for department in self.departments:
department.step()

self.organization.step()

self.datacollector.collect(self)
Loading