How to Design New Populations and Actions¶
As an example, we try to model viral infections in a community.
The Layout¶
We have a population which can be infected by a virus. Infected individuals start out with a small virus load which increases over time. When the load crosses a certain threshold, the agent will infect other agents in the same cell. If the laod increases past a second threshold, the individual dies. Agents how ever can have a partrial immunity, which will reduce the risk of infection and slow down the virus growth (at full immunity, the infection risk is 0 a the virus load does not grow).
Additionally, we’ll assume that immunity is inheritable properry: we allow for 5 different types of inheritance:
mix
: the baby’s immunity is the average of the parents’ immunitiesmat
: the baby’s immunity is set to the mother’s immunitypat
: the baby’s immunity is set to the father’s immunitymin
: the baby’s immunity is set to the lesser of the parents’ immunitiesmax
: the baby’s immunity is set to the larger of the parents’ immunities
Furthermore, a baby’s immunity can be modified by mutations.
Inheritance type and mutation rates are read from the population XML file.
Requirements¶
Population¶
We need sexual reproduction for our population.
This means we can build our new population starting from a copy of
tut_SexualPop
from tutorial 5.
Furthermore, our agents have 2 additional fields, a float for the virus load,
and a float for immunity.
Actions¶
We model the virus as action.
- The virus has 5 attributes
the infection probabiity
the initial load (the “amount of virus” a newly infected agent gets.
the virus growth rate (logidtic growth)
the contagion level (if the virus load exceeds this, the agent becomes contagious)
the lethality level (if the virus load exceeds this, the agent dies)
In its execute()
method it “grows” each agent’s virus load.
Then, if the virus load exceeds the lethality level in an agent,
it is regisatered for death.
Otherwise we try to infect all other agents in the same cell
Naively we would simply increase the virus load of each agent by a small amount,
but since execute()
is executed in parallel, this could lead to a problem
if two threads happen to increase the virus load of the same agent at
(more or less) the same time.
Avoiding Race Conditions¶
A race condition is a situatiom in parallel program where the execution of non-atomic operations depends on the order of the threads and the exact time at which they execute the code.
The oprator +=
is not atomic, for instance.
The statement x += 2
actually comprises three atomic instructions:
- get value of variable x into register 1
- add 2 to register 1
- put value of register 1 into variable x
So if x
has the value 3, and we execute the code
x += 2;
with two threads, the result may be either 5 or 7:

Depending on the timing and the order of execution, even simple parallelized code sections may give unexpected results.¶
In order to avoid a race condition, we do not change the viral load of the agents directly, but we keep the store the loads an agent gets in in an array of std::maps, one map per thread. Each of these maps relates an agent index to the amount of viral loads it gets. Inside such a map there is no threat of a race condition so we can simply add the incoming loads as they come in (we parallelize over the agents, so no two threads try to change the map entry of the same agent).
In the method finalize()
which is executed after execute()
we loop
through all maps and actually increase the viral load of each agent by the
amounts related to its index in the maps.
Similar considerations apply to creating the vectors of agent IDs for each cell. Here to we have an array of maps relating cell indexes to vectors of agent indexes. Again, there is one map for each thread, so we may push back agent indexes to the vectors in the map of a thread without problems.