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
};