Agents.jl: Reducing over an empty collection is not allowed

Created on 14 Apr 2020  路  9Comments  路  Source: JuliaDynamics/Agents.jl

We've got a problem in the data collector when we try to save data from a set of 0 agents.

Suppose we have

julia> model.agents
Dict{Int64,Agent} with 5 entries
...

and run! with an agent aggregation function, say (:weight, sum).

If agent_step! ends up using kill_agent!() or genocide!() such that

julia> model.agents
Dict{Int64,Agent} with 0 entries

at some point, we'll hit the [1] _empty_reduce_error() at ./reduce.jl:295.

enhancement discussion

All 9 comments

But is this really a bug...? How do you make sense of "aggregating" when there are no agents?

We could add the value missing if length(model.agents) == 0 and also put a note in the docstring of run!.

True. The error is by design in Julia core. It puts the burden on the user to check this, so perhaps at least some documentation around the issue.

From Daisyworld where I hit this:

black(agent) = agent.breed == :black

to

black(agent) = isnothing(agent) ? false : agent.breed == :black

works just fine.

Edit: well, it seems to in the repl, but fails when building docs. I think we may need to add the missing possibility. Or somehow enable a default value for the user.

Default value seems very sensible to me, and also the way to go! We could default to missing, if possible given our setup, but this may impact performance since the type of arrays in the dataframe must change to be a union over missing.

Exactly. I don't think we want to add missings if we can help it.

Solution in the Daisyworld case is:

total(v) = length(v) == 0 ? 0 : sum(v)

I'll add an extra discussion in run! about this in conjunction with Daisyworld when I push it (should be in the next hour or so).

use 0.0 instead of 0. This is not that much different from using missing, since 0 is not FLoat64 ;)

But @Libbum can you please check if using missing has measurable performance impact please? Since you have the model set up and all.

Sure. Will do.

With missing

julia> model
AgentBasedModel with 774 agents of type Daisy
julia> @btime collect_agent_data!(agent_df, model, adata, 1)
  10.271 渭s (4 allocations: 64 bytes)

julia> model
AgentBasedModel with 0 agents of type Daisy
julia> @btime collect_agent_data!(agent_df, model, adata, 2)
  1.695 渭s (26 allocations: 1.00 KiB)

Implementation:

   isconcretetype(current_type) || warn("Type is not concrete when using function $(agg) "*
        "on key $(k). Consider using type annotation, e.g. $(agg)(a)::Float64 = ...")
-      types[i+1] = current_type[]
+      types[i+1] = Union{Missing, current_type}[]
    end

    function collect_agent_data!(df, model, properties::Vector{<:Tuple}, step::Int=0)
    alla = allagents(model)
-   push!(df[!, 1], step)
-   for (i, (k, agg)) in enumerate(properties)
-       _add_col_data!(df[!, i+1], agg, k, alla)
+   if length(alla) > 0
+       push!(df[!, 1], step)
+       for (i, (k, agg)) in enumerate(properties)
+           _add_col_data!(df[!, i+1], agg, k, alla)
+       end
+   else
+       push!(df, vcat(step, repeat([missing]; outer=length(properties))))
    end
    return df
end

With 0.0 default

julia> model
AgentBasedModel with 774 agents of type Daisy
julia> @btime collect_agent_data!(agent_df, model, adata, 1)
  9.226 渭s (4 allocations: 64 bytes)

julia> model
AgentBasedModel with 0 agents of type Daisy
julia> @btime collect_agent_data!(agent_df, model, adata, 2)
  901.273 ns (3 allocations: 48 bytes)

Default is the clean winner. Adding missing requires us to always initialise an agent aggregate dataframe with all columns other than step with Union{Missing, X}, since we never know before time if length(alla) == 0 at any stage throughout the run.

To me, this means making sure users understand this by adding documentation, not by implementing guards. I'll add the docs and push up Daisyworld.

Alright, I agree. Let's simply document this and not do anything. Will also get 3.0 out faster. I always perceived this as not a bug and the users responsibility as well to be honest.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Datseris picture Datseris  路  7Comments

dunefox picture dunefox  路  11Comments

ndgnuh picture ndgnuh  路  8Comments

Datseris picture Datseris  路  3Comments

fbanning picture fbanning  路  10Comments