Agents.jl
Agents.jl copied to clipboard
Are Cellular Potts Models (CPMs) possible in Agents.jl?
Cellular Potts Models are a type of grid-base ABM that are common when trying to modeling cellular behaviors (e.g. chemotaxis). What's interesting about them is that agents span multiple but connected grid spaces giving cells a measurable area across the space. The cellular agents are then updated randomly as they try to minimize an energy function that penalizes steps that, for example, make the cell too large.
The fact cells span multiple grid points seems to break a lot of key assumptions within Agents.jl. For example, an AbstractAgent
must have a field pos
which requires only one spatial position for the agent.
I started writing my own package CellularPotts.jl, but I'm quickly realizing how monumental a task this project is for one person. So that leads me to a few questions:
- Is it possible to model CPMs in Agent.jl ?
- If not how difficult would it be to integrate my package into parts of JuliaDynamics? I don't want to re-invent the wheel with all the amazing interactive visualizations, and Julia ecosystem integration you've done.
Also, Agents.jl is an amazing package, please keep up the great work!
Hi, and thanks for your kind words.
So, if I understand correctly, you want something like our GridSpaceSingle
, but an agent can occupy several grid cells at the same time. Correct?
Then no, this is not available right out of the box. But you most definitely can implement it as part of the Agents.jl system. After a first working prototype is running, this could become a new submodule (similar to how we have pathfinding, IO, etc...) and be accessible by other users as well.
I guess the simplest way to do it would be for you to create a new space type that represents this kind of scenario: https://juliadynamics.github.io/Agents.jl/stable/devdocs/#Creating-a-new-space-type-1 My guess is that the agent position type in this case would be a vector of coordinates (all cells the agent "occupies")
An alternative way would be to use the existing GridSpaceSingle
, and have an additional array in the model properties that is like the "occupation" of the grid.
As I don't have much time to learn more about this kind of science and its applications, it would be useful for you to write down a list of common functions you would need for this kind of simulations. Like, how would agents move, do you need to find their neighbors, do you create or kill agents, etc. I.e., what would you need from Agents.jl to actually create your model?
Yes, I believe you understand the gist of CPMs and I think creating a new space type might be the right solution. I'll try and create a minimal working example over the next week or so and see where I run into issues. For reference, here is an extremely well put together and interactive introduction to CPMs
As you have it organized now, are submodules new experimental features outside the core scope of Agents.jl?
Some common functions/features needed for this type of simulation:
- A collection of "penalty" functions that determine if a new step is favorable
- Example: An adhesion penalty that ensures pixels from the same cell stay together
- Agents move by extending or contracting their boundary to a new grid space
- These updates are typically random so something similar to the
Partially
scheduler might be a good default.
- These updates are typically random so something similar to the
- You don't need to find cell neighbors, but you do need grid neighbors (typically Moore neighbors/Chebyshev distance)
- Killing an agent can be done by updating all grid locations with the selected cell's ID to zero.
- You could also set their desired volume/area to zero and have them slowly disintegrate
- New agents are created by dividing a cell in half (reminiscent of mitosis)
As you have it organized now, are submodules new experimental features outside the core scope of Agents.jl?
Nope, just isolated functionality that targets specific usecases and has no reason to be part of the direct level API (i.e., set of functions you get access to when doing using Agents
)
I'm looking forwards to your draft implementation.
You don't need to find cell neighbors, but you do need grid neighbors (typically Moore neighbors/Chebyshev distance)
I do not understand this statement, as "moore neighbors" does not make sense to me in the context of these models. What are the "moore neighbors" of the cyan agent in this figure below? From which of its cells do you even count the radious of neighborhood?
I guess by moore you would mean anything that cells an adjacent edge...?
Killing an agent can be done by updating all grid locations with the selected cell's ID to zero.
Correct, and that's exactly what we do in GridSpaceSingle
as well. ID = 0 means "no agent".
So taking the cyan agent in the picture above, we generally don't need to know what cells are neighboring each other. You're right in thinking there isn't even one clear definition for the notion of neighboring cells (do you need at least two adjacent grid spaces to be from different cells? Maybe more? What if they only meet at a diagonal?).
Now imagine updating the cyan cell where the lowest grid square is changed from a 2 to a 3:
In this case you would need information about the neighboring grid points (highlighted in the yellow square) which are the Moore neighbors or the neighboring sites with Chebyshev distance = 1. Hopefully that makes a little more sense?
oh, I see. Then I would recommend to make a model without agents, but with GridSpaceSingle
, so that you do what we do in the forest fire model https://juliadynamics.github.io/AgentsExampleZoo.jl/dev/examples/forest_fire/
prior to your last comment i was under the impression that each of these "cells, amoebas" whatever they are, where considered one entity. Now I see that is clearly not the case. Each grid cell is update autonomously and pretty much it makes no difference whether you consider all "3"s individual entities or one entity. So, the forest fire example is the way to go.
I think that gets confusing from a user perspective. If I want to make a model with 100 cells that are either T-cells, B-Cells, or epithelial cells and the model returns that there are “0 agents” like in the forest fire model, how would a user deal with that? Would functions like kill_agent
still work in this framework?
Additionally, the updates like the one I show above do depend on cells as a whole entity. If cell 3 had a very small desired volume (i.e. the total number of 3’s) the probability of that update would be nearly zero. The opposite is also true, the update is very likely if the desired volume is large; it depends on the current size of the cell. I guess these agent properties could be saved as their own arrays/structures/etc. but then I feel like that kind of defeats the purpose of an ABM 🤷♂️
I assume when you use the word "cell", you do not mean a grid cell, but an "agent", i.e., a contiguous entity of several grid cells all with the same number.
To clarify, no one stops you from adding agents in the forest fire example. You can still have agents. But you want to additionally have a way to keep track of their size, and this is what an internal matrix, like in the forest fire example, would do.
I guess these agent properties could be saved as their own arrays/structures/etc.
nope, you can still have an agent whose property is a vector of occupied cells. Whether that is the most performant way to do it, and the most memory efficient way, that's another story that is probably of low priority at the moment.
the model returns that there are “0 agents” like in the forest fire model, how would a user deal with that?
Exactly the same way they do in the forest fire model: they don't care, because whether you call something an agent is much more arbitrary than you propose.
The forest fire model can be done with agents as well, where if a tree is burned you "kill" the agent, and when a new tree grows up you "create and add a new agent". Then the model would have one less or more agent. Or you can do it the way it is done in the forest fire example, where you simply stop caring about whether "an agent exists or not" and you start caring about "what is the color of this specific grid cell". So you can model the forest fire either as an Agent Based Model, or as a Cellular Automaton, where the concept of agents is not useful, as only the "color" of each spatial position is necessary for the simulation to function.
I'd recommend that you go ahead with a working code implementation. In your comment here (https://github.com/JuliaDynamics/Agents.jl/issues/647#issuecomment-1178242636) you eluded that each grid cell is updated individually. Hence, I suggested to do something like the forest fire model, because I already know from years of experience in these kind of simulations that it will be a more performant version. If you find the agent-based-version more intuitive, then you should start with that and make a new space type that allows for same IDs in different locations. It will be at most only 4x slower, but I think you having an intuitive framework outweights this performance gain.
Okay, thank you for this perspective. I guess I won't know what the best structure is for this model until I try using it with Agents.jl. When I have a MWE, should I do a pull request or just post it in this thread?
just post it here, don't stress too much about the formalities!