QHG Tutorial 05 - Sexual Reproduction

In this chapter we describe a class whose agent can, in addition to dying and moving, perform sexual reproduction..

For this we need to add one new action: RandomPair.

RandomPair finds fertile males and females in each cell and pairs off as many as possible (by each agent’s mate index to index of its partner). All othe mechanisms are the same as in the previous chapter, with the exception that agents are not their own mates.

Population code:

Action code:

The Header File

This class must include the header files for the new action, but the agent structure remains the same as in the previous example.

#ifndef __TUT_SEXUALPOP_H__
#define __TUT_SEXUALPOP_H__


#include "GetOld.h"
#include "OldAgeDeath.h"
#include "RandomMove.h"
#include "Fertility.h"
#include "Verhulst.h"

#include "RandomPair.h"

#include "SPopulation.h"


struct tut_SexualAgent : Agent {

    float m_fAge;
    float m_fLastBirth;
    int   m_iMateIndex;
};

The class gets an additional pointer for the new action:

class tut_SexualPop : public  SPopulation<tut_SexualAgent> {
public:
    tut_SexualPop(SCellGrid *pCG, PopFinder *pPopFinder, int iLayerSize, IDGen **apIDG, uint32_t *aulState, uint *aiSeeds);
    virtual ~tut_SexualPop();

    int addPopSpecificAgentData(int iAgentIndex, char **ppData);
    void addPopSpecificAgentDataTypeQDF(hid_t *hAgentDataType);

    int makePopSpecificOffspring(int iAgent, int iMother, int iFather);

protected:
    GetOld<tut_SexualAgent>       *m_pGO;
    OldAgeDeath<tut_SexualAgent>  *m_pOAD;
    RandomMove<tut_SexualAgent>   *m_pRM;
    Verhulst<tut_SexualAgent>     *m_pVerhulst;
    Fertility<tut_SexualAgent>    *m_pFert;

    RandomPair<tut_SexualAgent>   *m_pPair;

};

The input/output auxiliary functions addPopSpecificAgentData() and addPopSpecificAgentDataTypeQDF() are identical to the ones in the previous chapterhave to be modified to take into account the new agent member fields. The method makePopSpecificOffspring() which allows the population to perform specific operations after an agent is born (e.g. initialize member fields to particular values).

Code: tut_SexualPop.h

The Implementation

In the implementation we have to include hdf5.h because we need to modify some HDF5 structures:

#include <omp.h>
#include <hdf5.h>

Furthermore, we have to add the includes of the cpp file for the new action and include the header file of this class:

#include "SPopulation.cpp"
#include "LayerBuf.cpp"
#include "Prioritizer.cpp"
#include "Action.cpp"


#include "GetOld.cpp"
#include "OldAgeDeath.cpp"
#include "RandomMove.cpp"
#include "Fertility.cpp"
#include "Verhulst.cpp"

#include "RandomPair.cpp"

#include "tut_SexualPop.h"

The constructor must create instances of all actions and add them to the priority list.

//----------------------------------------------------------------------------
// constructor
//
tut_SexualPop::tut_SexualPop(SCellGrid *pCG, PopFinder *pPopFinder, int iLayerSize, IDGen **apIDG, uint32_t *aulState, uint *aiSeeds)
    : SPopulation<tut_SexualAgent>(pCG, pPopFinder, iLayerSize, apIDG, aulState, aiSeeds) {


    m_pGO  = new GetOld<tut_SexualAgent>(this, m_pCG, "");
    m_pOAD = new OldAgeDeath<tut_SexualAgent>(this, m_pCG, "", m_apWELL);
    m_pRM  = new RandomMove<tut_SexualAgent>(this, m_pCG, "", m_apWELL);
    m_pFert     = new Fertility<tut_SexualAgent>(this, m_pCG, "");
    m_pVerhulst = new Verhulst<tut_SexualAgent>(this, m_pCG, "", m_apWELL);

    m_pPair = new RandomPair<tut_SexualAgent>(this, m_pCG, "", m_apWELL);

    m_prio.addAction(m_pGO);
    m_prio.addAction(m_pOAD);
    m_prio.addAction(m_pRM);
    m_prio.addAction(m_pFert);
    m_prio.addAction(m_pVerhulst);

    m_prio.addAction(m_pPair);
}

