I am trying to simulate the spread of mold in a petri dish using a cellular automata based approach. Thanks to the answer in my other question Stochastic cellular automata - algorithm limited by 1 cell per timestep, I am attempting a different approach using a priority queue.
Context
The petri dish can be thought of as a grid of 1mm x 1mm squares, each called a Cell. Each square has a value for the rate of spread (RoS) of mold in that type of food, which is available as empirically measured data. For this question, let's just assume the petri dish is all the same food type with an RoS of 1 mm/hr.
We seed the dish by saying a particular Cell is moldy. Then we kick off an iteration. For every cell, we calculate at what time its 8 neighbors will become moldy. We add those cells to a priority queue, with the priority being the time at which they should become moldy. For example, examining the first step:
At the first step, there is only 1 moldy cell. We can expect that neighbors 2, 4, 5, and 7 will become moldy after 1 hour (it takes 1 hour for mold to spread 1 mm for a RoS of 1mm/hr), and we can expect that neighbors 1, 3, 6, and 8 would become moldy after $\sqrt(2)$ hours, as the distance between diagonal cells is not 1mm, but rather $\sqrt(2)$ mm. We can say that the time at which a neighbor will become moldy can be returned by a function $g(food, distance)$. So, when iterating over this first cell's neighbors, we should expect 2 items in the queue: neighbors 2, 4, 5, and 7 will become moldy after 1 hour, and neighbors 1, 3, 6, and 8 will become moldy after $\sqrt(2)$ hours.
We have thus completed the first iteration over all moldy cells, and we move on to the next item in the queue, which is to iterate over all cells that will become moldy at 1 hour, and calculate their neighbors times to become moldy. A quick pseudocode of the algorithm might look like this
mold_next = PriortyQueue({Initially moldy cells each flagged to t=0})
while mold_next is not empty and t<max_timestep:
c = pop(mold_next)
if c is moldy: # Very important
continue at next iteration of while loop
make c moldy
for all neighbours n of c:
add n to mold_next at a time drawn from g(food, distance)
The problem
My iteration of this (written in typescript and too complicated to post, or even extract to a minimum reproducible example), has a problem. The shape that emerges is not a circle, as one might expect, but rather, always a square. Let's look at the first few timesteps:
Note the last few steps are not cut off - there are no moldy cells outside the bounds of the image. Take a look at a gif showing more timesteps:
The pattern that emerges is that as the shape grows, it "grows in squares". Meaning from the first cell, moldy cells emerge at the four cadinal direction of the cell. Then, the corners are filled in. Then again, moldy cells emergy from the four cardinal directions at the center of each side, and begin "filling in" towards the edge, until again we have a square. It is not until a square "completes" that the next cell emerges in the cardinal direction.
Searching for clues
I am trying to understand why this is. Looking at the first iteration:
Let's say we are beginning the iteration in this state: The first cell generated its 2 queue items, as described earlier in this question. We are now iterating over the subsequent queue item, which says that cells 2, 4, 5, and 7, will become moldy. We've set them to moldy. Now we iterate over them. The cell in question is the one the arrows are pointing to, say cell $C_2$. The 2 queue items that the first cell generated says that this cell will become moldy at $t_0 + \sqrt(2)$. The cells from which the black arrow originates mold at $t_0 + 1$, and they each add an item to the queue for $C_2$, which would be at $t_0 + 2$, but by $t_0 + 2$, this cell would have already become moldy (at $t_0 + \sqrt(2)$), and as the algorithm says, if c is moldy, continue at next iteration of while loop
.
But somehow this pattern has created a squarish emergence, where the corners are always filling in first. Looking a few timesteps down:
Iterating over these cells, I would expect $C_n$ to mold before all $C_1$, $C_2$, and $C_3$, but that is not the case. The order of molding is $C_1$, $C_2$, $C_3$, then $C_n$. I am struggling to understand why this happens.
Fundamentally flawed logic vs implentation bug?
Is there a fundamental problem with my algorithm design? Or do I perhaps have an implementation bug? Should the algorithm I've described not cause a shape that is roughly circular to emerge?