Step 15: Emigration

Overview

At this step we add an emigration module. Emigration is optional and can be deactivated. Users have various choices in how emigration is modeled. In order to allow for alignment, emigration is decided on an annual basis each mid-year.

The new Emigration.mpp Module

This module implements emigration. Users can choose three approaches:

  • Emigration based on age and sex specific emigration rates: Individual emigration based on time-invariant rates
  • Emigration based on a parameter of total emigration: Individual emigration based on age and sex specific rates proportionally adjusted to meet aggregate totals
  • Family emigration: This option keeps families together and meets the overall total emigration targets. In the current implementation, the person with the shortest waiting time is chosen and takes the whole family abroad until the total target is met. This approach does not maintain the age-specific rates and might be further developed to better fill a given age-distribution of emigrants

The module is prepared to include individual-level differences in emigration rates (e.g. by nationality) and automatically adjusts individual rates in order to meet aggregate targets. Emigration is decided each mid-year and happens only annualy at this point of time. Emigrants leave the country and are immediately removed from the simulation.



////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor sets
////////////////////////////////////////////////////////////////////////////////////////////////////

//EN Residents by sex and age group
actor_set Person asResidentSexAge5[sex][age5_index]
    filter is_alive && is_resident && in_projected_time;

//EN Residents by sex and age group - ordered by waiting time
actor_set Person asResidentOrderedSexAge5[sex][age5_index]
    filter is_alive && is_resident && in_projected_time
    order emigr_wait;

//EN Residents by sex - ordered by waiting time
actor_set Person asResidentOrdered
    filter is_alive && is_resident && in_projected_time
    order emigr_wait;

//EN Entire resident population
actor_set Person asAllResidents filter is_alive && is_resident;

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


partition AGE5_PART                                         //EN Age Groups
{
    5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
    65, 70, 75, 80, 85, 90
};

range AGE5_INDEX { 0,18 };                                  //EN Age group

classification EMIGR_SET                                    //EN Emigration settings
{
    ES_AGERATES,                                            //EN Meet rates by age group and sex
    ES_TOTALS,                                              //EN Meet aggregate totals
        ES_FAMILY                                                                   //EN Family Emigration
};

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

parameters
{

    logical     ModelEmigration;                            //EN Switch emigration on/off
    EMIGR_SET   EmigrationSettings;                         //EN Emigration targets
    double      EmigrationRates[SEX][AGE5_PART];            //EN Emigration rates by sex and age
    long        EmigrationTotal[SIM_YEAR_RANGE];            //EN Total number of emigrants
};

parameter_group PG_EmigrationBase                           //EN Emigration Base Version
{
    ModelEmigration, EmigrationSettings,
    EmigrationRates, EmigrationTotal
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor states and Events
////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    SIM_YEAR_RANGE sim_year = COERCE(SIM_YEAR_RANGE, calendar_year);    //EN Year
    AGE5_INDEX  age5_index = split(integer_age, AGE5_PART);     //EN Age Group
    logical     is_resident = { TRUE };                         //EN Is resident
    logical     has_emigrated = { FALSE };                      //EN Person has emigrated
    double      emigr_prob = { 0.0 };                           //EN Emigration Probabilty
    void        DoEmigrate();                                   //EN Do emigrate
    TIME        emigr_wait = { TIME_INFINITE };                 //EN Hypothetical waiting time
    integer     DoEmigrateChildren();                           //EN Send children abroad
};

actor Clock
{
    void        DoEmigration();                                 //EN Do Emigration
    hook        DoEmigration, MidYearClockEvent, 5;                //EN hook to mid year
    double      AdjustedProb(double dProb, double dFactor);     //EN Adjust Probability
    double      ProbFromOdds(double dOdds);                     //EN Get Probability from Odds
    double      OddsFromProb(double dProb);                     //EN Get Odds from Probability
};

actor Globals                                                   //EN Actor Globals
{
    //EN Emigration alignment factors
    double      emigration_alignment[SIM_YEAR_RANGE][SEX][AGE5_PART];
};

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

