Skip to content
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

AgentSet: Add set method #2254

Merged
merged 4 commits into from
Aug 28, 2024
Merged

AgentSet: Add set method #2254

merged 4 commits into from
Aug 28, 2024

Conversation

EwoutH
Copy link
Member

@EwoutH EwoutH commented Aug 28, 2024

This PR adds a set method to the AgentSet class that allows setting a specified attribute of all agents within the set to a given value. This method provides a streamlined way to update agent attributes in bulk, without having to resort to lambda or other callable functions.

Motive

The motivation behind this feature is to simplify and expedite the process of modifying attributes across all agents in an AgentSet. Previously, this required iterating manually over the agents or using a lambda or other callable function. This new method makes the operation more elegant.

Implementation

The set method was added to the AgentSet class. It iterates through all agents in the set and applies the setattr function to assign the specified value to the desired attribute. The method operates in place, modifying the existing agents directly.

The (now updated) AgentSet itself is returned, which allows for chaining.

You can both set existing Agent variables or set new ones.

Usage Examples

# Set the 'has_license' attribute to True for all agents in the AgentSet
agents.set('has_license', True)

Note that it doesn't matter if has_license already exists in the Agent, both work.

Together with #2253, you can now set a value for a fraction of your AgentSet:

# Select 40% of the agents from the AgentSet
model.agents.select(p=0.4).set('has_license', True)

It's also possible to set multiple things by chaining the set() commands:

agents.set('has_license', True).set('age', 45).set('continent', 'Europe')

Additional Notes

It feels logical that if you have a get method, you also should have a set method.

Edit:

  • Modified to return an AgentSet, so you can keep the output or keep chaining.
  • It modified the Agents itself, like with do(), so inplace isn't needed.
  • We don't allow dicts, those can be added later if needed.

Added a `set` method to the `AgentSet` class that allows setting a specified attribute of all agents within the set to a given value. This method provides a streamlined way to update agent attributes in bulk.
@EwoutH EwoutH added the feature Release notes label label Aug 28, 2024
@EwoutH
Copy link
Member Author

EwoutH commented Aug 28, 2024

An open question is if this needs inplace and/or return parameters. Could be included in this PR, but doesn't have to.

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔴 +4.7% [+3.2%, +6.2%] 🔵 +0.6% [+0.5%, +0.8%]
BoltzmannWealth large 🔵 -0.1% [-0.9%, +0.6%] 🔵 +4.3% [+2.8%, +5.8%]
Schelling small 🔵 +0.8% [+0.4%, +1.2%] 🔵 -0.0% [-0.3%, +0.2%]
Schelling large 🔵 +0.6% [-0.2%, +1.4%] 🔴 +4.1% [+3.1%, +5.0%]
WolfSheep small 🔵 +0.5% [-0.9%, +2.0%] 🔵 +1.2% [+0.9%, +1.4%]
WolfSheep large 🔵 +0.8% [+0.4%, +1.2%] 🔴 +6.7% [+5.4%, +7.9%]
BoidFlockers small 🔵 +3.0% [+2.2%, +3.8%] 🔵 +0.7% [-0.4%, +1.8%]
BoidFlockers large 🔵 +1.7% [+1.1%, +2.4%] 🔵 +1.5% [+0.7%, +2.2%]

@quaquel
Copy link
Member

quaquel commented Aug 28, 2024

good idea, and probably an inplace keyword should be included preferably in this PR. It keeps the API clear and consistent across methods.

@EwoutH
Copy link
Member Author

EwoutH commented Aug 28, 2024

Can we think of cases where you want to continue the chain and return an AgentSet? In that case, should a default return=False which you can switch to True be also included?

Maybe I'm now mixing things up with inplace.

Edit: Changing set seems useful for multiple variables, so that should be possible.

agentset.set('has_license', True).set('has_car', True)

Or even allow a dict?

agentset.set({'has_license': True, 'has_car': True})

@EwoutH
Copy link
Member Author

EwoutH commented Aug 28, 2024

I see three scenarios here:

  1. I want to modify my agentset inplace
  2. I want to modify my agentset inplace and return that updated agentset (to assign or continue chaining)
  3. I want to create a new agentset (inplace) and return that new agentset (to assign or continue chaining)

