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

Lakshayb30/issue2221 #2250

Closed
wants to merge 3 commits into from

Conversation

lakshayb30
Copy link

@lakshayb30 lakshayb30 commented Aug 26, 2024

New function under class agentset to create multiple agents of a single class.

Added a new method create_agents() to class AgentSet, which is able to create as many agents of the same class without looping through each one.
The create_agents method has been implemented within the AgentSet class. It takes the class of agents, the number of agents to be created, and other arguments to be passed to the constructor of the agent as input. The use of list comprehension is used to create the agents and then returns the list of the created agents.
an example for its usage :
agent_set.create_agents(BarCustomer, self.num_agents, memory_size, crowd_threshold, num_strategies)

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
Schelling small 🔵 -0.1% [-0.4%, +0.2%] 🔵 +0.6% [+0.4%, +0.8%]
Schelling large 🔵 +0.0% [-0.5%, +0.4%] 🔵 +2.3% [+1.0%, +4.0%]
WolfSheep small 🔵 +0.0% [-1.1%, +1.3%] 🔵 +0.7% [+0.4%, +1.0%]
WolfSheep large 🔵 +1.0% [+0.7%, +1.4%] 🔵 +1.2% [-0.4%, +2.9%]
BoidFlockers small 🔵 -0.8% [-1.6%, -0.2%] 🔵 +0.2% [-0.6%, +1.1%]
BoidFlockers large 🔵 -0.7% [-1.1%, -0.4%] 🔵 +0.6% [+0.2%, +1.0%]

@EwoutH
Copy link
Member

EwoutH commented Aug 26, 2024

Thanks for the PR, could you add a proper title and description to it?

See https://github.com/projectmesa/mesa/blob/main/.github/PULL_REQUEST_TEMPLATE/feature.md?plain=1

@lakshayb30
Copy link
Author

Sorry for the inconvenience @EwoutH , I have updated the description for my PR.

@EwoutH
Copy link
Member

EwoutH commented Aug 26, 2024

Thanks!

I have a few conceptual thoughts:

  1. Maybe a function should be part of the Agent class itself. Then you can do something like this in the Model:
Wolf.create(n=5, model=self, energy=4)
Sheep.create(n=5, model=self, energy=2)
  1. Adding to the Agent to the model.agents AgentSet happens automatically, so I don't think we need to add a agent_set as an input. Except if we want to be able to directly add them to another AgentSet, then Agent.create() should return an AgentSet.
  • If we want to do this, that would be a feature for a separate PR.
  1. Passing model=self is a bit ugly, but it does allow creating Agents from everywhere. Can we default to the (model) class from which the create() method is called?
  2. Obviously you can't set an n parameter in the Agent anymore, since that's reserved for. I think that's acceptable.
  3. I think Automate and enforce unique unique_id in Agents #2213 should be sorted out first before we can properly get automatic unique_id assignment working.

Then we need to make a call if the convenience of such a function is worth it against the implicitness. I think it might be (and I love beautiful code).

Your PR is already useful, because now we're thinking and talking about these things!

@EwoutH
Copy link
Member

EwoutH commented Sep 9, 2024

Hi @lakshayb30!

I don't know if you're still interested in working on this, but if so, automatic unique_id assignment is now merged (#2213), which should make this feature more feasible / easy / elegant.

@EwoutH
Copy link
Member

EwoutH commented Sep 9, 2024

Here's an implementation that might work:

import inspect

class Agent:
    def __init__(self, model: 'Model', **kwargs):
        self.model = model
        for key, value in kwargs.items():
            setattr(self, key, value)
        self.model.register_agent(self)

    @classmethod
    def create(cls: Type['Agent'], n: int = 1, **kwargs) -> list['Agent']:
        frame = inspect.currentframe().f_back
        model = frame.f_locals.get('self')
        
        if not isinstance(model, Model):
            raise ValueError("create() must be called from within a Model instance method")

        return [cls(model=model, **kwargs) for _ in range(n)]

The create() method uses inspect to find the calling frame and determine if it's being called from within a Model instance method. If called from a Model instance method, it automatically passes the model instance to the agent constructor.

Then you should be able to do something like:

class PredatorPreyModel(Model):
    def __init__(self):
        ...
        Wolf.create(n=5, energy=4)
        Sheep.create(n=10, energy=2)

@EwoutH
Copy link
Member

EwoutH commented Sep 21, 2024

What would be really powerful you could input weighted distributions (in dict form, with the keys as values and values as weights) and random distributions.

class PredatorPreyModel(Model):
    def __init__(self):
        ...
        Wolf.create(n=5, energy=4)
        Sheep.create(n=10, energy={3: 0.25, 4: 0.5, 5:0.25})
        Grass.create(n=10, growth_rate=random.triangular(20, 60, 30))

Implementation wise you check for a function (if so evaluate it) and dicts (if so sample it).

@quaquel
Copy link
Member

quaquel commented Sep 21, 2024

What would be really powerful you could input weighted distributions (in dict form, with the keys as values and values as weights) and random distributions.

I would separate this out into several steps (which is also why this is not an a good first issue

  1. Have a factory method for creating N agents.
  2. Let this factory method take either a default value, a list of values, or indeed some distribution. The tricky thing with distributions is that they should default to a seeded random number generator for reproducibility. This should be possible because an Agent requires the model instance when created so you can get this inside a create method.
  3. Possibly also have a from_dataframe factory method?

The tricky thing now will be to handle the 3 options under 2 in an elegant way. And, to make it even a bit more complicated, hybrids of the three options.

you probably get something roughly like

@classmethod
def create(cls, n, model, **kwargs):
	for i in range(n):
		agent_kwargs = {}
		for key, value in kwargs:
			if isinstance(value, list):
				value = value[i]
			if isinstance(value, callable):
				value = value()  # not sure this works, should be tied to an rng etc.
			agent_kwargs[key] value
		agent = cls(model, **agent_kwargs)

@quaquel
Copy link
Member

quaquel commented Oct 19, 2024

This PR is stale and is picked up again in #2351.

@quaquel quaquel closed this Oct 19, 2024
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