-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added a new example: city walking behaviour #241
base: main
Are you sure you want to change the base?
added a new example: city walking behaviour #241
Conversation
Looks like quite a serious model! If you could add to the PR description:
|
Thanks for the extended descriptions. Have you profiled the model to identify the main performance bottlenecks? |
@projectmesa/maintainers I’m a bit in doubt on the complexity of this example. On the one hand it shows you can create ABMs with detailed environments and complex behavior, on the other hand I don’t know an example this extended would really benefit our users. I would like to hear some of your stances. |
I haven't, can you tell how can I do that? |
Some starting point: Most IDEs also have a profiler built-in. |
Here is the table I built from the data collected by the profiler running the model 10 times. Performance Analysis Results
|
Okay, this is quite useful already! It shows that most of the runtime is taken up in the Agent You can try to speed that up, but if you want I can also give it a try (hopefully tomorrow or Monday). |
I'll do what I can, but I'm not particularly confident. Can we use caching to prevent duplicate lookups in |
Hypothesis for model results not correlating with the paper:
|
In theory this should work:
|
I have successfully visualized the property layers, and they are functioning as intended. Before moving on to the other points, I reread the paper and realized I had overlooked the concept of households. I will now implement the household concept and check the results again. |
Awesome, that's amazing to hear! Did you figure out what the bug was or what I missed yesterday? |
CC @tpike3, Households sounds like Meta agents. Maybe you could assist here! |
@EwoutH Unfortunately no, but since they are just a bunch of numpy arrays I exported the data and made some graphs based on the values. I can confirm they are implemented as they are intended.
I read a bit about MetaAgents, and from my understanding, they are groups of agents that form during the model runtime based on certain properties. While I can see their usefulness, I don’t think they are particularly relevant to this model. This is because the household system is defined during the human placement, and a household is simply the cell where they spawn. Therefore, in my opinion, this can be handled during initialization. |
Thanks, sounds good! |
@EwoutH I have a question: is it correct to use |
it's better to use |
I have thoroughly reviewed the model in every aspect and believe I have covered everything correctly. In my opinion, the only remaining task is to make the model efficient enough to run on an 800x800 grid or decide to keep this simpler model after applying smaller optimizations. Current Profile: Running the model with 20x20 grid and 1350 humans for 10 steps.
|
Awesome, I will do a full review this weekend. |
@EwoutH I would love to hear your thoughts on this. |
self.cell = cell | ||
|
||
|
||
class WalkingBehaviorModel: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename this to avoid confusion with the Mesa model.
no_of_others: int = 475, | ||
scenario: str = "random_random", | ||
seed=None, | ||
simulator=ABMSimulator(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you actually using DEVS functionality? I can't find it in your code, so I'm curious if you use the functionality.
for agent in x.agents_by_type[Human] | ||
if agent.SES == 1 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_basic_ses2": lambda x: ( # average basic-needs trips for SES=2 | ||
sum( | ||
agent.basic_needs_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 2 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_basic_ses3": lambda x: ( # average basic-needs trips for SES=3 | ||
sum( | ||
agent.basic_needs_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 3 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_basic_ses4": lambda x: ( # average basic-needs trips for SES=4 | ||
sum( | ||
agent.basic_needs_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 4 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_basic_ses5": lambda x: ( # average basic-needs trips for SES=5 | ||
sum( | ||
agent.basic_needs_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 5 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_leisure_ses1": lambda x: ( # average leisure trips for SES=1 | ||
sum( | ||
agent.leisure_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 1 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_leisure_ses2": lambda x: ( # average leisure trips for SES=2 | ||
sum( | ||
agent.leisure_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 2 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_leisure_ses3": lambda x: ( # average leisure trips for SES=3 | ||
sum( | ||
agent.leisure_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 3 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_leisure_ses4": lambda x: ( # average leisure trips for SES=4 | ||
sum( | ||
agent.leisure_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 4 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
"avg_leisure_ses5": lambda x: ( # average leisure trips for SES=5 | ||
sum( | ||
agent.leisure_trips | ||
for agent in x.agents_by_type[Human] | ||
if agent.SES == 5 | ||
) | ||
/ len(x.agents_by_type[Human]) | ||
if x.agents_by_type[Human] | ||
else 0 | ||
), | ||
} | ||
|
||
self.datacollector = DataCollector(model_reporters) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can generate these a bit more elegantly, can you check if something like this works?
model_reporters = { | |
"avg_walk_ses1": lambda x: ( # average daily walking trips for SES=1 | |
sum( | |
agent.daily_walking_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 1 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_walk_ses2": lambda x: ( # average daily walking trips for SES=2 | |
sum( | |
agent.daily_walking_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 2 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_walk_ses3": lambda x: ( # average daily walking trips for SES=3 | |
sum( | |
agent.daily_walking_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 3 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_walk_ses4": lambda x: ( # average daily walking trips for SES=4 | |
sum( | |
agent.daily_walking_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 4 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_walk_ses5": lambda x: ( # average daily walking trips for SES=5 | |
sum( | |
agent.daily_walking_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 5 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_work_ses1": lambda x: ( # average work trips for SES=1 | |
sum( | |
agent.work_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 1 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_work_ses2": lambda x: ( # average work trips for SES=2 | |
sum( | |
agent.work_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 2 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_work_ses3": lambda x: ( # average work trips for SES=3 | |
sum( | |
agent.work_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 3 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_work_ses4": lambda x: ( # average work trips for SES=4 | |
sum( | |
agent.work_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 4 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_work_ses5": lambda x: ( # average work trips for SES=5 | |
sum( | |
agent.work_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 5 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_basic_ses1": lambda x: ( # average basic-needs trips for SES=1 | |
sum( | |
agent.basic_needs_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 1 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_basic_ses2": lambda x: ( # average basic-needs trips for SES=2 | |
sum( | |
agent.basic_needs_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 2 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_basic_ses3": lambda x: ( # average basic-needs trips for SES=3 | |
sum( | |
agent.basic_needs_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 3 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_basic_ses4": lambda x: ( # average basic-needs trips for SES=4 | |
sum( | |
agent.basic_needs_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 4 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_basic_ses5": lambda x: ( # average basic-needs trips for SES=5 | |
sum( | |
agent.basic_needs_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 5 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_leisure_ses1": lambda x: ( # average leisure trips for SES=1 | |
sum( | |
agent.leisure_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 1 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_leisure_ses2": lambda x: ( # average leisure trips for SES=2 | |
sum( | |
agent.leisure_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 2 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_leisure_ses3": lambda x: ( # average leisure trips for SES=3 | |
sum( | |
agent.leisure_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 3 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_leisure_ses4": lambda x: ( # average leisure trips for SES=4 | |
sum( | |
agent.leisure_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 4 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
"avg_leisure_ses5": lambda x: ( # average leisure trips for SES=5 | |
sum( | |
agent.leisure_trips | |
for agent in x.agents_by_type[Human] | |
if agent.SES == 5 | |
) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
), | |
} | |
self.datacollector = DataCollector(model_reporters) | |
def create_model_reporters(): | |
"""Create a dictionary of model reporters with minimal boilerplate""" | |
reporters = {} | |
metrics = ['daily_walking_trips', 'work_trips', 'basic_needs_trips', 'leisure_trips'] | |
for metric in metrics: | |
for ses in range(1, 6): | |
name = f"avg_{metric.split('_')[0]}_ses{ses}" # e.g. "avg_walk_ses1" | |
# Create the reporter function | |
reporters[name] = lambda x, m=metric, s=ses: ( | |
sum(getattr(agent, m) for agent in x.agents_by_type[Human] if agent.SES == s) | |
/ len(x.agents_by_type[Human]) | |
if x.agents_by_type[Human] | |
else 0 | |
) | |
return reporters | |
# Generate the model_reporters dict | |
model_reporters = create_model_reporters() | |
self.datacollector = DataCollector(model_reporters) |
|
||
def step(self): | ||
"""Advance the model by one step.""" | ||
self.agents_by_type[Human].shuffle_do("step") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there humans added or removed after initialisation? Otherwise you could generate the humans
AgentSet once, and then simply use that.
self.agents_by_type[Human].daily_walking_trips = 0 | ||
self.agents_by_type[Human].work_trips = 0 | ||
self.agents_by_type[Human].basic_needs_trips = 0 | ||
self.agents_by_type[Human].leisure_trips = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe give Human
a method reset_daily_walking_trips
and call that one here.
class GroceryStore(Workplace, FixedAgent): | ||
def __init__(self, model: Model, cell=None): | ||
Workplace.__init__(self, store_type="Grocery Store") | ||
FixedAgent.__init__(self, model) | ||
self.cell = cell | ||
|
||
|
||
class NonFoodShop(Workplace, FixedAgent): | ||
def __init__(self, model: Model, cell=None): | ||
Workplace.__init__(self, store_type="Non-Food Shop") | ||
FixedAgent.__init__(self, model) | ||
self.cell = cell | ||
|
||
|
||
class SocialPlace(Workplace, FixedAgent): | ||
def __init__(self, model: Model, cell=None): | ||
Workplace.__init__(self, store_type="Social Place") | ||
FixedAgent.__init__(self, model) | ||
self.cell = cell | ||
|
||
|
||
class Other(Workplace, FixedAgent): | ||
def __init__(self, model: Model, cell=None): | ||
Workplace.__init__(self, store_type="Other") | ||
FixedAgent.__init__(self, model) | ||
self.cell = cell |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why these are classes, and not just a Place
with a place_type
variable.
It might be that there's a very good reason, just curious.
@lru_cache(maxsize=1024) # noqa | ||
def get_max_walking_distance(self, ability: float, activity: ActivityType) -> float: | ||
"""Cached calculation of max walking distance based on ability.""" | ||
return self.BASE_MAX_DISTANCES[activity] * ability | ||
|
||
@staticmethod | ||
@lru_cache(maxsize=4096) | ||
def calculate_distance(x1: int, y1: int, x2: int, y2: int) -> float: | ||
"""Cached distance calculation between two points.""" | ||
dx = x2 - x1 | ||
dy = y2 - y1 | ||
return math.sqrt(dx * dx + dy * dy) | ||
|
||
def get_distance(self, cell1, cell2) -> float: | ||
"""Get distance between cells using cache.""" | ||
key = (cell1, cell2) | ||
if key not in self._distance_cache: | ||
x1, y1 = cell1.coordinate | ||
x2, y2 = cell2.coordinate | ||
self._distance_cache[key] = self.calculate_distance(x1, y1, x2, y2) | ||
return self._distance_cache[key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@quaquel this example model uses the cell space. Since it's one of the first user models using it, could you check if:
- everything is used according to best practices
- there are inconsistencies, missing function or opportunities to improve the cell space
Add Walking Behavior Agent-Based Model
Summary
This PR introduces an Agent-Based Model (ABM) for simulating walking behavior in a hypothetical city. The model explores how socioeconomic status (SES), built environment, and social factors dynamically influence walking patterns. The following files and components have been added to the repository:
Files Added
README.md:
Model.py
:SES
valuesAgents.py
:Key Features Added
1. Initialization Parameters
2. Environmental Layers
safety_cell_layer
): Dynamic safety values according to the scenarios influencing route selection.aesthetic_cell_layer
): Center-focused aesthetic value distribution impacting walking preferences.3. Simulation Scenarios
4. Agent Characteristics
5. Behavioral Feedback Mechanisms
Social Influence:
Walking Experience:
Density of Walkers:
Total Distance Walked:
Daily Attitude Update:
6. Data Collection System
Known Shortcomings
to be compared with