Step 8: Refined Fertility

Model Description

At this step we add a refined fertility module able to account for different levels of childlessness and the different age patterns of first birth by education group.

The new FertilityByEducation.mpp Module

This module - in its current state - is a very simple extension of the base fertility model allowing to control for educational differences in first births, both by timing and final outcome, i.e. it allows to model childlessness by education, which is an important variable for the use of the model. Users can select to use or not to use the model by a selection parameter. Aggregate outcomes (births by age) are identical as in the base model. Besides the on/off switch, the model has two parameters, namely

  • Cohort first birth rates by education for birth cohorts in reproductive age
  • Childlessness by education for older birth cohorts

If switched on, the algorithm is as follows:

  • The base module produces birth events, but does not assign the babies to a mother yet. The baby just knows the age of the mother.
  • First birth events of this module flag women expecting a first birth
  • The babies are first given to women of the appropriate age flagged for first birth
  • The remaining babies are distributed randomly to women of the according age who are already mothers

The module initialized the state is_mother at the start of the simulation using the information from links in the starting population file as well as the parameters which allow to calculated the expected rates of childlessness by year of birth, education and age. The state is_mother is imputed by the function ImputeIsMother() in the following way:

  • Using family links from the starting population file, all women living with children in the family are marked as mothers
  • The number of expected mothers by age and education is calculated from the population sizes in the starting population and the first birth and childlessness parameters.
  • If the number of expected mothers is higher than the number of recorded mothers (which should always be the case, as children can have moved out) childless women are selected randomly (for given age and education) and their status set to mother.

This module can be added or removed from the model without any code change in other modules. The only exception are the country/context specific time ranges set in _CountryContext.mpp.



////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
////////////////////////////////////////////////////////////////////////////////////////////////////

//EN Potential mothers for first birth by age
actor_set Person asWomenWaitingFirstBirth[fertile_age]
    filter is_alive && is_potential_mother && waits_for_first_baby;

//EN Potential mothers for higher order births by age
actor_set Person asPotentialMothersHigherBirths[fertile_age]
    filter is_potential_mother && is_mother;

//EN Childless women by year of birt and education
actor_set Person asChildlessWomen[year_of_birth][birth1_group] filter !is_mother && sex==FEMALE;



////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////////////////////////////////////////

classification BIRTH1_GROUP                         //EN Population Groups
{
    B1G_00,                                         //EN Education Low
    B1G_01,                                         //EN Education Medium
    B1G_02                                          //EN Education High
};