void Clock::DoEmigration()
{
    if (clock_year >= MIN(SIM_YEAR_RANGE) && ModelEmigration == TRUE)
    {
        double dExpectedEmigrants = 0.0;
        long   nExpectedEmigrants = 0;
        double dExpectedEmigrationProb = 0.0;
        double dCurrentEmigrationProb = 0.0;
        long   nGroupSize = 0;
        // for each age group by sex
        // find, record and apply an alignment factor to meet target group emigration rates
        for (int nSex = 0; nSex < SIZE(SEX); nSex++)
        {
            for (int nGroup = 0; nGroup < SIZE(AGE5_INDEX); nGroup++)
            {
                // The expected emigration pribability in this group
                dExpectedEmigrationProb = EmigrationRates[nSex][nGroup];

                // Update the individual emigration probabilities of all members of this group
                dCurrentEmigrationProb = 0.0;
                nGroupSize = asResidentSexAge5[nSex][nGroup]->Count();
                for (long nIndex = 0; nIndex < nGroupSize; nIndex++)
                {
                    auto prPerson = asResidentSexAge5[nSex][nGroup]->Item(nIndex);
                    double dPersonalEmiProb = 0.0;
                    // Calculate the current personal probability to emigrate

                    // /////////////////////////////////////////////////////////////////////////////////
                    // At this place, individual level odds can be introduced do adjust individual rates
                    // =================================================================================
                    /* dPersonalEmiProb = ProbFromOdds(OddsFromProb(EmigrationRates[prPerson->sex][prPerson->age5_index])
                        * EmigrationOdds[prPerson->sex][prPerson->nation_agg][prPerson->age5_index] ); */
                    // //////////////////////////////////////////////////////////////////////////////////

                    dPersonalEmiProb = EmigrationRates[prPerson->sex][prPerson->age5_index];
                    prPerson->emigr_prob = dPersonalEmiProb;
                    dCurrentEmigrationProb = dCurrentEmigrationProb + dPersonalEmiProb;
                }
                // This is the current overall probability in the group
                if (nGroupSize > 0) dCurrentEmigrationProb = dCurrentEmigrationProb / nGroupSize;

                // Find alignment factor for individual emigration probabilities
                double dLower = 0.0;
                double dUpper = 200.0;
                double dCenter = 100.0;
                int nMaxIter = 1000000;
                bool bEnterAlignment = FALSE;
                while (abs(dCurrentEmigrationProb - dExpectedEmigrationProb) > 0.00001 && nMaxIter > 0 && nGroupSize > 0)
                {
                    bEnterAlignment = TRUE;
                    dCurrentEmigrationProb = 0.0;
                    dCenter = (dLower + dUpper) / 2.0;
                    for (long nIndex = 0; nIndex < nGroupSize; nIndex++)
                    {
                        auto prPerson = asResidentSexAge5[nSex][nGroup]->Item(nIndex);
                        dCurrentEmigrationProb = dCurrentEmigrationProb + AdjustedProb(prPerson->emigr_prob, dCenter);
                    }
                    dCurrentEmigrationProb = dCurrentEmigrationProb / nGroupSize;
                    nMaxIter--;
                    if (dCurrentEmigrationProb > dExpectedEmigrationProb) dUpper = dCenter;
                    else dLower = dCenter;
                }
                // Record it
                if (bEnterAlignment) asGlobals->Item(0)->emigration_alignment[RANGE_POS(SIM_YEAR_RANGE, clock_year)][nSex][nGroup] = dCenter;
                else asGlobals->Item(0)->emigration_alignment[RANGE_POS(SIM_YEAR_RANGE, clock_year)][nSex][nGroup] = 1.0;
                // Apply it to update all individual emigration probabilities
                if (bEnterAlignment)
                {
                    for (long nIndex = 0; nIndex < nGroupSize; nIndex++)
                    {
                        auto prPerson = asResidentSexAge5[nSex][nGroup]->Item(nIndex);
                        prPerson->emigr_prob = AdjustedProb(prPerson->emigr_prob, dCenter);
                    }
                }
                // set hypothetical waiting times
                for (long nIndex = 0; nIndex < nGroupSize; nIndex++)
                {
                    auto prPerson = asResidentSexAge5[nSex][nGroup]->Item(nIndex);
                    double dProb = prPerson->emigr_prob;
                    if (dProb == 1) prPerson->emigr_wait = 0.0;
                    else if (dProb == 0) prPerson->emigr_wait = TIME_INFINITE;
                    else prPerson->emigr_wait = -log(RandUniform(20)) / -log(1 - dProb);
                }
            }
        }

        // Make people emigrate
        // The model used can be chosen by user
        // (1) Option 1: Individual Emigration meeting rates by age group and sex

        if (EmigrationSettings == ES_AGERATES)
        {
            // for each age group by sex
            for (int nSex = 0; nSex < SIZE(SEX); nSex++)
            {
                for (int nGroup = 0; nGroup < SIZE(AGE5_INDEX); nGroup++)
                {
                    // integer number of emigrants in group
                    dExpectedEmigrants = EmigrationRates[nSex][nGroup] * (asResidentSexAge5[nSex][nGroup]->Count());
                    nExpectedEmigrants = int(dExpectedEmigrants);
                    if (RandUniform(21) < dExpectedEmigrants - (double)nExpectedEmigrants) nExpectedEmigrants++;

                    // Call emigration for persons with shortest waiting time
                    for (int nIndex = 0; nIndex < nExpectedEmigrants; nIndex++)
                    {
                        auto prPerson = asResidentOrderedSexAge5[nSex][nGroup]->Item(0);
                        prPerson->DoEmigrate();
                    }
                }
            }
        }
                // (2) Option 2: Individual emigration meeting aggregate yearly targets
                else if (EmigrationSettings == ES_TOTALS)
                {
                        // integer number of emigrants in group
                        dExpectedEmigrants = (double)EmigrationTotal[RANGE_POS(SIM_YEAR_RANGE, clock_year)] / asGlobals->Item(0)->person_weight;
                        nExpectedEmigrants = int(dExpectedEmigrants);
                        if (RandUniform(23) < dExpectedEmigrants - (double)nExpectedEmigrants) nExpectedEmigrants++;

                        // Call emigration for persons with shortest waiting time
                        for (int nIndex = 0; nIndex < nExpectedEmigrants; nIndex++)
                        {
                                auto prPerson = asResidentOrdered->Item(0);
                                prPerson->DoEmigrate();
                        }
                }
                // (3) Option 3: Family Emigration
                else if (EmigrationSettings == ES_FAMILY)
                {
                        // integer number of emigrants in group
                        dExpectedEmigrants = (double)EmigrationTotal[RANGE_POS(SIM_YEAR_RANGE, clock_year)] / asGlobals->Item(0)->person_weight;
                        nExpectedEmigrants = int(dExpectedEmigrants);
                        if (RandUniform(35) < dExpectedEmigrants - (double)nExpectedEmigrants) nExpectedEmigrants++;

                        // Call emigration for persons with shortest waiting time
                        int nIndex = 0;
                        while ( nIndex < nExpectedEmigrants )
                        {
                                auto prPerson = asResidentOrdered->Item(0);
                                // find family head
                                if (prPerson->family_role == FR_SPOUSE && prPerson->lSpouse) prPerson = prPerson->lSpouse;
                                else if (prPerson->family_role == FR_CHILD)
                                {
                                        if (prPerson->lHHFather && prPerson->lHHFather->family_role == FR_HEAD) prPerson = prPerson->lHHFather;
                                        else if (prPerson->lHHMother && prPerson->lHHMother->family_role == FR_HEAD) prPerson = prPerson->lHHMother;
                                }
                // send children abroad
                if (prPerson->family_role != FR_CHILD)
                {
                   nIndex = nIndex + prPerson->DoEmigrateChildren();
                }
                // send spouse abroad
                                if (prPerson->lSpouse)
                                {
                                        prPerson->lSpouse->DoEmigrate();
                                        nIndex++;
                                }
                // leave country
                prPerson->DoEmigrate();
                                nIndex++;
                        }
                }
    }
}