The destructor must delete all instances created in the constructor:

//----------------------------------------------------------------------------
// destructor
//
tut_SexualPop::~tut_SexualPop() {

    if (m_pGO != NULL) {
        delete m_pGO;
    }
    if (m_pOAD != NULL) {
        delete m_pOAD;
    }
    if (m_pRM != NULL) {
        delete m_pRM;
    }
    if (m_pFert != NULL) {
        delete m_pFert;
    }
    if (m_pVerhulst != NULL) {
        delete m_pVerhulst;
    }
    if (m_pPair != NULL) {
        delete m_pPair;
    }
}

The methods addPopSpecificAgentData() and addPopSpecificAgentDataTypeQDF() are practically identical to the ones in the previous chapter, so there’s no need to look at them in more detail.

The method makePopSpecificOffspring() initializes the agents’ new fields. It can leaabve the gender field unchanged, because Spopulation sets the gender randomly to male or female.

//----------------------------------------------------------------------------
// makePopSpecificOffspring
//
int tut_SexualPop::makePopSpecificOffspring(int iAgent, int iMother, int iFather) {
    int iResult = 0;


    m_aAgents[iAgent].m_fAge = 0.0;
    m_aAgents[iAgent].m_fLastBirth = 0.0;
    m_aAgents[iAgent].m_iMateIndex = -3;
    // SPopulation assigns random genders to a baby, ehich is ok here

    return iResult;
}

The mate index is set to a negative number to denote that this agent is not paired (yet).

Code: tut_SexualPop.cpp

XML and DAT

The xml file for the population attributes does not contain a new <module> entry, because RandomPair does not need any parameters. It must, however, be registerd for a priority level.

<class name="tut_SexualPop" species_name="sapiens" species_id="104">

  <module name="OldAgeDeath">
    <param  name="OAD_max_age" value="60.0"/>
    <param  name="OAD_uncertainty" value="0.1"/>
  </module>
  <module name="RandomMove">
    <param  name="RandomMove_prob" value="0.07"/>
  </module>

 <module name="Verhulst">
    <param  name="Verhulst_b0" value="0.8" />
    <param  name="Verhulst_d0" value="0.001" />
    <param  name="Verhulst_theta" value="0.1" />
    <param  name="Verhulst_K" value="20" />
  </module>

  <module name="Fertility">
    <param  name="Fertility_interbirth" value="2.0"/>
    <param  name="Fertility_max_age" value="50.0"/>
    <param  name="Fertility_min_age" value="15.0"/>
  </module>

  <priorities>
    <prio  name="GetOld" value="8"/>
    <prio  name="OldAgeDeath" value="10"/>
    <prio  name="RandomMove" value="7"/>
    <prio  name="Fertility" value="2"/>
    <prio  name="RandomPair" value="3"/>
    <prio  name="Verhulst" value="6"/>

  </priorities>

</class>

The dat file tut_Sexual.dat defines postions and attributes for 20 agents, the last attribute pertainng to the new member m_fAge. It is similar to the dat file in the previous example, except that the agents’ genders are not exclusively female:

#Longitude;Latitude;LifeState;AgentID;BirthTime;Gender;Age;LastBirth
8;47;1; 1;-10.0;0;10.0;-1.0
8;47;1; 2;-10.0;1;10.0;-1.0
8;47;1; 3;-10.0;0;10.0;-1.0
8;47;1; 4;-10.0;1;10.0;-1.0
...
8;47;1;18;-10.0;1;10.0;-1.0
8;47;1;19;-10.0;0;10.0;-1.0
8;47;1;20;-10.0;1;10.0;-1.0

