I am trying to set up a simple exclusion process on a 2D grid space where the jumps to neighboring cells are only allowed when unoccupied and the rates are asymmetric (right, up, left, and down moves do not have the same probability and depend on an internal variable "angle" of the agent).
At the moment I am having trouble identifying the appropriate neighbor node from the node_neighbors list, in particular when one of the neighbors falls on the other side of the domain (due to periodicity).
I am attaching my agent_step! function.
Thanks so much.
ps: I am starting with Julia and Agents.jl, so please excuse any silly mistakes I made in the attached code (I'd also be very keen to hear of any improvements to make the code more efficient).
Hi @mbruna!
Yes, this is a problem we've been working on previously (#294, #295) and I think has fallen by the wayside a little since we've been doing some major changes for an upcoming 4.0 release.
I'm pretty sure it's only due to the periodicity problem and may be fixed with the new GridSpace in the master branch, but I don't remember checking explicitly. Leave it with me for a bit and I'll see what I can find out.
Can I ask what version of Agents you're using? 3.7.1 or the current master branch?
(Edit: I can answer that myself. You're not using the master, since we changed node_neighbors to nearby_positions #317)
(contents of agent_step.txt)
function agent_step!(agent, model)
# Compute probabilities of 4 spatial moves (1- right, 2- up, 3-left, 4-down)
蟺j = [exp(cos(agent.theta)), exp(sin(agent.theta)), exp(-cos(agent.theta)), exp(-sin(agent.theta))]
# Wiener process for the angles
agent.theta += sqrt(2*model.DR*model.dt)*randn()
# Move agents in space
# check if agent will move (generate random number between 0 and 1):
r = rand()
# since a no-move is always the most likely, let's start with that
if (r >= sum(蟺j))
# no move
return
end
# if I made it here I will move (provided the direction I pick is empty)
neighbor_cells = node_neighbors(agent, model)
# For unbiased diffusion we'd do:
# target = rand(neighbor_cells)
# For the biased diffusion I need to be able to select in an efficient way specific neighbors (also accounting for periodic boundary conditions)
if r < 蟺j[1]
# move to right
target = # this should be the neighbour to the right of agent
elseif r>= 蟺j[1] && r< 蟺j[1] + 蟺j[2]
# move up
target = # this should be the neighbour up of agent
elseif r >= 蟺j[1] + 蟺j[2] && r < (蟺j[1] + 蟺j[2] + 蟺j[3])
# move left
target = # this should be the neighbour to the left of agent
else
# move down
target = # this should be the neighbour down of agent
end
# move agent if target site is unoccupied
if length(get_node_contents(target,model)) == 0
move_agent!(agent, target,model)
end
end
For me it is not clear why this is an Agents.jl related issue, so please help me out a bit. The core code we care about is:
# For the biased diffusion I need to be able to select in an efficient way specific neighbors (also accounting for periodic boundary conditions)
if r < 蟺j[1]
# move to right
target = # this should be the neighbour to the right of agent
elseif r>= 蟺j[1] && r< 蟺j[1] + 蟺j[2]
# move up
target = # this should be the neighbour up of agent
elseif r >= 蟺j[1] + 蟺j[2] && r < (蟺j[1] + 蟺j[2] + 蟺j[3])
# move left
target = # this should be the neighbour to the left of agent
else
# move down
target = # this should be the neighbour down of agent
end
so in principle you're asking how to find the "up" or "down" direction. Is there a bug somewhere...?
In principle you take the difference of the nearby location and the agent location. This gives you a vector. Then you compute the angle of this vector, atan(vec[2], vec[1]) which gives you the direction. What am I missing?
Based off of the discussion in #347, then no—it's not a bug, just something we could make into a quick helper function set at some stage in the future if need be.
@mbruna: for now, what it'd suggest is using something like this:
right(agent, model) =
(mod1(agent.pos[1]+1, model.space.dimensions[1]), agent.pos[2])
left(agent, model) =
(mod1(agent.pos[1]-1, model.space.dimensions[1]), agent.pos[2])
up(agent, model) =
(agent.pos[1], mod1(agent.pos[2]+1, model.space.dimensions[2]))
down(agent, model) =
(agent.pos[1], mod1(agent.pos[2]-1, model.space.dimensions[2]))
should there be 1 single function direction(agent, nearby_position, model) that gives you some kind of indicator of the nearby_pos is to the "east" or "west" or etc. of the agent?
That was essentially my question in #347, although I think you're missing the requirements slightly. We would want to know what is the grid position to the left (for example) of any given position, regardless of the boundary condition.
Rather than direction(agent, nearby_position, model) -> :west, it would be direction(agent, :west, model) -> one position left.
Sure, but :west is not useful as an output, besides the user cares to find the position "west" of the given position.
So I guess we want something like validpos(agent, :west, model).
Yes, exactly
@mbruna, I've implemented a walk! function in the current master branch (which will soon be the 4.0 release of Agents).
Since you're new to Agents, it may be best if you use the current master instead, since there will be a lot of changes for you to get accustomed to in the near future. It's quite stable at the moment, we are just adding some new features before release.
You can see updated documentation here. Since we're moving to this new version soon, I won't propagate the walk! function back to 3.7. If you're hesitant to upgrade just yet, the functions I outlined above should work for your current question regardless.
Let us know if you have any other issues.
(you add the master version by doing Pkg.add("Agents#master"))
@mbruna, I've implemented a
walk!function in the current master branch (which will soon be the 4.0 release of Agents).Since you're new to Agents, it may be best if you use the current master instead, since there will be a lot of changes for you to get accustomed to in the near future. It's quite stable at the moment, we are just adding some new features before release.
You can see updated documentation here. Since we're moving to this new version soon, I won't propagate the
walk!function back to 3.7. If you're hesitant to upgrade just yet, the functions I outlined above should work for your current question regardless.Let us know if you have any other issues.
@Libbum and @Datseris many thanks for your helpful replies and for adding the walk! function to the master branch. This is exactly what I was trying to do. I managed to move to the master branch and amend my code accordingly. I am now able to implement the agent_step! function that I needed, but I have one further question to make the code simpler and more efficient. The walk! function with directions would work fine in my problem if I did allow more than one agent per lattice site. But I only want to allow the move if the site is empty. What would be the best way to get around this?
At the moment I am not using the new walk! function. Instead I have
right(agent, model) =
(mod1(agent.pos[1]+1, size(model.space)[1]), agent.pos[2])
left(agent, model) =
(mod1(agent.pos[1]-1, size(model.space)[1]), agent.pos[2])
up(agent, model) =
(agent.pos[1], mod1(agent.pos[2]+1, size(model.space)[2]))
down(agent, model) =
(agent.pos[1], mod1(agent.pos[2]-1, size(model.space)[2]))
and then I amended the if loop I had to
if r < 蟺j[1]
# move to right
target = right(agent,model)
elseif r>= 蟺j[1] && r< 蟺j[1] + 蟺j[2]
# move up
target = up(agent,model)
elseif r >= 蟺j[1] + 蟺j[2] && r < (蟺j[1] + 蟺j[2] + 蟺j[3])
# move left
target = left(agent,model)
else
# move down
target = down(agent,model)
end
# move agent if target site is unoccupied
if length(ids_in_position(target,model)) == 0
move_agent!(agent, target,model)
end
I tried to define a function similar to your walk!() function but adding a condition that the move is only executed if the target site is empty, but I had lots of errors to do with the directions type. I was also wondering if there would be a neater way to input my condition on the direction to take (at the moment you have a very neat way of doing this when the target is chosen at random amongst empty sites).
Thanks again!
We'll look at extending walk! a little better in #352 to include an empty check. For the current implementation, you can extend your directional functions like this:
function right(agent, model)
new = (mod1(agent.pos[1]+1, size(model.space)[1]), agent.pos[2])
isempty(new, model) ? new : agent.pos
end
So that target = right(agent, model) will return the step to the right if there are no other agents there, or the current agent position if the position is occupied.
Another way could be something like this:
function right_if_empty!(agent, model)
new = (mod1(agent.pos[1]+1, size(model.space)[1]), agent.pos[2])
if isempty(new, model)
agent.pos = new
end
end
Which would modify the agent position directly, only if the position on the right is empty.
I've just seen the open case #352 about making the walk! function take in a vector instead of north/south/etc, plus a clause that only makes the move if the target position is empty. That would work very well for my current problem and the extensions I am thinking about. I will keep an eye on the new commits, and for now I will use your suggestion above which is neater than what I had. Thanks again!
The new walk! implementation is now in master, which should work for your case now!
If you're happy staying with the right, left functions you've written: I have given you an example that's not 100% complete.
Setting agent.pos = new will change the position of the agent itself, but not update the position of the agent in the space. It's better to use move_agent!(agent, new, model) instead of that line.
The new
walk!implementation is now in master, which should work for your case now!great, I will try this out now.
If you're happy staying with the
right,leftfunctions you've written: I have given you an example that's not 100% complete.Setting
agent.pos = newwill change the position of the agent itself, but not update the position of the agent in the space. It's better to usemove_agent!(agent, new, model)instead of that line.
yes, thanks, that's how I had done it already: (say the right move was selected)
target = right(agent,model)
move_agent!(agent, target, model)
One slightly off topic question I have (when pulling the master branch). I get the following:
(@v1.4) pkg> add Agents#master
Updating git-repo `https://github.com/JuliaDynamics/Agents.jl.git`
Resolving package versions...
julia version requirement for package `Agents [46ada45e]` not satisfied
Updating `~/.julia/environments/v1.4/Project.toml`
[46ada45e] ~ Agents v3.7.1 #master (https://github.com/JuliaDynamics/Agents.jl.git)
Updating `~/.julia/environments/v1.4/Manifest.toml`
[46ada45e] ~ Agents v3.7.1 #master (https://github.com/JuliaDynamics/Agents.jl.git)
but I am still able to run things. Do you know why do I get this message?
You're using the global environment, so it's probable that you have a package that depends on Agents there too (my guess is AgentsPlots?) which is causing that warning.
Since the master branch is not changing anything concerning plotting, I don't think you'll have an problem at all there, but if so, open up another issue and we can track the conflict a bit more.
As an aside, have a read through this page about environments. Generally it's best to separate your workspace for different projects once you get involved with more than one Julia package. @Datseris has also written DrWatson, which can help manage environments and workflows—could be of benefit to you down the line.
Thanks for this, I will have a look. But I am not sure how this would help in my case since you are right, I am using AgentsPlots (in the same notebook, to plot the system configurations). So if I understand this correctly, even using environments I would have Agents and AgentsPlots in the same one?
I am already using DrWatson for this project (decided to start from scratch like this), but for now I was testing Agents using a notebook and including all the packages required at the top of the notebook instead of using the DrWatson approach to packages.
On a separate matter, after pulling master again and trying walk! I get the following error:
MethodError: no method matching walk!(::ActiveAgent, ::Tuple{Int64,Int64}, ::AgentBasedModel{ActiveAgent,GridSpace{2,true},typeof(random_activation),Dict{Symbol,Real}})
Closest candidates are:
walk!(::AbstractAgent, !Matched::Type{#s75} where #s75<:Agents.Direction, ::AgentBasedModel{#s74,#s73,F,P} where P where F where #s73<:GridSpace{2,true} where #s74<:AbstractAgent) at /Users/mbruna/.julia/packages/Agents/ne9rq/src/spaces/utilities.jl:96
walk!(::AbstractAgent, !Matched::Type{#s72} where #s72<:Agents.Direction, ::AgentBasedModel{#s71,#s55,F,P} where P where F where #s55<:GridSpace{2,true} where #s71<:AbstractAgent, !Matched::Int64) at /Users/mbruna/.julia/packages/Agents/ne9rq/src/spaces/utilities.jl:96
walk!(::AbstractAgent, !Matched::Type{#s75} where #s75<:Agents.Direction, !Matched::AgentBasedModel{#s74,#s73,F,P} where P where F where #s73<:GridSpace{2,false} where #s74<:AbstractAgent) at /Users/mbruna/.julia/packages/Agents/ne9rq/src/spaces/utilities.jl:110
what am I doing wrong? I tried to look through the new doc and your tests and you seemed to be using walk! like this (giving it a tuple for the direction).
If you have
mutable struct ActiveAgent
...
end
this needs to be a subtype of AbstractAgent, so change it to:
mutable struct ActiveAgent <: AbstractAgent
...
end
This won't do it as it's already what I had
mutable struct ActiveAgent <: AbstractAgent
id::Int # The identifier number of the agent
pos::Tuple{Int,Int} # The x, y location of the agent on a 2D grid
theta::Float64 # The direction of the agent
end
OK, I'll need some more details. Can you paste your current script? I'll take a closer look.
Another possible issue, is that you need to actually update your local version. In the REPL, type ] to get into the package management mode, then type up. Restart the REPL and try to run your script again.
You were right, the update sorted it. Thanks!