JuliaDynamics / Agents.jl

Agent-based modeling framework in Julia
https://juliadynamics.github.io/Agents.jl/stable/
MIT License
725 stars 117 forks source link

example of heterogeneous agents #543

Closed drdozer closed 3 years ago

drdozer commented 3 years ago

The agents API appears to support models where agents come from more than one struct. However, there are no examples of this in the examples. All of them model agents using a single struct with some field to switch behaviour. Would it be possible to have an example where there is more than one kind of agent which requires more than one struct to model?

Libbum commented 3 years ago

We are working on removing this methodology from our codebase due to the inherent performance issues we face at scale, so we've moved our examples away from this pattern. For small systems the issue isn't that noticeable though.

https://github.com/JuliaDynamics/Agents.jl/blob/master/examples/predator_prey_multiagent.jl

Is still around, but not listed. So feel free to take a look through that.

drdozer commented 3 years ago

Thanks @Libbum. I think perhaps not all of the example text has been fully copy-edited. In my situation I'm wanting to build a very simplified model of a game to explore speedrun strategies. Here there are at least 4 very different kinds of agents - the player, the tech tree, and two different categories of entities the player can interact with. Will I still hit performance issues if I encode my distinctive behaviours into different structs, but them wrap them in a union field, or group them with an abstract type in a single agent struct? So something like:

abstract type AgentType end

struct Player <: AgentType end

struct TechTree <: AgentType end

struct Machine1 <: AgentType end

struct Machine2 <: AgentType end

struct SpeedrunAgent <: AbstractAgent
  id::Int
  pos::NTuple{2,Int}
  agent_type::AgentType
end

# dispatch on the agent type
step!(model, agent) = step!(model, agent, agent.agent_type)

I honestly don't think performance is likely to be my primary concern, but I would prefer to use agents in the way it is intended.

AayushSabharwal commented 3 years ago

This would still dispatch dynamically, since the value of the agent_type field is only known at runtime. It would have the same/similar performance issues as non-concrete agent types. However, this really wouldn't be as noticeable until you scale it up.

If you still want to avoid dynamic dispatch, you can turn agent_type into a Symbol field, and do something similar to predator-prey.

Libbum commented 3 years ago

Since the version I linked to is not in our documentation it is no longer updated and may indeed fail on recent versions. The concept is there though.

As Aayush points out, there's no need to go through that process, using a union type is sufficient.

@agent Player GridAgent{2} begin
    # ...
end
@agent TechTree GridAgent{2} begin
    # ...
end
@agent MachineOne GridAgent{2} begin
    # ...
end
@agent MachineTwo GridAgent{2} begin
    # ...
end

space = GridSpace((100, 100))
model = ABM(Union{Player, TechTree, MachineOne, MachineTwo}, space)

agent_step!(agent::Player, model) = # ...
agent_step!(agent::TechTree, model) = # ...
agent_step!(agent::MachineOne, model) = # ...
agent_step!(agent::MachineTwo, model) = # ...

Is the template you should start with.

drdozer commented 3 years ago

@Libbum I hadn't even considered using a union type. I'm still very new to julia, so often don't see the obvious. thanks :D