Feel free to modify the values in the xml file and see how this changes the outcome. But keep in mind that high calue for RandomMove_prob can cause the population to disperse and render pairing and reproduction impossible.

Grid

For this example we use a subdivided icosahedron

Compiling and Running

Again, we assume that you have created the tutorial directories with the script build_tut_dirs.py.

First lets create alternative population and action directories with the script build_alt_modpop.py:

${QHG4_DIR}/useful_stuff/build_alt_modpop.py tut_05 tut_SexualPop

This builds alternative population directories tut_05_pop and tut_05_mod with the files needed to compile tut_ParthenoPop.

To compile, change to the QHG4 root directory and type:

OPT=1 SHORT=tut_05 make clean QHGMain

Here we set the environemnt variable OPT to get optimized compilation (see Compiling QHG)

Now cd to the tutorial subdirectory “tutorial_05’ and start QHGMain

${QHG4_DIR}/app/QHGMain --read-config=tutorial_05.cfg > tutorial_05.out

This tells QHGMain to read the parameters from the file tutorial_05.cfg and write all output to the file tutorial_05.out (this output contains more information than the logfile)

The config file tutorial_05.cfg was created when the tutorial directories were created with build_tut_dirs.py.

--data-dirs=${QHG4_DIR}/tutorial_data/grids/
--events='write|grid+geo+pop:sapiens@20000'
--grid=ico32s.qdf
--log-file=tutorial_05.log
--num-iters=10000
--output-dir=output/tutorial_05
--output-prefix=tut
--pops='tut_SexualPop.xml:tut_SexualPop.dat'
--shuffle=92244
--start-time=-10000
data-dirs

A list of directories to search for data (not relevant for the OldAgeDie populations).

events

A list of events (see Events). Here a single event is defined: write output at step 20000 (or at the end of the simulation run).

grid

Path to the grid on which to run the simulation.

log-file

Log file for ‘high level’ messages.

num-iters

Number of iterations for the simulation (10000 steps instead of 3000 steps, because this population grows more slowly).

output-dir

Output directory for qdf files.

output-prefix

A prefix prepended to the names of all output files

pops

The files for population attributes (tut_SexualPop.xml) and agent attributes (tut_SexualPop.dat). These files were created by build_tut_dirs.py.

shuffle

A kind of seed for the random number generators. Same shuffle values result in identical simulations.

start-time

The ‘real’ time corresponding to step 0.

The Output File

The output files, in this case tutorial_05.out, always have the same structure. First there are messages concerning the start of the program and intialisation. If the simulation does not run, there’s a chance that you find th readoon why here.

The second part is a list of every step describing the numbers of births, deaths and moves, as well as the time the step took to execute.

At the end there is a short summary of the state at the end of the program: number of iterations, total time of execution, nuber of surviving agents. This is follwed by the contents of the log file.

If you look at the output for the steps, you see that the population grows slower than the one in the previous example, and the population stabilizes at a lower level. This has to do with the fact that only half of the population can have offspring, whereas in the previous chapter, every inidividual had offspring.

After step 9995 (0.002099 s): total    94852 agents
  sapiens    4993 births r
  sapiens    4968 deaths r
  sapiens      18 births p
  sapiens    5368 moves
After step 9996 (0.002057 s): total    94877 agents
  sapiens    4890 births r
  sapiens    5012 deaths r
  sapiens      78 deaths p
  sapiens    5286 moves
After step 9997 (0.002287 s): total    94755 agents
  sapiens    4974 births r
  sapiens    4833 deaths r
  sapiens      38 deaths p
  sapiens    5382 moves

The summary:

-----
Finished Simulation
Number of threads: 8
Number of iterations: 10000
Used time: 25.914365 (00:00:25)
Number of agents after last step
  sapiens:     94801
    total:     94801