Remove agent from space, but not scheduler

Hello,

I have an agent based model in which some agents are removed from the space so they cannot interact with other agents for some number of iterations. I can remove agents with remove_agent! and put them in a separate vector, and add them back to the space after the target iterations have elapsed. However, because this has to be performed separately from the main agent_step! loop, it may introduce unintended order effects into the model. My question is this: can I remove an agent from the space, but keep them in the scheduler?

Thanks!

Hm this sounds like a complicated way to do it…

Perhaps you shouldn’t remove them from the space at all, but rather add an additional agent property that keeps track of this “absence”. Then when calling nearby_agents simply filter these agents out. What’s the problem with doing this?

Thanks for your reply. One potentially complicating factor is that a each cell in the grid can only be occupied at most by a single agent. If I relax that constraint, I need a way to move an agent to a cell that is either unoccupied, or occupied by an agent that is “suspended”. Currently, I use randomwalk! with GridSpaceSingle. What is the best way to do it with GridSpace?

just use GridSpace and allow multiple agents to occupy the same location? I am confused why that’s not an option.

I think you have to explain what you want to achieve instead of what you are trying to do to achieve that. See XY Problem.

Ok. Let me see if I can clarify. In the model, agents move randomly within a radius to an unoccupied position in GridSpaceSingle. In other words, multiple agents cannot occupy the same position. Some agents are suspended from the simulation for a period of time. While suspended, the agent does not move and it does not occupy a position in the space. However, its suspension time counter is incremented on each iteration. I want to make sure that the suspended agents are added back to the space (in a different random, unoccupied position) after this period as elapsed. Right now I remove suspended agents, and track them in a separate vector, which is not ideal for ensuring the agents are processed in a random order.

Your advice is to use GridSpace and allow multiple agents to occupy a single position, in which case the suspended agent can be ignored (as though it has been removed). This approach can work, but it requires manually controlling which agents occupy a given position. For example, a suspended and non-suspended agent can occupy the same position, but it is not acceptable for two non-suspended agents to occupy the same position. In this case, my question is which function do I use to move agents to a random location within a radius which is either unoccupied or occupied only by a suspended-agent?

Okay. This seems to me the best way to go about what you want to achieve. Now, can you also explain what is your problem with the random order processing? Do you want both the suspended and non-suspended agents to act in a random sequence? And it is not enough for you to first activate all non-suspendent agents randomly (with the existing random scheduler), and then a model_step! function goes through all suspendent agents again randomly (by just getting a random sequence of indices of the suspendent vector)?

That’s a good question. I am not sure whether it matters or not. Sometimes seemingly unimportant implementational details affect the results more than expected. Right now, my code essentially performs the following on each iteration in model_step!:

  1. randomly activate suspended agents (some will get added back to the space, some will remain suspended)
  2. randomly activate non-suspended agents in a separate loop

As a result, suspended and non-suspended agents are activated separately rather than interspersed within one random sequence. To answer your question, I would like to activate all agents within the same random sequence to test whether it matters.

the best way is to use Agents.remove_agent_from_space!, this function is unexported but it should do what it says. I think that having this function private is not good for flexibility, because to me it is clear that the space should have its own way to remove ids from it, each different component should have its way to be updated without affecting the other components, in Agents.jl we tie everything to the model, but this is wrong from a design perspective in my opinion.

1 Like

This is easy to achieve with a custom scheduler, see here:

In sort, you shouldn’t pass an agent_step! function into the StandardABM consturctor. Only a model_step!. You should still define and use an agent_step! function, but only inside the model_step! . Inside this step you create two vectors: one containing all IDs (suspendent and not) and one a boolean saying whether an ID is suspended. They you randomly sort these vectors and you iterate through them. if boolean is true, call the agent_step!. if not, reduce the suspension countdown.

1 Like

Yes, good point. We can document it, say that it is called internally by remove_agent! and thus no need to call it then. Then added to the tests and export it. We should create a test with a model that uses this functionality to make sure it works.

Probably open an issue to keep track of it.

1 Like

I opened also an issue for a general separation of concerns for spaces: Refactoring the access to space related functions to move towards multispace models · Issue #910 · JuliaDynamics/Agents.jl · GitHub, I don’t have much time for development, but if someone wants to try this refactoring I would be happy to review

1 Like

Thank you both for your help working through this issue. I think Agents.remove_agent_from_space! is what I am looking for. It appears to work as expected:

agent = model[1]
Agents.remove_agent_from_space!(agent, model)
agent.id in allids(model) # evaluates to true
isempty(agent.pos, model) # evaluates to true

I really wouldn’t recommend using this in any “production code” as it hasn’t been tested to be used outside remove_agent! and it may lead to undefined behavior. For now I would argue you should instead use the custom scheduler, which will continue to work even when we make remove_agent_from_space! available to end-users.

Thanks for letting me know about the potential for unexpected behavior. Its not clear to me how the custom scheduler solves the problems outlined above. Would you mind elaborating a bit on a solution with a custom scheduler?

The scheduler allows you to do what you want to do: iterate randomly through both suspended and non suspended agents. when the suspension countdown is over, you add the agent back.

Can you elaborate on why this solution does not satisfy you, because I may have misunderstood what you want to achieve?

My bet is that it shouldn’t have undefined behaviours, though, it’s clear that, strictly speaking, this brand new issue opened by @Datseris should be solved before being 100%(99%?) sure that this is the case

I think the remaining issue is how to move the agent to a valid position. If I use GridSpaceSingle, I need to remove a suspended agent from its position so that other agents may occupy that position. If I use GridSpace, I can leave a suspended agent in its position, but I need a way to move non-suspended agents to a space that is either (1) unoccupied, or (2) only occupied by a suspend agent. Does that make sense?

I tend to agree with your assessment. My informal tests seem to suggest that Agents.add_agent_to_space! and Agents.remove_agent_to_space! appropriately add and remove the agent from the space. I am will to tolerate some degree of uncertainty for now. If @Datseris has an alternative solution for moving agents with a custom scheduler, I will consider that. Thanks again for the help!

Yes this seems the best for now.

1 Like