Open jacobusmmsmit opened 3 years ago
What is adata
for this case? Could be a bug, but perhaps this was a conscious change we made for 4.x, it'll depend on your adata
.
adata
is defined as in the example code:
sheep(a) = typeof(a) == Sheep
wolves(a) = typeof(a) == Wolf
grass(a) = typeof(a) == Grass && a.fully_grown
adata = [(sheep, count), (wolves, count), (grass, count)]
OK, thanks—I'll take a look.
this line is the problem. It assumes at least one agent of each type in the model. Should be a quick fix.
I quickly identified the error just before bed last night. Looking at it a little more closely this morning, this may be a more nuanced problem than originally anticipated.
The behavior we should expect here is something more like the single case:
mutable struct Agent3 <: AbstractAgent
id::Int
pos::Tuple{Int,Int}
weight::Float64
end
model = ABM(Agent3, GridSpace((5,5)))
agent3(a) = a isa Agent3
adata = [(agent3, count)]
julia> data, _ = run!(model, dummystep, 1; adata)
ERROR: ArgumentError: Model must have at least one agent to initialize data collection
So the current argument error is not helpful and needs to be fixed at the very least. If the model has no instance of an agent type it has no way of identifying the properties of that agent in the general case.
In v3.7, multiagent models like predator prey were not properly supported by the collection routines, whereas they are now. This allowed for an edge case that you're using in 3.7, where the model has one or more agent, but not one or more agent of each type.
I'd need to know a little more about your motivations in this example to be able to provide functionality for it. This line of questioning goes for single-agent models too for the most part.
run!
, but kill_agents!
on the first tick of the model (not ideal)run!
.I don't agree that this is a bug, or something to be fixed. As Julia says, collection must be non-empty
, You need at least one agent to collect agent data, that's normal to expect.
We can change the error to say Model must have at least one agent to initialize data collection
instead if adata
is not nothing
and nagents(model) == 0
, but that's about it.
Tim's last idea (using the data collection interface) for a specific case where a model must start without any agents is the best solution. The convenience of run!
cannot be applied in this case.
Thank you for the responses. In my case there will be agents of all type in my simulation, just perhaps not at the beginning. I am trying to construct a quiver plot of the one step population makeup over a triangle simplex of possible population makeups (I have three types in my model, hence triangle simplex).
From the image you can see that this needs to start sometimes where there are indeed no agents of a certain type. I was thinking of doing this with a paramscan
but I'm not sure how best to do so.
For reference here is how I am currently doing this in Agents.jl v3.7
sitters(a) = typeof(a) == Sitter
identifiers(a) = typeof(a) == Identifier
cheaters(a) = typeof(a) == Cheater
adata = [(sitters, count), (identifiers, count), (cheaters, count)]
total_agents = 30
for i in 0:total_agents, j in 0:total_total_agents - i
initialise_agents!(model, i, j, total_agents - i - j)
results, _ = run!(model, agent_step!, model_step!, 1; adata=adata, replicates=100);
results = results |>
df -> filter(:step => !=(0), df) |>
df -> combine(df, :count_sitters => mean => :xend, :count_identifiers => mean => :yend)
results[:, :xstart] .= i
results[:, :ystart] .= j
final_results = [final_results; results]
end
It is rather rudimentary.
Since we cannot know how your (or anyone else's) agents look before hand, we cannot provide this service in general via run!
, so you'll need to swap that out with a custom run function and use the data collection interface.
Using the current implementation as a basis, something like:
model = init_function(...) # Add at least one agent of each type
results = init_agent_dataframe(model, adata) # This gets us the initial table layout correct
total_agents = 30
for i in 0:total_agents, j in 0:total_total_agents - i
# My understanding is that this function calls `genocide!`, so initial agents will be cleared
initialise_agents!(model, i, j, total_agents - i - j)
# `results` will be updated
custom_run!(results, model, agent_step!, model_step!, 1; adata)
...
end
function custom_run!(df_agent, model, agent_step!, model_step!, n; adata=nothing)
s = 0
while s < n
collect_agent_data!(df_agent, model, adata, s)
step!(model, agent_step!, model_step!, 1)
s += 1
end
collect_agent_data!(df_agent, model, adata, s)
return nothing
end
Replicates are handled this way. How this is extended nicely for cases like this is in the plans for the future. We've been working recently on seeding #420 to get replicates to work better #415. For the moment though, a custom implementation that wraps your custom_run!
is the best bet to get this working in v4.x.
@jacobusmmsmit I'm coming back to this issue soon since we just sorted out some big questions about seeding and replicates. I think we can now do what you're asking a bit easier.
Describe the bug Initialising a model with 0 agent of a certain type, then passing
adata
to therun!
function where you count the number of each type gives this error:ERROR: ArgumentError: collection must be non-empty
Minimal Working Example In the
predator-prey
example, you can setn_wolves = 0
and the error will appear when run.Agents.jl version
Agents v4.1.3 (note: this was not an issue in Agents v3.7 as this is what I was previously using before upgrading and having this break.) Julia v1.5.3