Of course you can just ignore the return for 1. So return is not really needed, just return it and the user can decide if they use it or not. Okay, I get it.

Now we're in a bit of a pickle, because with .set() inplace=True seems like a good default. But most other functions have it False by default.

I think the most similar function is .do(). That also only does it only in place. This is basically a .do() with less steps.

To summarize:

  • Always return the new AgentSet
  • We need to pick one of two options:
    1. Like do(), and just always do it "in place"
    2. Add an inplace defaulting to True

@quaquel
Copy link
Member

quaquel commented Aug 28, 2024

good analysis. There is no point to return a new agent set, just as with do.

EwoutH and others added 2 commits August 28, 2024 13:00
Return the AgentSet itself after performing the `set()` method, to be able to either directly assign the new AgentSet or keep chaining.

This is consistent with how `do()`.
Copy link
Contributor

@Corvince Corvince left a comment

Choose a reason for hiding this comment

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

Nice and simple 🔝

@EwoutH
Copy link
Member Author

EwoutH commented Aug 28, 2024

Thanks, I will add a few unittest tonight and merge.

@EwoutH
Copy link
Member Author

EwoutH commented Aug 28, 2024

What I just now realized and what's quite powerful is that you also can set completely new variables this way.

Punching above it's weight!

@EwoutH EwoutH merged commit de58ddb into main Aug 28, 2024
10 checks passed
@EwoutH EwoutH deleted the agentset_set branch August 29, 2024 18:58
@EwoutH EwoutH added the backport label Sep 1, 2024
EwoutH added a commit that referenced this pull request Sep 1, 2024
Adds a `set` method to the `AgentSet` class that allows setting a specified attribute of all agents within the set to a given value. This method provides a streamlined way to update agent attributes in bulk, without having to resort to `lambda` or other callable functions.

The motivation behind this feature is to simplify and expedite the process of modifying attributes across all agents in an `AgentSet`. Previously, this required iterating manually over the agents or using a `lambda` or other callable function. This new method makes the operation more elegant.

The `set` method was added to the `AgentSet` class. It iterates through all agents in the set and applies the `setattr` function to assign the specified value to the desired attribute. The method operates in place, modifying the existing agents directly.

The (now updated) AgentSet itself is returned, which allows for chaining.

You can both set existing Agent variables or set new ones.
EwoutH added a commit that referenced this pull request Sep 1, 2024
Adds a `set` method to the `AgentSet` class that allows setting a specified attribute of all agents within the set to a given value. This method provides a streamlined way to update agent attributes in bulk, without having to resort to `lambda` or other callable functions.

The motivation behind this feature is to simplify and expedite the process of modifying attributes across all agents in an `AgentSet`. Previously, this required iterating manually over the agents or using a `lambda` or other callable function. This new method makes the operation more elegant.

The `set` method was added to the `AgentSet` class. It iterates through all agents in the set and applies the `setattr` function to assign the specified value to the desired attribute. The method operates in place, modifying the existing agents directly.

The (now updated) AgentSet itself is returned, which allows for chaining.

You can both set existing Agent variables or set new ones.
@EwoutH
Copy link
Member Author

EwoutH commented Sep 4, 2024

Interesting case where I couldn't use set():

# Update currently available modes after having assigned cars
self.agents.do(lambda a: setattr(a, 'currently_available_modes', a.available_modes))

Maybe there might be a way to also do this nicely. Or just accept that you have to use an do-lambda-setattr.

@quaquel
Copy link
Member

quaquel commented Sep 4, 2024

let me check if I understand this correctly, you want to set an agent attribute on the basis of another agent attribute?

@EwoutH
Copy link
Member Author

EwoutH commented Sep 4, 2024

No on the basis of it's own agent attribute. I updated the available_modes attribute for all agents from the model, and I just want to copy that to currently_available_modes for each agent, before starting all trips.

It would be nice to be able to refer to the agent itself without the whole lambda thing. Not only in set(), but also in some other methods.

(not high priority)

@quaquel
Copy link
Member

quaquel commented Sep 4, 2024

Sorry, that is what I was trying to say, and, indeed, I agree. Would be a good issue to open and a feature to add at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport feature Release notes label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants