I'm working on a model where agents have a vector dynamic state, not related with the position in space, that I wish to measure each step.
A simplified version of my code is
using Agents, LinearAlgebra, LightGraphs
mutable struct FakeAgent <: AbstractAgent
id::Int
pos::Int
state::Vector{Float64}
end
function fakestep!(agent,model)
i = agent.id
j = rand(1:size(agent.state,1))
x = randn()
model[agent.id].state[j] += x
return nothing
end
N = 2
model = ABM(FakeAgent,
GraphSpace(complete_graph(N));
scheduler=random_activation)
K = 3
# initialize agents to have obvious states: model.agents[i].state = [i, i, i]
for i in 1:N
a = FakeAgent(i,i, i*ones(K))
add_agent_pos!(a,model)
end
(adf,_) = run!(model, fakestep!, 10; adata=[:state])
first(adf,6)
which gives
"""
6Γ3 DataFrame
β Row β step β id β state β
β β Int64 β Int64 β Array{Float64,1} β
βββββββΌββββββββΌββββββββΌββββββββββββββββββββββββββββββ€
β 1 β 0 β 1 β [3.71784, -1.2705, 0.75856] β
β 2 β 0 β 2 β [0.69792, 2.55628, 1.05595] β
β 3 β 1 β 1 β [3.71784, -1.2705, 0.75856] β
β 4 β 1 β 2 β [0.69792, 2.55628, 1.05595] β
β 5 β 2 β 1 β [3.71784, -1.2705, 0.75856] β
β 6 β 2 β 2 β [0.69792, 2.55628, 1.05595] β
"""
Notice how the state vector is always the last state after all steps.
If I fix the step function:
function fakestep_fixed!(agent,model)
i = agent.id
j = rand(1:size(agent.state,1))
x = randn()
new_state = copy(agent.state)
new_state[j] += x
model[agent.id].state = new_state
return nothing
end
then I get what is expected
"""
6Γ3 DataFrame
β Row β step β id β state β
β β Int64 β Int64 β Array{Float64,1} β
βββββββΌββββββββΌββββββββΌββββββββββββββββββββββββββ€
β 1 β 0 β 1 β [1.0, 1.0, 1.0] β
β 2 β 0 β 2 β [2.0, 2.0, 2.0] β
β 3 β 1 β 1 β [1.0, 1.0, 1.27255] β
β 4 β 1 β 2 β [2.0, 2.0, 1.23249] β
β 5 β 2 β 1 β [0.22367, 1.0, 1.27255] β
β 6 β 2 β 2 β [2.32241, 2.0, 1.23249] β
"""
I could, instead of rewriting the step function, use mdata and collect all the agents at once (works, but is not convenient).
I'm not sure if it is a bug or just a misuse of Julia or DataFrames.
(PS: Code running on Linux, with Julia 1.5.1 and Agents.jl v3.5.0)
This does indeed look like undefined behaviour. Thanks for reporting it!
Before running the model, you can clearly see
julia> [a.state for a in allagents(model)]
2-element Array{Array{Float64,1},1}:
[2.0, 2.0, 2.0]
[1.0, 1.0, 1.0]
Thus the step 0 info in the first run is incorrect for sure - even before the run! function starts stepping the model.
Will need to investigate why though, give us some time to find out.
As an aside: model[agent.id].state[j] += x can just be agent.state[j] += x
Yep, so we're not doing a deep copy when collecting containers it looks like:
julia> adata=[:state]
1-element Array{Symbol,1}:
:state
julia> df_agent = init_agent_dataframe(model, adata)
0Γ3 DataFrames.DataFrame
julia> collect_agent_data!(df_agent, model, adata, 0)
2Γ3 DataFrames.DataFrame
β Row β step β id β state β
β β Int64 β Int64 β Arrayβ¦ β
βββββββΌββββββββΌββββββββΌββββββββββββββββββ€
β 1 β 0 β 1 β [1.0, 1.0, 1.0] β
β 2 β 0 β 2 β [2.0, 2.0, 2.0] β
julia> model[1].state[2] += randn()
1.9550338329763712
julia> collect_agent_data!(df_agent, model, adata, 2)
4Γ3 DataFrames.DataFrame
β Row β step β id β state β
β β Int64 β Int64 β Array{Float64,1} β
βββββββΌββββββββΌββββββββΌββββββββββββββββββββββ€
β 1 β 0 β 1 β [1.0, 1.95503, 1.0] β
β 2 β 0 β 2 β [2.0, 2.0, 2.0] β
β 3 β 2 β 1 β [1.0, 1.95503, 1.0] β
β 4 β 2 β 2 β [2.0, 2.0, 2.0] β
As an aside:
model[agent.id].state[j] += xcan just beagent.state[j] += x
Thanks for replying and for the tip.
Also, thanks for all the work! This package will speed up the setup for my model :)
And we thank you @flipgthb for opening such a great issue, with a true MWE, a lot of details, and pointing out an important consequence of our data collection! Making a good issue is hard work ;)
@flipgthb we'll release 3.6 soon, just a few things to clear up. To fix your issue, you should now run
adf,_ = run!(model, fakestep!, 10; adata=[:state], obtainer = copy)
Most helpful comment
And we thank you @flipgthb for opening such a great issue, with a true MWE, a lot of details, and pointing out an important consequence of our data collection! Making a good issue is hard work ;)