Agents.jl: Best practices for AbstractAgent

Created on 24 Mar 2020  路  7Comments  路  Source: JuliaDynamics/Agents.jl

Extending the discussion in the review section of #159, we should identify the best method for constructing AbstractAgent types.

Arguments so far:

  • Types should be concrete, composite / UnionAll types should be avoided as they cause a performance loss
  • The opposite is true, avoid fields with abstract type

Possible place of misunderstanding:
When implementing #159 I was testing against simple types such as Agent3 from the tests,

mutable struct Agent3 <: AbstractAgent
  id::Int
  pos::Tuple{Int,Int}
  weight::Float64
end

and the Cell type in the CA modules.

mutable struct Cell{T<:Integer, Y<:AbstractString} <: AbstractAgent
  id::T
  pos::Tuple{T, T}
  status::Y
end

The point we were discussing had to do with my checks against something to this effect:

space = GridSpace((10,10))
model1 = ABM(Agent3, space)
model2 = ABM(Cell, space)

This, I believe is where the confusion lies, and the abstract type discussions clear up.

  1. mutable struct Cell{T<:Integer, Y<:AbstractString} <: AbstractAgent is 'more' correct than mutable struct Agent3 <: AbstractAgent and should be more performant.
  2. model2 = ABM(Cell, space) should throw an error
  3. model2 = ABM(Cell{Int64, String}, space) should be the correct and suggested method.

Please feel free to offer other interpretations.

performance discussion

All 7 comments

This discussion is trivial once a thorough understanding of the type system is obtained. This is where: https://docs.julialang.org/en/v1/manual/types/

Your statement 1 is not valid in general. It depends on what Agent3 is. Is it concrete or not?

this:

mutable struct Agent3
id::Int
end

and then Agent3 is a concrete type. Cell{Int64, String} is also a concrete type (BTW never use Int64, ALWAYS use Int in its place). Cell is NOT a concrete type in this discussion.

Then, Dict{A, B} is ONLY a concrete type if BOTH A and B are concrete.

I can also strongly recommend my tutorial here: https://youtu.be/Fi7Pf2NveH0?t=3442

Thanks for the clarifications. This is my misunderstanding. I'll take a look at this again with new eyes an see if there's not something better we can do with the guards and example types.

This now shows how truly important it is to provide a constructor for ABM that takes an agent instance as an input. We did yet another mistake in #159 to not implement it.

Not everyone is deeply familiar with the type system, and in all honesty Agent based modelling doesn't really require deep programming familiarity in general; all the more reason to not study the type system. I expect many users of Agents.jl to not know this subtle, yet crucial difference.

Therefore we always throw errors at non-concrete A, and in the error message we say "if you are not sure, just pass an agent type instance".

Just for further clarity, I'm struggling with your statement about position 1. not being true in general.

There's a discussion in the docs saying (what I interpret to be) the opposite:

julia> mutable struct MyType{T<:AbstractFloat}
a::T
end

This is a better choice than

julia> mutable struct MyStillAmbiguousType
a::AbstractFloat
end

because the first version specifies the type of a from the type of the wrapper object.

Can you elaborate on that a little?

AbstractFloat is not a concrete type. I discuss all these things in the video, and therefore don't want to spend too much time here repeating. In short

struct Agent
a::A
b::B
c::C

is a concrete type only if A, B, C are also concrete. There is this StillAMbiguousType is ambiguous is because its field a is not a concrete type. Notice on the other hand that

struct Agent{A, B, C}
a::A
b::B
c::C

is a parametric type on A, B, C. It is not the same. Here, Agent by itself will NEVER be a concrete type, no matter what you do. you need to specify A, B, C.

For the rest, please see the video.


If you want the same type of structure to have various different possibilities for the types of its fields, _then_ you use parametric types.

BTW, I haven't said it anywhere yet, but this is a good opportunity: I find it useless to use parametric types in our test suites and examples. The parametricity serves no purpose and gives no benefit. It only makes the code more convoluted.

The most often case of using parametric types is when you are _developing_ something. And indeed, our struct AgentBasedModel is parametrically typed since we _develop_ it for someone else, and don't know in advance the type of Agents they will create. But the Agent type doesn't have to be parametric. The user creates it, and I bet big dollar that 99,99% of cases, as a user you know what will be the field types of your agents.

@kavir1698

Ah, yes I see. Thanks (I'm watching your video in parallel).

Great. I'll look into altering the error messages as discussed already and add the second AgentBasedModel(agent::AbstractAgent) constructor.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Datseris picture Datseris  路  7Comments

hbsmith picture hbsmith  路  13Comments

Libbum picture Libbum  路  5Comments

pitmonticone picture pitmonticone  路  8Comments

itsdfish picture itsdfish  路  11Comments