JuliaDynamics / Agents.jl

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

New function `replicate!(agent, model; different_kwargs…)` #809

Closed tatakof closed 1 year ago

tatakof commented 1 year ago

Is your feature request related to a problem? Please describe.

I'm working on a project involving an ABM for animal movement and disease ecology. Following the Predator-prey dynamics example, we use the reproduce! function to manage the reproduction of agents:

function reproduce!(agent::A, model) where {A}
    agent.energy /= 2
    id = nextid(model)
    offspring = A(id, agent.pos, agent.energy, agent.reproduction_prob, agent.Δenergy)
    add_agent_pos!(offspring, model)
    return
end

The problem with this function is that it's not very general, it assumes that the agent has certain properties (e.g., energy, reproduction_prob, \Deltaenergy). Thus it can not be used in a different scenario with different agents. But if the function could somehow infer the properties of the agent, it would be more general and could be reused across different scenarios.

Describe the solution you'd like A good solution would be a new function replicate!(agent, model; different_kwargs...) that given an agent, it creates a new agent at the same position and with the same fields, but with the possibility to specify certain fields that should be different.

Cheers!

Datseris commented 1 year ago

function replicate!(agent::AbstractAgent, model::ABM; kwargs...)

    # Get the properties of the agent
    properties = fieldnames(typeof(agent))

    # Create a dictionary to hold the new agent's properties
    new_properties = Dict{Symbol, Any}()

    # Copy the properties from the old agent to the new agent
    for property in properties
        if property != :id && property != :pos
            new_properties[property] =
            getfield(agent, property)
        end
    end

    # Override any properties specified in kwargs
    for (key, value) in kwargs
        new_properties[key] = value
    end

    # Create the new agent
    add_agent!(agent.pos, model; new_properties...)

end

pasting here some reference code from Francis

Tortar commented 1 year ago

I think this should be faster, even if probably less than manually creating the agent:

function replicate!(agent::AbstractAgent, model::ABM; kwargs...)
    newagent = deepcopy(agent)
    # Override any properties specified in kwargs
    for (key, value) in kwargs
        setfield!(newagent, key, value)
    end
    newagent.id = nextid(model)
    add_agent_pos!(newagent, model)
end
Tortar commented 1 year ago

actually seems very slow, but anyway a deepcopy is necessary since we can't pass by reference all attributes (a vector for example)

Tortar commented 1 year ago

But this is fast! (even if slower than manually creating the agent e.g. in WolfSheep-large in the Comparison repo on my computer the time goes from 81 ms to 100 ms)

function replicate!(agent, model; kwargs...)
    newagent = deepcopy(agent)
    for (key, value) in kwargs
        setfield!(newagent, key, value)
    end
    newagent.id = nextid(model)
    add_agent_pos!(newagent, model)
end

function Base.deepcopy(agent::A) where {A<:AbstractAgent}
    return A((deepcopy(getfield(agent, k)) for k ∈ fieldnames(A))...)
end
Tortar commented 1 year ago

No, actually it went up by much less! from 81 ms to 83 ms! So I think it's good enough