integer Person::DoEmigrateChildren()
{
    int ReturnValue = 0;
    int nChildIndex;
    auto prChild = (sex == FEMALE) ? mlHHMotherChildren->GetNext(0, &nChildIndex) : mlHHFatherChildren->GetNext(0, &nChildIndex);
        while (prChild != NULL)
        {
            prChild->DoEmigrate();
        ReturnValue++;
                prChild = (sex == FEMALE) ? mlHHMotherChildren->GetNext(nChildIndex + 1, &nChildIndex) : mlHHFatherChildren->GetNext(nChildIndex + 1, &nChildIndex);
        }
    return      ReturnValue;
}

double Clock::AdjustedProb(double dProb, double dFactor)
{
    return ProbFromOdds(OddsFromProb(dProb) * dFactor);
}

// Get Probability from Odds
double Clock::ProbFromOdds(double dOdds)
{
    return dOdds / (1 + dOdds);
}

// Get Odds from Probability
double Clock::OddsFromProb(double dProb)
{
    if (dProb < 1) return dProb / (1 - dProb);
    else return TIME_INFINITE;
}

void Person::DoEmigrate()
{
    has_emigrated = TRUE;
    is_resident = FALSE;
    Finish();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
////////////////////////////////////////////////////////////////////////////////////////////////////

table Person TabEmigration                                    //EN Emigration Rates
[is_alive && in_projected_time]
{
    sex + *
    {
        transitions(is_resident, TRUE, FALSE) / duration()    //EN Emigration rates decimals=4
    }
    * split(integer_age, AGE5_PART)
    * sim_year
};

table Person TabEmigration2                                     //EN Emigration Numbers
[is_alive && in_projected_time]
{
    sex + *
    {
        transitions(is_resident, TRUE, FALSE)                   //EN Emigration numbers
    }
    * split(integer_age, AGE5_PART) +
    * sim_year
};

table_group TG_Emigration        //EN Emigration tables
{
    TabEmigration, TabEmigration2
};