mesa icon indicating copy to clipboard operation
mesa copied to clipboard

Add `create_agent` factory method to Agent

Open quaquel opened this issue 1 year ago • 5 comments

This is a first attempt at addressing #2221. This version allows for the creation of N agents using either a given argument/keyword argument for all agents, or by specifying the argument/keyword argument for all agents, or combinations of these (e.g., a default argument for all agents, and a list of values (so one for each agent) for another argument).

The current code is WIP and needs testing. There are some obvious issues with it that I am aware of (e.g., if an argument is a sequence but should be the same for all agents, the current code will fail). I deliberately did not include support for passing distributions etc. for arguments and keyword arguments because that would make the code even more complex.

I have updated 2 benchmark models to showcase the API and test its peformance.

@EwoutH is this roughly in line with your ideas as voiced in #2221?

quaquel avatar Oct 13 '24 14:10 quaquel

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small šŸ”“ +11.6% [+10.6%, +12.6%] 🟢 -3.9% [-4.0%, -3.6%]
BoltzmannWealth large šŸ”“ +15.7% [+15.3%, +16.3%] šŸ”µ -0.3% [-0.8%, +0.1%]
Schelling small šŸ”µ +0.9% [+0.3%, +1.4%] šŸ”µ +0.1% [-0.1%, +0.3%]
Schelling large šŸ”µ +1.4% [+0.3%, +2.6%] šŸ”µ +1.3% [-0.6%, +3.4%]
WolfSheep small šŸ”“ +4.1% [+3.8%, +4.5%] 🟢 -7.6% [-10.8%, -4.5%]
WolfSheep large šŸ”“ +5.7% [+4.1%, +8.1%] šŸ”µ +3.7% [+0.7%, +7.4%]
BoidFlockers small šŸ”µ +1.2% [+0.7%, +1.6%] šŸ”µ -0.8% [-1.5%, -0.2%]
BoidFlockers large šŸ”µ +2.4% [+1.2%, +3.7%] šŸ”µ +0.4% [-0.1%, +0.9%]

github-actions[bot] avatar Oct 13 '24 15:10 github-actions[bot]

Thanks for taking a shot as this, I’m intrigued why it increases code complexity in the examples instead of decreases it. Probably because of the lists that need to be created before create is called.

Let me think a bit about this. Maybe some elegant AgentSet stuff can help here.

Talking about that, would we like to (optionally) return an AgentSet with the just created Agents? Then you can directly start chaining.

EwoutH avatar Oct 13 '24 15:10 EwoutH

Thanks for taking a shot as this, I’m intrigued why it increases code complexity in the examples instead of decreases it. Probably because of the lists that need to be created before create is called.

Lines of code and code complexity are not the same thing. But yes, since we are using python.random rather than numpy.random, we need list expressions. This adds a bit of overhead because we iterate seperately over n_agents for all arguments.

Talking about that, would we like to (optionally) return an AgentSet with the just created Agents? Then you can directly start chaining.

I have been wondering about this as well. On the one hand, it woudl be convenient to have it. On the other hand, it would add overhead and I can't immediately think of a use case were you want to operate on the agents immediately in Model.__init__

quaquel avatar Oct 13 '24 15:10 quaquel

I have updated the code a little bit. You can now pass either a sequence of length n (i.e., the number of agents to create) or any other object as argument/keyword argument. If a given argument is a sequence of length n the code assumes that each entry is for a different agent. Otherwise, the code assumes that the entire object needs to be passed to each Agent.

quaquel avatar Oct 15 '24 10:10 quaquel

I updated the benchmarks to use self.rgn. This cleans up the number of lines of code a bit and is quite convenient.

The current implementation needs further refinement. I check explicitly for each argument and keyword argument if it is a list and if its length equals the number of agents to be created. This is too restrictive. For example, a tuple or ndarray fails on this test. It also rules out generator objects (as in the boltzman position example where I have to wrap zip inside list). However, I also think this might become quite a convenient way to instantiate many agents.

What do others think? I am mainly looking for feedback at the API level and input to develop this generator method one step further.

Also, it might be convenient to have some additional methods on CellCollection, one to draw N cells with replacement and one to draw N cells without replacement. For placing agents randomly both are quite usefull. The first in case of a a Grid where capacity > 1. The second where capacity == 1 and N agents < total number of cells.

quaquel avatar Oct 16 '24 20:10 quaquel

I have picked this up again, and it's ready for a review.

It now supports list, numpy array, and tuples. Since the code relies on checking whether the length of each keyword argument is equal to n, it is not possible to support generators directly. You can check the benchmark models for examples on how to use it, but for convenience, I put it here as well:


    Wolf.create_agents(
        self,
        self.initial_wolves,
        self.rng.random(self.initial_wolves) * 2 * wolf_gain_from_food,
        wolf_reproduce,
        wolf_gain_from_food,
        [
            self.grid.all_cells.select_random_cell()
            for _ in range(self.initial_wolves)
        ],  # this is why I want to add draw_cells or something like that to CellCollection in a seperate PR
    )

   # in the simple case of width == height, you could reduce this to a one-liner
    positions = list(
        zip(
            self.rng.integers(0, self.grid.width, n),
            self.rng.integers(0, self.grid.height, n),
        )
    )
    MoneyAgent.create_agents(self, self.num_agents, pos=positions)

quaquel avatar Nov 04 '24 18:11 quaquel

Could you rebase this PR and resolve conflicts? The benchmark models got removed, for example.

Sorry for the trouble.

EwoutH avatar Dec 03 '24 14:12 EwoutH

done

quaquel avatar Dec 03 '24 14:12 quaquel

I was contemplating a bit about when this would be really useful (yeah yeah I know, I opened the original issue), especially because those newly created agents are now lost in the void of model.agents. What if you want to do anything with these just created agents?

What might be really useful if it returns a convenient AgentSet with the just created Agents. Then, you can immediately let them do something, track them separate as a group, etc.

EwoutH avatar Dec 03 '24 17:12 EwoutH

What might be really useful if it returns a convenient AgentSet with the just created Agents.

I have been going back and forth on this. It's easy to add and might be useful in some cases, but I'm not sure it would be used a lot. Happy to add it in if you think it is useful.

quaquel avatar Dec 03 '24 21:12 quaquel

Can would personally like it. I can also add it tomorrow.

EwoutH avatar Dec 03 '24 21:12 EwoutH

just added it

quaquel avatar Dec 03 '24 22:12 quaquel

I updated the PR description based on the final implementation, could you check it?

EwoutH avatar Dec 04 '24 12:12 EwoutH