Skip to content

feat: Implement random activation by type #1162

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

Merged
merged 1 commit into from
Feb 26, 2022
Merged

feat: Implement random activation by type #1162

merged 1 commit into from
Feb 26, 2022

Conversation

rht
Copy link
Contributor

@rht rht commented Feb 20, 2022

This is the least controversial way of putting in random activation by
type into the library, by simply using RandomActivationByBreed found in
sugarscape_cg and wolf_sheep (I have checked that the schedule.py in
both examples are identical).

Some differences:

  • replace the term "breed" by a more general "type"
  • add Mypy typings

If people want to get logging specific to one agent type, they can
either

  • loop through all agents, and filter by their type
  • loop through model.scheduler.agents_by_type[type_class]

We don't support agents that change their type, because in Python, you
can't have an object that changes its intrinsic type over its lifetime.

There could perhaps be a RandomActivationByAttribute, that groups
agents by their attributes, which may change throughout the course of a
simulation.

@rht
Copy link
Contributor Author

rht commented Feb 20, 2022

@EwoutH check this PR out. It's much less controversial than #1142, and so require fewer points to be discussed for a consensus.

@codecov
Copy link

codecov bot commented Feb 20, 2022

Codecov Report

Merging #1162 (61f8539) into main (d346c48) will increase coverage by 0.07%.
The diff coverage is 93.33%.

❗ Current head 61f8539 differs from pull request most recent head 9e60191. Consider uploading reports for the commit 9e60191 to get more accurate results

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1162      +/-   ##
==========================================
+ Coverage   89.28%   89.36%   +0.07%     
==========================================
  Files          19       19              
  Lines        1269     1297      +28     
  Branches      259      264       +5     
==========================================
+ Hits         1133     1159      +26     
  Misses        100      100              
- Partials       36       38       +2     
Impacted Files Coverage Δ
mesa/time.py 94.73% <93.33%> (-0.79%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d346c48...9e60191. Read the comment docs.

@EwoutH
Copy link
Member

EwoutH commented Feb 20, 2022

From an architecture perspective I agree this is the right course of action and a nice first step to full multiple agent types support.

Haven't looked closely at the exact implementation, I can do that tomorrow if you want (however I think I'm not going to find much).

Copy link
Member

@EwoutH EwoutH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, I added two comments, one about custom step orders and a documentation nitpick.

mesa/time.py Outdated
agent_class: Type[Agent] = type(agent)
del self.agents_by_type[agent_class][agent.unique_id]

def step(self, by_type: bool = True) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add something like an optional order input that allows to input the order in which the agent types are executed? Maybe that input could be either the string "reversed" to loop through reversed(self.agents_by_type) or a custom list with agent types.

I suggested a code snipped below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea. Also good to compare with the API of NetLogo, MASON, and Agents.jl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can allow three strings for order: regular (default), reverse and shuffle, and a list of agent types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't find a wolf-sheep model for MASON. It is also N/A in https://juliadynamics.github.io/Agents.jl/stable/comparison/.

mesa/time.py Outdated
Comment on lines 251 to 263
if by_type:
for agent_class in self.agents_by_type:
self.step_type(agent_class)
self.steps += 1
self.time += 1
else:
super().step()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if by_type:
for agent_class in self.agents_by_type:
self.step_type(agent_class)
self.steps += 1
self.time += 1
else:
super().step()
if by_type:
if order == "regular"
for agent_class in self.agents_by_type:
self.step_type(agent_class)
if order == "reverse"
for agent_class in reversed(self.agents_by_type):
self.step_type(agent_class)
else:
for agent_class in order:
self.step_type(agent_class)
self.steps += 1
self.time += 1
else:
super().step()

@rht
Copy link
Contributor Author

rht commented Feb 22, 2022

2 points that need consensus:

First, the by_type boolean is not necessary, and complicates the API without much benefit. If the user wants the regular RandomActivation, they can simply use RandomActivation.

Second, the step order of each type. I can't think of a use case when a modeler wants to do the reverse of the original insertion order of the agent types during the model initialization. I think a more general solution is to have an argument called agent_type_sequence, with a type of Optional[Sequence[Type[Agent]]]. The modeler can specify any order they want.

@EwoutH
Copy link
Member

EwoutH commented Feb 22, 2022

First, the by_type boolean is not necessary, and complicates the API without much benefit. If the user wants the regular RandomActivation, they can simply use RandomActivation.

Agreed, lets drop the by_type boolean.

Second, the step order of each type. I can't think of a use case when a modeler wants to do the reverse of the original insertion order of the agent types during the model initialization. I think a more general solution is to have an argument called agent_type_sequence, with a type of Optional[Sequence[Type[Agent]]]. The modeler can specify any order they want.

A custom order like Optional[Sequence[Type[Agent]]] is indeed the most important. You can of course implement reversing or shuffling the order yourself, but it might be nice to just very quickly be able experiment and iterate with it. But maybe it complicated the API too much.

@rht
Copy link
Contributor Author

rht commented Feb 23, 2022

Shuffling may be in demand, but I'm not sure about the reverse order.

@rht
Copy link
Contributor Author

rht commented Feb 24, 2022

I forgot to say that I removed the unnecessary by_type arg, and added shuffle_types and shuffle_agents. At least, featurewise, it is the same as Agents.jl. I'm tempted to say that adding custom types order argument can be deferred to a separate PR. We can always iterate on this activation before the next release.

@rht
Copy link
Contributor Author

rht commented Feb 24, 2022

Bump. @tpike3 thoughts so far? I think this is ready to merge. Once you ack, I will squash and merge. After that, I will port my implementation of Sugarscape with trading ({G1}, {M, T}) and make a PR for it.

@tpike3
Copy link
Member

tpike3 commented Feb 24, 2022

Dang, I am way behind... @rht and @EwoutH this is awesome. This is the implementation I am most familiar with so am ready to merge and I agree the terms (e.g type vs breed and shuffle) are the preferred.

We can always iterate on this activation before the next release.

To @rht's point thinking future customizations in new PRs before the next release ... I will concur and support two additional features in later PRs

  1. I concur on the ability for a customized type order. To me the ability to customize is critical as growing simulation in different ways is often more insightful than the actual results
  2. If I am reading it right since I did not get a chance to play around with the code but don't want to hold up progress, we should have the option to run all agents in a random order regardless of type.. in some models it will matter and is supporting evidence to Lorenz's seminal work on Deterministic Nonperiodic Flow.

@rht
Copy link
Contributor Author

rht commented Feb 24, 2022

we should have the option to run all agents in a random order regardless of type

You can do this with:

if fully_random:
    self.schedule = RandomActivation(self)
else:
    self.scheduler = RandomActivationByType(self)

And so we can keep the API minimal.

@tpike3
Copy link
Member

tpike3 commented Feb 26, 2022

we should have the option to run all agents in a random order regardless of type

You can do this with:

if fully_random:
    self.schedule = RandomActivation(self)
else:
    self.scheduler = RandomActivationByType(self)

And so we can keep the API minimal.

@rht That is perfect! I didn't even think of that...so obvious I looked right over it. Squash and merge at your discretion

This is the least controversial way of putting in random activation by
type into the library, by simply using RandomActivationByBreed found in
sugarscape_cg and wolf_sheep (I have checked that the schedule.py in
both examples are identical).

Some differences:
- replace the term "breed" by a more general "type"
- add Mypy typings

If people want to get logging specific to one agent type, they can
either
- loop through all agents, and filter by their type
- loop through model.scheduler.agents_by_type[type_class]

We don't support agents that change their type, because in Python, you
can't have an object that changes its intrinsic type over its lifetime.

There could perhaps be a `RandomActivationByAttribute`, that groups
agents by their attributes, which may change throughout the course of a
simulation.
@rht
Copy link
Contributor Author

rht commented Feb 26, 2022

Yay! Can't wait for the sugarscape variations being in the repo (at least the ones that doesn't require Multi-level Mesa).

@rht rht merged commit b622d1d into projectmesa:main Feb 26, 2022
EwoutH added a commit to EwoutH/mesa that referenced this pull request Dec 17, 2023
Tracks agents in the model with a defaultdict.

This PR adds a new `agents` dictionary to the Mesa `Model` class, enabling native support for handling multiple agent types within models. This way all modules can know which agents and agents type are in the model at any given time, by calling `model.agents`.

NetLogo has had agent types, called [`breeds`](https://ccl.northwestern.edu/netlogo/docs/dict/breed.html), built-in from the start. It works perfectly in all NetLogo components, because it's a first class citizen and all components need to be designed to consider different breeds.

In Mesa, agent types are an afterthought at best. Almost nothing is currently designed with multiple agent types in mind. That has caused several issues and limitations over the years, including:

- projectmesa#348
- projectmesa#1142
- projectmesa#1162

Especially in scheduling, space and datacollection, lack of a native, consistent construct for agent types severely limits the possibilities. With the discussion about patches and "empty" this discussion done again. You might want empty to refer to all agents or only a subset of types or single type. That's currently cumbersome to implement.

Basically, by always having dictionary available of which agents of which types are in the model, you can always trust on a consistent construct to iterate over agents and agent types.

- The `Model` class now uses a `defaultdict` to store agents, ensuring a set is automatically created for each new agent type.
- The `Agent` class has been updated to leverage this feature, simplifying the registration process when an agent is created.
- The `remove` method in the `Agent` class now uses `discard`, providing a safer way to remove agents from the model.
tpike3 pushed a commit that referenced this pull request Dec 18, 2023
Tracks agents in the model with a defaultdict.

This PR adds a new `agents` dictionary to the Mesa `Model` class, enabling native support for handling multiple agent types within models. This way all modules can know which agents and agents type are in the model at any given time, by calling `model.agents`.

NetLogo has had agent types, called [`breeds`](https://ccl.northwestern.edu/netlogo/docs/dict/breed.html), built-in from the start. It works perfectly in all NetLogo components, because it's a first class citizen and all components need to be designed to consider different breeds.

In Mesa, agent types are an afterthought at best. Almost nothing is currently designed with multiple agent types in mind. That has caused several issues and limitations over the years, including:

- #348
- #1142
- #1162

Especially in scheduling, space and datacollection, lack of a native, consistent construct for agent types severely limits the possibilities. With the discussion about patches and "empty" this discussion done again. You might want empty to refer to all agents or only a subset of types or single type. That's currently cumbersome to implement.

Basically, by always having dictionary available of which agents of which types are in the model, you can always trust on a consistent construct to iterate over agents and agent types.

- The `Model` class now uses a `defaultdict` to store agents, ensuring a set is automatically created for each new agent type.
- The `Agent` class has been updated to leverage this feature, simplifying the registration process when an agent is created.
- The `remove` method in the `Agent` class now uses `discard`, providing a safer way to remove agents from the model.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants