ABM + DiffEq

ABM + DiffEq#

https://juliadynamics.github.io/Agents.jl/stable/examples/diffeq/

using Agents
using Distributions
using CairoMakie
using OrdinaryDiffEq
using DiffEqCallbacks
CairoMakie.activate!(px_per_unit = 1.0)

Fisher agents

@agent struct Fisher(NoSpaceAgent)
    competence::Int
    yearly_catch::Float64
end

Set fishing quota

function fish!(integrator, model)
    integrator.p[2] = integrator.u[1] > model.min_threshold ?
        sum(a.yearly_catch for a in allagents(model)) : 0.0
    Agents.step!(model, 1)
end
fish! (generic function with 1 method)

Fish population change

function fish_stock!(ds, s, p, t)
    max_population, h = p
    ds[1] = s[1] * (1 - (s[1] / max_population)) - h
end
fish_stock! (generic function with 1 method)

Agents catch the fish

function agent_cb_step!(agent, model)
    agent.yearly_catch = rand(abmrng(model), Poisson(agent.competence))
end
agent_cb_step! (generic function with 1 method)

ABM model embedded in the callback

function initialise_cb(; min_threshold = 60.0, nagents = 50)
    model = StandardABM(Fisher; agent_step! = agent_cb_step!,
                        properties = Dict(:min_threshold => min_threshold))
    for _ in 1:nagents
        competence = floor(rand(abmrng(model), truncated(LogNormal(), 1, 6)))
        add_agent!(model, competence, 0.0)
    end
    return model
end
initialise_cb (generic function with 1 method)

Setup the problem

modelcb = initialise_cb()
tspan = (0.0, 20.0 * 365.0)
initial_stock = 400.0
max_population = 500.0
prob = OrdinaryDiffEq.ODEProblem(fish_stock!, [initial_stock], tspan, [max_population, 0.0])

# Each Dec 31st, we call fish! that adds our catch modifier to the stock, and steps the ABM model
fish = DiffEqCallbacks.PeriodicCallback(i -> fish!(i, modelcb), 364)
# Stocks are replenished again
reset = DiffEqCallbacks.PeriodicCallback(i -> i.p[2] = 0.0, 365)

@time sol = solve(prob, Tsit5();  callback = CallbackSet(fish, reset))
  1.908397 seconds (2.57 M allocations: 175.862 MiB, 22.18% gc time, 99.86% compilation time)
retcode: Success
Interpolation: specialized 4th order "free" interpolation
t: 2277-element Vector{Float64}:
    0.0
    0.1379730351325356
    0.5564761658770004
    1.1196169933783673
    1.7672551586109673
    2.552567516636946
    3.4680808539852217
    4.564946454459349
    5.876373627177295
    7.483768553665588
    ⋮
 7281.457109540513
 7282.23924037299
 7283.850408175915
 7285.286484468872
 7287.276531094173
 7289.463636568615
 7292.299075131584
 7295.740430610987
 7300.0
u: 2277-element Vector{Vector{Float64}}:
 [400.0]
 [410.58300851936434]
 [437.32809938708135]
 [462.27758078114454]
 [479.52406721317647]
 [490.4502350704483]
 [496.1319303647314]
 [498.7002281857691]
 [499.64738194876736]
 [499.9274082148435]
 ⋮
 [439.56738306366134]
 [426.577159631794]
 [414.5471852704679]
 [410.67781332932077]
 [408.8406914922738]
 [408.297236879001]
 [408.14638999373864]
 [408.1188811546075]
 [408.115060711314]
discrete = vcat(sol(0:365:(365 * 20))[:,:]...)
f = Figure(size = (600, 400))
ax = f[1, 1] = Axis(
        f,
        xlabel = "Year",
        ylabel = "Stock",
        title = "Fishery Inventory",
    )
lines!(ax, discrete, linewidth = 2, color = :blue)
f
_images/c2708bea047a73ff3e5ca2e94bc1c1250b97ff0cc2b43a54a32e9e8b4541d1e1.png

This notebook was generated using Literate.jl.