Agent Based Modeling in Julia


#1

I couldn’t find “established” packages on Agent-Based Modeling (ABM) in Julia. Is there any work that it is interesting to follow on this subject?

And also, is there any Julia interface to [NetLogo](https://ccl.northwestern.edu/netlogo/) similar to [RNetLogo](http://rnetlogo.r-forge.r-project.org/) (in R) or [PyNetLogo](https://github.com/quaquel/pyNetLogo) (in Python)?


#2

Not that I know of, but since multiple dispatch is one of the defining features of the language, you should be able to program ABMs very easily from scratch. I would even argue that this is easier than investing in a “framework”.


#3

I think that programming ABMs from scratch is way beyond my programming expertise. ABM frameworks like NetLogo, Mason, Repast, Mesa are the result of years of hard work of several developers.
Nonetheless, I wonder the reason why you said that multiple dispatch eases this work…


#4

I am not familiar with these projects, but a cursory look at NetLogo suggests that it is a GUI-based model definition environment. Which indeed must have been very time consuming to develop, but if you are willing to write code and understand your models, just programming the underlying algorithm in Julia without the GUI part should be significantly easier.

Multiple dispatch helps because it solves the expression problem. In particular, you can think of ABMs as a collection of mutable objects (agents) which interact. You can define the agents as Julia objects (eg mutable struct), and then the interactions as functions with the relevant methods. This should give you a very flexible way of coding these models.

If you describe a simple algorithm that you want to implement, and show what you have attempted so far, you are very likely to get help here.


#5

Well, when I think in a simple ABM algorithm, the first idea that comes to my mind is the Shelling’s segregation model. It’s not what I want to study but it is, in fact, a classic example.

Googling it I found that (for me one of best disseminators of Julia), Sargent & Stachurski have already done it in Julia. It can be read in Shelling’s segregation model in Lectures in Quantitative Economics (QuantEcon).

It is possible to find there a reference to what you said about mutable struct:

mutable struct Agent{TI<:Integer, TF<:AbstractFloat}


#6

I’ll second that–for part of my dissertation I implemented a fairly complex agent-based simulation in Julia, and multiple dispatch made it pretty straightforward to structure. The basic pattern I used was something like this:

abstract type AbstractAnimal end

mutable struct Species1 <: AbstractAnimal
    # put your state variables here
end

mutable struct Species2 <: AbstractAnimal
    # put some (possibly different) state variables here
end

function update!(animal::Species1, group::Array{AbstractAnimal})
    # change animal's state based on some interaction with group
end

function update!(animal::Species2, group::Array{AbstractAnimal})
    # change animal's state based on some (different) interaction with group
end

function update!(group::Array{AbstractAnimal})
    for animal in group
        update!(animal, group)
    end
end

If you decide you want to add different species or behaviors, you can just define a new species type and update! method to go with it. I haven’t used NetLogo either, but from skimming some of the example code on the website, this approach doesn’t seem any more conceptually complicated…


#7

I have tried developing agent based models with Julia, but ran into performance issues with a design pattern like yours, which seemed like a natural way to approach the problem. Here is a very simple example illustrating poor performance with a heterogeneous vector of agents. Note that the performance is poor even though the methods are performing the same action. Does anyone have any advice?

using BenchmarkTools
abstract type A; end 

mutable struct B <: A
    x::Int 
end 

mutable struct C <: A 
    x::Int 
end 

incrementSuper(v::A) = v.x += 1 
incrementSub(v::B) = v.x += 1
incrementSub(v::C) = v.x += 1

a = A[B(1) for i in 1:10^5]
b = A[C(1) for i in 1:10^5]
mixed = [a;b]
same = [B(1) for i in 1:(2*10^5)]

@btime incrementSuper.(mixed)
@btime incrementSuper.(same)
@btime incrementSub.(mixed)
@btime incrementSub.(same)

Results:

27.589 ms (35 allocations: 1.53 MiB)
403.286 μs (26 allocations: 1.53 MiB)
3.208 ms (200032 allocations: 4.58 MiB)
401.495 μs (26 allocations: 1.53 MiB)

#8

Is the performance good when relying so much on multiple dispatch here? I’d worry that since the output of the group array is not type stable, this would not perform well relative to a code which does conditional dispatch based on the value of an integer. Perhaps the 0.7 optimizations for small unions will help here.

Edit: I have the same concern as the previous post.


#9

Indeed. I also hope there will be optimizations for these use cases.


#10

Check out https://github.com/tkoolen/TypeSortedCollections.jl and/or https://github.com/rdeits/ConcreteInterfaces.jl


#11

Interesting…I just checked my dissertation code, and realized I didn’t actually use arrays of mixed agent types anywhere–each kind of agent was run in its own simulation (i.e. your incrementSuper.(same) example), so I didn’t run into the performance issue. (At least, not before confidently posting hastily-written pseudocode in a public forum :upside_down_face:) Good to know about.


#12

Thanks these packages look useful. Do you have any idea whether one of these ideas will get implemented “under the hood” in base Julia? It would be nice to have Julia take care of those details.


#13

I don’t think there are any plans to do so, especially since there would be no performance advantage to having them in base Julia vs. being in a package.


#14

Together with @pszufe I will be running a tutorial on writing ABMs in Julia, http://ssc2018.dsv.su.se/programme (Monday August 20, 13:00 to 17:00).

In particular we have WIP to translate https://github.com/bkamins/EventSimulation.jl into a more ABM like framework similar to Mason (with any custom scheduling - not only tick-based, libraries supporting grids etc.). An interesting thing you might want to look up in the documentation is that you do not need update! method as above, as in Julia you can program agent structure to be a functor (https://docs.julialang.org/en/latest/manual/methods/#Function-like-objects-1) so you then just “call” an object.

As for performance abstract collections are of course bad, but they impact the speed of simulation only if agents do very little work (i.e. when looping itself takes a significant proportion of time). In complex ABMs this is often negligible as agents do computing-intensive work in their update! step anyway.

The issue with https://github.com/tkoolen/TypeSortedCollections.jl is I think that in ABMs you often have to iterate over agents in a random (or some specific order). In the worst case you can do what was already mentioned - have a single concrete type that is responsible for scheduling which points (e.g. via an Int) to what type of agent you actually have to handle and store separate types in separate collections (admittedly this is not a “clean” solution).


#15

Note that operations on heterogeneous arrays have gotten much faster in 0.7. On my machine, in v0.6.3, the timings for your operations are

incrementSuper.(mixed) =>  48.440 ms (800018 allocations: 13.73 MiB)
incrementSuper.(same)  =>  512.013 μs (2 allocations: 1.53 MiB)
incrementSub.(mixed)   =>  4.904 ms (200002 allocations: 4.58 MiB)
incrementSub.(same)    =>  513.724 μs (2 allocations: 1.53 MiB)

and in v0.7-beta2:

incrementSuper.(mixed) =>  10.282 ms (399497 allocations: 7.62 MiB)
incrementSuper.(same)  =>  293.553 μs (2 allocations: 1.53 MiB)
incrementSub.(mixed)   =>  325.503 μs (2 allocations: 1.53 MiB)
incrementSub.(same)    =>  272.394 μs (2 allocations: 1.53 MiB)

#16

It would be of great interest if you publish this or a similar tutorial on ABM in Julia.


#17

Regarding EventSimulation.jl you can see some tutorial examples here:
https://github.com/bkamins/EventSimulation.jl/blob/master/docs/src/tutorial.md

We also plan to rewrite some classical MASON simulations (such as Flockers) to Julia. Rewriting the logic is easy, however doing the graphics (which I think is important to understand ABM by beginners) is more work.


#18

Julia 0.7 small unions optimisation should help with heterogeneous arrays.
I think

Though one might need to trigger the restriction from vector Any down to vector union manually


#19

Well, I didn’t think it was quite ready to go public, but since you’re asking, here you go:

It’s very limited, and very raw, but it might be something to build on if there is interest in the community.


#20

Here is a write-up of what I think is the simplest (by which I understand using only Base and relatively easy to understand) approach at current state of Julia compiler https://juliasnippets.blogspot.com/2018/07/abc-of-abm-in-julia.html.