Issue updating agent properties - all values are the same! - Agents.jl

I apologize in advance if the issue is trivial. I’m still learning how to use Agents.jl and I cannot figure out what this issue is.

I will post my code and comment around it. The issue is stated below.

The packages in use:

using LightGraphs
using SparseArrays: findnz                       
using Plots  
using Statistics

Defining my agent:

mutable struct AgentZ <: AbstractAgent
    id::Int
    cond::Array{Float64,1}
    delta::Float64
    lambda::Float64
    learning_rate::Float64
    extinction_rate::Float64 
    signal::Array{Float64,1}
end

Function to create model:

function create_model(; num_agents,num_signals,p)

space = nothing
properties = Dict(
:network => erdos_renyi(num_agents, p),
)
    

model = AgentBasedModel(
        AgentZ,
        space,
        scheduler = Schedulers.randomly,
        properties = properties,
    )

    
for i=1:num_agents
add_agent!(
            model,
            cond,
            delta,
            lambda,
            learning_rate,
            extinction_rate,
            signal,
            )
end

        
return model
end

This is a function that assigns to each agent’s signal the value 0 or 1. This part works.

function update_sig!(model)
    
    for i in allagents(model)
       i.signal=rand(0:1,num_signals)
    end

end

This function, conditioned on the random signal, changes an agent’s cond by plus/minus 1. The issue lies here. When I step the model forward agent.cond, for all agents, takes the same value, where some should go down and some up.

function update_cond!(agent)
    
    if  agent.signal[1] == 1
        agent.cond[1] += 1
    else
        agent.cond[1] -= 1 
    end

end

Defining the agent_step!

function agent_step!(agent,model)
update_sig!(model)
update_cond!(agent)
end

Setting the parameters and creating the model:

num_signals = 1
num_agents = 10
p=0.3
cond = zeros(num_signals).+.001
delta  = 0
lambda = 1
learning_rate = .05
extinction_rate = 1
signal = zeros(num_signals)
model= create_model(;num_agents,num_signals,p)

You can check the issue here by stepping the model and printing the value of the two properties for all agents, signal and cond. Notice signal works well, but cond takes the same value across all the agents. Where is the mistake, how do I get cond to take the proper values?

step!(model, agent_step!)
for i =1:num_agents
print(model[i].cond)
end
for i =1:num_agents
print(model[i].signal)
end

Thank you,
Bfried1

I think you are using the same array cond for every agent. You could replace it with copy(cond) in the add_agent! call.

3 Likes

Yep, the underlying issue as @mikkoku has already stated is that cond is a global variable in your setup, which is then referenced by each agent in your model.

Agents.jl handles this for you on the data collection side when you provide the appropriate obtainer value to run!:

obtainer = identity : method to transfer collected data to the DataFrame . Typically only change this to copy if some data are mutable containers (e.g. Vector ) which change during evolution, or deepcopy if some data are nested mutable containers.

Since its more feasible for users to generate their own initialization functions, this property of containers in Julia is one aspect of the language that can catch people out.

Using copy when adding your cond and signal Arrays to each agent will be the solution here.

I’d also recommend an alternate solution: don’t set all of the agent properties in the global scope—add them as keyword arguments to create_model like you do with num_agents etc.

Something like

function create_model(; num_agents = 10,
                        num_signals = 1,
                        p = 0.3,
                        delta = 0,
                        lambda = 1,
                        learning_rate = 0.05,
                        extinction_rate = 1)

space = nothing
properties = Dict(
:network => erdos_renyi(num_agents, p),
)
    

model = AgentBasedModel(
        AgentZ,
        space,
        scheduler = Schedulers.randomly,
        properties,
    )

    
for i=1:num_agents
add_agent!(
            model,
            zeros(num_signals).+.001, # Since this is not assigned anywhere,
            delta,                    # a new Array will be created each step of the for loop
            lambda,
            learning_rate,
            extinction_rate,
            zeros(num_signals),
            )
end
       
return model
end
2 Likes

Thank you! @mikkoku

@Libbum, thank you for the information and great suggestion! Answers like this are what keeps the Julia community growing.

@mikkoku @Libbum,
Both your solutions worked, but now a new issue has arisen. The changes in cond do not correctly correspond to the signal.

Updated code:

function create_model(; num_agents,num_signals,p)

space = nothing
properties = Dict(
:network => SimpleWeightedGraph(num_agents),
)
    

model = AgentBasedModel(
        Agent_Zero,
        space,
        scheduler = Schedulers.randomly,
        properties = properties,
    )

    
for i=1:num_agents
add_agent!(
            model,
            copy(cond), # noted change 
            delta,
            lambda,
            learning_rate,
            extinction_rate,
            copy(signal), # noted change 
            )
end
        
return model
end

After using step!(model, agent_step!) once I check the parameters to find:

for i =1:num_agents
print(model[i].cond)
end

Output:[-0.999][1.001][-0.999][-0.999][-0.999][1.001][-0.999][-0.999][1.001][1.001]

for i =1:num_agents
print(model[i].signal)
end

Output:[0.0][0.0][1.0][0.0][0.0][1.0][0.0][1.0][1.0][0.0]

As you can see there is something off. Just looking at the first two values for each output, the signal for the first two agents is 0, yet one’s cond went up and the other’s down. I’ve also noticed that this issue does not happen when num_agents=1.

Thank you,
bfried1

I found the issue:

function update_sig!(model)
    
    for i in allagents(model)
       i.signal=rand(0:1,num_signals)
    end

end

Is called for each agent, when it should be called only once.

I wasn’t sure if this was your intention or not initially.

What’s the logic you need here?

Do you need a random set of signals for each agent, then based on a change in the signal you would like to modify the cond property?
For that, you could do

function agent_step!(agent, model)
    agent.signal = rand(0:1, length(agent.signal))
    update_cond!(agent)
end

Do you need a random set of signals (but only one set), that change each step, then based on a change in the signal you would like to modify the cond property of each agent?

Better to then add signal as a model property and have a model step. Note: by default agent properties are calculated first, but we can change that with the agents_first boolean passed to step!

# Remove `signal` as a property from `Agent_Zero`

function create_model(; num_agents,num_signals,p)

space = nothing
properties = Dict(
:network => SimpleWeightedGraph(num_agents),
:signal => signal, # Note: you can do this here if you want, since it's never a copy
)
    

model = AgentBasedModel(
        Agent_Zero,
        space,
        scheduler = Schedulers.randomly,
        properties = properties,
    )

    
for i=1:num_agents
add_agent!(
            model,
            copy(cond), # noted change 
            delta,
            lambda,
            learning_rate,
            extinction_rate,
            )
end
        
return model
end

function model_step!(model)
    model.signal = rand(0:1, length(model.signal))
end

function agent_step!(agent, model)
# Essentially `update_cond!`
    if  model.signal[1] == 1
        agent.cond[1] += 1
    else
        agent.cond[1] -= 1 
    end
end

# Step with
step!(model, agent_step!, model_step!, 1; agents_first = false)

I also notice that your update_cond! function only ever cares about the first value in signal and cond. Is this intentional? Or should all of the values be updated?

There are a number of ways to do this. An example:

agent.cond[model.signal .== 1] .+= 1
agent.cond[model.signal .== 0] .-= 1

You can also simplify this to

agent.cond[model.signal] .+= 1
agent.cond[.!model.signal] .-= 1

if you changed signal to rand(Bool, num_signals).

1 Like