JuliaDynamics / Agents.jl

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

Unbounded increase of agents in a GridSpaceSingle model #757

Closed Datseris closed 1 year ago

Datseris commented 1 year ago

Below i am attaching an implementation of a spatial rock paper scissors model we used in a tutorial about Agents.jl. The model is a grid space single, yet agents increase in numbers infinitely, even though we are only using the basic Agents.jl API. Something is wrong somewhere and there is a leakage. One of hte API functions probably needs an additional check...

using Agents, Random

@agent Strategy GridAgent{2} begin
    #:rock, :paper or :scissors
    type::Symbol
end

function initialize_model(;
        dims = (60, 60),
        n_rock = 800,
        n_paper = n_rock,
        n_scissors = n_rock,
        selection_rate = 1.0,
        reproduction_rate = 1.0,
        seed = 23182,
    )

    rng = MersenneTwister(seed)
    space = GridSpaceSingle(dims, periodic = true, metric = :manhattan)
    #model properties
    properties = (
        selection_rate = selection_rate,
        reproduction_rate = reproduction_rate,
    )
    model = AgentBasedModel(Strategy, space;
        properties, rng, scheduler = Schedulers.randomly
    )
    # Add agents to the model to a random but unoccupied position
    # using the `add_agent_single!` function
    for (strategy, N) in zip((:rock, :paper, :scissors), (n_rock, n_paper, n_scissors))
        for _ in 1:N
            add_agent_single!(model, strategy)
        end
    end
    return model
end

# Time evolution rules:
function strategy_step!(strategy, model)
    # propensities
    a1 = model.selection_rate * nagents(model)
    a2 = model.reproduction_rate * nagents(model)
    # total propensities
    a0 = a1 + a2 
    p = rand(abmrng(model))
    if 0 <= p <= a1/a0
        selection!(strategy, model)
    elseif a1/a0 <= p < (a1+a2)/a0
        reproduce!(strategy, model)
    end
end

function selection!(strategy, model)
    contender = random_nearby_agent(strategy, model)
    if !isnothing(contender)
        if strategy.type == :rock && contender.type == :scissors
            kill_agent!(contender, model)
        elseif strategy.type == :scissors && contender.type == :paper
            kill_agent!(contender, model)
        elseif strategy.type == :paper && contender.type == :rock
            kill_agent!(contender, model)
        end
    end
    return
end

function reproduce!(strategy, model)
    for pos in nearby_positions(strategy, model)
        # empty position to put an offspring in
        if isempty(pos, model)
            add_agent!(strategy.pos, model, strategy.type)
        end
        break
    end
    return
end

function model_step!(model)
    if nagents(model) > length(positions(model))
        error("model has more agents than positions")
    end
end

model = initialize_model()

step!(model, strategy_step!, model_step!)
model

step!(model, strategy_step!, model_step!, 100)
model
Datseris commented 1 year ago

By analyzing some agents, looping through the random agents of the model (when agents are more than possible positions) I see that for some agents

julia> isempty(strategy.pos, model)
true

which doesn't make sense. If an agent is in a GridSpaceSingle model its position can never be empty...

Tortar commented 1 year ago

I looked into it, this is the problem:

will do a PR about that myself if nobody is working about it at the moment :-)

I think the best fix would be throwing an error if a position is not empty in add_agent_to_space, but this would mean that the code above will fail

Datseris commented 1 year ago

but this would mean that the code above will fail

This is something I don't understand. I'm using isempty and add_agent_single! exclusively. So I should never encounter the failing condition, right? Yet I do. I'm confused why :D

Datseris commented 1 year ago

ah i'm a f**cking moron:

if isempty(pos, model)
            add_agent!(strategy.pos, model, strategy.type)
        end

I should add the agent in pos not strategy.pos. Oh well, such is the life, thanks to my moronity we uncovered a rather serious bug!

Tortar commented 1 year ago

Bugs are usually found by breaking some assumptions :D