davidrmiller / biosim4

Biological evolution simulator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Peeps::queueForDeath() queues already dead Indiv-s in RadioactiveChallenge

artofimagination opened this issue · comments

Symptom:
Some individuals are queued for death even after they marked as dead already when RadioActiveWalls mode is on. This results indirectly allowing for individuals to be added to move queue even if they have no movement scheduled.
As a result getPopulationDensityAlongAxis will assert with assert(dir != Compass::CENTER);

Expected:
No assert and dead individuals never should be queued twice.

This is my first ever github community contribution, so I am not familiar with the procedure. I am happy to create a PR with the fix, if you think it needs to be fixed. Please leave a comment and I get the work done.

Hi @artofimagination , welcome to the project! Thank you very much for looking over the code and making this report.

I'd like to understand the bug better, especially the conditions that will trigger that assertion. (It doesn't surprise me that the "radioactive" mode has bugs -- that code was thrown in somewhat carelessly without a lot of scrutiny.)

If you feel so inclined, feel free to submit a PR showing a proposed fix. Alternatively, if the fix is relatively simple, feel free to describe the erroneous code path and your proposed fix in comments here and I'd be happy to test and apply the fix.

The assert happens, because the grid location for a non moving individual is marked as empty by accident. It can happen, in case of double draining a dead individual from the death queue.
For example:

  • indiv 1 gets on death queue in sim step 1. Its grid location gets cleared.
  • step 2 happens and indiv 2 moves in the location where indiv 1 was.
  • in step 3 indiv 1 will be put on deathqueue and will be drained before indiv 2. Because it has the same location as indiv 2 when drained it will clear that grid location, so basically clear the location of indiv 2.
  • this is all good as long as the indiv 2 is moving away from this location
  • assert happens when the above cases are executed and indiv 2 happens to stay at the same location.
  • so it will not move, but it will go on move queue, because if (grid.isInBounds(newLoc) && grid.isEmptyAt(newLoc)) { is met for the accidental steps above

The fix is easy. Just need to add a condition in void Peeps::queueForDeath(const Indiv &indiv) to check if the individual is alive:

void Peeps::queueForDeath(const Indiv &indiv)
{
    #pragma omp critical
    {
         if (indiv.alive)
            deathQueue.push_back(indiv.index);
    }
}

Also in this case the alive check can be removed from executeActions in kill forward condition branch this case.

           if (grid.isInBounds(otherLoc) && grid.isOccupiedAt(otherLoc)) {
                Indiv &indiv2 = peeps.getIndiv(otherLoc);
                if (indiv2.alive) {    <------- can be removed
                    assert((indiv.loc - indiv2.loc).length() == 1);
                    peeps.queueForDeath(indiv2);
                }
            }

I guess I don't have to make a PR, because it is a one liner fix actually.

@artofimagination , thanks for the clear and thorough description and for the simple fix. I'll verify it and make the fix in the main branch in the near future.

@artofimagination , Thanks to your notes, I think I understand the error, but I would apply a slightly different fix. Here is my attempt to document the intended process:

During sim step S1, live agent A1 at location L1 gets added to the deferred death queue, possibly multiple times. (Bug: it is an error for the radioactive code to queue a dead agent for death.) For the remainder of S1, A1 remains alive and continues to occupy L1 preventing other agents from queuing a move to L1. Depending on the peeps ordering, A1 (and possibly other agents) may also be queued for deferred movement to some other L2.

After S1 ends and before S2 starts, the death queue is drained causing A1 to be marked dead and L1 cleared, allowing L1 to be available for occupation during S2. The movement queue is then drained. (Bug: it is an error to honor a queued movement for an agent marked dead during the draining of the death queue.) If multiple agents were queued for movement to the same L2, only the first is moved, the others are ignored.

During S2, the process repeats without conflict.

The bug fixes needed are: (1) The radioactive code should not try to kill an already-dead agent. (2) Draining the movement queue should ignore dead agents.

Ah true, drainMoveQueue should also ignore dead agents.

ezoic increase your site revenue