classification SELECTED_FERTILITY_MODEL             //EN Fertility model options
{
    SFM_MACRO,              //EN Macro model (age only)
    SFM_ALIGNE_AGE          //EN Micro model with aligned number of births by age
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
////////////////////////////////////////////////////////////////////////////////////////////////////

parameters
{
    //EN Fertility model selection
    SELECTED_FERTILITY_MODEL selected_fertility_model;

    //EN First Birth Rates
    double  FirstBirthCohortRates[BIRTH1_GROUP][FERTILE_AGE_RANGE][YOB_BIRTH1];

    //EN Childlessnesss in older population
    double ChildlessnessYob[YOB_START50][BIRTH1_GROUP];
};

parameter_group PG03_Fertility_Top              //EN Fertility
{
    selected_fertility_model,
    PG03a_Fertility_Model_A,
    PG03b_Fertility_Model_B
};

parameter_group PG03b_Fertility_Model_B         //EN Fertility: Refined Version
{
    FirstBirthCohortRates
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor States and Functions
////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    //EN Population group for modeling of births
    BIRTH1_GROUP birth1_group = (educ_fate == EL3_LOW) ? B1G_00 :
        (educ_fate == EL3_MEDIUM) ? B1G_01 : B1G_02;

    FERTILE_AGE_RANGE   fertile_age = COERCE(FERTILE_AGE_RANGE, integer_age);   //EN Age

    logical is_mother = { FALSE };                          //EN Is Mother
    logical waits_for_first_baby = { FALSE };              //EN Waiting for first birth

    void SetFertilityModelSwitches();                       //EN Initializing model choices
    hook SetFertilityModelSwitches, Start;                  //EN hooked to Start function

    void MakeBaby();                                        //EN Function creating a baby
    event timeRefineModelBirthEvent, RefineModelBirthEvent; //EN Birth event
    event timeFirstBirthFlagEvent, FirstBirthFlagEvent;     //EN First birth flag event

    int linked_kids = count(mlChild);
};

actor Clock
{
    void ImputeIsMother();
    hook ImputeIsMother, StartYearClock;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////
//// Implementation
//////////////////////////////////////////////////////////////////////////////////////////////////////

void Person::SetFertilityModelSwitches()
{
    if (selected_fertility_model == SFM_MACRO) // Macro model (age only)
    {
        use_base_fertility_model =  TRUE ;            // Use the base model
        use_base_fertility_for_alignment =  FALSE ;   // Base model not used for alignment
    }
    else                                              // Refined model with aligned births
    {
        use_base_fertility_model =  FALSE ;           // Do not use the base model
        use_base_fertility_for_alignment = TRUE ;     // Base model used for alignment
    }
}

TIME Person::timeFirstBirthFlagEvent()
{
    TIME dEventTime = { TIME_INFINITE };
    double dHazard = { 0.0 };
    if (is_potential_mother && !use_base_fertility_model && use_base_fertility_for_alignment
        && !is_mother && !waits_for_first_baby)
    {
        dHazard = FirstBirthCohortRates[birth1_group][RANGE_POS(FERTILE_AGE_RANGE, fertile_age)][RANGE_POS(YOB_BIRTH1, year_of_birth)];
        if (dHazard > 0.0 ) dEventTime = WAIT(-log(RandUniform(16)) / dHazard);
    }
    return dEventTime;
}

void Person::FirstBirthFlagEvent()
{
    waits_for_first_baby = TRUE;
}

void Person::MakeBaby()
{
    auto peChild = new Person;    // Create and point to a new actor
    peChild->Start(NULL, this);   // Call Start() function of the new actor and pass own address
}

TIME Person::timeRefineModelBirthEvent()
{
    double dEventTime = TIME_INFINITE;
    if (baby_looking_for_mother) //EN The base model to which the model is aligned produced a baby
    {
        dEventTime = WAIT(0);
    }
    return dEventTime;
}

void Person::RefineModelBirthEvent()
{
    baby_looking_for_mother = FALSE;

    if (asWomenWaitingFirstBirth[RANGE_POS(FERTILE_AGE_RANGE,integer_age)]->Count() > 0)
    {
        auto peMother = asWomenWaitingFirstBirth[RANGE_POS(FERTILE_AGE_RANGE, integer_age)]->GetRandom(RandUniform(17));
        peMother->MakeBaby();
        peMother->is_mother = TRUE;
        peMother->waits_for_first_baby = FALSE;
    }
    else if ( asPotentialMothersHigherBirths[RANGE_POS(FERTILE_AGE_RANGE, integer_age)]->Count() > 0)
    {
        auto peMother = asPotentialMothersHigherBirths[RANGE_POS(FERTILE_AGE_RANGE, integer_age)]->GetRandom(RandUniform(18));
        peMother->MakeBaby();
    }
    else
    {
        //this should not happen
        //get the baby herself
        MakeBaby();
        is_mother = TRUE;
        waits_for_first_baby = FALSE;
    }
}


void Clock::ImputeIsMother()
{
    if (clock_year == MIN(SIM_YEAR_RANGE))
    {
        int nPopSize;
        int nPersons[SIZE(YOB_START15)][SIZE(BIRTH1_GROUP)];
        int nExpected[SIZE(YOB_START15)][SIZE(BIRTH1_GROUP)];
        int nRecorded[SIZE(YOB_START15)][SIZE(BIRTH1_GROUP)];
        // initialize with 0
        for (int nJ = 0; nJ < SIZE(YOB_START15); nJ++)
        {
            for (int nGroup = 0; nGroup < SIZE(BIRTH1_GROUP); nGroup++)
            {
                nPersons[nJ][nGroup] = 0; nExpected[nJ][nGroup] = 0; nRecorded[nJ][nGroup] = 0;
            }
        }
        // assign status is_mother where there are children in hh
        // count women by age and known mothers by age
        nPopSize = asAllPersons->Count();
        for (int nI = 0; nI < nPopSize; nI++)
        {
            auto prPerson = asAllPersons->Item(nI);
            if (WITHIN(YOB_START15, prPerson->year_of_birth) && prPerson->sex == FEMALE)
            {
                int nYOB = prPerson->year_of_birth - MIN(YOB_START15);
                nPersons[nYOB][prPerson->birth1_group] = nPersons[nYOB][prPerson->birth1_group] + 1;

                if (prPerson->linked_kids > 0 || (prPerson->lSpouse && prPerson->lSpouse->linked_kids >0))
                {
                    nRecorded[nYOB][prPerson->birth1_group] = nRecorded[nYOB][prPerson->birth1_group] + 1;
                    prPerson->is_mother = TRUE;
                }
            }
        }
        //calculate expected number of mothers by yob
        for (int nGroup = 0; nGroup < SIZE(BIRTH1_GROUP); nGroup++)
        {
            for (int nJ = 0; nJ < SIZE(YOB_START15); nJ++)
            {
                if (nJ < SIZE(YOB_START50)) // older population
                {
                    nExpected[nJ][nGroup] = round(nPersons[nJ][nGroup] * (1 - ChildlessnessYob[nJ][nGroup]));
                }
                else // still in childbearing age
                {
                    double dChildless = 1.0;
                    for (int nAgeIndex = 0; nAgeIndex < SIZE(FERTILE_AGE_RANGE) && nJ + MIN(YOB_START15) + MIN(FERTILE_AGE_RANGE) + nAgeIndex < MIN(SIM_YEAR_RANGE); nAgeIndex++)
                    {
                        dChildless = dChildless * exp(-FirstBirthCohortRates[nGroup][nAgeIndex][nJ - SIZE(YOB_START50)]);
                    }
                    nExpected[nJ][nGroup] = round(nPersons[nJ][nGroup] * (1 - dChildless));
                }
            }
        }
        //randomly choose childless women to become mothers in order to meet target numbers
        for (int nGroup = 0; nGroup < SIZE(BIRTH1_GROUP); nGroup++)
        {
            for (int nJ = 0; nJ < SIZE(YOB_START15); nJ++)
            {
                if (nExpected[nJ][nGroup] > nRecorded[nJ][nGroup])
                {
                    for (int nI = 0; nI < nExpected[nJ][nGroup] - nRecorded[nJ][nGroup]; nI++)
                    {
                        if (asChildlessWomen[RANGE_POS(ALL_YEAR_RANGE, MIN(YOB_START15) + nJ)][nGroup]->Count() > 0)
                        {
                            auto prMother = asChildlessWomen[RANGE_POS(ALL_YEAR_RANGE, MIN(YOB_START15) + nJ)][nGroup]->GetRandom(RandUniform(19));
                            prMother->is_mother = TRUE;
                        }
                    }
                }
            }
        }
    }
}

table Person TabChildlessAtStartTab //EN Childlessness
[sex==FEMALE && calendar_year == 2010]
{
    {
        duration(is_mother,TRUE)/duration()
    }
    * integer_age
    * educ_fate +
};

Update of a version specific age-range in _CountryContext.mpp


range YOB_BIRTH1 { 1960, 2050 };                 //EN Year of birth
range YOB_START15{ 1900, 1994 };                //EN Year of birth (in startpop age 15+)
range YOB_START50{ 1900, 1959 };                //EN Year of birth (in startpop age 50+)
//