
Model Code
This section is a technical documentation of the microWELT 3.0 model, including the model code. It is organised module by module. Each module documentation contains a text description followed by the code. The modules are grouped as follows:
Model Overview
The actors
The socio-demographic core
Economic activity
Labor related income
Tax-Benefit System
Health and Care
The simulation engine and associated modules
Model output
This documentation is automatically generated from the latest version of the model code.
Model Overview
MicroWELT 3.0 SUST - is a model built within the Horizon Europe SustainWELL project. It builds on the MicroWELT simulation platform, extending its scope to the modeling of longitudinal activity careers, earnings, social insurance, and tax-benefit calculation and accounting.
The model inherits most features from existing MicroWELT applications. MicroWELT is a modular, open-source modelling platform developed for the comparative study of interactions between population ageing, socio-demographic change, and welfare state regimes. MicroWELT follows a continuous-time, interacting population framework and supports the alignment of aggregate results with official population projections.The model is X-compatible, meaning it can compile source code using two programming technologies: Modgen and the new open-source environment openM++.
MicroWELT simulates three types of actors (agents): observations, persons, and an observer. ‘Observations’ correspond to records in a starting population file and are used to generate the simulated population through sampling and cloning. Observations are linked to nuclear families. They are temporary actors and are destroyed once the simulated population is created. Persons are the main units of the simulation. The single observer actor is used for processes that require aggregated information, such as model alignment.
In a nutshell, the model consists of the following components and modeled behaviors, most of which corresponding to various modules which include their own detailed documentation.
Previousely existing modules:
The simulation engine, which generates all actors known at the start of the simulation. Most importantly, it generates the initial population from a starting population microdata file.
Education, which takes into account the intergenerational transmission of education and supports trend scenarios as well as scenarios in which changes are driven by the intergenerational transmission of education.
Demography: For mortality and fertility, microWELT reproduces Eurostat’s population projections at the aggregate level, but adds detail at the individual level by taking into account variations in first birth cohort rates and resulting childlessness, progression to second births and longevity by education. Net migration is modelled on the basis of Eurostat projections by age and sex, but with the aim of keeping families together.
Partnerships are modelled from the female perspective, taking into account age, presence and age of children in the family and education. Partners are matched by assortative mating, based on distributions of age differences and education.
LTC needs, arrangements and gaps are modelled, taking into account age, gender and education, as well as the availability of a spouse and the number of children.
Model output is produced through a comprehensive set of output tables.
New modules:
Health and health status transitions used as explanatory variable in various processes, including employment, disability pensions, and mortality.
School enrolment as base of modelling the private and public consumption of education and related education benefits and family transfers.
Longitudinal activity careers distinguishing the states never active, employed, enemployed, family leave, out of labor force, retired. The model also distinguishes between full-time and part-time employment.
Earnings and earnings-replacements (4 modules), i.e. social insurance benefits connected to individual work careers such as unemployment benefits, maternity and parental leave benefits, amd pensions.
Tax-Benefit calculation (8 modules), consistent with Euromod - based on parameters derived from a synthetic tax-benefit database produced by the Euromod Hypothetical Household (HHoT) tool covering the heterogeneity of the population alongside various dimensions. The model distinguishes income taxes, social insurance contributions, and benefits grouped to family benefits, education benefits, old-age benefits, and social benefits according the National Transfer Accounting (NTA) logic.
Benefits not covered by the Euromod HHoT tool such as health benefits, housing benefits, and education grants.
Consumption distinguishing both private and public consumtion of education, health, long-term care, and all other consumtion modeled on the family and the individual level.
Longitudinal accounting of transfers including family transfers.
Childcare provided by parents (hours) and childcare arrangements.
Additional comprehensive model output is produced through an extensive set of output tables, which cover public and private transfer flows and support the comparative analysis of the operation of welfare states.
Model Actors
ActorObservation.mpp
The Observation actor module contains the basic information that defines the Observation actor. Observatios are created as internal representations of the records in the starting population file. They are used to create Person actors of the initial simulated population, which may be smaller or larger than the initial population file. The weights of the observations are used to determine whether and how often an individual observation is represented in the simulated population. All simulated individuals have the same weight. Observation actors are temporary; once the simulated population is created, the Observation actors are destroyed to free up memory space.
In the pre-simulation phase, the file size of the starting population is determined and, based on the record weights and the size of the simulated population, the scaling factor for automatic population scaling of the simulation outputs is determined. The starting population file is a csv-file with a header row containing variable names. Both the file name and the size of the simulation are model parameters. The record layout of the starting population file is defined in this module (PERSON_MICRODATA_COLUMNS).
Variables of the starting population:
Family ID: 1234
Weight: 543.21
Time of birth: 1966 (a random number is added if the time of birth is integer)
Sex: 0 female, 1 male
Education level: 0 (ISCED 2 or lower), 1 (ISCED 3), 2 (ISCED 4), 3 (ISCED 5 or higher)
Role in family: 0 head, 1 spouse, 2 child. (The choice of head is arbitrary; in the simulation, the female partner is considered to be the head)
Currently attending school: 0 no, 1 yes
Activity status: 0 never active, 1 employed, 2 unemployed, 3 family leave, 4 out of labor force, 5 retired
Employment type: 0 not employed, 1 part-time, 2 full-time
Health limitation: 0 non, 1 limited health
Wage
Place in any wage distribution: location in the empirical residual distribution
Pension
Years worked
When the model is extended, new variables need to be added to this list. A link to the corresponding Observation is passed as a parameter to the Start function of the Person actors, so that the values of the variables can be accessed to initialise the Person actors.
Parameters:
File name** of the starting population csv file
Simulation size:** Number of simulated actors representing the initial population. In addition to the simulation size, users can also set the number of replicates (how often the simulation is repeated; run in parallel). This is done in the general scenario settings. A typical simulation size that eliminates most of the Monte Carlo variation in the aggregate results while keeping run times low (depending on computer power, ~1h) is 8 x 400,000.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN All observations
actor_set Observation asObservations;
//EN Observations by family ID
actor_set Observation asObservationByFam[fam_id];
//EN All oldest observations in family
actor_set Observation asObservationOldest filter obs_oldest;
//EN Simulated oldest observations in family
actor_set Observation asSimulatedObservationOldest filter obs_oldest && obs_weight > 0;
//EN Observations by family ID - oldest
actor_set Observation asObservationByFamOldest[fam_id] filter obs_oldest;
//EN Observations by family ID - excluding oldest
actor_set Observation asObservationByFamNotOldest[fam_id] filter !obs_oldest;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification PERSON_MICRODATA_COLUMNS //EN List of Starting population variables
{
PMC_FAMID, //EN Household ID
PMC_WEIGHT, //EN Weight
PMC_BIRTH, //EN Time of birth
PMC_SEX, //EN Sex
PMC_EDUC, //EN Education level
PMC_ROLE, //EN Role in family
PMC_INSCHOOL, //EN Currently attending school
PMC_ACTIVITY, //EN Activity status
PMC_EMPTYPE, //EN Employment type
PMC_HEALTHLIM, //EN Health limitation
PMC_WAGE, //EN Wage
PMC_WAGEPLACE, //EN Place in any wage distribution
PMC_PENSION, //EN Pension
PMC_YEARSWORK //EN Years worked
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameters
{
file MicroDataInputFile; //EN File name of starting population
double StartPopSampleSize; //EN Simulated sample size of starting population
model_generated long MicroDataInputFileSize; //EN File size of starting population
model_generated double ScalingFactor; //EN Scaling factor (actor weight)
};
parameter_group P0_ModelSettings //EN Starting population
{
MicroDataInputFile, StartPopSampleSize
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor states and functions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Observation //EN Actor Observations
{
double pmc[PERSON_MICRODATA_COLUMNS]; //EN Person micro record columns
integer obs_weight = { 1 }; //EN Observation integer weight
FAM_ID fam_id = { 0 }; //EN Family ID
TIME obs_birth = { TIME_INFINITE }; //EN Time of birth
logical obs_oldest = { FALSE }; //EN Is oldest of family
void Start(const input_csv& input); //EN Function starting the actor
void Finish(); //EN Function destroying the actor
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observation::Start(const input_csv& in_csv)
{
// Initialize all attributes (OpenM++).
initialize_attributes();
for (int nJ = 0; nJ < SIZE(PERSON_MICRODATA_COLUMNS); nJ++)
{
pmc[nJ] = in_csv[nJ];
}
fam_id = (int)pmc[PMC_FAMID];
obs_birth = pmc[PMC_BIRTH];
time = MIN(ALL_YEAR);
// Have the entity enter the simulation (OpenM++).
enter_simulation();
};
void Observation::Finish()
{
// Have the entity exit the simulation (OpenM++).
exit_simulation();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Simulation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
// Open starting population file
input_csv inCsv;
inCsv.open(MicroDataInputFile);
// Determine the starting population file size and scaling factor
long lRecordCount = 0;
double dPopSize = 0.0;
while (inCsv.read_record(lRecordCount))
{
lRecordCount++;
dPopSize = dPopSize + inCsv[PMC_WEIGHT];
}
ScalingFactor = dPopSize / StartPopSampleSize;
MicroDataInputFileSize = lRecordCount - 1;
// Close starting population file
inCsv.close();
}
ActorPerson.mpp
The Person actor module contains the basic information that defines the Person actor. The most important function is the Start() function, which initialises all states of a person at creation. This includes initialising time. When the Start function is called (which is done by the simulation engine to create the initial population and immigrants; and by mothers giving birth) the following parameters are passed to the person:
Creation type: identifies whether a person comes from the starting population file, is an immigrant or enters by birth.
Pointer to observation: for persons created from Observations (the starting population file), this pointer allows access to the variable values from the file.
Pointer to creator: For persons created from the starting population file, this is the oldest person of the family who is created first; for births during the simulation the pointer links to the mother. It allows to access information from the the person and to establish family relationships.
Year of immigration: this parameter is only relevant for immigrants.
Sex of immigrant: this parameter is only relevant for immigrants.
After the Start function, a person is part of the simulation. At this point, immediately after birth, the “SetAliveEvent” event is called, which handles family and other actor links, and calls initialisation functions that require the person to be already in the simulation (and therefore cannot be performed in the Start function).
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN All Person actors
actor_set Person asAllPerson filter is_alive;
//EN Residets by age and sex
actor_set Person asResidentsAgeSex[integer_age][sex] filter is_alive && is_resident;
//EN Residets by age sex and education
actor_set Person asResidentsAgeSexEduc[integer_age][sex][educ_level3] filter is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification SEX //EN Sex
{
FEMALE, //EN Female
MALE //EN Male
};
classification CREATION_TYPE //EN Creation type
{
CT_START, //EN Person from startpop
CT_CHILD, //EN Person born in simulation
CT_IMMIGRANT //EN Immigrant
};
range AGE_MAX26 { 0, 26 }; //EN Age
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
double test_scale = { 0.0 };
// Simple states
SEX sex = { FEMALE }; //EN Sex
FAMILY_ROLE family_role_start = { FR_HEAD }; //EN Family role
logical is_resident = { TRUE }; //EN Ever resident
double time_of_birth = { 2000.0 }; //EN Time of birth
ALL_YEAR year_of_birth = { 2000 }; //EN Year of birth
CREATION_TYPE creation_type = { CT_START }; //EN Creation type
Person_ptr ptr_creator; //EN Pointer to creator
// Derived states
AGE_MAX26 age_max26 = COERCE(AGE_MAX26, integer_age); //EN Age
SIM_YEAR sim_year = COERCE(SIM_YEAR, calendar_year); //EN Year
logical in_projected_time = (calendar_year >= MIN(SIM_YEAR)); //EN In projected time
AGE_65P age_65p = COERCE(AGE_65P, integer_age); //EN Age
// Functions
//EN Start
void Start(CREATION_TYPE cCreationType, // Creation type
Observation_ptr prObs, // Pointer to observation
Person_ptr prCreator, // Pointer to creator
int nYearOfImmigration, // Year of immigration
SEX nImmiSex, // Sex of immigrant
double dPersonWeight); // Person weight
void Finish(); //EN Finish
event timeSetAliveEvent, SetAliveEvent; //EN Set alive
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::Start(CREATION_TYPE cCreationType, Observation_ptr prObs, Person_ptr prCreator, int nYearOfImmigration, SEX cImmiSex, double dPersonWeight)
{
// Initialize all attributes (OpenM++)
initialize_attributes();
// Setting the actor weight (Modgen only)
Set_actor_weight(dPersonWeight); Set_actor_subsample_weight(dPersonWeight);
test_scale = dPersonWeight;
TIME dTime = 0.0;
creation_type = cCreationType;
if (prCreator) ptr_creator = prCreator;
else ptr_creator = NULL;
// Person from starting population
if (creation_type == CT_START)
{
// Determine time (cannot be born before creator)
dTime = prObs->pmc[PMC_BIRTH];
if (int(dTime) == dTime) dTime = dTime + RandUniform(2);
if (ptr_creator && dTime < ptr_creator->time_of_birth) dTime = ptr_creator->time_of_birth + 0.00001;
// Initialise states
is_resident = TRUE;
sex = (SEX)(int)prObs->pmc[PMC_SEX];
family_role_start = (FAMILY_ROLE)(int)prObs->pmc[PMC_ROLE];
educ_start = (EDUC_LEVEL4)(int)prObs->pmc[PMC_EDUC];
in_educ_start = ((int)prObs->pmc[PMC_INSCHOOL] == 1) ? TRUE : FALSE;
activity_start = (ACTIVITY)(int)prObs->pmc[PMC_ACTIVITY];
health_cat = (HEALTH_CAT)(int)prObs->pmc[PMC_HEALTHLIM];
if (activity_start == ACT_RETIRED) base_pension = prObs->pmc[PMC_PENSION];
wage_place = prObs->pmc[PMC_WAGEPLACE];
if ((int)prObs->pmc[PMC_EMPTYPE] == 1) full_part_time = (FPT_PART);
contribution_time_start = prObs->pmc[PMC_YEARSWORK];
}
// Person born in simulation
else if (creation_type == CT_CHILD)
{
dTime = ptr_creator->time;
is_resident = TRUE;
double dSexRatio = SexRatio[RANGE_POS(SIM_YEAR, int(dTime))];
if (RandUniform(30) < dSexRatio / (100.0 + dSexRatio)) sex = MALE;
else sex = FEMALE;
wage_place = RandUniform(65);
}
// Immigrants
else if (creation_type == CT_IMMIGRANT)
{
is_resident = FALSE;
sex = cImmiSex;
time_of_immigration = nYearOfImmigration + RandUniform(42);
is_unattended = TRUE;
int nAgeAtImmigration;
Lookup_AgeImmigrants(RandUniform(43), sex, RANGE_POS(SIM_YEAR, nYearOfImmigration), &nAgeAtImmigration);
dTime = time_of_immigration - nAgeAtImmigration - RandUniform(44);
wage_place = RandUniform(78);
}
// All
age = 0;
time = dTime;
calendar_year = int(time);
time_of_birth = time;
year_of_birth = int(time);
// Have the entity enter the simulation (OpenM++).
enter_simulation();
}
void Person::Finish()
{
is_alive = FALSE;
// Have the entity exit the simulation (OpenM++).
exit_simulation();
}
TIME Person::timeSetAliveEvent()
{
if (!is_alive) return WAIT(0.0);
else return TIME_INFINITE;
}
void Person::SetAliveEvent()
{
// Set alive and link to Observer
is_alive = TRUE;
lObserver = asObserver->Item(0);
// Link family members in starting population
if ((creation_type == CT_START && ptr_creator) || creation_type == CT_CHILD) LinkToFamilyWhenSetAlive();
// Assign education
if (creation_type == CT_START || creation_type == CT_CHILD)
{
setParentsEducAtBirth();
setEducAtBirth();
}
else if (creation_type == CT_IMMIGRANT)
{
setImmiEducAtBirth();
}
// Assign lifetime childlessness for men
if (sex == MALE && (creation_type == CT_CHILD || creation_type == CT_IMMIGRANT))
{
setMaleLifetimeChildlessnessAtBirth();
}
// Initiate slower ageing for LTC
InitTimeNextLtcAgeUpdate();
// Update health status (set random waiting time to death)
UpdateHealth();
year_spell = TRUE;
doInitAccount();
}
ActorObserver.mpp
The Observer module contains the basic information associated with an Observer actor. A single Observer actor is instantiated in a simulation. All people are linked to the Observer at birth. The Observer is mainly used for alignment and to improve efficiency, e.g. by implementing a single year change clock instead of year change events at the individual level. At the beginning and end of each year, and in the middle of each month, clock events are called, which are used to call functions to be performed at those times. For example, at the beginning of each year the observer loops through the whole population and calls an individual level function that handles the year change, e.g. by incrementing the calendar year.
At the end of the year, just before the projected time begins, a series of initialisation functions are called. These include functions for imputing the number of children, including those not currently observed in the family, and the initial initialisation of long-term care needs and arrangements, which are then updated according to a mid-month schedule. These functions - as well as other functionalities of the Observer - are implemented and documented in the relevant modules.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Links & Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
link Person.lObserver Observer.mlObserverToPerson[]; //EN Link between person and observer
actor_set Observer asObserver; //EN Actor set Observer
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Observer //EN Actor for synchronising population-level activities
{
void Start();
void Finish();
int observer_year = { 1900 }; //EN Calendar Year
ALL_YEAR observer_tab_year = COERCE(ALL_YEAR, observer_year); //EN Year
TIME next_observer_year_end = { TIME_INFINITE }; //EN Time of next calendar year end
event timeObserverYearEndEvent, ObserverYearEndEvent; //EN Year end event
TIME next_observer_year_start = { TIME_INFINITE }; //EN Time of next calendar year start
event timeObserverYearStartEvent, ObserverYearStartEvent; //EN Year start event
TIME next_observer_midmonth = { TIME_INFINITE }; //EN Time of next mid-month
event timeObserverMidMonthEvent, ObserverMidMonthEvent; //EN Mid-month event
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::Start()
{
// Initialize all attributes (OpenM++).
initialize_attributes();
time = MIN(ALL_YEAR);
observer_year = int(time);
age = 0;
next_observer_year_end = WAIT(0.0);
next_observer_midmonth = WAIT(1.0 / 24.0);
if (ModelMigration) next_emigration_event = MIN(SIM_YEAR) + 0.5;
doInitParentsEducArray();
// Have the entity enter the simulation (OpenM++).
enter_simulation();
}
void Observer::Finish()
{
// Have the entity exit the simulation (OpenM++).
exit_simulation();
}
// Calendar year change
TIME Observer::timeObserverYearStartEvent() { return next_observer_year_start; }
TIME Observer::timeObserverYearEndEvent() { return next_observer_year_end; }
TIME Observer::timeObserverMidMonthEvent() { return next_observer_midmonth; }
void Observer::ObserverYearEndEvent()
{
int nPerson = asAllPerson->Count();
for (int nJ = 0; nJ < nPerson; nJ++)
{
Person_ptr paPerson = asAllPerson->Item(nJ);
paPerson->YearEnd();
}
// End of last year before simulation starts
if (observer_year == MIN(SIM_YEAR) - 1)
{
ImputeObservedNumberChildren();
ImputeNumberChildren50p();
ImputeFirstAndSecondBirths36to49();
ImputeUnobservedFirstBeforeObservedBirths();
SetRemainingFertilityStatesAtSimulationStart();
ImputeMaleParity();
UpdateLongTermCare();
UpdateChildcareHours();
ResetAllBenefits();
UpdateOldageBenefit();
UpdateFamilyBenefit();
UpdateEducationBenefit();
UpdateSocialBenefit();
UpdateOldageBenefit();
InitializeEnrolmentAtStart();
}
next_observer_year_end = WAIT(1.0);
next_observer_year_start = WAIT(0.0);
}
void Observer::ObserverYearStartEvent()
{
observer_year = int(time);
resetEducStack();
int nPerson = asAllPerson->Count();
for (int nJ = 0; nJ < nPerson; nJ++)
{
Person_ptr paPerson = asAllPerson->Item(nJ);
paPerson->YearStart();
}
// Start of simulation
if (observer_year == MIN(SIM_YEAR))
{
UpdatePartnershipStatus();
}
// Set clock
next_observer_year_start = TIME_INFINITE;
}
void Observer::ObserverMidMonthEvent()
{
if (observer_year >= MIN(SIM_YEAR))
{
UpdatePartnershipStatus();
UpdateLongTermCare();
UpdateChildcareHours();
ResetAllBenefits();
UpdateOldageBenefit();
UpdateFamilyBenefit();
UpdateEducationBenefit();
UpdateSocialBenefit();
UpdateOldageBenefit();
UpdateEnrolment();
UpdatePartTime();
}
next_observer_midmonth = WAIT(1.0/12.0);
}
The Socio-Demographic Core
Fertility.mpp
The fertility module implements births, including the imputation of past births. It is designed to simultaneously match official population projections - i.e. aggregate age-specific birth rates - and to take into account education-specific differences in age at first birth and the distribution of family sizes (0, 1, 2+ children). Family sizes are parameterised by education-specific cohort parameters, namely first birth rates by age and second birth rates by time since first birth. Children observed in the starting population are considered as own children.
First and second births that cannot be observed in the starting population because the children have already moved out are imputed, the algorithm depending on the age group:
For women aged 50+, the number of children is imputed by age and education (from a parameter). In addition, log odds are used to select women (of a given education and age) by their current partnership status. The algorithm takes into account observed children in the family, so the number of children can only increase.
Women under 36 are assumed to live with all their children, so family size is assumed to be equal to the observed number of children in the family.
Women aged 36-49: Based on first birth rates, the number of women expected to be a mother is calculated for each education group and age. This number is compared with the number of observed mothers and the gap is closed by finding suitable women who are assumed to have given birth more than 18 years ago (i.e. to children who have already moved out and therefore cannot be observed in the starting population). Once these women have been identified (as in the case of first births, the algorithm also takes into account current partnership status), the date of first birth is assigned. For the remaining time window (from the imputed first birth to 18 years before the start of the simulation), second births are assigned according to second birth rates. While this algorithm is intended to be a realistic allocation of motherhood, the number of second births so far does not take into account cases where one child is observed in the family but this child is not the first child, i.e. the first child has already moved out. To account for these cases, the probability that an observed single child actually has an older sibling is calculated and additional first births are imputed.
The fertility module focuses on women. Apart from the observed number of children from the starting population, family characteristics from the male perspective are treated in a separate module.
Within the simulation, births are modeled the following way:
Birth events are created based on age-specific period rates. The women triggering the event are not considered to be the mothers; the most likely women of similar age to give birth still has to be identified.
Events for expected first births are created by applying education and cohort-specific first birth rates. Women expecting a first birth are given first priority to become the mothers of the babies created by the birth events. Applying age-sepcific first birth rates implicitly determines education-specific cohort childlessness.
Events for expecting a second birth are created by applying education and cohort specific second birth rates. Women expecting a second birth are prioritised to become the mothers of the babies created by the birth events if no woman expects a first birth.
Higher order births are randomly assigned to women of the given age who already have two or more children.
Parameters:
Age-specific fertility rates: this parameter is usually taken from official population projections. As explained above, it is used as an adjustment target, creating birth events without deciding which woman of the given age will be the mother of the child.
First birth cohort rates by education: This (age-specific) parameter - available e.g. from the Human Fertility Database - is used to model ‘expected’ first births. The parameter is also used to impute past births that cannot be observed in the starting population because the children have already moved out. As the required cohort data are only available for the past and are age-censored for cohorts that have not yet reached the end of their reproductive life, the parameterisation requires scenario assumptions.
Duration-specific parity progression to second child by education: This parameter is used to model ‘expected’ second births by time since first birth. The parameter is also used to impute past births that cannot be observed in the starting population because the children have already moved out. Obtaining this parameter typically involves estimation from survey data and calibration to scenario-based projections of education-specific parity progressions.
Distribution of number of children by age and education for women aged 50+. This parameter is used to impute family size. It is usually obtained from retrospective information collected in survey data such as SHARE.
Odds ratio of having at least one child comparing women in a couple with single women by age group. This parameter is used to impute family size to women aged 50 and over. It is usually estimated from retrospective information collected in survey data such as SHARE.
Odds ratios of having two or more children comparing mothers in a partnership with mothers not currently in a partnership, by age group. This parameter is used to impute family size to women aged 50 and over. It is usually estimated from retrospective information collected in survey data such as SHARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Women by education and family size
actor_set Person asWomenByEducFamilySize[year_of_birth][educ_level3][has_spouse][number_children2]
filter sex == FEMALE && is_alive && is_resident;
//EN Fertile women by fertility status
actor_set Person asFertileWomenByStatus[fertile_age][fertility_status]
filter in_projected_time && sex == FEMALE && WITHIN(FERTILE_AGE,integer_age)
&& (waiting_for_first_birth || waiting_for_second_birth || ready_for_higher_birth)
&& is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification NUMBER_CHILDREN2 //EN Number Children
{
NC2_0, //EN No children
NC2_1, //EN One child
NC2_2P //EN Two or more children
};
classification FERTILITY_STATUS //EN Fertility status
{
FST_WAIT_FIRST, //EN Waiting for first birth
FST_WAIT_SECOND, //EN Waiting for second birth
FST_READY_3P //EN Ready for 3rd and higher order birth
};
classification CHILD_ORDER2 //EN Child order
{
CO2_1, //EN First child
CO2_2P //EN Second child
};
range FERTILE_AGE { 15, 49 }; //EN Age
range FERT_PROG { 0, 12 }; //EN Years since first birth
partition ORCHILD_AGEGROUP { 65, 75, 85 }; //EN Age group
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_CHILDREN //EN Fertility
{
NumberChildren50p,
OddsRatioAnyChildrenIfCouple,
OddsRatio2PChildrenIfCouple,
FirstBirthCohortRates,
ParityProgressionSecond,
AgeSpecificFertility,
SexRatio
};
parameters
{
double NumberChildren50p[EDUC_LEVEL3][YOB_START_50P][NUMBER_CHILDREN2]; //EN Distribution number of children 50+
double OddsRatioAnyChildrenIfCouple[ORCHILD_AGEGROUP]; //EN Odds Ratio any children if in partnership
double OddsRatio2PChildrenIfCouple[ORCHILD_AGEGROUP]; //EN Odds Ratio 2+ children if in partnership
double FirstBirthCohortRates[EDUC_LEVEL3][FERTILE_AGE][YOB_BIRTH1]; //EN First birth cohort rates
double ParityProgressionSecond[EDUC_LEVEL3][YOB_BIRTH1][FERT_PROG]; //EN Parity progression 2nd child
double AgeSpecificFertility[FERTILE_AGE][SIM_YEAR]; //EN Age specific fertility rate
double SexRatio[SIM_YEAR]; //EN Sex Ratio (males per 100 females)
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Observer
{
void ImputeObservedNumberChildren(); //EN Impute observed number of children
void ImputeNumberChildren50p(); //EN Impute number of children 50+
void ImputeFirstAndSecondBirths36to49(); //EN Impute first births women 36-49 at start
void ImputeUnobservedFirstBeforeObservedBirths(); //EN Impute unobseerved first births before observed
void SetRemainingFertilityStatesAtSimulationStart(); //EN Set remaining fertility-related states at start
};
actor Person
{
int sim_births = { 0 }; //EN Births in simulation
int sim_higher_births = { 0 }; //EN Third and higher order births
NUMBER_CHILDREN2 number_children2 = { NC2_0 }; //EN Number children
double time_first_birth = { TIME_INFINITE }; //EN Time of first birth
double time_second_birth = { TIME_INFINITE }; //EN Time of second birth
logical first_birth_is_imputed = { FALSE }; //EN First birth is imputed
logical waiting_for_first_birth = { FALSE }; //EN Waiting for first birth
logical waiting_for_second_birth = { FALSE }; //EN Waiting for second birth
logical ready_for_higher_birth = { FALSE }; //EN Ready for 3rd and higher order birth
FERTILE_AGE fertile_age = COERCE(FERTILE_AGE, integer_age); //EN Age
//EN Fertility status
FERTILITY_STATUS fertility_status =
(waiting_for_first_birth) ? FST_WAIT_FIRST :
(waiting_for_second_birth) ? FST_WAIT_SECOND : FST_READY_3P;
//EN Years since first birth
int years_since_first_birth = { 99 }; //EN Years since first birth
TIME time_next_year_since_first_birth = { TIME_INFINITE }; //EN Next year index change for years since first birth
event timeYearsSinceFirstBirthEvent, YearsSinceFirstBirthEvent; //EN Year index change event for years since first birth
void GetBaby(); //EN Get a baby
event timeFirstBirthFlagEvent, FirstBirthFlagEvent; //EN First birth event
event timeSecondBirthFlagEvent, SecondBirthFlagEvent; //EN Second birth event
event timeBirthEvent, BirthEvent; //EN Birth event
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeYearsSinceFirstBirthEvent()
{
if (number_children2 == NC2_1) return time_next_year_since_first_birth;
else return TIME_INFINITE;
}
void Person::YearsSinceFirstBirthEvent()
{
if (years_since_first_birth < MAX(FERT_PROG))
{
years_since_first_birth++;
time_next_year_since_first_birth = WAIT(1.0);
}
else
{
years_since_first_birth = 99;
time_next_year_since_first_birth = TIME_INFINITE;
}
}
//////////////////////////////////
// Imputations
//////////////////////////////////
void Observer::ImputeObservedNumberChildren()
{
int nPopSize = asAllPerson->Count();
for (int nI = 0; nI < nPopSize; nI++)
{
auto prPerson = asAllPerson->Item(nI);
if (prPerson->children_in_family == 1)
{
prPerson->number_children2 = NC2_1;
}
else if (prPerson->children_in_family > 1)
{
prPerson->number_children2 = NC2_2P;
}
else prPerson->number_children2 = NC2_0;
if (prPerson->number_children2 != NC2_0)
{
prPerson->time_first_birth = prPerson->tob_oldest_child_in_family;
}
}
}
void Observer::ImputeNumberChildren50p()
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
for (int nYob = MIN(YOB_START_50P); nYob <= MAX(YOB_START_50P); nYob++)
{
int nAgeIndex = SPLIT(MIN(SIM_YEAR) - nYob, ORCHILD_AGEGROUP);
// Childless versus mothers
int nChildlessSingle = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count();
int nChildlessCouple = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count();
int nMotherSingle = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_2P]->Count();
int nMotherCouple = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_2P]->Count();
double dExpectedMothers = double(nChildlessSingle + nChildlessCouple + nMotherSingle + nMotherCouple) *
((NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_1]
+ NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_2P]) /
(NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_0]
+ NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_1]
+ NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_2P]));
while (nMotherSingle + nMotherCouple < dExpectedMothers
&& (asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count() > 0))
{
double dWeightedCouple = OddsRatioAnyChildrenIfCouple[nAgeIndex] * double(asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count());
double dWeightedAll = dWeightedCouple
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count();
// apply odds ratio to decide picking a single woman or woman with partner
logical bSelectCouple = FALSE;
if (RandUniform(9) < dWeightedCouple / dWeightedAll) bSelectCouple = TRUE;
if ((bSelectCouple && asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count() > 0)
|| asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count() == 0)
{
auto prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->GetRandom(RandUniform(10));
prPerson->number_children2 = NC2_1;
nMotherCouple++;
}
else
{
auto prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->GetRandom(RandUniform(11));
prPerson->number_children2 = NC2_1;
nMotherSingle++;
}
}
// One versus 2+ children
int nOneChildSingle = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count();
int nMoreChildrenSingle = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_2P]->Count();
int nOneChildCouple = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count();
int nMoreChildrenCouple = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_2P]->Count();
double dExpected2p = double(nOneChildSingle + nMoreChildrenSingle + nOneChildCouple + nMoreChildrenCouple) *
(NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_2P] /
(NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_1]
+ NumberChildren50p[nEduc][RANGE_POS(YOB_START_50P, nYob)][NC2_2P]));
while (nMoreChildrenSingle + nMoreChildrenCouple < dExpected2p
&& (asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count() > 0))
{
double dWeightedCouple = OddsRatio2PChildrenIfCouple[nAgeIndex] * double(asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count());
double dWeightedAll = dWeightedCouple
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count();
// apply odds ratio to decide picking a single woman or woman with partner
logical bSelectCouple = FALSE;
if (RandUniform(12) < dWeightedCouple / dWeightedAll) bSelectCouple = TRUE;
if ((bSelectCouple && asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count() > 0)
|| asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count() == 0)
{
auto prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->GetRandom(RandUniform(13));
prPerson->number_children2 = NC2_2P;
nMoreChildrenCouple++;
}
else
{
auto prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->GetRandom(RandUniform(15));
prPerson->number_children2 = NC2_2P;
nMoreChildrenSingle++;
}
}
}
}
}
void Observer::ImputeFirstAndSecondBirths36to49()
{
int nMothers = 0;
int nExpectedMothers = 0;
int nWomen = 0;
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
for (int nYob = MIN(YOB_START_36TO49); nYob <= MAX(YOB_START_36TO49); nYob++)
{
int nAgeIndex = SPLIT(MIN(SIM_YEAR) - nYob, ORCHILD_AGEGROUP);
// Calculate number of expected mothers
nMothers = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_1]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_1]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_2P]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_2P]->Count();
nWomen = nMothers
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count();
double dChildless = 1.0;
for (int nAgeIndex = 0; nAgeIndex < SIZE(FERTILE_AGE) && nYob + MIN(FERTILE_AGE) + nAgeIndex < MIN(SIM_YEAR); nAgeIndex++)
{
dChildless = dChildless * exp(-FirstBirthCohortRates[nEduc][nAgeIndex][RANGE_POS(YOB_BIRTH1, nYob)]);
}
nExpectedMothers = round(nWomen * (1 - dChildless));
// Impute motherhood & date of first birth
while (nMothers < nExpectedMothers
&& (asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count()
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count() > 0))
{
double dWeightedCouple = OddsRatioAnyChildrenIfCouple[nAgeIndex] * double(asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count());
double dWeightedAll = dWeightedCouple
+ asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count();
Person_ptr prPerson = NULL;
// Motherhood
logical bSelectCouple = FALSE;
if (RandUniform(16) < dWeightedCouple / dWeightedAll) bSelectCouple = TRUE;
if ((bSelectCouple && asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->Count() > 0)
|| asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->Count() == 0)
{
prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][TRUE][NC2_0]->GetRandom(RandUniform(17));
}
else
{
prPerson = asWomenByEducFamilySize[RANGE_POS(ALL_YEAR, nYob)][nEduc][FALSE][NC2_0]->GetRandom(RandUniform(18));
}
prPerson->number_children2 = NC2_1;
nMothers++;
// Date of first birth
double dProbBirth[SIZE(FERTILE_AGE)];
double dSurvivalChildless = 1.0;
double dSum = 0.0;
double dSumProb = 0.0;
bool bFound = FALSE;
double dRandom = RandUniform(19);
for (int nAgeIndex = 0; nAgeIndex < MIN(SIM_YEAR) - nYob - 19 - MIN(FERTILE_AGE); nAgeIndex++)
{
dProbBirth[nAgeIndex] = dSurvivalChildless * (1.0 - exp(-FirstBirthCohortRates[nEduc][nAgeIndex][RANGE_POS(YOB_BIRTH1, nYob)]));
dSurvivalChildless = dSurvivalChildless * exp(-FirstBirthCohortRates[nEduc][nAgeIndex][RANGE_POS(YOB_BIRTH1, nYob)]);
dSum = dSum + dProbBirth[nAgeIndex];
}
for (int nAgeIndex = 0; nAgeIndex < MIN(SIM_YEAR) - nYob - 19 - MIN(FERTILE_AGE) && !bFound; nAgeIndex++)
{
dSumProb = dSumProb + dProbBirth[nAgeIndex] / dSum;
if (dRandom < dSumProb)
{
bFound = TRUE;
prPerson->time_first_birth = prPerson->time_of_birth + MIN(FERTILE_AGE) + nAgeIndex + RandUniform(20);
prPerson->first_birth_is_imputed = TRUE;
// 2nd birth
bool bSecond = FALSE;
double dTimeAtRiskSecondBirth = MIN(SIM_YEAR) - 18 - prPerson->time_first_birth;
int nPeriod = 0;
while (nPeriod < SIZE(FERT_PROG) && nPeriod < dTimeAtRiskSecondBirth && !bSecond && nAgeIndex + nPeriod < MIN(SIM_YEAR) - nYob - 19 - MIN(FERTILE_AGE))
{
double dHazard = ParityProgressionSecond[nEduc][RANGE_POS(YOB_BIRTH1, nYob)][nPeriod];
double dWait = TIME_INFINITE;
if (dHazard > 0) dWait = -log(RandUniform(21)) / dHazard;
if (dWait < 1.0)
{
bSecond = TRUE;
prPerson->time_second_birth = prPerson->time_first_birth + nPeriod + dWait;
prPerson->number_children2 = NC2_2P;
}
else nPeriod++;
}
}
}
}
}
}
}
void Observer::ImputeUnobservedFirstBeforeObservedBirths()
{
int nPersons = asAllPerson->Count();
for (int nIndex = 0; nIndex < nPersons; nIndex++)
{
Person_ptr prPerson = asAllPerson->Item(nIndex);
if (prPerson->sex == FEMALE && prPerson->is_resident && !prPerson->first_birth_is_imputed && prPerson->number_children2 == NC2_1 &&
prPerson->integer_age >= 36 && prPerson->integer_age <= 49)
{
int nAgeAtObservedBirth = (int)(prPerson->tob_oldest_child_in_family - prPerson->time_of_birth);
int nYob = prPerson->year_of_birth;
// Probability of observation without previous birth
double dProbFirst = 1.0;
for (int nAge = MIN(FERTILE_AGE); nAge <= nAgeAtObservedBirth; nAge++)
{
double dBirthRate = FirstBirthCohortRates[prPerson->educ_level3][RANGE_POS(FERTILE_AGE, nAge)][RANGE_POS(YOB_BIRTH1, nYob)];
double dBirthProb = 1.0 - exp(-dBirthRate);
if (nAge < nAgeAtObservedBirth) dProbFirst = dProbFirst * (1.0 - dBirthProb);
else dProbFirst = dProbFirst * dBirthProb;
}
// Probability of observation with previous birth
int nMaxAgeUnobserved = (int)(time - 19.0 - prPerson->time_of_birth);
if (nMaxAgeUnobserved > nAgeAtObservedBirth) nMaxAgeUnobserved = nAgeAtObservedBirth; //observed is > 18
double dProbSecond = 0.0;
double dSumProbSecond = 0.0;
for (int nAgeFirst = MIN(FERTILE_AGE); nAgeFirst <= nMaxAgeUnobserved; nAgeFirst++)
{
dProbSecond = 1.0;
for (int nAge = MIN(FERTILE_AGE); nAge <= nAgeAtObservedBirth; nAge++)
{
if (nAge <= nAgeFirst)
{
double dBirthRate = FirstBirthCohortRates[prPerson->educ_level3][RANGE_POS(FERTILE_AGE, nAge)][RANGE_POS(YOB_BIRTH1, nYob)];
double dBirthProb = 1.0 - exp(-dBirthRate);
if (nAge < nAgeFirst) dProbSecond = dProbSecond * (1.0 - dBirthProb);
else dProbSecond = dProbSecond * dBirthProb;
}
else
{
int nFertProg = nAge - nAgeFirst - 1;
if (!WITHIN(FERT_PROG, nFertProg)) dProbSecond = 0; // Period between births too long
else
{
double dBirthRate = ParityProgressionSecond[prPerson->educ_level3][RANGE_POS(YOB_BIRTH1, nYob)][nFertProg];
double dBirthProb = 1.0 - exp(-dBirthRate);
if (nAge < nAgeAtObservedBirth) dProbSecond = dProbSecond * (1.0 - dBirthProb);
else dProbSecond = dProbSecond * dBirthProb;
}
}
}
dSumProbSecond = dSumProbSecond + dProbSecond;
}
// Sample if first birth is added
if (RandUniform(22) < dSumProbSecond / (dSumProbSecond + dProbFirst))
{
prPerson->number_children2 = NC2_2P;
}
}
}
}
void Observer::SetRemainingFertilityStatesAtSimulationStart()
{
int nPersons = asAllPerson->Count();
for (int nIndex = 0; nIndex < nPersons; nIndex++)
{
Person_ptr prPerson = asAllPerson->Item(nIndex);
if (prPerson->sex == FEMALE && prPerson->is_resident && prPerson->number_children2 == NC2_1 && prPerson->time_first_birth < time)
{
double dTimeSinceFirstBirth = time - prPerson->time_first_birth;
int nTimeSinceFirstBirth = int(dTimeSinceFirstBirth);
if (nTimeSinceFirstBirth <= MAX(FERT_PROG))
{
prPerson->years_since_first_birth = nTimeSinceFirstBirth;
prPerson->time_next_year_since_first_birth = WAIT(dTimeSinceFirstBirth - nTimeSinceFirstBirth);
}
else
{
prPerson->years_since_first_birth = 99;
prPerson->time_next_year_since_first_birth = TIME_INFINITE;
}
}
if (prPerson->sex == FEMALE && prPerson->is_resident && prPerson->number_children2 == NC2_2P)
{
prPerson->ready_for_higher_birth = TRUE;
}
}
}
//////////////////////////////////
// Births in simulation
//////////////////////////////////
void Person::GetBaby()
{
// Find a spouse if not in a partnership
if (!has_spouse) FindSpouse();
// Create the baby
Person_ptr ptrPerson = new Person();
ptrPerson->Start(CT_CHILD, NULL, this, 0, MALE, test_scale);
sim_births++;
ActDecideLeave();
if (number_children2 == NC2_0)
{
number_children2 = NC2_1;
waiting_for_first_birth = FALSE;
time_next_year_since_first_birth = WAIT(1.0);
years_since_first_birth = 0;
}
else if (number_children2 == NC2_1)
{
number_children2 = NC2_2P;
waiting_for_second_birth = FALSE;
ready_for_higher_birth = TRUE;
}
else
{
sim_higher_births++;
}
}
TIME Person::timeBirthEvent()
{
TIME dEventTime = TIME_INFINITE;
if (in_projected_time && is_resident && sex == FEMALE && WITHIN(FERTILE_AGE, integer_age))
{
double dHazard = AgeSpecificFertility[RANGE_POS(FERTILE_AGE, fertile_age)][RANGE_POS(SIM_YEAR, calendar_year)];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(26)) / dHazard);
}
return dEventTime;
}
void Person::BirthEvent()
{
Person_ptr prMother = NULL;
// Anybody waiting for first birth?
if (asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_WAIT_FIRST]->Count() > 0)
{
prMother = asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_WAIT_FIRST]->GetRandom(RandUniform(27));
}
// Second birth?
else if (asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_WAIT_SECOND]->Count() > 0)
{
prMother = asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_WAIT_SECOND]->GetRandom(RandUniform(28));
}
// Higher order
else if (asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_READY_3P]->Count() > 0)
{
prMother = asFertileWomenByStatus[RANGE_POS(FERTILE_AGE, fertile_age)][FST_READY_3P]->GetRandom(RandUniform(29));
}
// If mother is found, she gets a baby
if (prMother) prMother->GetBaby();
}
TIME Person::timeFirstBirthFlagEvent()
{
TIME dEventTime = TIME_INFINITE;
if (in_projected_time && is_resident && sex == FEMALE && WITHIN(FERTILE_AGE, integer_age)
&& number_children2 == NC2_0 && !waiting_for_first_birth)
{
double dHazard = FirstBirthCohortRates[educ_level3][RANGE_POS(FERTILE_AGE, fertile_age)][RANGE_POS(YOB_BIRTH1, year_of_birth)];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(23)) / dHazard);
}
return dEventTime;
}
void Person::FirstBirthFlagEvent()
{
waiting_for_first_birth = TRUE;
}
TIME Person::timeSecondBirthFlagEvent()
{
TIME dEventTime = TIME_INFINITE;
if (in_projected_time && is_resident && sex == FEMALE && WITHIN(FERTILE_AGE, integer_age)
&& number_children2 == NC2_1 && !waiting_for_second_birth)
{
if (WITHIN(FERT_PROG, years_since_first_birth))
{
double dHazard = ParityProgressionSecond[educ_level3][RANGE_POS(YOB_BIRTH1, year_of_birth)][years_since_first_birth];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(25)) / dHazard);
}
}
return dEventTime;
}
void Person::SecondBirthFlagEvent()
{
waiting_for_second_birth = TRUE;
}
Mortality.mpp
The mortality module implements mortality by age, sex, education and health. It is designed for cases where mortality projections taking into account educational differences are not readily available, but have to be derived by combining information from (1) official population projections with (2) data and scenarios on remaining life expectancy at ages 25 and 65 by education and (3) age-specific mortality differences (relative risks) by education observed today. In terms of educational attainment, this module distinguishes between three levels: low (ISCED 2 and below), medium (ISCED 3 and 4) and high (ISCED 5 and above). Health status is accounted for in a final step, drawing on the health transition model.
Users have three choices of how to simulate mortality:
Base model: This option does not model mortality by education and health, but simply applies aggregate period mortality rates by age and sex (typically taken from official population projections).
Detailed model: This option models mortality by education using target remaining life expectancies at ages 25 and 65 and relative risk profiles from parameters. For each year and level of education, the period mortality rates of the base model are calibrated to produce education-specific period life tables. (This step is performed in the pre-simulation function of this module). In the final step, once the age, sex and education of the next person to die have been determined, the person with the shortest waiting time, accounting for health status, is chosen from the pool of people with these characteristics.
Detailed model adjusted to base model total mortality: This option additionally adjusts mortality by age and sex to the base model. This means that mortality projections taken from official population projections are reproduced in aggregate (by age and sex), while maintaining the relative risk structure between education and health groups.
In order to construct education-specific life tables, two calibration factors by education level (applied together with the age patterns in relative risks) are determined by numerical simulation (binary search). First, a calibration factor is sought to fit the remaining life expectancy at age 65. Second, using this factor for the 65+ population, another calibration factor is determined for the younger ages to fit the remaining life expectancy at age 25. As relative risks by education usually have an age shape (relative differences typically decrease with age), the calibration factors are applied together with the parameter of current age-specific relative risks. In other words, individual mortality is calculated by applying the relative risk factor - rescaled by the calibration factors - to the mortality rate by age and sex taken from official population projections. The underlying assumption is that age patterns in relative risks remain the same over time. For the starting year, this approach is consistent with the direct application of life tables by education (as far as the remaining life expectancies in the parameters are consistent with these life tables), so no calibration is required at the start.
The parameterisation allows the creation of scenarios for the evolution of educational differences in life expectancy, such as convergence scenarios where the gaps between groups narrow or close.
The third model option allows for an additional adjustment of the results of the education-specific model to the aggregate mortality of the base model. This adjustment preserves the relative differences in mortality risks by education. It is implemented by separating the birth events by age and sex produced by the base model from the selection of those selected to die, the latter being based on individual random waiting times taking education into account. Statistically, this approach is equivalent to modifying the baseline mortality hazard (but maintaining the relative risks by education) in such a way that, for a given composition of the population by education, the overall mortality rate is equal to the target mortality rate.
Parameters:
Model option: allows the user to choose between the three model options described above.
Period mortality by age and sex: this parameter is usually taken from official population projections.
Remaining life expectancy at ages 25 and 65 by education, sex and period. Recent estimates are available in the literature and/or can be calculated from period mortality rates by age, sex, and education. The parameter allows the construction of scenarios on the evolution of educational differences, e.g. concergence scenarios.
Current age-specific relative mortality risks by education and sex. This parameter can be calculated by comparing mortality rates by age, sex and education with mortality rates by age and sex. The parameter is used to capture the age patterns in relative risks, but - due the model alignments described above - does not affect education-specific life expectancies.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN All mortal persons
actor_set Person asAllMortalPersonsForMortalityAlignment[sex][integer_age]
filter SelectedMortalityModel == MOM_ALIGNED && in_projected_time && is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification MORTALITY_MODEL //EN Mortality model selection
{
MOM_BASE, //EN Base model
MOM_DETAIL, //EN Detailed model
MOM_ALIGNED //EN Detailed model aligned to base
};
classification LIFE_EXPECT //EN Life Expectancy
{
LE_25, //EN Life expectancy at 25
LE_65 //EN Life expectancy at 65
};
range AGE_RANGE { 0, 105 }; //EN Age
range AGE_25P { 25, 105 }; //EN Age
range AGE_65P { 65, 105 }; //EN Age
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG02_Mortality //EN Mortality
{
SelectedMortalityModel,
MortalityTable,
LifeExpectancy,
MortalityAdjustment
};
parameters
{
MORTALITY_MODEL SelectedMortalityModel; //EN Mortality model selection
double MortalityTable[SEX][AGE_RANGE][SIM_YEAR]; //EN Mortality base rates
double LifeExpectancy[SEX][LIFE_EXPECT][SIM_YEAR][EDUC_LEVEL3]; //EN Period life expectancy
double MortalityAdjustment[SEX][AGE_25P][EDUC_LEVEL3]; //EN Mortality relative risks profile
//EN Mortality hazards by education
model_generated double MortalityDetailedHazard[SEX][EDUC_LEVEL3][AGE_RANGE][SIM_YEAR];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor states and functions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical is_alive = { FALSE }; //EN Alive
void HandleMortality(); //EN Handle Mortality
void Death(); //EN Death
double getTimeToDeath(); //EN Return time to death
event timeDeathAtMaxLifespanEvent, DeathAtMaxLifespanEvent; //EN Death at max lifespan
event timeMortalityBaseEvent, MortalityBaseEvent; //EN Mortality event base model
event timeMortalityDetailedEvent, MortalityDetailedEvent; //EN Mortality event detailed model
};
actor Observer
{
Person_ptr GetNextToDie(Person_ptr prPerson); //EN Identify next person to die
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeDeathAtMaxLifespanEvent() { return time_of_birth + MAX(AGE_RANGE) + 0.99999; }
void Person::DeathAtMaxLifespanEvent() { Death(); }
void Person::Death()
{
// Update accounts
doUpdateAccount();
is_alive = FALSE;
MaintainLinksAtDeath();
Finish();
}
void Person::HandleMortality()
{
// Base model
if (SelectedMortalityModel == MOM_BASE) Death(); // this person dies if no alignment
// Detailed: find person with shortest waiting time by health status - with age, sex, and education of person currently selected to die
else if (SelectedMortalityModel == MOM_DETAIL)
{
if (asMortalsByAgeSexEducation[integer_age][sex][educ_level3]->Count() > 0)
{
asMortalsByAgeSexEducation[integer_age][sex][educ_level3]->Item(0)->Death();
}
else Death();
}
// Detaile dmodel aligned
else lObserver->GetNextToDie(this)->Death(); // person to die to be found
}
TIME Person::timeMortalityBaseEvent()
{
TIME dEventTime = TIME_INFINITE;
double dMortalityHazard = MortalityTable[sex][integer_age][RANGE_POS(SIM_YEAR, calendar_year)];
// check if a person is at risk
if (in_projected_time && is_resident && dMortalityHazard > 0.0 && SelectedMortalityModel != MOM_DETAIL)
{
// determine the event time
dEventTime = WAIT(-log(RandUniform(14)) / dMortalityHazard);
}
return dEventTime;
}
void Person::MortalityBaseEvent()
{
HandleMortality();
}
TIME Person::timeMortalityDetailedEvent()
{
TIME tEventTime = TIME_INFINITE;
double dHazard = MortalityDetailedHazard[sex][educ_level3][integer_age][RANGE_POS(SIM_YEAR, calendar_year)];
if (in_projected_time && is_resident && dHazard > 0.0 && SelectedMortalityModel == MOM_DETAIL)
{
tEventTime = WAIT(-TIME(log(RandUniform(31)) / dHazard));
}
return tEventTime;
}
void Person::MortalityDetailedEvent()
{
HandleMortality();
}
double Person::getTimeToDeath()
{
double dWaitingTime = TIME_INFINITE;
double dHazard = MortalityDetailedHazard[sex][educ_level3][integer_age][RANGE_POS(SIM_YEAR, calendar_year)];
if (dHazard > 0.0) dWaitingTime = -log(RandUniform(32)) / dHazard;
return dWaitingTime;
}
Person_ptr Observer::GetNextToDie(Person_ptr prPers)
{
long nPopSize = asAllMortalPersonsForMortalityAlignment[prPers->sex][prPers->integer_age]->Count();
Person_ptr ptrNextDead = NULL;
double dWaitToDeath = TIME_INFINITE;
for (long nI = 0; nI < nPopSize; nI++)
{
Person_ptr ptrThisOne = asAllMortalPersonsForMortalityAlignment[prPers->sex][prPers->integer_age]->Item(nI);
double dThisWaitTime = ptrThisOne->getTimeToDeath();
if (dThisWaitTime <= dWaitToDeath)
{
dWaitToDeath = dThisWaitTime;
ptrNextDead = ptrThisOne;
}
}
// now the one with shortest waiting time by health model
if (ptrNextDead && asMortalsByAgeSexEducation[ptrNextDead->integer_age][ptrNextDead->sex][ptrNextDead->educ_level3]->Count() > 0)
{
ptrNextDead = asMortalsByAgeSexEducation[ptrNextDead->integer_age][ptrNextDead->sex][ptrNextDead->educ_level3]->Item(0);
}
return ptrNextDead;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Simulation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
// Mortality rates by population group are found by binary search. For each simulated year, the overall mortality table
// is adjusted to obtain target life expectancies at age 65 and at age 25 for each distinguished population group.
// Local variables
double dTarget, dCenter, dResult, dAlive, dDeaths, dLower, dUpper;
int nIterations;
if (SelectedMortalityModel != MOM_BASE)
{
// Find trend factors to fit the life expectancy at 65
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR); nYear++)
{
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
dTarget = LifeExpectancy[nSex][LE_65][nYear][nGroup]; // Target life expectancy
dResult = 0.0; // Search result: life expectancy
dLower = 0.01; // Lower limit of calibration factor
dUpper = 100.0; // Upper limit of calibration factor
nIterations = 100000; // Maximal iterations
while (abs(dResult - dTarget) > 0.0001 && nIterations > 0)
{
nIterations--;
dCenter = (dLower + dUpper) / 2.0; // New calibration factor for probing
dResult = 0.0;
dAlive = 1.0; // Proportion of people still alive
// Life expectancy calculated applying calibration factor
for (int nAge = 65; nAge < SIZE(AGE_RANGE); nAge++)
{
// proportion of deaths in year: survival = exp(-hazard)
dDeaths = dAlive * (1 - exp(-MortalityTable[nSex][nAge][nYear] * (1.0 + (dCenter - 1.0) * MortalityAdjustment[nSex][RANGE_POS(AGE_25P, nAge)][nGroup])));
dAlive = dAlive - dDeaths;
// people dying in this year are assumed to die in the middle of the year
dResult = dResult + dDeaths * 0.5 + dAlive;
}
// Moving the search limits for next iteration
if (dTarget < dResult) dLower = dCenter;
else dUpper = dCenter;
}
// applying the best solution to the mortality parameter
for (int nAge = 65; nAge < SIZE(AGE_RANGE); nAge++)
{
MortalityDetailedHazard[nSex][nGroup][nAge][nYear] = MortalityTable[nSex][nAge][nYear] * (1.0 + (dCenter - 1.0) * MortalityAdjustment[nSex][RANGE_POS(AGE_25P, nAge)][nGroup]);
}
}
}
}
// Find trend factors to fit the life expectancy at 25 (while keeping the trend for 65+)
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR); nYear++)
{
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
dTarget = LifeExpectancy[nSex][LE_25][nYear][nGroup]; // Target life expectancy
dResult = 0.0; // Search result: life expectancy
dLower = 0.01; // Lower limit of calibration factor
dUpper = 100.0; // Upper limit of calibration factor
nIterations = 100000; // Maximal iterations
while (abs(dResult - dTarget) > 0.0001 && nIterations > 0)
{
nIterations--;
dCenter = (dLower + dUpper) / 2.0; // New calibration factor for probing
dResult = 0.0;
dAlive = 1.0; // Proportion of people still alive
// Life expectancy calculated applying calibration factor
for (int nAge = 25; nAge < SIZE(AGE_RANGE); nAge++)
{
// proportion of deaths in year: survival = exp(-hazard)
if (nAge < 65) // Apply searched adjustment factor only for ages below 65
{
dDeaths = dAlive * (1 - exp(-MortalityTable[nSex][nAge][nYear] * (1.0 + (dCenter - 1.0) * MortalityAdjustment[nSex][RANGE_POS(AGE_25P, nAge)][nGroup])));
}
else // apply known factor for ages 65+
{
dDeaths = dAlive * (1 - exp(-MortalityDetailedHazard[nSex][nGroup][nAge][nYear]));
}
dAlive = dAlive - dDeaths;
// people dying in this year are assumed to die in the middle of the year
dResult = dResult + dDeaths * 0.5 + dAlive;
}
// Moving the search limits for next iteration
if (dTarget < dResult) dLower = dCenter;
else dUpper = dCenter;
}
// applying the best solution to the mortality parameter
for (int nAge = 25; nAge < 65; nAge++)
{
MortalityDetailedHazard[nSex][nGroup][nAge][nYear] = MortalityTable[nSex][nAge][nYear] * (1.0 + (dCenter - 1.0) * MortalityAdjustment[nSex][RANGE_POS(AGE_25P, nAge)][nGroup]);
}
}
}
}
// Copy over parameters for age < 25
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR); nYear++)
{
for (int nAge = 0; nAge < 25; nAge++)
{
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
MortalityDetailedHazard[nSex][nGroup][nAge][nYear] = MortalityTable[nSex][nAge][nYear];
}
}
}
}
}
}
Migration.mpp
The migration module handles net migration by age and sex, parameters typically taken from official (e.g. Eurostat) population projections. Immigrants are created by the simulation engine, their number and age distribution being calculated from the net migration parameter in the pre-simulation function in this module. Immigrants arrive at random times within a year. In contrast, emigration is modelled as occurring only once in the middle of each year. Emigration is handled by the Observer actor, the event implemented in this module. The migration module also initialises the educational and family characteristics of migrants and links migrant mothers to children arriving in the same year. The concept of net migration does not allow for the modelling of life course heterogeneity by place of origin, so immigrants are assumed to be no different from residents.
Like all other persons, immigrants are created at birth. Unlike residents, they are not subject to life course events such as mating or mortality until they immigrate. Instead, they acquire most of their characteristics by cloning from a resident host. This happens at two points in time:
At birth, babies sample their educational ‘destiny’ and their parents’ education from resident babies.
At the time of immigration, a resident host of the same age, sex and education is randomly selected and relevant characteristics are cloned. For women, this includes characteristics such as number of children and whether a first or second birth is currently expected. If a female host lives with dependent children, the corresponding female immigrant tries to find children of the same age in the pool of immigrants arriving in the same year (so far unattended). While this approach treats the fertility of immigrant women as similar to that of the resident population, it does not treat partnerships separately. All immigrants arrive as singles (including single mothers with dependent children) and, from the next mid-month event onwards, become subject to the periodic partnership updates treated in the partnerships module.
Parameters:
Migration On/Off
Number of net migrants by age, sex, and year
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Unattended migrants
actor_set Person asUnattendedImmigrantChildren[integer_age]
filter is_alive && creation_type == CT_IMMIGRANT && is_unattended && immi_this_year;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_MIGRATION //EN Migration
{
ModelMigration,
NetMigrationSexAgePeriod
};
parameters
{
logical ModelMigration; //EN Migration On/Off
double NetMigrationSexAgePeriod[SEX][AGE_RANGE][SIM_YEAR]; //EN Net migration by age and sex
model_generated double NumberImmigrants[SIM_YEAR][SEX]; //EN Number of immigrants
model_generated cumrate AgeImmigrants[SEX][SIM_YEAR][AGE_RANGE]; //EN Age distribution of immigrants
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical is_unattended = { FALSE }; //EN Unattended immigrant
//EN Scheduled to immigrate in this year
logical immi_this_year = (int(time_of_immigration) == calendar_year) ? TRUE : FALSE;
void setImmiEducAtBirth(); //EN Sample education from host at birth
void setImmiStatesAtImmigration(); //EN Sample states from host at immigration
void doEmigrate(); //EN Emigrate
TIME time_of_immigration = { TIME_INFINITE }; //EN Time of first immigration
event timeImmigrationEvent, ImmigrationEvent; //EN Immigration event
};
actor Observer
{
TIME next_emigration_event = { TIME_INFINITE }; //EN Next emigration event
event timeEmigrationEvent, EmigrationEvent; //EN Emigration event
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::doEmigrate()
{
is_resident = FALSE;
Death();
}
void Person::setImmiStatesAtImmigration()
{
logical bHostExists = FALSE;
if (asResidentsAgeSexEduc[integer_age][sex][educ_level3]->Count() > 0) bHostExists = TRUE;
// Female in reproductive age or above
if (sex == FEMALE && integer_age >= MIN(FERTILE_AGE) && !lives_with_mother && bHostExists)
{
Person_ptr ptrHost = asResidentsAgeSexEduc[integer_age][sex][educ_level3]->GetRandom(RandUniform(48));
number_children2 = ptrHost->number_children2;
waiting_for_first_birth = ptrHost->waiting_for_first_birth;
waiting_for_second_birth = ptrHost->waiting_for_second_birth;
ready_for_higher_birth = ptrHost->ready_for_higher_birth;
// loop through children in hosts family
int nIndex = 0;
Person_ptr ptrHostChild = ptrHost->mlRecentMotherChildren->GetNext(0, &nIndex);
while (ptrHostChild)
{
if (ptrHostChild->lives_with_mother)
{
integer nChildAge = ptrHostChild->integer_age;
if (asUnattendedImmigrantChildren[nChildAge]->Count() > 0)
{
Person_ptr ptrOwnChild = asUnattendedImmigrantChildren[nChildAge]->GetRandom(RandUniform(49));
ptrOwnChild->lFirstMother = (Person_ptr)this;
ptrOwnChild->lRecentMother = (Person_ptr)this;
ptrOwnChild->is_unattended = FALSE;
ptrOwnChild->lives_with_mother = TRUE;
}
}
// pointer to next child
ptrHostChild = ptrHost->mlRecentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
// Set clock for time since first birth
if (number_children2 == NC2_1)
{
int nIndex = 0;
// Get the link to the child (there should only be one -- otherwise the first)
Person_ptr ptrChild = mlRecentMotherChildren->GetNext(0, &nIndex);
// If no child was found and linked: take time of birth of host child as proxy
if (!ptrChild)
{
ptrChild = ptrHost->mlRecentMotherChildren->GetNext(0, &nIndex);
}
// Now set the clock
if (ptrChild)
{
time_first_birth = ptrChild->time_of_birth;
double dTimeSinceFirstBirth = time - time_first_birth;
int nTimeSinceFirstBirth = int(dTimeSinceFirstBirth);
if (nTimeSinceFirstBirth <= MAX(FERT_PROG))
{
years_since_first_birth = nTimeSinceFirstBirth;
time_next_year_since_first_birth = WAIT(dTimeSinceFirstBirth - nTimeSinceFirstBirth);
}
else
{
years_since_first_birth = 99;
time_next_year_since_first_birth = TIME_INFINITE;
}
}
}
}
}
void Person::setImmiEducAtBirth()
{
EDUC_LEVEL4 cEduc = EL4_ISCED2;
PARENTS_EDUC cParEduc = PED_UNKNOWN;
if (asResidentsAgeSex[0][sex]->Count() > 0)
{
Person_ptr ptrBabyHost = asResidentsAgeSex[0][sex]->GetRandom(RandUniform(45));
cEduc = ptrBabyHost->educ_level4;
cParEduc = ptrBabyHost->parents_educ;
}
educ_level4 = cEduc;
parents_educ = cParEduc;
}
TIME Person::timeImmigrationEvent()
{
if (!is_resident) return time_of_immigration;
else return TIME_INFINITE;
}
void Person::ImmigrationEvent()
{
is_resident = TRUE;
setImmiStatesAtImmigration();
}
TIME Observer::timeEmigrationEvent() { return next_emigration_event; }
void Observer::EmigrationEvent()
{
// for each age by sex
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
double dExpectedEmigrants = 0.0;
long nExpectedEmigrants = 0;
if (NetMigrationSexAgePeriod[nSex][nAge][RANGE_POS(SIM_YEAR, observer_year)] < 0) // there is net emigration
{
// determine number of emigrants
dExpectedEmigrants = -NetMigrationSexAgePeriod[nSex][nAge][RANGE_POS(SIM_YEAR, observer_year)] / ScalingFactor;
nExpectedEmigrants = int(dExpectedEmigrants);
if (RandUniform(47) < dExpectedEmigrants - (double)nExpectedEmigrants) nExpectedEmigrants++;
// make somebody emigrate
for (int nIndex = 0; nIndex < nExpectedEmigrants; nIndex++)
{
if (asResidentsAgeSex[nAge][nSex]->Count() > 0)
{
Person_ptr prPerson = asResidentsAgeSex[nAge][nSex]->GetRandom(RandUniform(46));
prPerson->doEmigrate();
}
}
}
}
}
next_emigration_event = WAIT(1.0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Simulation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
// Initialize
for (int nYear = 0; nYear < SIZE(SIM_YEAR); nYear++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
NumberImmigrants[nYear][nSex] = 0.0;
}
}
// Net migration: Immigrants
if (ModelMigration)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR); nYear++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
double dSumImmigrants = 0;
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
if (NetMigrationSexAgePeriod[nSex][nAge][nYear] > 0)
{
AgeImmigrants[nSex][nYear][nAge] = NetMigrationSexAgePeriod[nSex][nAge][nYear];
dSumImmigrants = dSumImmigrants + NetMigrationSexAgePeriod[nSex][nAge][nYear];
}
else
{
AgeImmigrants[nSex][nYear][nAge] = 0.0;
}
}
NumberImmigrants[nYear][nSex] = dSumImmigrants;
}
}
}
}
Partnerships.mpp
This module implements processes for maintaining the partnership status of women over the life course (union formation, dissolution, matching a suitable partner). The female partnership status is updated monthly according to observed partnership patterns by education, age, and age of the youngest child.
The model maintains the patterns contained in the parameters. Thus we assume that these patterns are stable and changes in aggregate partnership characteristics only result from compositional changes in the female population like changes in the education composition, childlessness or timing of births. The model follows a ‘minimum necessary corrections’ approach changing the union status of women only to meet aggregate numbers. In reality, unions are more unstable, i.e. the model does not move women out of a union and others in if the aggregate proportion does not change. The current version is longitudinally consistent only on the cohort level by education and the number of children ever born (childless, one child, two or more children). Alignments can be switched off for higher ages (see below).
Partner matching is modelled by age and education. We model only two-sex couples. For age differences, we assume that the patterns of observed age differences in couples by age persist over time. One difficulty in assigning a partner is that the distribution of age differences changes with age at union formation. For example, a young man cannot have a much younger spouse (or vice versa), while the spread of observed age differences increases with age. As information on union formation and duration is usually not available in surveys and administrative data are only available for marriages, we follow an indirect approach based on the observed age patterns in existing partnerships. The algorithm is as follows:
Based on the distribution parameter of age differences between spouses, we calculate the expected number of partners by age for the age of the seeking woman in the simulation.
We then calculate the number of actual partners by age in the simulated population.
By comparing the expected and observed distributions, we identify the age with the largest negative gap for which there is at least one available male partner.
Having identified the pool of available partners, a second criterion is education, which is sampled from a distributional parameter. Current patterns are assumed to be persistent and maintainable over time. Although the model is female-driven, the number of men available for partnerships can be limited by setting parameters for the maximum proportion of men in partnerships according to age group and level of education. This prevents changes in the educational composition (e.g. a diminishing proportion of men in the lowest education group) from causing unrealistic changes in the likelihood of men being in a partnership according to their education level (e.g. all men in the lowest education group being in partnerships).
Parameters:
Partnerships of women with dependent children: probability of being in a partnership by education, age group and age group of youngest child. This parameter is usually estimated from survey data such as SILC. These probabilities are assumed to remain constant in the future.
Partnerships of women not living with dependent children: probability of being in a partnership by education and age. This parameter is usually estimated from survey data such as SILC. These probabilities are assumed to remain constant in the future.
Highest age at union dissolution other than widowhood: This parameter makes it possible to switch off the adjustment to (lower) target rates. This is useful for creating scenarios that assume union stability at higher ages, where, due to improvements in mortality, it can be assumed that people stay in unions longer because of the increase in life expectancy of the partner.
Highest age at union formation: This parameter allows to switch off the adjustment to (higher) target rates. This is useful for sensitivity analysis, i.e. to create scenarios in which no new union formation occurs from a certain age.
Option to adjust the union status of persons of the staring population to the target parameters before the start of a simulation. This enables identical initial partnership patterns to be created for scenario comparisons with scenarios that restrict the age of union adjustments.
Union formation risks 65+: this parameter allows to generate scenarios, which combine the assumption of union stability at ages 65+ together with new union formations
Distribution of partner ages by age of female partner: This parameter is usually estimated from survey data such as SILC. It is assumed that these distributions will remain constant in the future.
Distribution of partner’s education by education level of female partner. This parameter is usually based on educational patterns of currently young couples, estimated from survey data such as SILC, It is assumed that these distributions will remain constant in the future.
The maximum proportion of men available as spouses by age group and education.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Women living with dependent children
actor_set Person asWomenWithChildren[educ_level3][child_agegr][moth_agegr][has_spouse]
filter is_alive&& is_resident&& in_projected_time&& sex == FEMALE && WITHIN(SPOUSE_AGE, integer_age) &&
children_in_family > 0 && !is_blocked_from_marriage;
//EN Women not living with dependent children
actor_set Person asWomenNoChildren[educ_level3][partnership_age][has_spouse]
filter is_alive&& is_resident&& in_projected_time&& sex == FEMALE && WITHIN(SPOUSE_AGE, integer_age) &&
children_in_family == 0 && !is_blocked_from_marriage;
//EN Potential male spouses by age and education
actor_set Person asAvailableMaleForPartnership[partnership_age][educ_level3]
filter is_alive&& is_resident&& in_projected_time&& sex == MALE && !has_spouse
&& !is_blocked_from_marriage && WITHIN(SPOUSE_AGE, integer_age);
//EN Potential male spouses by age
actor_set Person asAvailableMaleForPartnershipAgeOnly[partnership_age]
filter is_alive&& is_resident&& in_projected_time&& sex == MALE && !has_spouse
&& !is_blocked_from_marriage && WITHIN(SPOUSE_AGE, integer_age);
//EN Women in a partnership by own and partner's age
actor_set Person asFemaleInPartnershipByAgeAndPartnerAge[partnership_age][partnership_spouse_age]
filter is_alive&& is_resident&& in_projected_time&& sex == FEMALE && has_spouse && WITHIN(SPOUSE_AGE, integer_age);
//EN Men by age, education and partnership status
actor_set Person asMenAgeEducPartnership[partnership_age][educ_level3][has_spouse]
filter is_alive&& is_resident&& in_projected_time&& sex == MALE && WITHIN(SPOUSE_AGE, integer_age) && !is_blocked_from_marriage;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range SPOUSE_AGE{ 15, 105 }; //EN Age
range SPOUSE_AGE_GT65{ 65, 105 }; //EN Age
partition CHILD_AGEGR_PART { 1, 3, 6, 9, 12, 15 }; //EN Age of youngest child
partition MOTH_AGEGR_PART { 20, 25, 30, 35, 40 }; //EN Age of mother at last birth
partition SPOUSE_AGEGR_PART_GT65{ 70, 75, 80, 85, 90, 95, 100 }; //EN 5-year age groups 65 to 105
classification MOTH_AGEGR //EN Age group mothers at birth
{
MOA_20, //EN Below 20
MOA_25, //EN 20 to 24
MOA_30, //EN 25 to 19
MOA_35, //EN 30 to 34
MOA_40, //EN 35 to 39
MOA_40P //EN 40+
};
classification CHILD_AGEGR //EN Age group child
{
CHA_00, //EN 0
CHA_01, //EN 1 to 2
CHA_03, //EN 3 to 5
CHA_06, //EN 6 to 8
CHA_09, //EN 9 to 11
CHA_12, //EN 12 to 14
CHA_15 //EN 15 to 17
};
classification PARTNER_LIMIT_AGEGR //EN Age group
{
PLA_00, //EN Below 45
PLA_01, //EN 45-64
PLA_02 //EN 65+
};
classification PARTNERSHIPS_MODEL //EN Partnerships model selection
{
PAM_BASE, //EN Base model
PAM_UNIONS65PLUS //EN Model with new union formations from age 65
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG07_FemalePartnerships //EN Partnerships
{
SelectedPartnershipModel,
CalibratePartnershipsBeforeSimulation,
InUnionProbWithChildren,
InUnionProbNoChildren,
UnionFormation65Plus,
MaxAgePartnershipFormationAlignment,
MaxAgePartnershipDissolutionAlignment,
ProbStayWithMother,
PartnerAgeDistribution,
PartnerEducDistribution,
MaxMaleRatioInPartnership
};
parameters
{
//EN Max ratio of men in partnership
double MaxMaleRatioInPartnership[EDUC_LEVEL3][PARTNER_LIMIT_AGEGR];
//EN Partnerships model selection
PARTNERSHIPS_MODEL SelectedPartnershipModel;
//EN Calibrate partnerships before simulation
logical CalibratePartnershipsBeforeSimulation;
//EN Probability to be in a partnership - Females living with children
double InUnionProbWithChildren[EDUC_LEVEL3][CHILD_AGEGR][MOTH_AGEGR];
//EN Probability to be in a partnership - Females not living with children
double InUnionProbNoChildren[SPOUSE_AGE][EDUC_LEVEL3];
//EN Risk of new union formation for women aged 65+
double UnionFormation65Plus[SPOUSE_AGEGR_PART_GT65];
//EN Max age partnership formation alignment
double MaxAgePartnershipFormationAlignment;
//EN Max age partnership dissolution alignment
double MaxAgePartnershipDissolutionAlignment;
//EN Distribution of partner ages by age of female partner
double PartnerAgeDistribution[SPOUSE_AGE][SPOUSE_AGE];
//EN Distribution of partner characteristics by female characteristics
cumrate PartnerEducDistribution[EDUC_LEVEL3][EDUC_LEVEL3];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Age at last birth
double age_last_birth = (sex == FEMALE && children_in_family > 0) ?
integer_age - age_youngest_child_in_family : TIME_INFINITE;
//EN Age group of youngest child of women
int child_agegr_part = split(age_youngest_child_in_family, CHILD_AGEGR_PART);
//EN Age group at last birth
int moth_agegr_part = split(age_last_birth, MOTH_AGEGR_PART);
//EN Age group at last birth
MOTH_AGEGR moth_agegr = (moth_agegr_part == 0) ? MOA_20 :
(moth_agegr_part == 1) ? MOA_25 :
(moth_agegr_part == 2) ? MOA_30 :
(moth_agegr_part == 3) ? MOA_35 :
(moth_agegr_part == 4) ? MOA_40 : MOA_40P;
//EN Age group child
CHILD_AGEGR child_agegr = (child_agegr_part == 0) ? CHA_00 :
(child_agegr_part == 1) ? CHA_01 :
(child_agegr_part == 2) ? CHA_03 :
(child_agegr_part == 3) ? CHA_06 :
(child_agegr_part == 4) ? CHA_09 :
(child_agegr_part == 5) ? CHA_12 : CHA_15;
//EN Age
SPOUSE_AGE partnership_age = COERCE(SPOUSE_AGE, integer_age);
//EN Age
SPOUSE_AGE_GT65 partnership_age_gt65 = COERCE(SPOUSE_AGE_GT65, integer_age);
//EN Age of partner
SPOUSE_AGE partnership_spouse_age = (has_spouse) ? COERCE(SPOUSE_AGE, lSpouse->integer_age) : MAX(SPOUSE_AGE);
logical is_blocked_from_marriage = { FALSE }; //EN Blocked from marriage
logical FindSpouse(); //EN Find and link spouse
event timeUnionFormation65PlusEvent, UnionFormation65PlusEvent; //EN Union formation event for women 65+
};
actor Observer
{
logical partnership_calibration_flag = { FALSE }; //EN Partnerships calibrated
void UpdatePartnershipStatus(); //EN Update Female Partnership Status
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdatePartnershipStatus()
{
long nTarget;
if (observer_year >= MIN(SIM_YEAR))
{
// unblock all
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
ptrPerson->is_blocked_from_marriage = FALSE;
}
// Block men from marriage pool
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
for (int nAge = MIN(SPOUSE_AGE); nAge <= MAX(SPOUSE_AGE); nAge++)
{
double dMenInPartnership = (double)asMenAgeEducPartnership[RANGE_POS(SPOUSE_AGE, nAge)][nGroup][TRUE]->Count();
double dMenNotInPartnership = (double)asMenAgeEducPartnership[RANGE_POS(SPOUSE_AGE, nAge)][nGroup][FALSE]->Count();
int nIndex = 0;
if (nAge >= 45) nIndex = 1;
if (nAge >= 65) nIndex = 2;
double dMaxInPartnership = MaxMaleRatioInPartnership[nGroup][nIndex] * (dMenInPartnership + dMenNotInPartnership);
int dToBeBlocked = int(1.0 + dMenNotInPartnership - (dMaxInPartnership - dMenInPartnership));
while (dToBeBlocked > 0 && asMenAgeEducPartnership[RANGE_POS(SPOUSE_AGE, nAge)][nGroup][FALSE]->Count() > 0)
{
Person_ptr prMen = asMenAgeEducPartnership[RANGE_POS(SPOUSE_AGE, nAge)][nGroup][FALSE]->GetRandom(RandUniform(61));
prMen->is_blocked_from_marriage = TRUE;
dToBeBlocked = dToBeBlocked - 1;
}
}
}
// block married men to prevent being remarried by new 65+ remarriage event
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
if (ptrPerson->sex == MALE && ptrPerson->has_spouse && ptrPerson->age > 65) ptrPerson->is_blocked_from_marriage = TRUE;
}
// Women with children
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
for (int nChildAge = 0; nChildAge < SIZE(CHILD_AGEGR); nChildAge++)
{
for (int nMothAge = 0; nMothAge < SIZE(MOTH_AGEGR); nMothAge++)
{
long nGroupSize = asWomenWithChildren[nGroup][nChildAge][nMothAge][FALSE]->Count()
+ asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count();
nTarget = round(InUnionProbWithChildren[nGroup][nChildAge][nMothAge] * nGroupSize);
if (nTarget > asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count())
{
// move some into partnership until target met
while (nTarget > asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count() &&
asWomenWithChildren[nGroup][nChildAge][nMothAge][FALSE]->Count() > 0)
{
auto prFam = asWomenWithChildren[nGroup][nChildAge][nMothAge][FALSE]->GetRandom(RandUniform(4));
if (!prFam->FindSpouse()) prFam->is_blocked_from_marriage = TRUE;
}
}
else if (nTarget < asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count())
{
while (nTarget < asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count() &&
asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->Count() > 0)
{
auto prFam = asWomenWithChildren[nGroup][nChildAge][nMothAge][TRUE]->GetRandom(RandUniform(33));
prFam->DissolvePartnership();
}
}
}
}
}
//Targets for women without children in hh
for (int nGroup = 0; nGroup < SIZE(EDUC_LEVEL3); nGroup++)
{
for (int nAge = 0; nAge < SIZE(SPOUSE_AGE); nAge++)
{
nTarget = round(InUnionProbNoChildren[nAge][nGroup]
* (asWomenNoChildren[nGroup][nAge][FALSE]->Count() + asWomenNoChildren[nGroup][nAge][TRUE]->Count()));
if (nTarget > asWomenNoChildren[nGroup][nAge][TRUE]->Count() && ((MIN(SPOUSE_AGE) + nAge <= MaxAgePartnershipFormationAlignment) || (CalibratePartnershipsBeforeSimulation && !partnership_calibration_flag)))
{
// move some into marriage until target met
while (nTarget > asWomenNoChildren[nGroup][nAge][TRUE]->Count() &&
asWomenNoChildren[nGroup][nAge][FALSE]->Count() > 0)
{
auto prFam = asWomenNoChildren[nGroup][nAge][FALSE]->GetRandom(RandUniform(58));
if (!prFam->FindSpouse()) prFam->is_blocked_from_marriage = TRUE;
}
}
else if (nTarget < asWomenNoChildren[nGroup][nAge][TRUE]->Count() && ((MIN(SPOUSE_AGE) + nAge <= MaxAgePartnershipDissolutionAlignment) || (CalibratePartnershipsBeforeSimulation && !partnership_calibration_flag)))
{
while (nTarget < asWomenNoChildren[nGroup][nAge][TRUE]->Count() &&
asWomenNoChildren[nGroup][nAge][TRUE]->Count() > 0)
{
auto prFam = asWomenNoChildren[nGroup][nAge][TRUE]->GetRandom(RandUniform(39));
prFam->DissolvePartnership();
}
}
}
}
partnership_calibration_flag = TRUE;
}
}
logical Person::FindSpouse()
{
bool bFoundSpouse = FALSE;
double dExpectedPartners[SIZE(SPOUSE_AGE)];
double dObservedPartners[SIZE(SPOUSE_AGE)];
double dSumExpectedPartners = 0;
double dSumObservedPartners = 0;
double dGap = 0.0;
double dLargestGap = 0.0;
int nAgePartner;
int nEducPartner;
Person_ptr ptrSpouse = NULL;
// Partner age
// Totals in distributional tables
for (int nAge = 0; nAge < SIZE(SPOUSE_AGE); nAge++)
{
dExpectedPartners[nAge] = PartnerAgeDistribution[RANGE_POS(SPOUSE_AGE, integer_age)][nAge];
dObservedPartners[nAge] = asFemaleInPartnershipByAgeAndPartnerAge[RANGE_POS(SPOUSE_AGE, integer_age)][nAge]->Count();
dSumExpectedPartners = dSumExpectedPartners + dExpectedPartners[nAge];
dSumObservedPartners = dSumObservedPartners + dObservedPartners[nAge];
}
// Standardize distributions
for (int nAge = 0; nAge < SIZE(SPOUSE_AGE); nAge++)
{
if (dSumExpectedPartners == 0.0) dSumExpectedPartners = 1.0;
if (dSumObservedPartners == 0.0) dSumObservedPartners = 1.0;
dExpectedPartners[nAge] = dExpectedPartners[nAge] / dSumExpectedPartners;
dObservedPartners[nAge] = dObservedPartners[nAge] / dSumObservedPartners;
}
// Find age with largest gap comparing distributions
for (int nAge = 0; nAge < SIZE(SPOUSE_AGE); nAge++)
{
dGap = dExpectedPartners[nAge] - dObservedPartners[nAge];
int nAvailableSpouses = asAvailableMaleForPartnership[nAge][EL3_LOW]->Count()
+ asAvailableMaleForPartnership[nAge][EL3_MEDIUM]->Count()
+ asAvailableMaleForPartnership[nAge][EL3_HIGH]->Count();
if (dExpectedPartners[nAge] > 0.0 && dGap >= dLargestGap && nAvailableSpouses > 0)
{
dLargestGap = dGap;
bFoundSpouse = TRUE;
nAgePartner = MIN(SPOUSE_AGE) + nAge;
}
}
// Partner Education
int ecode = 0;
if (bFoundSpouse)
{
Lookup_PartnerEducDistribution(RandUniform(40), educ_level3, &nEducPartner);
// Look if somebody with desired characteristics is available
if (asAvailableMaleForPartnership[RANGE_POS(SPOUSE_AGE, nAgePartner)][nEducPartner]->Count() > 0)
{
ptrSpouse = asAvailableMaleForPartnership[RANGE_POS(SPOUSE_AGE, nAgePartner)][nEducPartner]->GetRandom(RandUniform(36));
}
// Else take somebody with target age only - this should be rarely the case
else if (asAvailableMaleForPartnershipAgeOnly[RANGE_POS(SPOUSE_AGE, nAgePartner)]->Count() > 0)
{
ptrSpouse = asAvailableMaleForPartnershipAgeOnly[RANGE_POS(SPOUSE_AGE, nAgePartner)]->GetRandom(RandUniform(35));
}
else bFoundSpouse = FALSE;
}
// Start the partnership
if (bFoundSpouse) StartPartnership(ptrSpouse);
// Return success
return bFoundSpouse;
}
TIME Person::timeUnionFormation65PlusEvent()
{
TIME dEventTime = TIME_INFINITE;
if (SelectedPartnershipModel == PAM_UNIONS65PLUS)
{
double dUnionFormationHazard = 0.0;
// Determine if person is at risk
if (is_alive && is_resident && in_projected_time && sex == FEMALE && WITHIN(SPOUSE_AGE_GT65, integer_age) && !has_spouse)
{
// Determine the hazard
dUnionFormationHazard = UnionFormation65Plus[SPLIT(integer_age, SPOUSE_AGEGR_PART_GT65)];
//Determine event time
if (dUnionFormationHazard > 0.0) dEventTime = WAIT(-log(RandUniform(60)) / dUnionFormationHazard);
}
}
return dEventTime;
}
void Person::UnionFormation65PlusEvent()
{
// Find a spouse
FindSpouse();
}
Family.mpp
The family module manages and maintains family relationships. MicroWELT is based on the concept of nuclear families, where a family consists of a household head, a spouse (if present) and dependent children. Accordingly, each person has a family role: head, spouse or child. The female spouse is considered to be the head of the family. The model distinguishes between four types of family link:
Links between spouses, maintained over the simulation and dissolved upon union dissolution;
Links to ‘first’ parents (biological or the first known mothers and fathers, as observed in the starting population) are maintained for as long as the parents are alive.
Links to the ‘most recent’ parents (e.g. stepparents). These links are maintained as long as the ‘most recent’ parents are alive.
Links to cohabiting parents: these are the ‘most recent’ parents as long as children stay at home. The link is dissolved when the children move out.
The module contains a collection of functions that handle links at specific life history events:
Death: If there is no spouse but children in the family, each child checks whether it has a biological mother or father or a grandmother or grandfather still alive, in which case the child links to a new guardian (and - if present - to the spouse of this new social parent).
Partnerip formation: Partners are linked and form a new nuclear family. All children update their family links.
Dissolution of the union: Before the union between partners is dissolved, all children have to choose with whom they want to live. The choice is modelled by a set of simple rules and a probability to stay with the mother. If only one of the two parents is a biological parent, the children choose to stay with the biological parent. Otherwise, the choice is random, depending on the probability parameter.
Initial family ties of persons in the initial population: Links between spouses and to mothers and fathers; the observed parents are assumed to be the ‘first’ as well as the ‘recent’ parents.
Parameters:
Probability of living with mother after dissolution of parental partnership
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN All resident family heads
actor_set Person asAllResidentHeads filter is_alive&& is_resident&& family_role == FR_HEAD;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Family Links
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
link Person.lFirstFather Person.mlFirstFatherChildren[]; //EN First father - children
link Person.lFirstMother Person.mlFirstMotherChildren[]; //EN First mother - children
link Person.lRecentFather Person.mlRecentFatherChildren[]; //EN Most recent father - children
link Person.lRecentMother Person.mlRecentMotherChildren[]; //EN Most recent mother - children
link Person.lCurrentFather Person.mlCurrentFatherChildren[]; //EN Current father - children in family
link Person.lCurrentMother Person.mlCurrentMotherChildren[]; //EN Current mother - children in family
link Person.lSpouse; //EN Link to spouse
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification FAMILY_ROLE //EN Family role
{
FR_HEAD, //EN Head
FR_SPOUSE, //EN Spouse
FR_CHILD //EN Child
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameters
{
//EN Probability to stay with mother after partnership disslolution
double ProbStayWithMother;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
// Simple states
logical lives_with_father = { FALSE }; //EN Lives with father
logical lives_with_mother = { FALSE }; //EN Lives with mother
// Derived states
//EN Lives with parents
logical lives_with_parents = (lives_with_father || lives_with_mother) ? TRUE : FALSE;
//EN Lives with two parents
logical lives_with_two_parents = (lives_with_father && lives_with_mother) ? TRUE : FALSE;
//EN Has spouse
logical has_spouse = (lSpouse) ? TRUE : FALSE;
//EN Family role
FAMILY_ROLE family_role = (lSpouse && sex == MALE) ? FR_SPOUSE :
(lives_with_parents) ? FR_CHILD : FR_HEAD;
//EN Children in Family
short children_in_family = (sex == FEMALE) ?
sum_over(mlRecentMotherChildren, lives_with_mother) :
sum_over(mlRecentFatherChildren, lives_with_father);
//EN Time of birth oldest child in family
double tob_oldest_child_in_family =
(sex == FEMALE && children_in_family > 0) ? double(min_over(mlRecentMotherChildren, time_of_birth)) :
(sex == MALE && children_in_family > 0) ? double(min_over(mlRecentFatherChildren, time_of_birth)) : double(TIME_INFINITE);
//EN Youngest child in family
int age_youngest_child_in_family =
(sex == FEMALE && children_in_family > 0) ? double(min_over(mlRecentMotherChildren, integer_age)) :
(sex == MALE && children_in_family > 0) ? double(min_over(mlRecentFatherChildren, integer_age)) : TIME_INFINITE;
// Functions
void LinkToFamilyWhenSetAlive(); //EN Link family members
void StartPartnership(Person_ptr ptrSpouse); //EN Start partnership
void DissolvePartnership(); //EN Dissolve partnership
void MaintainLinksAtDeath(); //EN Maintain links at death
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::StartPartnership(Person_ptr ptrSpouse)
{
// link to partner
lSpouse = ptrSpouse;
// Partners children in family now live also with this woman
if (lSpouse->children_in_family > 0)
{
int nIndex = 0;
auto prChild = lSpouse->mlRecentFatherChildren->GetNext(0, &nIndex);
while (prChild)
{
if (prChild->lives_with_father)
{
prChild->lRecentMother = this;
prChild->lives_with_mother = TRUE; prChild->lCurrentMother = prChild->lRecentMother;
}
prChild = lSpouse->mlRecentFatherChildren->GetNext(nIndex + 1, &nIndex);
}
}
// Own children in family now live also with this spouse
if (children_in_family > 0)
{
int nIndex;
auto prChild = mlRecentMotherChildren->GetNext(0, &nIndex);
while (prChild)
{
if (prChild->lives_with_mother)
{
// add only new children
if (!prChild->lRecentFather || (prChild->lRecentFather && (Person_ptr)prChild->lRecentFather != (Person_ptr)lSpouse))
{
prChild->lRecentFather = lSpouse;
prChild->lives_with_father = TRUE; prChild->lCurrentFather = prChild->lRecentFather;
}
}
prChild = mlRecentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
void Person::DissolvePartnership()
{
// Children deciding with whom to stay
if (children_in_family > 0)
{
int nIndex = 0;
bool bStayWithMother = (RandUniform(34) < ProbStayWithMother);
// Child decides to stay with mother
Person_ptr prChild = mlRecentMotherChildren->GetNext(0, &nIndex);
while (prChild)
{
if (
// only process children still living with their social mother
bStayWithMother && prChild->lives_with_mother
// and only cases where this mother is the biological mother
&& ((prChild->lFirstMother && (Person_ptr)prChild->lFirstMother == (Person_ptr)this)
// or the child does not know her biological father
|| !prChild->lFirstFather
// or the biologicl father is not the current social father
|| (prChild->lFirstFather && (Person_ptr)prChild->lFirstFather != (Person_ptr)lSpouse))
)
{
prChild->lives_with_father = FALSE; prChild->lCurrentFather = NULL;
}
prChild = mlRecentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
// Those staying with social father stop living with their social mother
if (lSpouse->children_in_family > 0)
{
Person_ptr prChild = lSpouse->mlRecentFatherChildren->GetNext(0, &nIndex);
while (prChild)
{
if (prChild->lives_with_father)
{
prChild->lives_with_mother = FALSE;
prChild->lCurrentMother = NULL;
}
prChild = lSpouse->mlRecentFatherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
// Dissolve link to spouse
lSpouse = NULL;
}
void Person::MaintainLinksAtDeath()
{
int nIndex;
// Deceased person was a lone parent
if (!lSpouse && children_in_family > 0)
{
//try to find a guardian for children
Person_ptr prGuardian;
auto prChild = (sex == FEMALE) ? mlRecentMotherChildren->GetNext(0, &nIndex) : mlRecentFatherChildren->GetNext(0, &nIndex);
while (prChild)
{
if ((sex == FEMALE && prChild->lives_with_mother) || (sex == MALE && prChild->lives_with_father))
{
// Not living with deceased parent anymore
if (sex == FEMALE && prChild->lives_with_mother)
{
prChild->lives_with_mother = FALSE;
prChild->lCurrentMother = NULL;
}
else if (sex == MALE && prChild->lives_with_father)
{
prChild->lives_with_father = FALSE;
prChild->lCurrentFather = NULL;
}
// Try to identify a potential guardian
if (sex == FEMALE && prChild->lFirstFather) prGuardian = prChild->lFirstFather; // own biological father
else if (sex == MALE && prChild->lFirstMother) prGuardian = prChild->lFirstMother; // own biological mother
else if (lFirstMother) prGuardian = lFirstMother; // grandmother
else if (lFirstFather) prGuardian = lFirstFather; // grandfather
else prGuardian = NULL; // nobody found
// If somebody found link to this person - and, if available - the spouse of this person
if (prGuardian && prGuardian->sex == MALE)
{
prChild->lRecentFather = prGuardian;
prChild->lives_with_father = TRUE; prChild->lCurrentFather = prChild->lRecentFather;
if (prGuardian->lSpouse)
{
prChild->lRecentMother = prGuardian->lSpouse;
prChild->lives_with_mother = TRUE; prChild->lCurrentMother = prChild->lRecentMother;
}
}
else if (prGuardian && prGuardian->sex == FEMALE)
{
prChild->lRecentMother = prGuardian;
prChild->lives_with_mother = TRUE; prChild->lCurrentMother = prChild->lRecentMother;
if (prGuardian->lSpouse)
{
prChild->lRecentFather = prGuardian->lSpouse;
prChild->lives_with_father = TRUE; prChild->lCurrentFather = prChild->lRecentFather;
}
}
}
// set pointer to next child
prChild = (sex == FEMALE) ? mlRecentMotherChildren->GetNext(nIndex + 1, &nIndex) : mlRecentFatherChildren->GetNext(nIndex + 1, &nIndex);
}
}
// Deceased person was a parent in a partnership
else if (children_in_family > 0)
{
auto prChild = (sex == FEMALE) ? mlRecentMotherChildren->GetNext(0, &nIndex) : mlRecentFatherChildren->GetNext(0, &nIndex);
while (prChild)
{
// Not living with deceased parent anymore
if (sex == FEMALE && prChild->lives_with_mother)
{
prChild->lives_with_mother = FALSE;
prChild->lCurrentMother = NULL;
}
else if (sex == MALE && prChild->lives_with_father)
{
prChild->lives_with_father = FALSE;
prChild->lCurrentFather = NULL;
}
// set pointer to next child
prChild = (sex == FEMALE) ? mlRecentMotherChildren->GetNext(nIndex + 1, &nIndex) : mlRecentFatherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
void Person::LinkToFamilyWhenSetAlive()
{
// Family members from starting population
if (creation_type == CT_START && ptr_creator)
{
// Spouse
if (family_role_start == FR_HEAD || family_role_start == FR_SPOUSE) lSpouse = ptr_creator;
// Child
else if (family_role_start == FR_CHILD && ptr_creator->sex == MALE)
{
lRecentFather = ptr_creator;
lFirstFather = ptr_creator;
lives_with_father = TRUE; lCurrentFather = lRecentFather;
if (lFirstFather->lSpouse)
{
lRecentMother = lFirstFather->lSpouse;
lFirstMother = lFirstFather->lSpouse;
lives_with_mother = TRUE; lCurrentMother = lRecentMother;
}
}
else if (family_role_start == FR_CHILD && ptr_creator->sex == FEMALE)
{
lRecentMother = ptr_creator;
lFirstMother = ptr_creator;
lives_with_mother = TRUE; lCurrentMother = lRecentMother;
if (lFirstMother->lSpouse)
{
lRecentFather = lFirstMother->lSpouse;
lFirstFather = lFirstMother->lSpouse;
lives_with_father = TRUE; lCurrentFather = lRecentFather;
}
}
}
// Person born in simulation
else if (creation_type == CT_CHILD)
{
lRecentMother = ptr_creator;
lFirstMother = ptr_creator;
lives_with_mother = TRUE; lCurrentMother = lRecentMother;
if (lFirstMother->lSpouse)
{
lRecentFather = lFirstMother->lSpouse;
lFirstFather = lFirstMother->lSpouse;
lives_with_father = TRUE; lCurrentFather = lRecentFather;
// Fathers parity
if (lRecentFather->number_children2 == NC2_0) lRecentFather->number_children2 = NC2_1;
else if (lRecentFather->number_children2 == NC2_1) lRecentFather->number_children2 = NC2_2P;
}
}
}
MaleFamily.mpp
The modelling of family formation and dissolution in microWELT is female-driven, with males selected by assortative mating, taking into account age and education. Accordingly, male parity is updated with the birth of children by a partner. As only current partnerships and dependent children in the household can be observed in the starting population, information on other children is missing. This is taken into account by the following assumptions and approaches:
For men in the starting population living in a partnership, they are assumed to have the same number of children as their partner. This includes the imputed information on the number of children modelled in the fertility module (children who have already left home).
Male childlessness by education is modelled by a cohort parameter. To achieve this target childlessness, at the start of the simulation a proportion of currently childless men are flagged as never becoming fathers. If they become parents during the simulation, the flag is passed on to an unflagged childless man of the same age and education. During the simulation, flags are set at birth.
Single men aged 65+ at the start of the simulation who are not flagged as never becoming fathers are assumed to be fathers. Similarly, men who are not flagged as never becoming fathers when they turn 65 in the simulation are assumed to be fathers. This addresses a potential mismatch between the female-driven family dynamics and the male childlessness parameter and should affect only few people in the simulation. (No such correction is made if too many men are fathers at age 65 relative to the childlessness parameter). In all these cases of imputed fatherhood, the number of children (one versus 2 and more) is randomly decided on the basis of a parameter for the parity progression to the second child.
Parameters:
Male cohort childlessness by education
Male parity progression to second child used for imputation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Men of startpop by year of birth education and fatherhood
actor_set Person asMenStartpopYobEducFather[yob_past][educ_level3][never_father]
filter is_alive && creation_type == CT_START && sex == MALE;
//EN Men by year of birth and education who can be flagged as never father
actor_set Person asMenWhoCanBeFlaggedNeverFather[year_of_birth][educ_level3]
filter is_alive && is_resident && sex == MALE && !never_father && !known_father;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_MALEFAMILY //EN Male family
{
MaleChildlessness,
MaleParityProgressionToSecondChild
};
parameters
{
double MaleChildlessness[ALL_YEAR][EDUC_LEVEL3]; //EN Male cohort childlessness
double MaleParityProgressionToSecondChild; //EN Male parity progression 2nd child for imputation
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical never_father = { FALSE }; //EN Destined never becoming father
logical known_father = { FALSE }; //EN Known father
YOB_PAST yob_past = COERCE(YOB_PAST, year_of_birth); //EN Year of birth
void setMaleLifetimeChildlessnessAtBirth(); //EN Assign lifetime childlessness at birth
void setMissingMaleParityAt65(); //EN Assign male parity at 65 if not observed
event timeChangeNeverFatherFlagEvent, ChangeNeverFatherFlagEvent; //EN Change never father flagging
};
actor Observer
{
void ImputeMaleParity(); //EN Impute male parity at start of simulation
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeChangeNeverFatherFlagEvent()
{
if (in_projected_time && is_resident && sex == MALE && never_father && children_in_family > 0) return WAIT(0.0);
else return TIME_INFINITE;
}
void Person::ChangeNeverFatherFlagEvent()
{
// Update states
never_father = FALSE;
known_father = TRUE;
// Find anotherone to overtake never-father flag
if (asMenWhoCanBeFlaggedNeverFather[RANGE_POS(ALL_YEAR, year_of_birth)][educ_level3]->Count() > 0)
{
Person_ptr ptrPerson = asMenWhoCanBeFlaggedNeverFather[RANGE_POS(ALL_YEAR, year_of_birth)][educ_level3]->GetRandom(RandUniform(51));
ptrPerson->never_father = TRUE;
}
}
void Person::setMaleLifetimeChildlessnessAtBirth()
{
if (RandUniform(24) < MaleChildlessness[RANGE_POS(ALL_YEAR, year_of_birth)][educ_level3]) never_father = TRUE;
else never_father = FALSE;
}
void Observer::ImputeMaleParity()
{
// Check own family links and inherit parity of female spouse
int nPersons = asAllPerson->Count();
for (int nIndex = 0; nIndex < nPersons; nIndex++)
{
Person_ptr prPerson = asAllPerson->Item(nIndex);
if (prPerson->sex == MALE && prPerson->is_resident)
{
if (prPerson->has_spouse) prPerson->number_children2 = prPerson->lSpouse->number_children2;
if (prPerson->number_children2 != NC2_0) prPerson->known_father = TRUE;
}
}
// Set flag for never father to meet external targets
for (int nYob = 0; nYob < SIZE(YOB_PAST); nYob++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
int nNeverFather = asMenStartpopYobEducFather[nYob][nEduc][TRUE]->Count();
int nEverFather = asMenStartpopYobEducFather[nYob][nEduc][FALSE]->Count();
int nExpectedNeverFather = int((nNeverFather + nEverFather) * MaleChildlessness[nYob][nEduc]);
while (nExpectedNeverFather > nNeverFather && asMenWhoCanBeFlaggedNeverFather[nYob][nEduc]->Count() > 0)
{
Person_ptr ptrPerson = asMenWhoCanBeFlaggedNeverFather[nYob][nEduc]->GetRandom(RandUniform(50));
ptrPerson->never_father = TRUE;
nNeverFather++;
}
}
}
// For men 65+ who are not in a partnership, use never_father flag to assign family size
int nPerson = asAllPerson->Count();
for (int nJ = 0; nJ < nPerson; nJ++)
{
Person_ptr paPerson = asAllPerson->Item(nJ);
if (paPerson->is_resident && paPerson->sex == MALE && paPerson->integer_age >= 65 && !paPerson->has_spouse && !paPerson->never_father)
{
if (RandUniform(52) < MaleParityProgressionToSecondChild) paPerson->number_children2 = NC2_2P;
else paPerson->number_children2 = NC2_1;
paPerson->known_father = TRUE;
}
}
}
void Person::setMissingMaleParityAt65()
{
if (!known_father && !never_father)
{
if (RandUniform(53) < MaleParityProgressionToSecondChild) number_children2 = NC2_2P;
else number_children2 = NC2_1;
known_father = TRUE;
}
}
LeavingHome.mpp
The Leaving Home module deals with the economic emancipation of children. In the current model, children leave home at the age of 18, when they form their own nuclear family due to union formation a/o parenthood.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
event timeLeavingHomeEvent, LeavingHomeEvent; //EN Leaving home
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeLeavingHomeEvent()
{
if ((lives_with_parents || is_unattended ) && (integer_age >= 18 || has_spouse || children_in_family > 0)) return WAIT(0.0);
else return TIME_INFINITE;
}
void Person::LeavingHomeEvent()
{
lives_with_mother = FALSE; lCurrentMother = NULL;
lives_with_father = FALSE; lCurrentFather = NULL;
is_unattended = FALSE;
}
ParentsEducation.mpp
Parental education is used to model the intergenerational transmission of education. It refers to the highest educational attainment of both parents. Parental education can be at one of three levels - low (ISCED 2 and below), medium (ISCED 3 and 4), high (ISCED 5 and above) - or unknown. Modelling the educational ‘destiny’ of a child requires knowledge of the educational composition of the parents of the child’s birth cohort, as odds ratios based on parental education need to be taken into account, while at the same time meeting outcome targets regarding the educational distribution of the child’s birth cohort.
Parental education is initialised at birth. Information on the current educational distribution of parents is provided by an observer function that tracks and allows retrieval of this distribution information based on the past 12 months, updated at each birth.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification PARENTS_EDUC //EN Parents education
{
PED_LOW, //EN Low
PED_MEDIUM, //EN Medium
PED_HIGH, //EN High
PED_UNKNOWN //EN Unknown
};
classification PARENTS_EDUC3 //EN Parents education
{
PE3_LOW, //EN Low
PE3_MEDIUM, //EN Medium
PE3_HIGH //EN High
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
PARENTS_EDUC parents_educ = { PED_UNKNOWN }; //EN Parents education
void setParentsEducAtBirth(); //EN Set parents education
};
actor Observer
{
int parents_educ_array[ALL_MONTH][PARENTS_EDUC]; //EN Array of parents education distribution
void doInitParentsEducArray(); //EN Initialise parents educatipn array
void doAddBirthToEducArray(PARENTS_EDUC cEduc); //EN Add a birth to the array
double getShareParentsEduc(PARENTS_EDUC cEduc); //EN Share of parents with education cEduc
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::doInitParentsEducArray()
{
for (int nMonth = 0; nMonth < SIZE(ALL_MONTH); nMonth++)
{
for (int nEduc = 0; nEduc < SIZE(PARENTS_EDUC); nEduc++)
{
parents_educ_array[nMonth][nEduc] = 0;
}
}
};
void Observer::doAddBirthToEducArray(PARENTS_EDUC cEduc)
{
int nMonth = int((time - MIN(ALL_YEAR)) * 12.0);
parents_educ_array[nMonth][cEduc] = parents_educ_array[nMonth][cEduc] + 1;
}
double Observer::getShareParentsEduc(PARENTS_EDUC cEduc)
{
double dReturnValue = 0.0;
double dSelected = 0.0;
double dAll = 0.0;
int nMonth = int((time - MIN(ALL_YEAR)) * 12.0);
for (int nPeriod = nMonth - 12; nPeriod < nMonth && nPeriod >= 0; nPeriod++)
{
for (int nEduc = 0; nEduc < SIZE(PARENTS_EDUC); nEduc++)
{
if (nEduc == (int)cEduc) dSelected = dSelected + parents_educ_array[nPeriod][cEduc];
if (cEduc == PED_UNKNOWN || (PARENTS_EDUC)nEduc != PED_UNKNOWN)
{
dAll = dAll + parents_educ_array[nPeriod][(PARENTS_EDUC)nEduc];
}
}
}
if (dAll > 0.0) dReturnValue = dSelected / dAll;
return dReturnValue;
}
void Person::setParentsEducAtBirth()
{
// set parents education
int nEduc = 0;
if (lFirstFather) nEduc = (int)lFirstFather->educ_level3;
if (lFirstMother)
{
if ((int)lFirstMother->educ_level3 > nEduc) nEduc = lFirstMother->educ_level3;
}
if (lFirstFather || lFirstMother) parents_educ = PARENTS_EDUC(nEduc);
else parents_educ = PED_UNKNOWN;
// store in matrix for distributional information
if (creation_type == CT_START || creation_type == CT_CHILD)
{
lObserver->doAddBirthToEducArray(parents_educ);
}
}
Education.mpp
The education module decides the highest education attained by a person accounting for gender, year of birth, and parents’ education. The individual educational “destiny” is decided at birth. Gender and cohort specific outcome distributions can be specified by parameters. In addition, the model allows the specification of relative differences in educational attainment by parental education (intergenerational transmission of education, parameterised by odds ratios). In this way, for a given cohort’s educational distribution, parental background is taken into account when deciding who will receive which education. This alignment to cohort targets can also be switched off from a given point in time, after which educational change is driven entirely by the changing educational composition of the parents’ generation. For individuals in the initial population, the educational information on attendance and attainment from the starting population file is respected:
Older cohorts (born before 1990; the cut-off is set in the Context module) retain the same education as in the starting population.
Younger cohorts (born after 2000; the cut-off is set in the Context module) have their educational trajectories (re)assigned on the basis of the model parameters.
For intermediate cohorts, educational information from the starting population (school attendance, current highest level of education) is used, but higher levels of education can still be achieved by those who are enrolled in education. The model attempts to simultaneously respect the information from the starting population and meet the cohort targets set in the parameters. This is achieved through a combination of sampling and storage/retrieval of educational attainment. If a sampled education does not match the individual’s starting population information, it is stored in an array maintained by the observer and sampling is repeated. If the array is not empty, persons first check whether it contains an educational outcome that matches the individual characteristics of the starting population record.
Within the simulation, information on educational outcome targets and relative differences by parental education, together with the educational composition of parents, is used to determine individual progression rates consistent with the targets. This is achieved by numerical simulation (binary search for base odds which, when combined with odds ratios by parental education, give the target probabilities for the parental education distribution at that point in time). The distribution of parental education is derived within the simulation on the basis of births in the last 12 months. The information on births by parental education and month is maintained by the observer.
Parameters:
The distribution of educational attainment (4 levels) by cohort and gender. The levels are ISCED 2 or lower, ISCED 3, ISCED 4, and ISCED 5 or higher.
Odds ratios by sex and parental education (3 levels; ISCED 3 and 4 are combined) for each transition between school levels.
The (first) year from which educational attainment by gender and parental education is fixed. This parameter makes it possible to switch off the adjustment of outcomes to the distribution of outcomes and to model the change in education entirely as a result of the changing composition of parental education.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification EDUC_LEVEL4 //EN Education level
{
EL4_ISCED2, //EN ISCED 2-
EL4_ISCED3, //EN ISCED 3
EL4_ISCED4, //EN ISCED 4
EL4_ISCED5 //EN ISCED 5+
};
classification EDUC_LEVEL3 //EN Education level
{
EL3_LOW, //EN Low
EL3_MEDIUM, //EN Medium
EL3_HIGH //EN High
};
classification EDUC_TRANS //EN Education transitions
{
ETR_ISCED3, //EN ISCED2 -> ISCED3
ETR_ISCED4, //EN ISCED3 -> ISCED4
ETR_ISCED5 //EN ISCED4 -> ISCED5
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_EDUCATION //EN Education
{
EducDistributionPara, EducOdds, FrozenEducCohort
};
parameters
{
double EducDistributionPara[SEX][EDUC_YOB][EDUC_LEVEL4]; //EN Education distribution
double EducOdds[SEX][PARENTS_EDUC3][EDUC_TRANS]; //EN Odds ratios education transitions
int FrozenEducCohort; //EN Last aligned education cohort
model_generated cumrate EducDistribution[SEX][EDUC_YOB][EDUC_LEVEL4]; //EN Education distribution
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
EDUC_LEVEL4 educ_level4 = { EL4_ISCED2 }; //EN Education level
EDUC_LEVEL4 educ_start = { EL4_ISCED2 }; //EN Education level in starting population
logical in_educ_start = { FALSE }; //EN In education in starting population
//EN Education level
EDUC_LEVEL3 educ_level3 = (educ_level4 == EL4_ISCED2) ? EL3_LOW :
(educ_level4 == EL4_ISCED5) ? EL3_HIGH : EL3_MEDIUM;
void setEducAtBirth(); //EN Set education at birth
EDUC_LEVEL4 getEducLevel(PARENTS_EDUC cParEduc); //EN Sample an education level
};
actor Observer
{
int educ_stack[EDUC_LEVEL4]; //EN Education stack
double frozen_educ_transition_rates[SEX][PARENTS_EDUC3][EDUC_TRANS]; //EN Education transition rates
logical educ_rates_frozen = { FALSE }; //EN Education transition rates are frozen
void resetEducStack(); //EN Reset education stack
void doAddEducToStack(EDUC_LEVEL4 cEduc); //EN Add to the education stack
bool getEducFromStack(EDUC_LEVEL4 cEduc); //EN Get education from stack
EDUC_LEVEL4 GetMostPopulatedEducFromStack(EDUC_LEVEL4 cEduc); //EN Return and decrement most populated possible education
bool hasThisOrHigherEducInStack(EDUC_LEVEL4 cEduc); //EN This or higher education in stack
double AdjustedProbability(double dProb, double dLogOdd, double dAdj); //EN Probability adjustment
event timeFreezeEducTransRatesEvent, FreezeEducTransRatesEvent; //EN Freeze education transition rates
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool Observer::getEducFromStack(EDUC_LEVEL4 cEduc)
{
bool bEducAvailable = FALSE;
if (educ_stack[cEduc] > 0)
{
educ_stack[cEduc] = educ_stack[cEduc] - 1;
bEducAvailable = TRUE;
}
return bEducAvailable;
}
bool Observer::hasThisOrHigherEducInStack(EDUC_LEVEL4 cEduc)
{
bool bEducAvailable = FALSE;
for (int nIndex = int(cEduc); nIndex < SIZE(EDUC_LEVEL4); nIndex++)
{
if (educ_stack[(EDUC_LEVEL4)nIndex] > 0) bEducAvailable = TRUE;
}
return bEducAvailable;
}
EDUC_LEVEL4 Observer::GetMostPopulatedEducFromStack(EDUC_LEVEL4 cEduc)
{
EDUC_LEVEL4 cReturnLevel = cEduc;
int nCount = 0;
for (int nIndex = int(cEduc); nIndex < SIZE(EDUC_LEVEL4); nIndex++)
{
if (educ_stack[(EDUC_LEVEL4)nIndex] > nCount)
{
nCount = educ_stack[(EDUC_LEVEL4)nIndex];
cReturnLevel = (EDUC_LEVEL4)nIndex;
}
}
educ_stack[cReturnLevel] = educ_stack[cReturnLevel] - 1;
return cReturnLevel;
}
void Observer::resetEducStack()
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
educ_stack[nEduc] = 0;
}
}
void Observer::doAddEducToStack(EDUC_LEVEL4 cEduc)
{
educ_stack[cEduc] = educ_stack[cEduc] + 1;
}
void Person::setEducAtBirth()
{
// Use info from starting population
if (creation_type == CT_START && year_of_birth < MIN(EDUC_YOB))
{
educ_level4 = educ_start;
}
// Cohorts which know parents education, (re)assign education (starting population info ignored)
else if ((creation_type == CT_START && year_of_birth > MAX(EDUC_IMPUTE)) || creation_type == CT_CHILD)
{
educ_level4 = getEducLevel(parents_educ);
}
// In-between cohorts from starting population, current school attendance or highest education reached to be respected
else if (creation_type == CT_START)
{
bool bIsSet = FALSE;
int nMaxIter = 100;
while (!bIsSet && nMaxIter > 0)
{
nMaxIter--;
// If not attending school or highest possibel level attained, startpop education is final fate
if (!in_educ_start || (int)educ_start == SIZE(EDUC_LEVEL4)-1)
{
if (lObserver->getEducFromStack(educ_start))
{
educ_level4 = educ_start;
bIsSet = TRUE;
}
else lObserver->doAddEducToStack(getEducLevel(PED_UNKNOWN));
}
// Education is at least as high as observed in starting population
else
{
if (lObserver->hasThisOrHigherEducInStack(educ_start))
{
educ_level4 = lObserver->GetMostPopulatedEducFromStack(educ_start);
bIsSet = TRUE;
}
else lObserver->doAddEducToStack(getEducLevel(PED_UNKNOWN));
}
}
if (!bIsSet) // this should happen only rarely
{
educ_level4 = getEducLevel(PED_UNKNOWN);
}
}
}
EDUC_LEVEL4 Person::getEducLevel(PARENTS_EDUC cParEduc)
{
EDUC_LEVEL4 cEducReturnValue = EL4_ISCED2;
int nEduc = 0;
// If parents education unknown just sample education
if (cParEduc == PED_UNKNOWN)
{
Lookup_EducDistribution(RandUniform(3), (int)sex, RANGE_POS(EDUC_YOB, (int)year_of_birth), &nEduc);
cEducReturnValue = (EDUC_LEVEL4)nEduc;
}
// If rates are frozen (no alignment to targets) use frozen rates
else if (lObserver->educ_rates_frozen)
{
bool bDoProgress = TRUE;
for (int nTransition = 0; nTransition < SIZE(EDUC_TRANS) && bDoProgress; nTransition++)
{
if (RandUniform(8) < lObserver->frozen_educ_transition_rates[sex][(PARENTS_EDUC3)int(cParEduc)][(EDUC_TRANS)nTransition]) nEduc++;
else bDoProgress = FALSE;
}
cEducReturnValue = (EDUC_LEVEL4)nEduc;
}
// find tranition rates which align to aggregate outcomes
else
{
///////////////////////////////
// First educational transition
// Distribution of parents education
double dParGroupShare[SIZE(PARENTS_EDUC3)];
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = lObserver->getShareParentsEduc((PARENTS_EDUC)nParGroup);
}
double dFactor1 = 1.0;
double dTargetProb = 1.0 - EducDistributionPara[sex][RANGE_POS(EDUC_YOB, year_of_birth)][EL4_ISCED2];
// Find probability based on odds and aligned to totals
if (dTargetProb == 1.0) cEducReturnValue = EL4_ISCED3;
else if (dTargetProb == 0.0) cEducReturnValue = EL4_ISCED2;
else
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][nParGroup][ETR_ISCED3]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Check if individual transits to next level
double dIndividualProbability = lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][cParEduc][ETR_ISCED3]), dFactor1);
if (RandUniform(5) < dIndividualProbability) cEducReturnValue = EL4_ISCED3;
else cEducReturnValue = EL4_ISCED2;
}
// Continue to next transition?
if (cEducReturnValue == EL4_ISCED3)
{
///////////////////////////////
// Second educational transition
// Update distribution of parents education
double dSumShares = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] * lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][nParGroup][ETR_ISCED3]), dFactor1);
dSumShares = dSumShares + dParGroupShare[nParGroup];
}
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] / dSumShares;
}
// Find probability based on odds and aligned to totals
dTargetProb = (dTargetProb - EducDistributionPara[sex][RANGE_POS(EDUC_YOB, year_of_birth)][EL4_ISCED3]) / dTargetProb;
dFactor1 = 1.0;
if (dTargetProb == 1.0) cEducReturnValue = EL4_ISCED4;
else if (dTargetProb == 0.0) cEducReturnValue = EL4_ISCED3;
else
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][nParGroup][ETR_ISCED4]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Check if individual transits to next level
double dIndividualProbability = lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][cParEduc][ETR_ISCED4]), dFactor1);
if (RandUniform(6) < dIndividualProbability) cEducReturnValue = EL4_ISCED4;
else cEducReturnValue = EL4_ISCED3;
}
// Continue to next transition?
if (cEducReturnValue == EL4_ISCED4)
{
///////////////////////////////
// Third educational transition
// Update distribution of parents education
double dSumShares = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] * lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][nParGroup][ETR_ISCED4]), dFactor1);
dSumShares = dSumShares + dParGroupShare[nParGroup];
}
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] / dSumShares;
}
// Find probability based on odds and aligned to totals
dTargetProb = EducDistributionPara[sex][RANGE_POS(EDUC_YOB, year_of_birth)][EL4_ISCED5] /
( EducDistributionPara[sex][RANGE_POS(EDUC_YOB, year_of_birth)][EL4_ISCED4]
+ EducDistributionPara[sex][RANGE_POS(EDUC_YOB, year_of_birth)][EL4_ISCED5]);
dFactor1 = 1.0;
if (dTargetProb == 1.0) cEducReturnValue = EL4_ISCED5;
else if (dTargetProb == 0.0) cEducReturnValue = EL4_ISCED4;
else
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][nParGroup][ETR_ISCED5]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Check if individual transits to next level
double dIndividualProbability = lObserver->AdjustedProbability(dTargetProb, log(EducOdds[sex][cParEduc][ETR_ISCED5]), dFactor1);
if (RandUniform(7) < dIndividualProbability) cEducReturnValue = EL4_ISCED5;
else cEducReturnValue = EL4_ISCED4;
}
}
}
}
// Return education
return cEducReturnValue;
}
double Observer::AdjustedProbability(double dProb, double dLogOdd, double dAdj)
{
if (dProb <= 0.0) return 0.0;
else if (dProb >= 1.0) return 1.0;
else
{
if (dProb >= 0.9999) dProb = 0.9999;
double dValue = log(dProb / (1.0 - dProb)) + dLogOdd + dAdj;
if (dValue > 50) dValue = 50;
double dExp = exp(dValue);
return dExp / (1 + dExp);
}
}
TIME Observer::timeFreezeEducTransRatesEvent()
{
if (!educ_rates_frozen && FrozenEducCohort > MIN(EDUC_IMPUTE)) //TODO CHECK
{
return (double)FrozenEducCohort;
}
else return TIME_INFINITE;
}
void Observer::FreezeEducTransRatesEvent()
{
educ_rates_frozen = TRUE;
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
///////////////////////////////
// First educational transition
double dTargetProb = 1.0 - EducDistributionPara[nSex][RANGE_POS(EDUC_YOB, FrozenEducCohort)][EL4_ISCED2];
double dFactor1 = 1.0;
// Distribution of parents education
double dParGroupShare[SIZE(PARENTS_EDUC3)];
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = getShareParentsEduc((PARENTS_EDUC)nParGroup);
if (dTargetProb == 1.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED3] = 1.0;
else if (dTargetProb == 0.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED3] = 0.0;
}
// Find probability based on odds and aligned to totals
if (dTargetProb > 0.0 && dTargetProb < 1.0)
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
AdjustedProbability(dTargetProb, log(EducOdds[nSex][nParGroup][ETR_ISCED3]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Store transition rates
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED3] =
AdjustedProbability(dTargetProb, log(EducOdds[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED3]), dFactor1);
}
}
////////////////////////////////
// Second educational transition
if (dTargetProb > 0.0)
{
dTargetProb = (dTargetProb - EducDistributionPara[nSex][RANGE_POS(EDUC_YOB, FrozenEducCohort)][EL4_ISCED3]) / dTargetProb;
dFactor1 = 1.0;
// Update distribution of parents education
double dSumShares = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] * AdjustedProbability(dTargetProb, log(EducOdds[nSex][nParGroup][ETR_ISCED3]), dFactor1);
dSumShares = dSumShares + dParGroupShare[nParGroup];
}
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] / dSumShares;
if (dTargetProb == 1.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED4] = 1.0;
else if (dTargetProb == 0.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED4] = 0.0;
}
// Find probability based on odds and aligned to totals
if (dTargetProb > 0.0 && dTargetProb < 1.0)
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
AdjustedProbability(dTargetProb, log(EducOdds[nSex][nParGroup][ETR_ISCED4]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Store transition rates
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED4] =
AdjustedProbability(dTargetProb, log(EducOdds[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED4]), dFactor1);
}
}
}
///////////////////////////////
// Third educational transition
if (dTargetProb > 0.0)
{
dTargetProb = EducDistributionPara[nSex][RANGE_POS(EDUC_YOB, FrozenEducCohort)][EL4_ISCED5] /
(EducDistributionPara[nSex][RANGE_POS(EDUC_YOB, FrozenEducCohort)][EL4_ISCED4]
+ EducDistributionPara[nSex][RANGE_POS(EDUC_YOB, FrozenEducCohort)][EL4_ISCED5]);
dFactor1 = 1.0;
// Update distribution of parents education
double dSumShares = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] * AdjustedProbability(dTargetProb, log(EducOdds[nSex][nParGroup][ETR_ISCED4]), dFactor1);
dSumShares = dSumShares + dParGroupShare[nParGroup];
}
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dParGroupShare[nParGroup] = dParGroupShare[nParGroup] / dSumShares;
if (dTargetProb == 1.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED5] = 1.0;
else if (dTargetProb == 0.0) frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED5] = 0.0;;
}
// Find probability based on odds and aligned to totals
if (dTargetProb > 0.0 && dTargetProb < 1.0)
{
// Search for factor
int nIterations = 0;
double dResultProb = 10.0;
double dLower = -10.0;
double dUpper = 10.0;
double dCenter = 0.0;
while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
{
nIterations++;
dCenter = (dLower + dUpper) / 2.0;
dResultProb = 0.0;
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
dResultProb = dResultProb + dParGroupShare[nParGroup] *
AdjustedProbability(dTargetProb, log(EducOdds[nSex][nParGroup][ETR_ISCED5]), dCenter);
}
if (dTargetProb > dResultProb) dLower = dCenter;
else dUpper = dCenter;
}
dFactor1 = dCenter;
// Store transition rates
for (int nParGroup = 0; nParGroup < SIZE(PARENTS_EDUC3); nParGroup++)
{
frozen_educ_transition_rates[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED5] =
AdjustedProbability(dTargetProb, log(EducOdds[nSex][(PARENTS_EDUC3)nParGroup][ETR_ISCED5]), dFactor1);
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Presimulation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nCohort = 0; nCohort < SIZE(EDUC_YOB); nCohort++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
EducDistribution[nSex][nCohort][nEduc] = EducDistributionPara[nSex][nCohort][nEduc];
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabEducAtStart //EN Educ at start
[creation_type == CT_START && trigger_entrances(in_projected_time,TRUE)]
{
{
value_in(in_educ_start) / unit //EN proportion enroled decimals=3
}
* integer_age
* educ_level4
};
EducationEnrolment.mpp
This module handles educational enrolment based on the enrolment patterns observed in the initial population. It is assumed that the age-specific enrolment rates will remain as they are today for a given educational outcome. This means that the dynamics are entirely driven by composition effects due to educational expansion. The enrolment rates used in the simulation are implemented as a state of the observer actor, which is initialised by the InitializeEnrolmentAtStart() function. This function is called before the simulation starts, once the educational fate of all members of the initial population has been determined based on their highest level of education, enrolment status, parental education, and expected cohort outcomes as defined in the scenario parameters.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Residentsby enrolment status
actor_set Person asResidentsByEnromentStatus[sex][integer_age][educ_level4][in_educ]
filter is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical in_educ = { FALSE }; //EN In education (enroled)
};
actor Observer
{
double EnrolmentRates[SEX][AGE_RANGE][EDUC_LEVEL4]; //EN Enrolment rates (in Startpop)
void InitializeEnrolmentAtStart(); //EN Initial enrolment status at start
void UpdateEnrolment(); //EN Update enrolment
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::InitializeEnrolmentAtStart()
{
long nPerson = asAllPerson->Count();
for (long nJ = 0; nJ < nPerson; nJ++)
{
// set education enrolment as in starting population
Person_ptr ptrPerson = asAllPerson->Item(nJ);
if (ptrPerson->creation_type == CT_START)
{
ptrPerson->in_educ = ptrPerson->in_educ_start;
}
// Create enrolment rates "parameter"
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
double dEnroled = (double)asResidentsByEnromentStatus[nSex][nAge][nEduc][TRUE]->Count();
double dNotEnroled = (double)asResidentsByEnromentStatus[nSex][nAge][nEduc][FALSE]->Count();
double dTotal = dEnroled + dNotEnroled;
if (dTotal > 0.0) EnrolmentRates[nSex][nAge][nEduc] = dEnroled / dTotal;
else EnrolmentRates[nSex][nAge][nEduc] = 0.0;
}
}
}
}
}
void Observer::UpdateEnrolment()
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
double dEnroled = (double)asResidentsByEnromentStatus[nSex][nAge][nEduc][TRUE]->Count();
double dNotEnroled = (double)asResidentsByEnromentStatus[nSex][nAge][nEduc][FALSE]->Count();
double dTotal = dEnroled + dNotEnroled;
// End enrolments if rate too high
while (dEnroled > 0.0 && dEnroled / dTotal > EnrolmentRates[nSex][nAge][nEduc])
{
Person_ptr ptrPerson = asResidentsByEnromentStatus[nSex][nAge][nEduc][TRUE]->GetRandom(RandUniform(76));
ptrPerson->in_educ = FALSE;
dEnroled = dEnroled - 1.0;
dNotEnroled = dNotEnroled + 1;
}
// Enrolments if rate too low
while (dNotEnroled > 0.0 && dEnroled / dTotal < EnrolmentRates[nSex][nAge][nEduc])
{
Person_ptr ptrPerson = asResidentsByEnromentStatus[nSex][nAge][nEduc][FALSE]->GetRandom(RandUniform(77));
ptrPerson->in_educ = TRUE;
dEnroled = dEnroled + 1.0;
dNotEnroled = dNotEnroled - 1;
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabSchoolEnrolemnt //EN School enrolment
[in_projected_time && is_resident]
{
educ_level4+ *
{
duration(in_educ,TRUE) / duration() //EN Education enrolment rate decimals=3
}
* integer_age
* sim_year
};
Economic activity
ActivityTransitions.mpp
This module implements economic activity status and longitudinal activity transitions. In terms of economic activity, the model distinguishes between:
Never active
Employed
Unemployed
Family leave
Out of labor force
Retired
The module incorporates longitudinal, consistent employment careers modelled in continuous time. Transitions between labour market states depend on a set of personal characteristics such as gender, age, and education as well as the duration in the respective state. Most transitions are based on piecewise constant hazard regression models:
Employed -> unemployed
Employed -> out
Unemployed -> employed
Unemployed -> out
Out -> employed
Out -> unemployed
Unemployment can be aligned to a (logistic) model of the prevalence of unemployment by individual characteristics. Such an alignment can be used to create scenarios that close gaps between groups, for example by improving employment opportunities for the elderly workforce or people with health limitations. Additionally, in a second step, total outcomes can be aligned to an overall unemployment rate (a scenario parameter). The alignment routine modifies the process from employment to unemployment, determining the proportion of the work-force, grouped by gender, education, age and health, affected by unemployment. Within each group, the selection of individuals who become unemployed is still determined by their individual transition hazards.
In the same way, labor force participation can optionally be aligned to a (logistic) model of the labor force participation (prevalence) by individual characteristics. Such an alignment can be used to create scenarios that close gaps between groups, for example by increasing labor force participation of the elderly, of people with health limitations, or women. The alignment routine modifies the process of leving the work-foerce from employment and unemployment, determining the target proportion of people in the labor force grouped by gender, education, age, health, and the age of the youngest child. Within each group, the selection of individuals who exit the labor force is still determined by their individual transition hazards.
The initial state durations are determined by sampling simulated duration spells created within the simulation. Donor characteristics are produced using the transition models that drive activity careers already in the past. Sampling occurs slightly more than two years before the simulation actually begins, since the longest duration spell is two years or more. Based on these sampled duration spells, transitions to the current state observed in the starting population are scheduled in the past.
First entry into the labor-force is modeled by entry hazards by age, gender and education. Retirement is modelled by applying a set of rules which determine, if a person leaving the work-force is assumed to permanently retire.
Parameters:
Activity transitions: collection of hazard regressions
First entry into the labour-force: hazards by gender and education group
Unemployment alignment options: no alignment. Alignment to prevalence scenario. Additional alignment to overall rates.
Probability of unemployment: logistic regression coefficients - used for optional alignment and for scenarios
Unemployment alignment targets: overall rates by year
Labor force participation alignment options: no alignment. Alignment to prevalence scenario.
Probability of labor force participation: logistic regression coefficients - used for optional alignment and for scenarios
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Unemployment group
actor_set Person asActUnemploymentGroup[educ_level4][health_cat][sex][act_age_unemp_index][activity]
filter WITHIN(ACT_UNEMP_AGE_RANGE, integer_age) && in_projected_time&& is_resident
order wait_time_to_unemployment;
//EN Lfp group
actor_set Person asActLfpGroup[educ_level4][health_cat][sex][act_age_lfp_index][age_lfp_child][activity]
filter WITHIN(LABOR_AGE, integer_age) && in_projected_time && is_resident;
//EN Lfp group employed or unemployed
actor_set Person asActLfpGroupEmpUnemp[educ_level4][health_cat][sex][act_age_lfp_index][age_lfp_child]
filter WITHIN(LABOR_AGE, integer_age) && in_projected_time && is_resident
&& (activity == ACT_EMPLOYED || activity == ACT_UNEMPLOYED)
order wait_time_to_lfexit;
//EN Sampling spell length group
actor_set Person asActSamplingGroup[educ_level4][sex][act_age_unemp_index][activity]
filter WITHIN(ACT_UNEMP_AGE_RANGE, integer_age) && calendar_year == MIN(SIM_YEAR)-3 && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification AGE_LFP_CHILD //EN Age youngest child
{
AYC_00_02, //EN 0-2
AYC_03_05, //EN 3-5
AYC_06_09, //EN 6-9
AYC_10P, //EN 10+
AYC_NON //EN No children
};
classification ACTIVITY //EN Activity
{
ACT_NEVER, //EN Never active
ACT_EMPLOYED, //EN Employed
ACT_UNEMPLOYED, //EN Unemployed
ACT_LEAVE, //EN Family leave
ACT_OUT, //EN Out of labor force
ACT_RETIRED //EN Retired
};
classification ACT_TRANSITION //EN Activity transition types
{
ATR_EMP_UNEMP, //EN Employed -> unemployed
ATR_EMP_OUT, //EN Employed -> out
ATR_UNEMP_EMP, //EN Unemployed -> employed
ATR_UNEMP_OUT, //EN Unemployed -> out
ATR_OUT_EMP, //EN Out -> employed
ATR_OUT_UNEMP //EN Out -> unemployed
};
classification ACT_UNEMP_ALIGNMENT //EN Unemployment alignment options
{
AUA_NON, //EN No alignment (use hazards)
AUA_LOGISTIC, //EN Alignment to logistic regression
AUA_TOTAL //EN Alignment to total target
};
classification ACT_LFP_ALIGNMENT //EN LFP alignment options
{
ALA_NON, //EN No alignment (use hazards)
ALA_LOGISTIC //EN Alignment to logistic regression
};
classification ACT_HAZARD //EN Activity transition hazards
{
ACH_DUR01, //EN 0-3 months
ACH_DUR02, //EN 3-6 months
ACH_DUR03, //EN 6-9 months
ACH_DUR04, //EN 9-12 months
ACH_DUR05, //EN 12-15 months
ACH_DUR06, //EN 15-18 months
ACH_DUR07, //EN 18-24 months
ACH_DUR08, //EN 24+ months
ACH_EDUC01, //EN ISCED 2
ACH_EDUC02, //EN ISCED 3
ACH_EDUC03, //EN ISCED 4
ACH_EDUC04, //EN ISCED 5+
ACH_AGE01, //EN Age below 25
ACH_AGE02, //EN Age 25-49
ACH_AGE03, //EN Age 50+
ACH_BADHEALTH //EN Health impairments
};
classification ACT_UNEMP_ODDS //EN Odds of unemployment
{
AUO_CONSTANT, //EN Constant
AUO_EDUC01, //EN ISCED 2
AUO_EDUC02, //EN ISCED 3
AUO_EDUC03, //EN ISCED 4
AUO_EDUC04, //EN ISCED 5+
AUO_BADHEALTH, //EN Health limitations
AUO_AGE01, //EN Age below 25
AUO_AGE02, //EN Age 25 - 29
AUO_AGE03, //EN Age 30 - 39
AUO_AGE04, //EN Age 40 - 49
AUO_AGE05, //EN Age 50 - 54
AUO_AGE06 //EN Age 55+
};
classification ACT_LFP_ODDS //EN Odds of LFP
{
ALO_CONSTANT, //EN Constant
ALO_EDUC01, //EN ISCED 2
ALO_EDUC02, //EN ISCED 3
ALO_EDUC03, //EN ISCED 4
ALO_EDUC04, //EN ISCED 5+
ALO_BADHEALTH, //EN Health limitations
ALO_AGE01, //EN Age 15-19
ALO_AGE02, //EN Age 20-24
ALO_AGE03, //EN Age 25-29
ALO_AGE04, //EN Age 30-34
ALO_AGE05, //EN Age 35-39
ALO_AGE06, //EN Age 40-44
ALO_AGE07, //EN Age 45-49
ALO_AGE08, //EN Age 50-54
ALO_AGE09, //EN Age 55-59
ALO_AGE10, //EN Age 60-64
ALO_AGE11, //EN Age 65-69
ALO_AGE12, //EN Age 70+
ALO_CHILD01, //EN Youngest child 0-2
ALO_CHILD02, //EN Youngest child 3-5
ALO_CHILD03, //EN Youngest child 6-9
ALO_CHILD04 //EN Youngest child 10+
};
range LABOR_AGE { 15, 75 }; //EN Labor Age
range ACT_UNEMP_AGE_RANGE { 15, 65 }; //EN Unemployment age range
partition ACT_DUR_PART { 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0 }; //EN Duration
partition ACT_AGE_PART { 25, 50 }; //EN Age group
partition ACT_AGE_UNEMP { 25, 30, 40, 50, 55 }; //EN Age group
partition ACT_AGE_LFP { 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70 }; //EN Age group
range ACT_AGE_UNEMP_INDEX { 0, 5 }; //EN Unemployment age index
range ACT_AGE_LFP_INDEX { 0, 11 }; //EN LFP age index
range ACT_ENTER_AGE { 15, 30 }; //EN Age
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_LONACT //EN Longitudinal activity
{
ActEmpUnempAlignment,
ActUnempAlignmentTargets,
ActUnemploymentOdds,
ActLfpAlignment,
ActLfpOdds,
ActFirstEntry,
ActTransitions
};
parameters
{
double ActUnempAlignmentTargets[SIM_YEAR][SEX]; //EN Unemployment alignment targets
double ActUnemploymentOdds[ACT_UNEMP_ODDS][SEX]; //EN Unemployment odds (used for alignment)
double ActLfpOdds[ACT_LFP_ODDS][SEX]; //EN LFP alignment targets
double ActFirstEntry[SEX][ACT_ENTER_AGE][EDUC_LEVEL4]; //EN First labor entry
double ActTransitions[SEX][ACT_HAZARD][ACT_TRANSITION]; //EN Activity transitions
ACT_UNEMP_ALIGNMENT ActEmpUnempAlignment; //EN Unemployment alignment options
ACT_LFP_ALIGNMENT ActLfpAlignment; //EN LFP alignment options
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Age group of youngest child
int agegr_lfp_child = split(age_youngest_child_in_family, CHILD_AGE_PART5);
//EN Youngest child
AGE_LFP_CHILD age_lfp_child =
(children_in_family && age_youngest_child_in_family < 3) ? AYC_00_02 :
(children_in_family && age_youngest_child_in_family < 6) ? AYC_03_05 :
(children_in_family && age_youngest_child_in_family < 10) ? AYC_06_09 :
(children_in_family && age_youngest_child_in_family < 18) ? AYC_10P : AYC_NON;
ACTIVITY activity = { ACT_NEVER }; //EN Activity
ACTIVITY activity_start = { ACT_NEVER }; //EN Activity at start
ACTIVITY act_before_leave = { ACT_NEVER }; //EN Activity before leave
logical act_spell = { FALSE }; //EN Activity spell
//EN Activity spouse
ACTIVITY activity_spouse = (has_spouse) ? lSpouse->activity : ACT_NEVER;
//EN Unemploament age index
ACT_AGE_UNEMP_INDEX act_age_unemp_index = COERCE(ACT_AGE_UNEMP_INDEX, split(integer_age, ACT_AGE_UNEMP));
//EN LFP age index
ACT_AGE_LFP_INDEX act_age_lfp_index = COERCE(ACT_AGE_LFP_INDEX, split(integer_age, ACT_AGE_LFP));
double waitActEmployedUnemployed(); //EN Waiting time to unemployment
double wait_time_to_unemployment = { TIME_INFINITE }; //EN Waiting time to unemployment
double waitActLfexit(); //EN Waiting time to lf exit
double wait_time_to_lfexit = { TIME_INFINITE }; //EN Waiting time to LF exit
//EN Duration index current activity
int act_dur = self_scheduling_split(active_spell_duration(act_spell, TRUE), ACT_DUR_PART);
int act_age_index = self_scheduling_split(age, ACT_AGE_PART); //EN Age group
event timeActNeverEmployedEvent, ActNeverEmployedEvent; //EN Event never -> employed
event timeActEmployedUnemployedEvent, ActEmployedUnemployedEvent; //EN Employed -> unemployed
event timeActEmployedOutEvent, ActEmployedOutEvent; //EN Employed -> out
event timeActUnemployedEmployedEvent, ActUnemployedEmployedEvent; //EN Unemployed -> employed
event timeActUnemployedOutEvent, ActUnemployedOutEvent; //EN Unemployed -> employed
event timeActOutEmployedEvent, ActOutEmployedEvent; //EN Out -> employed
event timeActOutUnemployedEvent, ActOutUnemployedEvent; //EN Out -> unemployed
event timeActRetireEvent, ActRetireEvent; //EN Out -> retire
TIME time_act_end_leave = { TIME_INFINITE }; //EN Time end leave
event timeActEndLeaveEvent, ActEndLeaveEvent; //EN End leave event
void doEnterAct(ACTIVITY cNewAct); //EN Enter new activity status
void doSampleActivityDuration(); //EN Sample activity spell duration
TIME time_impute_activity_start = { TIME_INFINITE }; //EN Time impute spell start
event timeImputeActivityStartEvent, ImputeActivityStartEvent; //EN Impute activity start
logical block_activity_transitions_until_start = { FALSE }; //EN Block activity trsansitions until start
};
actor Observer
{
TIME next_early_each_month = { TIME_INFINITE }; //EN Time of next early in month event
event timeEarlyEachMonthEvent, EarlyEachMonthEvent; //EN Early each month clock
void doAlignUnemployment(); //EN Align unemployment
double getUnemploymentRate(double dAlignmentFactor, SEX nSex); //EN Get current unemployment rate
void doAlignLfp(); //EN Align LFP
TIME activity_duration_sampling_is_done = { FALSE }; //EN Activity duration sampling is done
event timeSampleActivityDurationEvent, SampleActivityDurationEvent; //EN Sample activity spell duration event
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation Observer
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Observer::timeSampleActivityDurationEvent()
{
if (!activity_duration_sampling_is_done) return MIN(SIM_YEAR) - 2.02;
else return TIME_INFINITE;
}
void Observer::SampleActivityDurationEvent()
{
int nPerson = asAllPerson->Count();
for (int nJ = 0; nJ < nPerson; nJ++)
{
Person_ptr paPerson = asAllPerson->Item(nJ);
paPerson->doSampleActivityDuration();
}
activity_duration_sampling_is_done = TRUE;
}
TIME Observer::timeEarlyEachMonthEvent()
{
if (observer_year >= MIN(SIM_YEAR) && next_early_each_month == TIME_INFINITE) return WAIT(0.0001);
else return next_early_each_month;
}
void Observer::EarlyEachMonthEvent()
{
doAlignLfp();
doAlignUnemployment();
next_early_each_month = WAIT(1.0 / 12.0);
}
double Observer::getUnemploymentRate(double dAlignmentFactor, SEX nSex)
{
double dReturnValue = 0.0;
double dTotalPop = 0.0;
double dTotalUnemployed = 0.0;
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nHealth = 0; nHealth < SIZE(HEALTH_CAT); nHealth++)
{
for (int nAge = 0; nAge < SIZE(ACT_AGE_UNEMP_INDEX); nAge++)
{
double nTotal = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Count()
+ asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_LEAVE]->Count()
+ asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_UNEMPLOYED]->Count();
if (nTotal > 0.0)
{
dTotalPop = dTotalPop + nTotal;
// Calculate unemployment rate of group
double dOdds = ActUnemploymentOdds[AUO_CONSTANT][nSex]
* ActUnemploymentOdds[AUO_EDUC01 + nEduc][nSex]
* ActUnemploymentOdds[AUO_AGE01 + nAge][nSex]
* dAlignmentFactor;
if (nHealth == HC_BAD) dOdds = dOdds * ActUnemploymentOdds[AUO_BADHEALTH][nSex];
double dUnemploymentProb = dOdds / (dOdds + 1.0);
dTotalUnemployed = dTotalUnemployed + double(nTotal) * dUnemploymentProb;
}
}
}
}
if (dTotalPop > 0.0) dReturnValue = dTotalUnemployed / dTotalPop;
return dReturnValue;
}
void Observer::doAlignUnemployment()
{
double dAlignmentFactor[SIZE(SEX)];
for (int nSex = 0; nSex < SIZE(SEX); nSex++) dAlignmentFactor[nSex] = 1.0;
// Step 1: find alignment factor for logistic model
if (observer_year >= MIN(SIM_YEAR) && ActEmpUnempAlignment == AUA_TOTAL)
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
if (observer_year >= MIN(SIM_YEAR) && ActEmpUnempAlignment == AUA_TOTAL)
{
double dTargetRate = ActUnempAlignmentTargets[RANGE_POS(SIM_YEAR, observer_year)][nSex];
int nIterations = 100;
double dUpper = 10.0;
double dLower = 0.0;
double dCenter = (dUpper + dLower) / 2.0;
double dCurrentRate = getUnemploymentRate(dCenter, (SEX)nSex);
double dError = abs(dCurrentRate - dTargetRate);
while (dError > 0.001 && nIterations > 0)
{
if (dCurrentRate > dTargetRate) dUpper = dCenter;
else dLower = dCenter;
dCenter = (dUpper + dLower) / 2.0;
dCurrentRate = getUnemploymentRate(dCenter, (SEX)nSex);
dError = abs(dCurrentRate - dTargetRate);
nIterations--;
}
dAlignmentFactor[nSex] = dCenter;
}
}
// Step 2: Alignment to logistic model
if (observer_year >= MIN(SIM_YEAR) && ActEmpUnempAlignment != AUA_NON)
{
// Update waiting-times
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nHealth = 0; nHealth < SIZE(HEALTH_CAT); nHealth++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(ACT_AGE_UNEMP_INDEX); nAge++)
{
long nEmployed = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Count();
for (long nIndex = 0; nIndex < nEmployed; nIndex++)
{
Person_ptr ptrPerson = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Item(nIndex);
ptrPerson->wait_time_to_unemployment = ptrPerson->waitActEmployedUnemployed();
}
}
}
}
}
// Move people into unemployment
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nHealth = 0; nHealth < SIZE(HEALTH_CAT); nHealth++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(ACT_AGE_UNEMP_INDEX); nAge++)
{
double dEmployed = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Count()
+ asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_LEAVE]->Count();
double dUnemployed = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_UNEMPLOYED]->Count();
// Calculate unemployment rate of group
double dOdds = ActUnemploymentOdds[AUO_CONSTANT][nSex]
* ActUnemploymentOdds[AUO_EDUC01 + nEduc][nSex]
* ActUnemploymentOdds[AUO_AGE01 + nAge][nSex]
* dAlignmentFactor[nSex];
if (nHealth == HC_BAD) dOdds = dOdds * ActUnemploymentOdds[AUO_BADHEALTH][nSex];
double dUnemploymentProb = dOdds / (dOdds + 1.0);
double dTargetUnemployed = dUnemploymentProb * (dEmployed + dUnemployed);
int nMovers = int(dTargetUnemployed - dUnemployed);
while (nMovers > 0 && asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Count() > 0)
{
Person_ptr ptrMover = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_EMPLOYED]->Item(0);
ptrMover->ActEmployedUnemployedEvent();
nMovers--;
}
}
}
}
}
}
}
void Observer::doAlignLfp()
{
// Step 1: Alignment to logistic model
if (observer_year >= MIN(SIM_YEAR) && ActLfpAlignment != ALA_NON)
{
// Update waiting-times
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nHealth = 0; nHealth < SIZE(HEALTH_CAT); nHealth++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(ACT_AGE_LFP_INDEX); nAge++)
{
for (int nChild = 0; nChild < SIZE(AGE_LFP_CHILD); nChild++)
{
long nPerson = asActLfpGroupEmpUnemp[nEduc][nHealth][nSex][nAge][nChild]->Count();
for (long nIndex = 0; nIndex < nPerson; nIndex++)
{
Person_ptr ptrPerson = asActLfpGroupEmpUnemp[nEduc][nHealth][nSex][nAge][nChild]->Item(nIndex);
ptrPerson->wait_time_to_lfexit = ptrPerson->waitActLfexit();
}
}
}
}
}
}
// Move people out of the labor force
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nHealth = 0; nHealth < SIZE(HEALTH_CAT); nHealth++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nAge = 0; nAge < SIZE(ACT_AGE_LFP_INDEX); nAge++)
{
for (int nChild = 0; nChild < SIZE(AGE_LFP_CHILD); nChild++)
{
double dLaborForce =
asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_EMPLOYED]->Count()
+ asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_UNEMPLOYED]->Count()
+ asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_LEAVE]->Count();
double dOutOfLaborForce =
asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_NEVER]->Count()
+ asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_OUT]->Count()
+ asActLfpGroup[nEduc][nHealth][nSex][nAge][nChild][ACT_RETIRED]->Count();
// Calculate lfp rate of group
double dOdds =
ActLfpOdds[ALO_CONSTANT][nSex]
* ActLfpOdds[ALO_EDUC01 + nEduc][nSex]
* ActLfpOdds[ALO_AGE01 + nAge][nSex];
if (nChild < SIZE(AGE_LFP_CHILD) - 1) dOdds = dOdds * ActLfpOdds[ALO_CHILD01 + nChild][nSex];
if (nHealth == HC_BAD) dOdds = dOdds * ActLfpOdds[ALO_BADHEALTH][nSex];
double dLfpProb = dOdds / (dOdds + 1.0);
double dTargetOut = (1.0 - dLfpProb) * (dLaborForce + dOutOfLaborForce);
int nMovers = int(dTargetOut - dOutOfLaborForce);
while (nMovers > 0 && asActLfpGroupEmpUnemp[nEduc][nHealth][nSex][nAge][nChild]->Count() > 0)
{
Person_ptr ptrMover = asActLfpGroupEmpUnemp[nEduc][nHealth][nSex][nAge][nChild]->Item(0);
if (ptrMover->activity == ACT_EMPLOYED) ptrMover->ActEmployedOutEvent();
else ptrMover->ActUnemployedOutEvent();
nMovers--;
}
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation Person
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////
// Sample spell duration
//////////////////////////
void Person::doSampleActivityDuration()
{
if (is_resident && activity_start != ACT_NEVER && activity_start != ACT_RETIRED && activity_start != ACT_LEAVE)
{
ACT_AGE_UNEMP_INDEX cAgeIndex = COERCE(ACT_AGE_UNEMP_INDEX, SPLIT(age + 2.0, ACT_AGE_UNEMP));
double dDuration = 0.0;
if (asActSamplingGroup[educ_level4][sex][cAgeIndex][activity_start]->Count() > 0)
{
Person_ptr ptrPerson = asActSamplingGroup[educ_level4][sex][cAgeIndex][activity_start]->GetRandom(RandUniform(75));
int nDurIndex = ptrPerson->act_dur;
// { 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0 }
if (nDurIndex == 0) dDuration = 0.125;
else if (nDurIndex == 1) dDuration = 0.375;
else if (nDurIndex == 2) dDuration = 0.625;
else if (nDurIndex == 3) dDuration = 0.875;
else if (nDurIndex == 4) dDuration = 1.125;
else if (nDurIndex == 5) dDuration = 1.375;
else if (nDurIndex == 6) dDuration = 1.750;
else dDuration = 2.01;
}
else
{
dDuration = 0.5;
}
time_impute_activity_start = MIN(SIM_YEAR) - dDuration;
}
// treate those on leave
else if (is_resident && activity_start == ACT_LEAVE)
{
time_act_end_leave = MIN(SIM_YEAR) + 0.5;
if (activity != ACT_LEAVE) act_before_leave = activity;
else act_before_leave = ACT_EMPLOYED;
time_impute_activity_start = MIN(SIM_YEAR) - 0.5;
}
else if (is_resident && (activity_start == ACT_NEVER || activity_start == ACT_RETIRED))
{
time_impute_activity_start = MIN(SIM_YEAR) - 2.01;
}
}
TIME Person::timeImputeActivityStartEvent() { return time_impute_activity_start; }
void Person::ImputeActivityStartEvent()
{
doEnterAct(activity_start);
block_activity_transitions_until_start = TRUE;
time_impute_activity_start = TIME_INFINITE;
}
//////////////////////////
// Retirement
//////////////////////////
TIME Person::timeActRetireEvent()
{
TIME dEventTime = TIME_INFINITE;
if ( activity != ACT_RETIRED && (!block_activity_transitions_until_start || in_projected_time))
{
if (integer_age >= 58 && activity == ACT_OUT) dEventTime = WAIT(0.0);
else if (integer_age >= 65 && activity == ACT_UNEMPLOYED) dEventTime = WAIT(0.0);
else if (integer_age > MAX(LABOR_AGE)) dEventTime = WAIT(0.0);
}
return dEventTime;
}
void Person::ActRetireEvent()
{
doEnterAct(ACT_RETIRED);
if (in_projected_time ) doSetPension();
}
//////////////////////////
// Parental leave
//////////////////////////
TIME Person::timeActEndLeaveEvent() { return time_act_end_leave; }
void Person::ActEndLeaveEvent()
{
doEnterAct(act_before_leave);
time_act_end_leave = TIME_INFINITE;
}
//////////////////////////
// First entry
//////////////////////////
TIME Person::timeActNeverEmployedEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_NEVER && WITHIN(ACT_ENTER_AGE, integer_age))
{
double dHazard = ActFirstEntry[sex][RANGE_POS(ACT_ENTER_AGE, integer_age)][educ_level4];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(67)) / dHazard);
}
return dEventTime;
}
void Person::ActNeverEmployedEvent()
{
doEnterAct(ACT_EMPLOYED);
}
//////////////////////////
// Employed -> unemployed
//////////////////////////
TIME Person::timeActEmployedUnemployedEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if ( activity == ACT_EMPLOYED && (ActEmpUnempAlignment == AUA_NON || !in_projected_time || !is_resident))
{
double dHazard = ActTransitions[sex][act_dur][ATR_EMP_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_EMP_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_EMP_UNEMP];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_EMP_UNEMP];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(68)) / dHazard);
}
return dEventTime;
}
double Person::waitActEmployedUnemployed()
{
double dWaitTime = TIME_INFINITE;
// At risk?
if ( activity == ACT_EMPLOYED )
{
double dHazard = ActTransitions[sex][act_dur][ATR_EMP_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_EMP_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_EMP_UNEMP];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_EMP_UNEMP];
if (dHazard > 0.0) dWaitTime = -log(RandUniform(63)) / dHazard;
}
return dWaitTime;
}
void Person::ActEmployedUnemployedEvent()
{
// record recent earnings for unemployment benefit
if (earnings <= 0.0) recent_earnings = lObserver->average_earnings; // TODO REMOVE this option when wages modeled in past
else recent_earnings = earnings;
doEnterAct(ACT_UNEMPLOYED);
}
//////////////////////////
// Employed -> out
//////////////////////////
TIME Person::timeActEmployedOutEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_EMPLOYED && ActLfpAlignment == ALA_NON)
{
double dHazard = ActTransitions[sex][act_dur][ATR_EMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_EMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_EMP_OUT];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_EMP_OUT];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(70)) / dHazard);
}
return dEventTime;
}
void Person::ActEmployedOutEvent()
{
doEnterAct(ACT_OUT);
}
//////////////////////////
// Unemployed -> employed
//////////////////////////
TIME Person::timeActUnemployedEmployedEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_UNEMPLOYED)
{
double dHazard = ActTransitions[sex][act_dur][ATR_UNEMP_EMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_UNEMP_EMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_UNEMP_EMP];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_UNEMP_EMP];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(71)) / dHazard);
}
return dEventTime;
}
void Person::ActUnemployedEmployedEvent()
{
doEnterAct(ACT_EMPLOYED);
}
//////////////////////////
// Unemployed -> out
//////////////////////////
TIME Person::timeActUnemployedOutEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_UNEMPLOYED && ActLfpAlignment == ALA_NON)
{
double dHazard = ActTransitions[sex][act_dur][ATR_UNEMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_UNEMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_UNEMP_OUT];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_UNEMP_OUT];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(72)) / dHazard);
}
return dEventTime;
}
void Person::ActUnemployedOutEvent()
{
doEnterAct(ACT_OUT);
}
/////////////////////////////////////////////
// Waiting time Unemployed or employed -> out
/////////////////////////////////////////////
double Person::waitActLfexit()
{
double dWaitTime = TIME_INFINITE;
// Currently employed
if (activity == ACT_EMPLOYED)
{
double dHazard = ActTransitions[sex][act_dur][ATR_EMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_EMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_EMP_OUT];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_EMP_OUT];
if (dHazard > 0.0) dWaitTime = -log(RandUniform(69)) / dHazard;
}
// Currently unemployed
else if (activity == ACT_UNEMPLOYED)
{
double dHazard = ActTransitions[sex][act_dur][ATR_UNEMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_UNEMP_OUT]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_UNEMP_OUT];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_UNEMP_OUT];
if (dHazard > 0.0) dWaitTime = -log(RandUniform(79)) / dHazard;
}
return dWaitTime;
}
//////////////////////////
// Out -> Employed
//////////////////////////
TIME Person::timeActOutEmployedEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_OUT)
{
double dHazard = ActTransitions[sex][act_dur][ATR_OUT_EMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_OUT_EMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_OUT_EMP];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_OUT_EMP];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(73)) / dHazard);
}
return dEventTime;
}
void Person::ActOutEmployedEvent()
{
doEnterAct(ACT_EMPLOYED);
}
//////////////////////////
// Out -> Unmployed
//////////////////////////
TIME Person::timeActOutUnemployedEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_OUT)
{
double dHazard = ActTransitions[sex][act_dur][ATR_OUT_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + educ_level4][ATR_OUT_UNEMP]
* ActTransitions[sex][SIZE(ACT_DUR_PART) + SIZE(EDUC_LEVEL4) + act_age_index][ATR_OUT_UNEMP];
if (health_cat == HC_BAD) dHazard = dHazard + ActTransitions[sex][ACH_BADHEALTH][ATR_OUT_UNEMP];
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(74)) / dHazard);
}
return dEventTime;
}
void Person::ActOutUnemployedEvent()
{
doEnterAct(ACT_UNEMPLOYED);
}
//////////////////////////
// Status change
//////////////////////////
void Person::doEnterAct(ACTIVITY cNewAct)
{
if (!block_activity_transitions_until_start || in_projected_time)
{
// reset spell if not currently in leave or moving to leave
if (cNewAct != ACT_LEAVE && activity != ACT_LEAVE) act_spell = FALSE;
// update state
activity = cNewAct;
// full-time status of newly employed
if (activity == ACT_EMPLOYED)
{
prob_part_time = getProbPartTime();
if (RandUniform(89) < prob_part_time) full_part_time = FPT_PART;
else full_part_time = FPT_FULL;
}
// reset spell if not currently in leave or moving to leave
if (cNewAct != ACT_LEAVE && activity != ACT_LEAVE) act_spell = TRUE;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition YOB_PART { 2018, 2030 }; //EN Year
table Person tabActAge //EN Act Cohort pattern by age
{
split(year_of_birth,YOB_PART) *
integer_age*
{
duration(activity, ACT_NEVER) / duration(), //EN Never active decimals=2
duration(activity, ACT_EMPLOYED) / duration(), //EN Employed decimals=2
duration(activity, ACT_UNEMPLOYED) / duration(), //EN Unemployed decimals=2
duration(activity, ACT_LEAVE) / duration(), //EN Family leave decimals=2
duration(activity, ACT_OUT) / duration(), //EN Out of labor force decimals=2
duration(activity, ACT_RETIRED) / duration() //EN Retired decimals=2
}
};
table Person tabActActivity //EN Act activity status distribution by year
[in_projected_time && is_resident]
{
{
//EN Unemployment rate decimals=3
duration(activity, ACT_UNEMPLOYED) / (duration(activity, ACT_EMPLOYED) + duration(activity, ACT_UNEMPLOYED)),
duration(activity, ACT_NEVER) / duration(), //EN Never active decimals=2
duration(activity, ACT_EMPLOYED) / duration(), //EN Employed decimals=2
duration(activity, ACT_UNEMPLOYED) / duration(), //EN Unemployed decimals=2
duration(activity, ACT_LEAVE) / duration(), //EN Family leave decimals=2
duration(activity, ACT_OUT) / duration(), //EN Out of labor force decimals=2
duration(activity, ACT_RETIRED) / duration() //EN Retired decimals=2
}
* sim_year
};
table Person tabActUnemployment //EN Unemployment in aligned age range
[in_projected_time && is_resident && WITHIN(ACT_UNEMP_AGE_RANGE, integer_age)]
{
{
//EN Unemployment rate in aligned age range decimals=3
duration(activity, ACT_UNEMPLOYED) / (duration(activity, ACT_EMPLOYED) + duration(activity, ACT_UNEMPLOYED))
}
* sim_year
};
table Person tabActTransitions //EN Act activity transitions Startyear
[in_projected_time && is_resident && calendar_year == MIN(SIM_YEAR)]
{
integer_age+ *
{
exits(activity, ACT_NEVER), //EN Exits from never worked
transitions(activity,ACT_EMPLOYED,ACT_UNEMPLOYED), //EN Employed -> unemployed
transitions(activity,ACT_UNEMPLOYED,ACT_EMPLOYED), //EN Unemployed -> employed
entrances(activity,ACT_RETIRED) //EN Retirements
}
};
table Person tabActAtStart //EN Act activity at start validation
[trigger_entrances(in_projected_time,TRUE) && is_resident]
{
{
unit
}
* activity
* self_scheduling_split(active_spell_duration(act_spell, TRUE), ACT_DUR_PART)+
};
table Person tabUnemploymentTotals //EN [V TEST] Unemployment totals
[in_projected_time && is_resident && WITHIN(ACT_UNEMP_AGE_RANGE, integer_age)]
{
{
//EN Unemployment rate decimals=3
duration(activity,ACT_UNEMPLOYED) / (duration(activity,ACT_UNEMPLOYED) + duration(activity,ACT_EMPLOYED) + duration(activity,ACT_LEAVE))
}
*sim_year
* sex +
};
table Person tabLFPstuff //EN [V TEST] LFP totals
[in_projected_time && is_resident ]
{
sex+ *
age_lfp_child+ *
health_cat+ *
{
//EN LFP decimals=3
(duration(activity,ACT_UNEMPLOYED) + duration(activity,ACT_EMPLOYED) + duration(activity,ACT_LEAVE)) / duration()
}
* integer_age+
* sim_year
};

ActivityParttime.mpp
This module covers part-time work. The probability of part-time employment is calculated using logistic regression and takes into account gender, the presence of children in the family and their age, education level and age. The status is updated when new employment is entered and maintained monthly. As people tend to remain in the same status for extended periods rather than switching between statuses, the model aims to keep people in their current status while simultaneously meeting the modelled probabilities based on individual characteristics. As no longitudinal data are available for modelling full-time to part-time transitions, we apply an experimental approach. People are grouped into 50 part-time risk quantiles. Each month, a new preliminary state is assigned, and individuals scheduled to change their status are identified and flagged. Next, actors try to find another person within the same risk group who has been flagged for a transition in the opposite direction. If they find someone, both individuals’ transitions are cancelled. If they do not find someone, their status changes. Mobility between states can be increased by a parameter that determines the probability that an actor will try to remain in the current state.
Parameters:
Probability of part-time work: coefficients from logistic regression
Mobility between full-time and part-time states: Probability an actor tries to remain in the current state
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Actor set employed by parttime probability
actor_set Person asEmployedByParttimeProbability
filter in_projected_time && activity == ACT_EMPLOYED && is_resident
order prob_part_time;
//EN Actor set employed by parttime quantile, status and flag
actor_set Person asEmployedByFlag[full_part_time][quantile_part_time][flag_part_time_change]
filter in_projected_time && activity == ACT_EMPLOYED && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification PART_TIME_AGEGR //EN Age group
{
PTA_TO_24, //EN max 24
PTA_25_TO_34, //EN 25-34
PTA_35_TO_59, //EN 35-59
PTA_60_TO_64, //EN 60-64
PTA_65_PLUS //EN 65+
};
classification PART_TIME_PARA //EN Part-time parameters
{
PTP_BASE, //EN Intercept
PTP_INEDUC, //EN Enrolled
PTP_SICK, //EN Health limitations
PTP_CHILD_00, //EN Youngest child <3
PTP_CHILD_03, //EN Youngest child <6
PTP_CHILD_06, //EN Youngest child <10
PTP_CHILD_10, //EN Youngest child <15
PTP_TO_24, //EN Age max 24
PTP_25_TO_34, //EN 25-34
PTP_35_TO_59, //EN 35-59
PTP_60_TO_64, //EN 60-64
PTP_65_PLUS //EN 65+
};
range QUANTILE_PART_TIME { 0 , 49 }; //EN Part-time probability quantile
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_PART_TIME //EN Part-time
{
PartTimePara,
ProbToTradePartTimeStatus
};
parameters
{
double PartTimePara[SEX][PART_TIME_PARA][EDUC_LEVEL4]; //EN Part-time odds
double ProbToTradePartTimeStatus; //EN Probability to trade
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Age group
PART_TIME_AGEGR part_time_agegr =
(integer_age <= 24) ? PTA_TO_24 :
(integer_age <= 34) ? PTA_25_TO_34 :
(integer_age <= 59) ? PTA_35_TO_59 :
(integer_age <= 64) ? PTA_60_TO_64 : PTA_65_PLUS;
//EN Full-time part-time status
FULL_PART_TIME full_part_time = { FPT_FULL };
//EN Education
EDUC_LEVEL4 educ_part_time =
(integer_age >= 24) ? educ_level4 :
(integer_age >= 18 && educ_level4 != EL4_ISCED2) ? EL4_ISCED3 : EL4_ISCED2;
QUANTILE_PART_TIME quantile_part_time = { 0 }; //EN Parttime quantile
double prob_part_time = { 0.0 }; //EN Probability of parttime work
logical flag_part_time_change = { FALSE }; //EN Flag for changing part-time status
double getProbPartTime(); //EN Get probability working parttime
};
actor Observer
{
void UpdatePartTime(); //EN Update parttime status
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation Person
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double Person::getProbPartTime()
{
double dProbPartTime = 0.0;
if (activity == ACT_EMPLOYED)
{
double dOdds = PartTimePara[sex][PTP_BASE][educ_part_time];
if (in_educ) dOdds = dOdds * PartTimePara[sex][PTP_INEDUC][educ_part_time];
if (health_cat == HC_BAD) dOdds = dOdds * PartTimePara[sex][PTP_SICK][educ_part_time];
if (children_in_family && agegr_youngest_child == 0) dOdds = dOdds * PartTimePara[sex][PTP_CHILD_00][educ_part_time];
if (children_in_family && agegr_youngest_child == 1) dOdds = dOdds * PartTimePara[sex][PTP_CHILD_03][educ_part_time];
if (children_in_family && agegr_youngest_child == 2) dOdds = dOdds * PartTimePara[sex][PTP_CHILD_06][educ_part_time];
if (children_in_family && agegr_youngest_child == 3) dOdds = dOdds * PartTimePara[sex][PTP_CHILD_10][educ_part_time];
if (part_time_agegr == PTA_TO_24) dOdds = dOdds * PartTimePara[sex][PTP_TO_24][educ_part_time];
if (part_time_agegr == PTA_25_TO_34) dOdds = dOdds * PartTimePara[sex][PTA_25_TO_34][educ_part_time];
if (part_time_agegr == PTA_35_TO_59) dOdds = dOdds * PartTimePara[sex][PTA_35_TO_59][educ_part_time];
if (part_time_agegr == PTA_60_TO_64) dOdds = dOdds * PartTimePara[sex][PTA_60_TO_64][educ_part_time];
if (part_time_agegr == PTA_65_PLUS) dOdds = dOdds * PartTimePara[sex][PTA_65_PLUS][educ_part_time];
dProbPartTime = dOdds / (dOdds + 1.0);
}
return dProbPartTime;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation Observer
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdatePartTime()
{
// Set probability to work part-time and reset flag
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
ptrPerson->prob_part_time = ptrPerson->getProbPartTime();
ptrPerson->flag_part_time_change = FALSE;
}
// Assign probability quantile
long nTotal = asEmployedByParttimeProbability->Count();
int nQuantile = 0;
double dStep = double(nTotal) / double(SIZE(QUANTILE_PART_TIME));
double dUpperBound = dStep;
for (long nJ = 0; nJ < nTotal; nJ++)
{
Person_ptr ptrPerson = asEmployedByParttimeProbability->Item(nJ);
if (double(nJ) > dUpperBound)
{
dUpperBound = dUpperBound + dStep;
nQuantile++;
}
ptrPerson->quantile_part_time = COERCE(QUANTILE_PART_TIME, nQuantile);
}
// Flag status changes
for (long nJ = 0; nJ < nTotal; nJ++)
{
Person_ptr ptrPerson = asEmployedByParttimeProbability->Item(nJ);
double dProb = ptrPerson->getProbPartTime();
// new status part-time
if (RandUniform(84) < dProb)
{
if (ptrPerson->full_part_time == FPT_FULL) ptrPerson->flag_part_time_change = TRUE;
}
// new status full-time
else if (ptrPerson->full_part_time == FPT_PART) ptrPerson->flag_part_time_change = TRUE;
}
// Trade and process status changes
for (long nJ = 0; nJ < nTotal; nJ++)
{
Person_ptr ptrPerson = asEmployedByParttimeProbability->Item(nJ);
// currently full-time flagged to change to part-time
if (ptrPerson->flag_part_time_change == TRUE && ptrPerson->full_part_time == FPT_FULL)
{
// is there somebody to trade? then both can stay in current state
if (asEmployedByFlag[FPT_PART][ptrPerson->quantile_part_time][TRUE]->Count() > 0 && RandUniform(87) < ProbToTradePartTimeStatus)
{
Person_ptr ptrTradePartner = asEmployedByFlag[FPT_PART][ptrPerson->quantile_part_time][TRUE]->GetRandom(RandUniform(85));
ptrTradePartner->flag_part_time_change = FALSE;
ptrPerson->flag_part_time_change = FALSE;
int ii = 1;
}
else
{
ptrPerson->flag_part_time_change = FALSE;
ptrPerson->full_part_time = FPT_PART;
}
}
// currently part-time flagged to change to full-time
else if (ptrPerson->flag_part_time_change == TRUE && ptrPerson->full_part_time == FPT_PART)
{
// is there somebody to trade? then both can stay in current state
if (asEmployedByFlag[FPT_FULL][ptrPerson->quantile_part_time][TRUE]->Count() > 0 && RandUniform(88) < ProbToTradePartTimeStatus)
{
Person_ptr ptrTradePartner = asEmployedByFlag[FPT_FULL][ptrPerson->quantile_part_time][TRUE]->GetRandom(RandUniform(86));
ptrTradePartner->flag_part_time_change = FALSE;
ptrPerson->flag_part_time_change = FALSE;
}
else
{
ptrPerson->flag_part_time_change = FALSE;
ptrPerson->full_part_time = FPT_FULL;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabParttime //EN Parttime
[in_projected_time && activity==ACT_EMPLOYED && is_resident]
{
sex + *
{
duration(full_part_time,FPT_PART) / duration() //EN Proportion part-time decimals=3
}
*integer_age +
*sim_year
};
table Person tabParttimeQuantile //EN Part-time quantile
[in_projected_time && activity == ACT_EMPLOYED && is_resident]
{
sim_year *
quantile_part_time + *
{
weighted_duration(prob_part_time) / duration(), //EN Probability parttime decimals=3
duration(full_part_time,FPT_PART) / duration() //EN Probability parttime decimals=3
}
};
Tax-Benefit System

TaxBen-General.mpp
This module implements general tax- and benefit-related functionalities that are not specific to any particular taxes or benefits. These include a set of states that define family types and income categories, which are used as inputs for tax and benefit calculations.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification TAX_FAM_TYPE //EN Family type
{
TFT_00, //EN No children
TFT_01, //EN One child [0]
TFT_02, //EN One child [1-5]
TFT_03, //EN One child [6-14]
TFT_04, //EN One child [15-18]
TFT_05, //EN Two children [0][0]
TFT_06, //EN Two children [0][1-5]
TFT_07, //EN Two children [0][6-14]
TFT_08, //EN Two children [0][15-18]
TFT_09, //EN Two children [1-5][1-5]
TFT_10, //EN Two children [1-5][6-14]
TFT_11, //EN Two children [1-5][15-18]
TFT_12, //EN Two children [6-14][6-14]
TFT_13, //EN Two children [6-14][15-18]
TFT_14, //EN Two children [15-18][15-18]
TFT_15, //EN Three children [0][0][0]
TFT_16, //EN Three children [0][0][1-5]
TFT_17, //EN Three children [0][0][6-14]
TFT_18, //EN Three children [0][0][15-18]
TFT_19, //EN Three children [0][1-5][1-5]
TFT_20, //EN Three children [0][1-5][6-14]
TFT_21, //EN Three children [0][1-5][15-18]
TFT_22, //EN Three children [0][6-14][6-14]
TFT_23, //EN Three children [0][6-14][15-18]
TFT_24, //EN Three children [0][15-18][15-18]
TFT_25, //EN Three children [1-5][1-5][1-5]
TFT_26, //EN Three children [1-5][1-5][6-14]
TFT_27, //EN Three children [1-5][1-5][15-18]
TFT_28, //EN Three children [1-5][6-14][6-14]
TFT_29, //EN Three children [1-5][6-14][15-18]
TFT_30, //EN Three children [1-5][15-18][15-18]
TFT_31, //EN Three children [6-14][6-14][6-14]
TFT_32, //EN Three children [6-14][6-14][15-18]
TFT_33, //EN Three children [6-14][15-18][15-18]
TFT_34 //EN Three children [15-18][15-18][15-18]
};
classification TAX_FAM_TYPE_PARENTAL //EN Family type with parental leave
{
TFTP_01, //EN One child [0]
TFTP_05, //EN Two children [0][0]
TFTP_06, //EN Two children [0][1-5]
TFTP_07, //EN Two children [0][6-14]
TFTP_08, //EN Two children [0][15-18]
TFTP_15, //EN Three children [0][0][0]
TFTP_16, //EN Three children [0][0][1-5]
TFTP_17, //EN Three children [0][0][6-14]
TFTP_18, //EN Three children [0][0][15-18]
TFTP_19, //EN Three children [0][1-5][1-5]
TFTP_20, //EN Three children [0][1-5][6-14]
TFTP_21, //EN Three children [0][1-5][15-18]
TFTP_22, //EN Three children [0][6-14][6-14]
TFTP_23, //EN Three children [0][6-14][15-18]
TFTP_24 //EN Three children [0][15-18][15-18]
};
classification TAX_FAM_TYPE_PENPEN //EN Family type couple pension pension
{
TFTPP_00, //EN No children
TFTPP_04, //EN One child [15-18]
TFTPP_14, //EN Two children [15-18][15-18]
TFTPP_34 //EN Three children [15-18][15-18][15-18]
};
classification TAX_FAM_TYPE_SINGPEN //EN Family type single pensioner
{
TFTSP_00, //EN No children
TFTSP_04, //EN One child [15-18]
TFTSP_14, //EN Two children [15-18][15-18]
TFTSP_34 //EN Three children [15-18][15-18][15-18]
};
partition EARN_PART_EMPLOYED //EN Earnings partition employed
{
0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0,
3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
};
partition EARN_PART_PARENTAL //EN Earnings partition parental
{
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
};
partition EARN_PART_PENSION //EN Earnings partition pension
{
0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1,
1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35, 1.4, 1.45, 1.5, 1.55, 1.6, 1.65, 1.7, 1.75, 1.8, 1.85, 1.9, 1.95
};
partition EARN_PART_UNEMPLOYED //EN Earnings partition
{
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Earning index employed
int earn_index_employed = (activity == ACT_EMPLOYED) ? split(relative_earnings, EARN_PART_EMPLOYED) : 0;
//EN Earning index unemployed
int earn_index_unemployed = (activity == ACT_UNEMPLOYED) ? split(relative_unemployment_benefit, EARN_PART_UNEMPLOYED) : 0;
//EN Earning index parental
int earn_index_parental = (activity == ACT_LEAVE) ? split(relative_parental_benefit, EARN_PART_PARENTAL) : 0;
//EN Earning index pension
int earn_index_pension = (activity == ACT_RETIRED) ? split(relative_pension, EARN_PART_PENSION) : 0;
//EN Earning index employed spouse
int earn_index_employed_spouse = (has_spouse) ? int(lSpouse->earn_index_employed) : 0;
//EN Earning index unemployed spouse
int earn_index_unemployed_spouse = (has_spouse) ? int(lSpouse->earn_index_unemployed) : 0;
//EN Earning index parental spouse
int earn_index_parental_spouse = (has_spouse) ? int(lSpouse->earn_index_parental) : 0;
//EN Earning index pension spouse
int earn_index_pension_spouse = (has_spouse) ? int(lSpouse->earn_index_pension) : 0;
//EN Child age 0 living with father
logical is_child_0_father = (integer_age == 0 && lives_with_father) ? TRUE : FALSE;
//EN Child age 1-5 living with father
logical is_child_1to5_father = (integer_age >= 1 && integer_age <= 5 && lives_with_father) ? TRUE : FALSE;
//EN Child age 6-14 living with father
logical is_child_6to14_father = (integer_age >= 6 && integer_age <= 14 && lives_with_father) ? TRUE : FALSE;
//EN Child age 15-18 living with father
logical is_child_15to18_father = (integer_age >= 15 && integer_age <= 18 && lives_with_father) ? TRUE : FALSE;
//EN Child age 0 living with mother
logical is_child_0_mother = (integer_age == 0 && lives_with_mother) ? TRUE : FALSE;
//EN Child age 1-5 living with mother
logical is_child_1to5_mother = (integer_age >= 1 && integer_age <= 5 && lives_with_mother) ? TRUE : FALSE;
//EN Child age 6-14 living with mother
logical is_child_6to14_mother = (integer_age >= 6 && integer_age <= 14 && lives_with_mother) ? TRUE : FALSE;
//EN Child age 15-18 living with mother
logical is_child_15to18_mother = (integer_age >= 15 && integer_age <= 18 && lives_with_mother) ? TRUE : FALSE;
//EN Number children age 0
short children_0 = (sex == FEMALE) ?
sum_over(mlRecentMotherChildren, is_child_0_mother) :
sum_over(mlRecentFatherChildren, is_child_0_father);
//EN Number children age 1-5
short children_1to5 = (sex == FEMALE) ?
sum_over(mlRecentMotherChildren, is_child_1to5_mother) :
sum_over(mlRecentFatherChildren, is_child_1to5_father);
//EN Number children age 6-14
short children_6to14 = (sex == FEMALE) ?
sum_over(mlRecentMotherChildren, is_child_6to14_mother) :
sum_over(mlRecentFatherChildren, is_child_6to14_father);
//EN Number children age 6-14
short children_15to18 = (sex == FEMALE) ?
sum_over(mlRecentMotherChildren, is_child_15to18_mother) :
sum_over(mlRecentFatherChildren, is_child_15to18_father);
//EN Family type
TAX_FAM_TYPE tax_fam_type =
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 0) ? TFT_00 : // No children
(children_0 == 1 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 0) ? TFT_01 : // One child [0]
(children_0 == 0 && children_1to5 == 1 && children_6to14 == 0 && children_15to18 == 0) ? TFT_02 : // One child [1-5]
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 1 && children_15to18 == 0) ? TFT_03 : // One child [6-14]
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 1) ? TFT_04 : // One child [15-18]
(children_0 == 2 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 0) ? TFT_05 : // Two children [0][0]
(children_0 == 1 && children_1to5 == 1 && children_6to14 == 0 && children_15to18 == 0) ? TFT_06 : // Two children [0][1-5]
(children_0 == 1 && children_1to5 == 0 && children_6to14 == 1 && children_15to18 == 0) ? TFT_07 : // Two children [0][6-14]
(children_0 == 1 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 1) ? TFT_08 : // Two children [0][15-18]
(children_0 == 0 && children_1to5 == 2 && children_6to14 == 0 && children_15to18 == 0) ? TFT_09 : // Two children [1-5][1-5]
(children_0 == 0 && children_1to5 == 1 && children_6to14 == 1 && children_15to18 == 0) ? TFT_10 : // Two children [1-5][6-14]
(children_0 == 0 && children_1to5 == 1 && children_6to14 == 0 && children_15to18 == 1) ? TFT_11 : // Two children [1-5][15-18]
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 2 && children_15to18 == 0) ? TFT_12 : // Two children [6-14][6-14]
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 1 && children_15to18 == 1) ? TFT_13 : // Two children [6-14][15-18]
(children_0 == 0 && children_1to5 == 0 && children_6to14 == 0 && children_15to18 == 2) ? TFT_14 : // Two children [15-18][15-18]
(children_0 >= 3 && children_1to5 >= 0 && children_6to14 >= 0 && children_15to18 >= 0) ? TFT_15 : // Three children [0][0][0]
(children_0 >= 2 && children_1to5 >= 1 && children_6to14 >= 0 && children_15to18 >= 0) ? TFT_16 : // Three children [0][0][1-5]
(children_0 >= 2 && children_1to5 >= 0 && children_6to14 >= 1 && children_15to18 >= 0) ? TFT_17 : // Three children [0][0][6-14]
(children_0 >= 2 && children_1to5 >= 0 && children_6to14 >= 0 && children_15to18 >= 1) ? TFT_18 : // Three children [0][0][15-18]
(children_0 >= 1 && children_1to5 >= 2 && children_6to14 >= 0 && children_15to18 >= 0) ? TFT_19 : // Three children [0][1-5][1-5]
(children_0 >= 1 && children_1to5 >= 1 && children_6to14 >= 1 && children_15to18 >= 0) ? TFT_20 : // Three children [0][1-5][6-14]
(children_0 >= 1 && children_1to5 >= 1 && children_6to14 >= 0 && children_15to18 >= 1) ? TFT_21 : // Three children [0][1-5][15-18]
(children_0 >= 1 && children_1to5 >= 0 && children_6to14 >= 2 && children_15to18 >= 0) ? TFT_22 : // Three children [0][6-14][6-14]
(children_0 >= 1 && children_1to5 >= 0 && children_6to14 >= 1 && children_15to18 >= 1) ? TFT_23 : // Three children [0][6-14][15-18]
(children_0 >= 1 && children_1to5 >= 0 && children_6to14 >= 0 && children_15to18 >= 2) ? TFT_24 : // Three children [0][15-18][15-18]
(children_0 >= 0 && children_1to5 >= 3 && children_6to14 >= 0 && children_15to18 >= 0) ? TFT_25 : // Three children [1-5][1-5][1-5]
(children_0 >= 0 && children_1to5 >= 2 && children_6to14 >= 1 && children_15to18 >= 0) ? TFT_26 : // Three children [1-5][1-5][6-14]
(children_0 >= 0 && children_1to5 >= 2 && children_6to14 >= 0 && children_15to18 >= 1) ? TFT_27 : // Three children [1-5][1-5][15-18]
(children_0 >= 0 && children_1to5 >= 1 && children_6to14 >= 2 && children_15to18 >= 0) ? TFT_28 : // Three children [1-5][6-14][6-14]
(children_0 >= 0 && children_1to5 >= 1 && children_6to14 >= 1 && children_15to18 >= 1) ? TFT_29 : // Three children [1-5][6-14][15-18]
(children_0 >= 0 && children_1to5 >= 1 && children_6to14 >= 0 && children_15to18 >= 2) ? TFT_30 : // Three children [1-5][15-18][15-18]
(children_0 >= 0 && children_1to5 >= 0 && children_6to14 >= 3 && children_15to18 >= 0) ? TFT_31 : // Three children [6-14][6-14][6-14]
(children_0 >= 0 && children_1to5 >= 0 && children_6to14 >= 2 && children_15to18 >= 1) ? TFT_32 : // Three children [6-14][6-14][15-18]
(children_0 >= 0 && children_1to5 >= 0 && children_6to14 >= 1 && children_15to18 >= 2) ? TFT_33 : // Three children [6-14][15-18][15-18]
TFT_34;
//EN Family type with parental leave
TAX_FAM_TYPE_PARENTAL tax_fam_type_parental =
(tax_fam_type == TFT_01) ? TFTP_01 :
(tax_fam_type == TFT_05) ? TFTP_05 :
(tax_fam_type == TFT_06) ? TFTP_06 :
(tax_fam_type == TFT_07) ? TFTP_07 :
(tax_fam_type == TFT_08) ? TFTP_08 :
(tax_fam_type == TFT_15) ? TFTP_15 :
(tax_fam_type == TFT_16) ? TFTP_16 :
(tax_fam_type == TFT_17) ? TFTP_17 :
(tax_fam_type == TFT_18) ? TFTP_18 :
(tax_fam_type == TFT_19) ? TFTP_19 :
(tax_fam_type == TFT_20) ? TFTP_20 :
(tax_fam_type == TFT_21) ? TFTP_21 :
(tax_fam_type == TFT_22) ? TFTP_22 :
(tax_fam_type == TFT_23) ? TFTP_23 : TFTP_24;
//EN Family type couple pension pension
TAX_FAM_TYPE_PENPEN tax_fam_type_penpen =
(tax_fam_type == TFT_00) ? TFTPP_00 :
(tax_fam_type == TFT_04) ? TFTPP_04 :
(tax_fam_type == TFT_14) ? TFTPP_14 : TFTPP_34;
//EN Family type single pensioner
TAX_FAM_TYPE_SINGPEN tax_fam_type_singpen =
(tax_fam_type == TFT_00) ? TFTSP_00 :
(tax_fam_type == TFT_04) ? TFTSP_04 :
(tax_fam_type == TFT_14) ? TFTSP_14 : TFTSP_34;
};
actor Observer
{
//EN Reset all benefits
void ResetAllBenefits();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::ResetAllBenefits()
{
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
asAllPerson->Item(nJ)->oldage_benefit = 0.0;
asAllPerson->Item(nJ)->family_benefit = 0.0;
asAllPerson->Item(nJ)->education_benefit = 0.0;
asAllPerson->Item(nJ)->social_benefit = 0.0;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabFamilyType //EN Family type
[in_projected_time && is_resident]
{
sex+ *
{
duration()
}
* integer_age
* tax_fam_type+
};
TaxBen-Accounts.mpp
This module implements individual accounts. Currently (the list will be expanded as additional transfers etc. become available) people store yearly totals of:
Earnings
Unemployment benefits
Parental leave benefits
Public pensions
Own social insurance contributions
Social insurance employer contributions
Income tax
Old-age benefits
Family benefits
Education benefits
Social benefits
These accounts are used for lifetime accounting, such as calculating the present value of transfers. Updates are performed at the end of each calendar year, as well as upon death or emigration. The calculation of amounts is based on continuous time updates of the respective variable (such as earnings), thus adapting to all changes throughout the year.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification ACCOUNT_ITEMS //EN Account items
{
ACI_EARNINGS, //EN Earnings
ACI_UE_BENEFITS, //EN Unemployment benefits
ACI_LEAVE_BENEFITS, //EN Parental benefits
ACI_PENSION, //EN Public pensions
ACI_SI_OWN, //EN Social insurance own contributions
ACI_SI_EMPLOYER, //EN Social insurance employer contributions
ACI_INCOME_TAX, //EN Income tax
ACI_OLDAGE_BENEFIT, //EN Oldage benefit
ACI_FAMILY_BENEFIT, //EN Family benefit
ACI_EDUCATION_BENEFIT, //EN Education benefit
ACI_SOCIAL_BENEFIT //EN Social benefit
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
void doInitAccount(); //EN Initialise accounts
void doUpdateAccount(); //EN Update accounts
double account[AGE_RANGE][ACCOUNT_ITEMS]; //EN Personal account
double year_end_earnings = { 0.0 }; //EN Earnings
double year_end_ue_benefits = { 0.0 }; //EN Unemployment benefits
double year_end_leave_benefits = { 0.0 }; //EN Parental benefits
double year_end_pension = { 0.0 }; //EN Pension
double year_end_si_own = { 0.0 }; //EN Social insurance own
double year_end_si_employer = { 0.0 }; //EN Social insurabnce employer
double year_end_income_tax = { 0.0 }; //EN Income tax
double year_end_oldage_benefit = { 0.0 }; //EN Oldage benefit
double year_end_family_benefit = { 0.0 }; //EN Family benefit
double year_end_education_benefit = { 0.0 }; //EN Education benefit
double year_end_social_benefit = { 0.0 }; //EN Social benefit
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::doInitAccount()
{
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nItem = 0; nItem < SIZE(ACCOUNT_ITEMS); nItem++)
{
account[nAge][nItem] = 0.0;
}
}
}
void Person::doUpdateAccount()
{
if (in_projected_time)
{
account[integer_age][ACI_EARNINGS] = accum_earnings;
account[integer_age][ACI_UE_BENEFITS] = accum_unemployment_benefit;
account[integer_age][ACI_LEAVE_BENEFITS] = accum_parental_benefit;
account[integer_age][ACI_PENSION] = accum_pension;
account[integer_age][ACI_SI_OWN] = si_accum_contribution_own;
account[integer_age][ACI_SI_EMPLOYER] = si_accum_contribution_employer;
account[integer_age][ACI_INCOME_TAX] = accum_income_tax;
account[integer_age][ACI_OLDAGE_BENEFIT] = accum_oldage_benefit;
account[integer_age][ACI_FAMILY_BENEFIT] = accum_family_benefit;
account[integer_age][ACI_EDUCATION_BENEFIT] = accum_education_benefit;
account[integer_age][ACI_SOCIAL_BENEFIT] = accum_social_benefit;
year_end_earnings = accum_earnings;
year_end_ue_benefits = accum_unemployment_benefit;
year_end_leave_benefits = accum_parental_benefit;
year_end_pension = accum_pension;
year_end_si_own = si_accum_contribution_own;
year_end_si_employer = si_accum_contribution_employer;
year_end_income_tax = accum_income_tax;
year_end_oldage_benefit = accum_oldage_benefit;
year_end_family_benefit = accum_family_benefit;
year_end_education_benefit = accum_education_benefit;
year_end_social_benefit = accum_social_benefit;
};
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabYearlyAccountTotals2025 //EN Yearly accounts 2025
[trigger_entrances(calendar_year, 2025) && is_resident]
{
sex + *
activity + *
{
value_in(year_end_earnings) / unit, //EN Earnings
value_in(year_end_ue_benefits) / unit, //EN UE
value_in(year_end_leave_benefits) / unit, //EN Parental
value_in(year_end_pension) / unit, //EN Pension
value_in(year_end_si_own) / unit, //EN SI own
value_in(year_end_si_employer) / unit, //EN SI empoyer
value_in(year_end_income_tax) / unit, //EN Income tax
value_in(year_end_oldage_benefit) / unit, //EN OA benefit
value_in(year_end_family_benefit) / unit, //EN Family benefit
value_in(year_end_education_benefit) / unit, //EN Education benefit
value_in(year_end_social_benefit) / unit //EN Social benefit
}
* integer_age
};
TaxBen-IncomeTax.mpp
This module implements income taxes on earnings and earning-related incomes (pensions, leave benefits, unemployment benefits) based on parameter tables created using the Euromod Hypothetical Household Tool (HHoT). These tables are multidimensional by family type and fine-grained income categories of earnings, unemployment benefits, pensions, and maternity and parental leave. Family types were created by accounting for partnership status and family composition according to the number and age of children. There are separate parameters for singles and couples, combined with four income types for both partners. For instance, the parameter ‘Income tax couple employed x unemployed’ applies to couples where the person is employed and has an unemployed spouse. Income taxes are calculated at the indicidual level. Taxes are updated in continuouse time whenever the tax base changes.
Parameters:
Income tax single employed
Income tax single parental
Income tax single retired
Income tax single unemployed
Income tax couple employed x employed
Income tax couple employed x unemployed
Income tax couple employed x parental
Income tax couple employed x pension
Income tax couple employed x out
Income tax couple unemployed x employed
Income tax couple unemployed x unemployed
Income tax couple unemployed x parental
Income tax couple unemployed x pension
Income tax couple unemployed x out
Income tax couple parental x employed
Income tax couple parental x unemployed
Income tax couple parental x pension
Income tax couple parental x out
Income tax couple pension x employed
Income tax couple pension x unemployed
Income tax couple pension x parental
Income tax couple pension x pension
Income tax couple pension x out
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_INCOME_TAX //EN Income Tax
{
IncomeTaxSingleEmployed,
IncomeTaxSingleUnemployed,
IncomeTaxSingleParental,
IncomeTaxSingleRetired,
IncomeTaxCoupleEmployedEmployed,
IncomeTaxCoupleEmployedUnemployed,
IncomeTaxCoupleEmployedParental,
IncomeTaxCoupleEmployedPension,
IncomeTaxCoupleEmployedOut,
IncomeTaxCoupleUnemployedEmployed,
IncomeTaxCoupleUnemployedUnemployed,
IncomeTaxCoupleUnemployedParental,
IncomeTaxCoupleUnemployedPension,
IncomeTaxCoupleUnemployedOut,
IncomeTaxCoupleParentalEmployed,
IncomeTaxCoupleParentalUnemployed,
IncomeTaxCoupleParentalPension,
IncomeTaxCoupleParentalOut,
IncomeTaxCouplePensionEmployed,
IncomeTaxCouplePensionUnemployed,
IncomeTaxCouplePensionParental,
IncomeTaxCouplePensionPension,
IncomeTaxCouplePensionOut
};
parameters
{
//EN Income tax single employed
double IncomeTaxSingleEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Income tax single parental
double IncomeTaxSingleParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Income tax single retired
double IncomeTaxSingleRetired[TAX_FAM_TYPE_SINGPEN][EARN_PART_PENSION];
//EN Income tax single unemployed
double IncomeTaxSingleUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Income tax couple employed x employed
double IncomeTaxCoupleEmployedEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_EMPLOYED];
//EN Income tax couple employed x unemployed
double IncomeTaxCoupleEmployedUnemployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_UNEMPLOYED];
//EN Income tax couple employed x parental
double IncomeTaxCoupleEmployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_EMPLOYED][EARN_PART_PARENTAL];
//EN Income tax couple employed x pension
double IncomeTaxCoupleEmployedPension[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_PENSION];
//EN Income tax couple employed x out
double IncomeTaxCoupleEmployedOut[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Income tax couple unemployed x employed
double IncomeTaxCoupleUnemployedEmployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_EMPLOYED];
//EN Income tax couple unemployed x unemployed
double IncomeTaxCoupleUnemployedUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_UNEMPLOYED];
//EN Income tax couple unemployed x parental
double IncomeTaxCoupleUnemployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_UNEMPLOYED][EARN_PART_PARENTAL];
//EN Income tax couple unemployed x pension
double IncomeTaxCoupleUnemployedPension[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_PENSION];
//EN Income tax couple unemployed x out
double IncomeTaxCoupleUnemployedOut[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Income tax couple parental x employed
double IncomeTaxCoupleParentalEmployed[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_EMPLOYED];
//EN Income tax couple parental x unemployed
double IncomeTaxCoupleParentalUnemployed[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_UNEMPLOYED];
//EN Income tax couple parental x pension
double IncomeTaxCoupleParentalPension[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_PENSION];
//EN Income tax couple parental x out
double IncomeTaxCoupleParentalOut[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Income tax couple pension x employed
double IncomeTaxCouplePensionEmployed[TAX_FAM_TYPE][EARN_PART_PENSION][EARN_PART_EMPLOYED];
//EN Income tax couple pension x unemployed
double IncomeTaxCouplePensionUnemployed[TAX_FAM_TYPE][EARN_PART_PENSION][EARN_PART_UNEMPLOYED];
//EN Income tax couple pension x parental
double IncomeTaxCouplePensionParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PENSION][EARN_PART_PARENTAL];
//EN Income tax couple pension x pension
double IncomeTaxCouplePensionPension[TAX_FAM_TYPE_PENPEN][EARN_PART_PENSION][EARN_PART_PENSION];
//EN Income tax couple pension x out
double IncomeTaxCouplePensionOut[TAX_FAM_TYPE][EARN_PART_PENSION];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Labor income tax
double income_tax =
(!has_spouse && activity == ACT_EMPLOYED) ? earnings * IncomeTaxSingleEmployed[tax_fam_type][earn_index_employed] :
(!has_spouse && activity == ACT_UNEMPLOYED) ? unemployment_benefit * IncomeTaxSingleUnemployed[tax_fam_type][earn_index_unemployed] :
(!has_spouse && activity == ACT_LEAVE) ? parental_benefit * IncomeTaxSingleParental[tax_fam_type_parental][earn_index_parental] :
(!has_spouse && activity == ACT_RETIRED) ? pension * IncomeTaxSingleRetired[tax_fam_type_singpen][earn_index_pension] :
(has_spouse && activity == ACT_EMPLOYED && activity_spouse == ACT_EMPLOYED) ? earnings * IncomeTaxCoupleEmployedEmployed[tax_fam_type][earn_index_employed][earn_index_employed_spouse] :
(has_spouse && activity == ACT_EMPLOYED && activity_spouse == ACT_UNEMPLOYED) ? earnings * IncomeTaxCoupleEmployedUnemployed[tax_fam_type][earn_index_employed][earn_index_unemployed_spouse] :
(has_spouse && activity == ACT_EMPLOYED && activity_spouse == ACT_LEAVE) ? earnings * IncomeTaxCoupleEmployedParental[tax_fam_type_parental][earn_index_employed][earn_index_parental_spouse] :
(has_spouse && activity == ACT_EMPLOYED && activity_spouse == ACT_RETIRED) ? earnings * IncomeTaxCoupleEmployedPension[tax_fam_type][earn_index_employed][earn_index_pension_spouse] :
(has_spouse && activity == ACT_EMPLOYED) ? earnings * IncomeTaxCoupleEmployedOut[tax_fam_type][earn_index_employed] :
(has_spouse && activity == ACT_UNEMPLOYED && activity_spouse == ACT_EMPLOYED) ? unemployment_benefit * IncomeTaxCoupleUnemployedEmployed[tax_fam_type][earn_index_unemployed][earn_index_employed_spouse] :
(has_spouse && activity == ACT_UNEMPLOYED && activity_spouse == ACT_UNEMPLOYED) ? unemployment_benefit * IncomeTaxCoupleUnemployedUnemployed[tax_fam_type][earn_index_unemployed][earn_index_unemployed_spouse] :
(has_spouse && activity == ACT_UNEMPLOYED && activity_spouse == ACT_LEAVE) ? unemployment_benefit * IncomeTaxCoupleUnemployedParental[tax_fam_type_parental][earn_index_unemployed][earn_index_parental_spouse] :
(has_spouse && activity == ACT_UNEMPLOYED && activity_spouse == ACT_RETIRED) ? unemployment_benefit * IncomeTaxCoupleUnemployedPension[tax_fam_type][earn_index_unemployed][earn_index_pension_spouse] :
(has_spouse && activity == ACT_UNEMPLOYED) ? unemployment_benefit * IncomeTaxCoupleUnemployedOut[tax_fam_type][earn_index_unemployed] :
(has_spouse && activity == ACT_LEAVE && activity_spouse == ACT_EMPLOYED) ? parental_benefit * IncomeTaxCoupleParentalEmployed[tax_fam_type_parental][earn_index_parental][earn_index_employed_spouse] :
(has_spouse && activity == ACT_LEAVE && activity_spouse == ACT_UNEMPLOYED) ? parental_benefit * IncomeTaxCoupleParentalUnemployed[tax_fam_type_parental][earn_index_parental][earn_index_unemployed_spouse] :
(has_spouse && activity == ACT_LEAVE && activity_spouse == ACT_RETIRED) ? parental_benefit * IncomeTaxCoupleParentalPension[tax_fam_type_parental][earn_index_parental][earn_index_pension_spouse] :
(has_spouse && activity == ACT_LEAVE) ? parental_benefit * IncomeTaxCoupleParentalOut[tax_fam_type_parental][earn_index_parental] :
(has_spouse && activity == ACT_RETIRED && activity_spouse == ACT_EMPLOYED) ? pension * IncomeTaxCouplePensionEmployed[tax_fam_type][earn_index_pension][earn_index_employed_spouse] :
(has_spouse && activity == ACT_RETIRED && activity_spouse == ACT_UNEMPLOYED) ? pension * IncomeTaxCouplePensionUnemployed[tax_fam_type][earn_index_pension][earn_index_unemployed_spouse] :
(has_spouse && activity == ACT_RETIRED && activity_spouse == ACT_LEAVE) ? pension * IncomeTaxCouplePensionParental[tax_fam_type_parental][earn_index_pension][earn_index_parental_spouse] :
(has_spouse && activity == ACT_RETIRED && activity_spouse == ACT_RETIRED) ? pension * IncomeTaxCouplePensionPension[tax_fam_type_penpen][earn_index_pension][earn_index_pension_spouse] :
(has_spouse && activity == ACT_RETIRED) ? pension * IncomeTaxCouplePensionOut[tax_fam_type][earn_index_pension] :
0.0;
//EN Accumulated income tax in current year
double accum_income_tax = active_spell_weighted_duration(year_spell, TRUE, income_tax);
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TaxBen-Education.mpp
This module implements education benefits based on parameter tables created using the Euromod Hypothetical Household Tool (HHoT). These tables are multidimensional by family type and fine-grained income categories of earnings, unemployment benefits, pensions, and maternity and parental leave. Family types were created by accounting for partnership status and family composition according to the number and age of children, resulting in 35 types in total. There are separate parameters for singles and couples, combined with four income types for both partners. For instance, the parameter ‘Education benefit couple employed x unemployed’ applies to couples where one partner is employed and the other is unemployed. Benefit amounts are retrieved on the family level and distributed by the family head to children in education. Benefits are updated monthly.
Parameters:
Education benefit single employed
Education benefit single parental
Education benefit single retired
Education benefit single unemployed
Education benefit single out
Education benefit couple employed x employed
Education benefit couple employed x unemployed
Education benefit couple employed x parental
Education benefit couple employed x pension
Education benefit couple employed x out
Education benefit couple unemployed x unemployed
Education benefit couple unemployed x parental
Education benefit couple unemployed x pension
Education benefit couple unemployed x out
Education benefit couple parental x pension
Education benefit couple parental x out
Education benefit couple pension x pension
Education benefit couple pension x out
Education benefit couple out x out
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_EDUCATION_BENEFITS //EN Education benefits
{
EducationBenefitSingleEmployed,
EducationBenefitSingleParental,
EducationBenefitSingleRetired,
EducationBenefitSingleUnemployed,
EducationBenefitSingleOut,
EducationBenefitCoupleEmployedEmployed,
EducationBenefitCoupleEmployedUnemployed,
EducationBenefitCoupleEmployedParental,
EducationBenefitCoupleEmployedPension,
EducationBenefitCoupleEmployedOut,
EducationBenefitCoupleUnemployedUnemployed,
EducationBenefitCoupleUnemployedParental,
EducationBenefitCoupleUnemployedPension,
EducationBenefitCoupleUnemployedOut,
EducationBenefitCoupleParentalPension,
EducationBenefitCoupleParentalOut,
EducationBenefitCouplePensionPension,
EducationBenefitCouplePensionOut,
EducationBenefitCoupleOutOut
};
parameters
{
//EN Education benefit single employed
double EducationBenefitSingleEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Education benefit single parental
double EducationBenefitSingleParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Education benefit single retired
double EducationBenefitSingleRetired[TAX_FAM_TYPE_SINGPEN][EARN_PART_PENSION];
//EN Education benefit single unemployed
double EducationBenefitSingleUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Education benefit single out
double EducationBenefitSingleOut[TAX_FAM_TYPE];
//EN Education benefit couple employed x employed
double EducationBenefitCoupleEmployedEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_EMPLOYED];
//EN Education benefit couple employed x unemployed
double EducationBenefitCoupleEmployedUnemployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_UNEMPLOYED];
//EN Education benefit couple employed x parental
double EducationBenefitCoupleEmployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_EMPLOYED][EARN_PART_PARENTAL];
//EN Education benefit couple employed x pension
double EducationBenefitCoupleEmployedPension[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_PENSION];
//EN Education benefit couple employed x out
double EducationBenefitCoupleEmployedOut[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Education benefit couple unemployed x unemployed
double EducationBenefitCoupleUnemployedUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_UNEMPLOYED];
//EN Education benefit couple unemployed x parental
double EducationBenefitCoupleUnemployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_UNEMPLOYED][EARN_PART_PARENTAL];
//EN Education benefit couple unemployed x pension
double EducationBenefitCoupleUnemployedPension[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_PENSION];
//EN Education benefit couple unemployed x out
double EducationBenefitCoupleUnemployedOut[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Education benefit couple parental x pension
double EducationBenefitCoupleParentalPension[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_PENSION];
//EN Education benefit couple parental x out
double EducationBenefitCoupleParentalOut[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Education benefit couple pension x pension
double EducationBenefitCouplePensionPension[TAX_FAM_TYPE_PENPEN][EARN_PART_PENSION][EARN_PART_PENSION];
//EN Education benefit couple pension x out
double EducationBenefitCouplePensionOut[TAX_FAM_TYPE][EARN_PART_PENSION];
//EN Education benefit couple out x out
double EducationBenefitCoupleOutOut[TAX_FAM_TYPE];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Education benefit
double education_benefit = { 0.0 };
//EN Accumulated Education benefits in current year
double accum_education_benefit = active_spell_weighted_duration(year_spell, TRUE, education_benefit);
//EN Update education benefit
void doUpdateEducationBenefit();
};
actor Observer
{
//EN Update education benefit
void UpdateEducationBenefit();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateEducationBenefit()
{
// Update family benefit
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateEducationBenefit();
}
void Person::doUpdateEducationBenefit()
{
double dBenefit = 0.0;
// Singles
if (!has_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = EducationBenefitSingleEmployed[tax_fam_type][earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = EducationBenefitSingleUnemployed[tax_fam_type][earn_index_unemployed];
else if (activity == ACT_LEAVE) dBenefit = EducationBenefitSingleParental[tax_fam_type_parental][earn_index_parental];
else if (activity == ACT_RETIRED) dBenefit = EducationBenefitSingleRetired[tax_fam_type_singpen][earn_index_pension];
else dBenefit = EducationBenefitSingleOut[tax_fam_type];
}
// Couple symmetric
else if (activity == activity_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = EducationBenefitCoupleEmployedEmployed[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = EducationBenefitCoupleUnemployedUnemployed[tax_fam_type][earn_index_unemployed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_RETIRED) dBenefit = EducationBenefitCouplePensionPension[tax_fam_type_penpen][earn_index_pension][(int)lSpouse->earn_index_pension];
else dBenefit = EducationBenefitCoupleOutOut[tax_fam_type];
}
// combinations somebody employed
else if (activity == ACT_EMPLOYED || activity_spouse == ACT_EMPLOYED)
{
if (activity_spouse == ACT_UNEMPLOYED) dBenefit = EducationBenefitCoupleEmployedUnemployed[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_UNEMPLOYED) dBenefit = EducationBenefitCoupleEmployedUnemployed[tax_fam_type][(int)lSpouse->earn_index_employed][earn_index_unemployed];
else if (activity_spouse == ACT_LEAVE) dBenefit = EducationBenefitCoupleEmployedParental[tax_fam_type_parental][earn_index_employed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = EducationBenefitCoupleEmployedParental[tax_fam_type_parental][(int)lSpouse->earn_index_employed][earn_index_parental];
else if (activity_spouse == ACT_RETIRED) dBenefit = EducationBenefitCoupleEmployedPension[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = EducationBenefitCoupleEmployedPension[tax_fam_type][(int)lSpouse->earn_index_employed][earn_index_pension];
else if (activity_spouse == ACT_EMPLOYED) dBenefit = EducationBenefitCoupleEmployedOut[tax_fam_type][(int)lSpouse->earn_index_employed];
else dBenefit = EducationBenefitCoupleEmployedOut[tax_fam_type][earn_index_employed];
}
// remaining combinations somebody unemployed
else if (activity == ACT_UNEMPLOYED || activity_spouse == ACT_UNEMPLOYED)
{
if (activity_spouse == ACT_LEAVE) dBenefit = EducationBenefitCoupleUnemployedParental[tax_fam_type_parental][earn_index_unemployed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = EducationBenefitCoupleUnemployedParental[tax_fam_type_parental][(int)lSpouse->earn_index_unemployed][earn_index_parental];
if (activity_spouse == ACT_RETIRED) dBenefit = EducationBenefitCoupleUnemployedPension[tax_fam_type][earn_index_unemployed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = EducationBenefitCoupleUnemployedPension[tax_fam_type][(int)lSpouse->earn_index_unemployed][earn_index_pension];
else if (activity_spouse == ACT_UNEMPLOYED) dBenefit = EducationBenefitCoupleUnemployedOut[tax_fam_type][(int)lSpouse->earn_index_unemployed];
else dBenefit = dBenefit = EducationBenefitCoupleUnemployedOut[tax_fam_type][earn_index_unemployed];
}
// remaining combinations somebody parental leave
else if (activity == ACT_LEAVE || activity_spouse == ACT_LEAVE)
{
if (activity_spouse == ACT_RETIRED) dBenefit = EducationBenefitCoupleParentalPension[tax_fam_type_parental][earn_index_parental][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = EducationBenefitCoupleParentalPension[tax_fam_type_parental][(int)lSpouse->earn_index_parental][earn_index_pension];
else if (activity_spouse == ACT_LEAVE) dBenefit = EducationBenefitCoupleParentalOut[tax_fam_type_parental][(int)lSpouse->earn_index_parental];
else dBenefit = dBenefit = EducationBenefitCoupleParentalOut[tax_fam_type_parental][earn_index_parental];
}
// remaining combinations somebody pension
else if (activity == ACT_RETIRED || activity_spouse == ACT_RETIRED)
{
if (activity_spouse == ACT_RETIRED) dBenefit = EducationBenefitCouplePensionOut[tax_fam_type][(int)lSpouse->earn_index_pension];
else dBenefit = dBenefit = EducationBenefitCouplePensionOut[tax_fam_type][earn_index_pension];
}
education_benefit = dBenefit * lObserver->average_earnings;
}
TaxBen-Family.mpp
This module implements family benefits based on parameter tables created using the Euromod Hypothetical Household Tool (HHoT). These tables are multidimensional by family type and fine-grained income categories of earnings, unemployment benefits, pensions, and maternity and parental leave. Family types were created by accounting for partnership status and family composition according to the number and age of children. There are separate parameters for singles and couples, combined with four income types for both partners. For instance, the parameter ‘Family benefit couple employed x unemployed’ applies to couples where one partner is employed and the other is unemployed. Benefit amounts are retrieved on the family level and distributed by the family head to children. Benefits are updated monthly.
Parameters:
Family benefit single employed
Family benefit single parental
Family benefit single retired
Family benefit single unemployed
Family benefit single out
Family benefit couple employed x employed
Family benefit couple employed x unemployed
Family benefit couple employed x parental
Family benefit couple employed x pension
Family benefit couple employed x out
Family benefit couple unemployed x unemployed
Family benefit couple unemployed x parental
Family benefit couple unemployed x pension
Family benefit couple unemployed x out
Family benefit couple parental x pension
Family benefit couple parental x out
Family benefit couple pension x pension
Family benefit couple pension x out
Family benefit couple out x out
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_FAMILY_BENEFITS //EN Family benefits
{
FamilyBenefitSingleEmployed,
FamilyBenefitSingleParental,
FamilyBenefitSingleRetired,
FamilyBenefitSingleUnemployed,
FamilyBenefitSingleOut,
FamilyBenefitCoupleEmployedEmployed,
FamilyBenefitCoupleEmployedUnemployed,
FamilyBenefitCoupleEmployedParental,
FamilyBenefitCoupleEmployedPension,
FamilyBenefitCoupleEmployedOut,
FamilyBenefitCoupleUnemployedUnemployed,
FamilyBenefitCoupleUnemployedParental,
FamilyBenefitCoupleUnemployedPension,
FamilyBenefitCoupleUnemployedOut,
FamilyBenefitCoupleParentalPension,
FamilyBenefitCoupleParentalOut,
FamilyBenefitCouplePensionPension,
FamilyBenefitCouplePensionOut,
FamilyBenefitCoupleOutOut
};
parameters
{
//EN Family benefit single employed
double FamilyBenefitSingleEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Family benefit single parental
double FamilyBenefitSingleParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Family benefit single retired
double FamilyBenefitSingleRetired[TAX_FAM_TYPE_SINGPEN][EARN_PART_PENSION];
//EN Family benefit single unemployed
double FamilyBenefitSingleUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Family benefit single out
double FamilyBenefitSingleOut[TAX_FAM_TYPE];
//EN Family benefit couple employed x employed
double FamilyBenefitCoupleEmployedEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_EMPLOYED];
//EN Family benefit couple employed x unemployed
double FamilyBenefitCoupleEmployedUnemployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_UNEMPLOYED];
//EN Family benefit couple employed x parental
double FamilyBenefitCoupleEmployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_EMPLOYED][EARN_PART_PARENTAL];
//EN Family benefit couple employed x pension
double FamilyBenefitCoupleEmployedPension[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_PENSION];
//EN Family benefit couple employed x out
double FamilyBenefitCoupleEmployedOut[TAX_FAM_TYPE][EARN_PART_EMPLOYED];
//EN Family benefit couple unemployed x unemployed
double FamilyBenefitCoupleUnemployedUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_UNEMPLOYED];
//EN Family benefit couple unemployed x parental
double FamilyBenefitCoupleUnemployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_UNEMPLOYED][EARN_PART_PARENTAL];
//EN Family benefit couple unemployed x pension
double FamilyBenefitCoupleUnemployedPension[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_PENSION];
//EN Family benefit couple unemployed x out
double FamilyBenefitCoupleUnemployedOut[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];
//EN Family benefit couple parental x pension
double FamilyBenefitCoupleParentalPension[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_PENSION];
//EN Family benefit couple parental x out
double FamilyBenefitCoupleParentalOut[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];
//EN Family benefit couple pension x pension
double FamilyBenefitCouplePensionPension[TAX_FAM_TYPE_PENPEN][EARN_PART_PENSION][EARN_PART_PENSION];
//EN Family benefit couple pension x out
double FamilyBenefitCouplePensionOut[TAX_FAM_TYPE][EARN_PART_PENSION];
//EN Family benefit couple out x out
double FamilyBenefitCoupleOutOut[TAX_FAM_TYPE];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Family benefit
double family_benefit = { 0.0 };
//EN Accumulated family benefits in current year (own)
double accum_family_benefit = active_spell_weighted_duration(year_spell, TRUE, family_benefit);
//EN Update family benefit
void doUpdateFamilyBenefit();
};
actor Observer
{
//EN Update family benefit
void UpdateFamilyBenefit();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateFamilyBenefit()
{
// Update family benefit
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateFamilyBenefit();
}
void Person::doUpdateFamilyBenefit()
{
double dBenefit = 0.0;
// Singles
if (!has_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = FamilyBenefitSingleEmployed[tax_fam_type][earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitSingleUnemployed[tax_fam_type][earn_index_unemployed];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitSingleParental[tax_fam_type_parental][earn_index_parental];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitSingleRetired[tax_fam_type_singpen][earn_index_pension];
else dBenefit = FamilyBenefitSingleOut[tax_fam_type];
}
// Couple symmetric
else if (activity == activity_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = FamilyBenefitCoupleEmployedEmployed[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleUnemployedUnemployed[tax_fam_type][earn_index_unemployed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCouplePensionPension[tax_fam_type_penpen][earn_index_pension][(int)lSpouse->earn_index_pension];
else dBenefit = FamilyBenefitCoupleOutOut[tax_fam_type];
}
// combinations somebody employed
else if (activity == ACT_EMPLOYED || activity_spouse == ACT_EMPLOYED)
{
if (activity_spouse == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleEmployedUnemployed[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleEmployedUnemployed[tax_fam_type][(int)lSpouse->earn_index_employed][earn_index_unemployed];
else if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleEmployedParental[tax_fam_type_parental][earn_index_employed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitCoupleEmployedParental[tax_fam_type_parental][(int)lSpouse->earn_index_employed][earn_index_parental];
else if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleEmployedPension[tax_fam_type][earn_index_employed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleEmployedPension[tax_fam_type][(int)lSpouse->earn_index_employed][earn_index_pension];
else if (activity_spouse == ACT_EMPLOYED) dBenefit = FamilyBenefitCoupleEmployedOut[tax_fam_type][(int)lSpouse->earn_index_employed];
else dBenefit = FamilyBenefitCoupleEmployedOut[tax_fam_type][earn_index_employed];
}
// remaining combinations somebody unemployed
else if (activity == ACT_UNEMPLOYED || activity_spouse == ACT_UNEMPLOYED)
{
if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleUnemployedParental[tax_fam_type_parental][earn_index_unemployed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitCoupleUnemployedParental[tax_fam_type_parental][(int)lSpouse->earn_index_unemployed][earn_index_parental];
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleUnemployedPension[tax_fam_type][earn_index_unemployed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleUnemployedPension[tax_fam_type][(int)lSpouse->earn_index_unemployed][earn_index_pension];
else if (activity_spouse == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleUnemployedOut[tax_fam_type][(int)lSpouse->earn_index_unemployed];
else dBenefit = dBenefit = FamilyBenefitCoupleUnemployedOut[tax_fam_type][earn_index_unemployed];
}
// remaining combinations somebody parental leave
else if (activity == ACT_LEAVE || activity_spouse == ACT_LEAVE)
{
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleParentalPension[tax_fam_type_parental][earn_index_parental][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleParentalPension[tax_fam_type_parental][(int)lSpouse->earn_index_parental][earn_index_pension];
else if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleParentalOut[tax_fam_type_parental][(int)lSpouse->earn_index_parental];
else dBenefit = dBenefit = FamilyBenefitCoupleParentalOut[tax_fam_type_parental][earn_index_parental];
}
// remaining combinations somebody pension
else if (activity == ACT_RETIRED || activity_spouse == ACT_RETIRED)
{
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCouplePensionOut[tax_fam_type][(int)lSpouse->earn_index_pension];
else dBenefit = dBenefit = FamilyBenefitCouplePensionOut[tax_fam_type][earn_index_pension];
}
family_benefit = dBenefit * lObserver->average_earnings;
}
TaxBen-OldAge.mpp
This module implements old-age benefits based on parameter tables created using the Euromod Hypothetical Household Tool (HHoT). These tables are multidimensional, categorising families by type and income from earnings, unemployment benefits, pensions, and maternity and parental leave benefits. In order to receive old-age benefits, at least one person in the family must be retired. Family types are determined by partnership status and family composition according to the number of children. Separate parameters apply to singles and couples, combined with four income types for both partners. For example, the parameter ‘Old-age benefit: couple pension x out’ applies to couples where one partner is retired and the other is not in the labour force. Benefit amounts are calculated at family level and distributed by the family head to spouses in a way that aims to equalise incomes. Benefits are updated monthly.
Parameters:
Oldage benefit single retired
Oldage benefit couple pension x employed
Oldage benefit couple pension x unemployed
Oldage benefit couple pension x parental
Oldage benefit couple pension x out
Oldage benefit couple pension x pension
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_OLDAGE_BENEFITS //EN Old-age benefits
{
OldAgeSinglePension,
OldAgeCouplePensionEmployed,
OldAgeCouplePensionUnemployed,
OldAgeCouplePensionParental,
OldAgeCouplePensionOut,
OldAgeCouplePensionPension
};
parameters
{
//EN Oldage benefit single retired
double OldAgeSinglePension[TAX_FAM_TYPE_SINGPEN][EARN_PART_PENSION];
//EN Oldage benefit couple pension x employed
double OldAgeCouplePensionEmployed[TAX_FAM_TYPE][EARN_PART_PENSION][EARN_PART_EMPLOYED];
//EN Oldage benefit couple pension x unemployed
double OldAgeCouplePensionUnemployed[TAX_FAM_TYPE][EARN_PART_PENSION][EARN_PART_UNEMPLOYED];
//EN Oldage benefit couple pension x parental
double OldAgeCouplePensionParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PENSION][EARN_PART_PARENTAL];
//EN Oldage benefit couple pension x out
double OldAgeCouplePensionOut[TAX_FAM_TYPE][EARN_PART_PENSION];
//EN Oldage benefit couple pension x pension
double OldAgeCouplePensionPension[TAX_FAM_TYPE_PENPEN][EARN_PART_PENSION][EARN_PART_PENSION];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Oldage benefit
double oldage_benefit = { 0.0 };
//EN Accumulated oldage benefit in current year
double accum_oldage_benefit = active_spell_weighted_duration(year_spell, TRUE, oldage_benefit);
//EN Update oldage benefit
void doUpdateOldageBenefit();
};
actor Observer
{
//EN Update oldage benefit
void UpdateOldageBenefit();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateOldageBenefit()
{
// Update oldage benefit
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateOldageBenefit();
}
void Person::doUpdateOldageBenefit()
{
// head is only retired
if (activity == ACT_RETIRED)
{
if (!has_spouse) oldage_benefit = lObserver->average_earnings * OldAgeSinglePension[tax_fam_type_singpen][earn_index_pension];
else if (has_spouse && activity_spouse == ACT_EMPLOYED) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionEmployed[tax_fam_type][earn_index_pension][earn_index_employed_spouse];
else if (has_spouse && activity_spouse == ACT_UNEMPLOYED) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionUnemployed[tax_fam_type][earn_index_pension][earn_index_unemployed_spouse];
else if (has_spouse && activity_spouse == ACT_LEAVE) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionParental[tax_fam_type_parental][earn_index_pension][earn_index_parental_spouse];
else if (has_spouse && activity_spouse != ACT_RETIRED) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionOut[tax_fam_type][earn_index_pension];
}
// spouse is only retired
else if (activity != ACT_RETIRED && has_spouse && activity_spouse == ACT_RETIRED)
{
if (activity == ACT_EMPLOYED) lSpouse->oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionEmployed[tax_fam_type][earn_index_pension_spouse][earn_index_employed];
else if (activity == ACT_UNEMPLOYED) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionUnemployed[tax_fam_type][earn_index_pension_spouse][earn_index_unemployed];
else if (activity == ACT_LEAVE) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionParental[tax_fam_type_parental][earn_index_pension_spouse][earn_index_parental];
else if (activity != ACT_RETIRED) oldage_benefit = lObserver->average_earnings * OldAgeCouplePensionOut[tax_fam_type][earn_index_pension_spouse];
}
// both are retired
else if (activity == ACT_RETIRED && has_spouse && activity_spouse == ACT_RETIRED)
{
double dPensionHead = pension;
double dPensionSpouse = lSpouse->pension;
double dBenefit = lObserver->average_earnings * OldAgeCouplePensionPension[tax_fam_type_penpen][earn_index_pension][earn_index_pension_spouse];
if (dBenefit > 0.0)
{
// Pension of head is lower
if (dPensionHead <= dPensionSpouse)
{
double dDifference = dPensionSpouse - dPensionHead;
// benefit smaller than difference: head gets all
if (dBenefit <= dDifference)
{
oldage_benefit = dBenefit;
lSpouse->oldage_benefit = 0.0;
}
// benefit higher than difference: equalize
else
{
oldage_benefit = dDifference + (dBenefit - dDifference) / 2.0;
lSpouse->oldage_benefit = (dBenefit - dDifference) / 2.0;
}
}
// Pension of spouse is lower
else
{
double dDifference = dPensionHead - dPensionSpouse;
// benefit smaller than difference: head gets all
if (dBenefit <= dDifference)
{
lSpouse->oldage_benefit = dBenefit;
oldage_benefit = 0.0;
}
// benefit higher than difference: equalize
else
{
lSpouse->oldage_benefit = dDifference + (dBenefit - dDifference) / 2.0;
oldage_benefit = (dBenefit - dDifference) / 2.0;
}
}
}
}
}
Health and Care
LongtermCare.mpp
This module implements long-term care needs, hours and care arrangements for people aged 65+ using a comparative approach described in detail in the technical paper Comparative Modelling of Long-Term Care in Hours. This novel approach generalises an Austrian administrative procedure for assessing care needs and uses data from the Survey of Health, Ageing and Retirement in Europe (SHARE) to quantify the demand for and supply of long-term care in hours, distinguishing between
Care provided in nursing homes
Formal home care
Informal care by spouses
Other informal care
Care gap
The model takes into account a wide range of factors that influence care needs and arrangements, including age, gender, education, the presence of a spouse able to provide care and the number of children. Compared to a macro approach based on age and gender, future care needs and demand, in particular for formal care and nursing homes, are mitigated by the expansion of education (better educated people tend to need less long-term care later in life), the modelling of partnerships (improvements in longevity increase the likelihood of living with a partner) and the consideration of mortality differences by education.
In the baseline scenario, needs and care arrangements are modelled based on individual and family characteristics “as of today”, with care provision adapting to current LTC patterns. The model includes scenario support that considers different dimensions of the drivers of future change.
Constrained supply scenarios: Users can set a growth path for the supply of care, including restrictions on the supply of nursing homes (compared to current places), restrictions on formal home care services (compared to current supply in hours), and restrictions on informal care provided by others other than spouses (typically children; the growth path is applied to the hours that would be available using current supply patterns).
Demographic scenarios, such as changes in mortality assumptions or partnership status.
Scenarios of changing care needs, such as “morbidity compression”, which assumes that improvements in longevity slow the age-related process of LTC needs. Such scenarios modify the individual age applied in the LTC models.
Scenarios involving compositional effects, such as the effect of educational expansion. This is realised by allowing education effects to be switched off (equivalent to applying current age-specific patterns) or by allowing convergence towards the patterns of the highest educated group. Education scenarios modify the individual education variable entering the LTC models.
The LTC module follows a cross-sectional imputation approach with monthly updates. The regression models are based on SHARE data with hours of care needs being imputed applying administrative procedures based on limitations in Activities of Daily Living (ADL) and limitations in Instrumental Activities of Daily Living (IADL) and other related variables available in the SHARE data. Within the simulation, each monthly update follows the following steps:
LTC Needs Assessment Step 1: Determine whether a person has care needs based on current prevalence by age, sex and education. Parameters are estimated using logistic regression.
LTC Needs Assessment Step 2: Determination of hours of care from distribution tables based on age, gender and education. Parameters estimated by quantile regression.
Nursing Homes: Probability of being in a nursing home based on current prevalence by age, intensity of need, availability of a spouse capable of providing care (not having own care needs above a threshold) and number of children. Parameters are estimated using logistic regression. If the scenario does not restrict/set the supply of nursing home care, these individual probabilities are used directly. If the supply is set by the user, the individual probabilities are converted into random waiting times that are used to rank people, and the ranking is then used to allocate available nursing home places.
LTC Mix Step 1: Probability of receiving (any) home care for people not living in a nursing home and not having a spouse able to provide care. If no care is received, the hours needed are recorded as a care gap. For persons with a spouse able to provide care, it is implicitly assumed that some care is received. This follows the logic of the SHARE survey, which does not quantify care gaps when a spouse is present.
LTC Mix Step 2: Determination of the provisional (“as of today”) home care mix if home care is received. Individuals are grouped according to the intensity of their care needs, the presence of a spouse capable of providing care, and the number of children. Within each group, the same (average) care mix is applied. The care mix distinguishes between formal care at home, informal care by a spouse, other informal care and a care gap.
LTC Mix Step 3: Determination of available care. With the exception of the calculation of the supply of care available from others when current patterns are applied (a parameter of average hours provided by age and gender), this step is scenario-based. It adds up the ‘provisional’ hours by type of home-care and compares them with the available supply of care for each type for which the given scenario sets/limits the supply. For each type of care, the proportion of demand met by supply is calculated. This step is skipped if there are no restrictions on the supply of care. If supply is restricted/fixed by the user, the provisional demand is adjusted and for each care type, the hours not met by the given supply are recorded. Similarly, oversupply is recorded. In the case of care gaps due to limited supply, it is determined whether there is a spouse who is potentially able to cover these hours. This information is recorded, but no assumptions are made about whether and how gaps are closed.
Key characteristics available for model output include hours of care needed and care mix, which distinguishes different types of potential care gaps:
Nursing homes: Hours provided; number of people in nursing homes
- Formal home care:
Hours met by current provision
Hours not covered by current supply (care gap due to supply constraints)
Hours exceeding current demand and available to fill gaps in care
- Hours of informal home-care provided by someone other than the spouse
Hours met by current supply patterns
Hours not covered by current supply patterns (care gap due to supply constraints)
Hours above current demand and available to fill care gaps
- Hours of informal home-care provided by spouse
Hours meeting current patterns
Potential additional hours to close gaps due to supply constraints in formal home care and informal care by others
- Care gap
Initial gap based on current patterns (people receiving no or insufficient care)
Additional gaps (or available additional supply) due to supply scenarios as listed above
Potential additional hours provided by spouses as listed above
Care hours by type of care are accumulated over the life course. Although the cross-sectional imputation approach does not allow a detailed longitudinal analysis of distributions, it is possible to compare average hours by care type for population groups distinguished by characteristics such as birth cohort, sex, number of children, partnership status at age 65 and education.
Model parameters:
Prevalence of having LTC needs (any hours) by sex, age, and education
Decile means of LTC hours needed by persons with care needs, by sex, age, and education
Nursing home prevalence by sex, partnership status, number of children, age group, and hours needed
Probability of receiving any home care among persons not in a nursing home and not having a partner able to provide care, by need in hours and number of children
Home care mix of persons not in a nursing home: mix as shares of hours by category, by partnership status, number of children, and LTC needs in hours
Average hours of informal care provided to persons aged 65 and over (excluding spouses), by age and sex
All parameters are estimated from SHARE data as described in detail in the technical report Comparative Modelling of Long-Term Care in Hours
Scenario parameters:
Slower ageing: parameters that allow the ageing process to be manipulated. Users can set a starting age from which the rate of ageing is changed; a second parameter sets the new length of each year. This allows individual ageing to be stretched, assuming that due to improvements in mortality, care needs and care hours increase more slowly with age. For example, the parameter can be set so that a person aged 65 will age 4 years in 5 years. A person aged 70 will then be 69, a person aged 75 will be 73… and 90 will be the new 85, thus adjusting age to increasing life expectancy.
Turn off the effects of educational composition: In this scenario, the composition of education by age and sex is held constant, which is equivalent to modelling care needs in hours without taking education into account. This scenario mimics a model that does not account for educational differences. Compared to other scenarios, it quantifies the compositional effect of educational improvements.
Matching LTC to supply: on/off switch by LTC type
LTC supply: future supply by LTC type and calendar year (current supply = 1.0)
Convergence of LTC needs with those of people with the highest level of education Convergence path by calendar year (0.0 - 1.0; 0.0 if no convergence). This provides an alternative to a ‘slower ageing’ scenario, assuming that the lower LTC needs of the better educated are determined by behaviours that can and will be adapted by others.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN People waiting for place in nursing home
actor_set Person asPeopleWaitingForNursingHome
filter LtcAlignSupply[LAT_INST] && ltc_institution_wait < TIME_INFINITE && !in_care_home
order ltc_institution_wait;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification LTC_DECILE //EN Care deciles
{
CD_01, //EN Care decile 1
CD_02, //EN Care decile 2
CD_03, //EN Care decile 3
CD_04, //EN Care decile 4
CD_05, //EN Care decile 5
CD_06, //EN Care decile 6
CD_07, //EN Care decile 7
CD_08, //EN Care decile 8
CD_09, //EN Care decile 9
CD_10 //EN Care decile 10
};
classification SLOWER_AGEING_LTC //EN Slower Ageing Comparative LTC system
{
SAL_FROM_AGE, //EN From age
SAL_DURATION //EN Length of "slower" age year
};
classification ADULT_FAMILY_TYPE //EN Family type
{
AFT_SINGLE_0, //EN Single childless
AFT_SINGLE_1, //EN Single 1 child
AFT_SINGLE_2P, //EN Single 2+ children
AFT_COUPLE_0, //EN Couple childless
AFT_COUPLE_1, //EN Couple 1 child
AFT_COUPLE_2P //EN Couple 2+ children
};
classification CHILDREN //EN Children n/y
{
CHILD_NO, //EN Has no children
CHILD_YES //EN Has children
};
classification CHILDREN_GROUPED //EN Children grouped
{
CHILD_01, //EN Has 0-1 children
CHILD_2P //EN Has 2+ children
};
classification PARTNER //EN Partner
{
PARTNER_NO, //EN Has no partner
PARTNER_YES //EN Has partner
};
classification CARINGPARTNER //EN Caring partner
{
CPARTNER_NO, //EN Has no caring partner
CPARTNER_YES //EN Has caring partner
};
classification LTC_CARETYPE //EN Care type
{
LCA_FORMAL, //EN Formal Home Care
LCA_OTHER, //EN Informal other than partner
LCA_SPOUSE, //EN Partner
LCA_GAP //EN Gap
};
classification LTC_ALIGN_TYPES //EN Care types
{
LAT_INST, //EN Nursing homes
LAT_FORMAL, //EN Formal home care
LAT_OTHER //EN Informal other than partner
};
range AGE15P { 15, 105 }; //EN Age
range LTC_AGE { 65, 105 }; //EN Age
range LTC_NEEDHOURS { 0, 450 }; //EN LTC Hours
partition LTC_COHORT //EN Birth cohort
{
1963, 1973, 1983, 1993, 2003, 2013, 2023, 2033, 2043, 2053, 2063
};
partition LTC_NEED { 20, 40, 120 }; //EN LTC Need
partition PART65PER5 { 70, 75, 80, 85, 90, 95 }; //EN Age group
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_LTC_COMPARATIVE //EN Comparative LTC system
{
PG_LTC_PARA,
PG_LTC_SCENARIOS
};
parameter_group PG_LTC_PARA //EN LTC Parameters
{
LtcAnyHours,
LtcHoursDecileMeans,
LtcNursingHome,
LtcAnyHomeCareReceivedNoPartner,
LtcHomeCareMix,
LtcCareGivingHours
};
parameter_group PG_LTC_SCENARIOS //EN LTC Scenario settings
{
LtcSlowerAgeingPara,
LtcAlignSupply,
LtcSupply,
LtcConvergenceEducation,
LtcSwitchEducationCompositionEffectsOff,
LtcPartnerFillsGaps
};
parameters
{
// Model parameters
//EN Care prevalence any hours
double LtcAnyHours[SEX][LTC_AGE][EDUC_LEVEL3];
//EN Decile means of care hours
double LtcHoursDecileMeans[SEX][EDUC_LEVEL3][LTC_AGE][LTC_DECILE];
//EN Nursing home prevalence
double LtcNursingHome[SEX][PARTNER][NUMBER_CHILDREN2][LTC_AGE][LTC_NEED];
//EN Home care prevalence with need and no partner
double LtcAnyHomeCareReceivedNoPartner[LTC_NEEDHOURS][CHILDREN];
//EN Home care mix as a share of hours needed
double LtcHomeCareMix[CARINGPARTNER][CHILDREN_GROUPED][LTC_NEED][LTC_CARETYPE];
//EN Average hours of informal care given excl partner
double LtcCareGivingHours[AGE15P][SEX];
// Scenario parameters
double LtcSlowerAgeingPara[SLOWER_AGEING_LTC]; //EN Slower Ageing
logical LtcSwitchEducationCompositionEffectsOff; //EN Switch education composition effects off
logical LtcAlignSupply[LTC_ALIGN_TYPES]; //EN Align LTC to supply
double LtcSupply[LTC_ALIGN_TYPES][SIM_YEAR]; //EN LTC supply
double LtcConvergenceEducation[SIM_YEAR]; //EN LTC convergence to highest education (0 if non)
logical LtcPartnerFillsGaps; //EN Partner fills supply gaps
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical needs_ltc = { FALSE }; //EN Needs LT care
logical in_care_home = { FALSE }; //EN In care home
logical receives_any_home_care = { FALSE }; //EN Receives any home care
logical has_careable_partner = (has_spouse && !lSpouse->ltc_hours_180p);//EN Has a partner able to care
logical receives_care_from_partner = (ltc_hours_partner > 0.0); //EN Receives care from partner
double ltc_hours = { 0.0 }; //EN LT Care hours needed
double ltc_hours_institution = { 0.0 }; //EN Hours LTC in institution
double ltc_hours_formal = { 0.0 }; //EN Hours LTC formal home care
double ltc_hours_other = { 0.0 }; //EN Hours LTC informal home care others
double ltc_hours_partner = { 0.0 }; //EN Hours LTC informal home care partner
double ltc_hours_gap = { 0.0 }; //EN Hours LTC gap
double ltc_institution_prevalence = { 0.0 }; //EN Prevalence of institutionalisation
double ltc_institution_wait = { TIME_INFINITE }; //EN Waiting time institutionalisation
double ltc_hours_formal_excess = { 0.0 }; //EN Hours LTC formal home care excess supply
double ltc_hours_formal_gap = { 0.0 }; //EN Hours LTC formal home care not covered
double ltc_hours_other_excess = { 0.0 }; //EN Hours LTC informal home care excess supply
double ltc_hours_other_gap = { 0.0 }; //EN Hours LTC informal home care others not covered
double ltc_hours_partner_additional = { 0.0 }; //EN Hours LTC informal home care partner additional
//EN Hours LTC care by partner if supply gaps covered by partner
double ltc_hours_partner_total = (LtcPartnerFillsGaps) ?
double(ltc_hours_partner) :
double(ltc_hours_partner) + double(ltc_hours_partner_additional);
double ltc_cumh_institution = 12.0 * weighted_duration(ltc_hours_institution); //EN Cumulated hours LTC in institution
double ltc_cumh_formal = 12.0 * weighted_duration(ltc_hours_formal); //EN Cumulated hours formal LTC
double ltc_cumh_other = 12.0 * weighted_duration(ltc_hours_other); //EN Cumulated hours informal LTC by others
double ltc_cumh_partner = 12.0 * weighted_duration(ltc_hours_partner); //EN Cumulated hours informal LTC by spouse
double ltc_cumh_gap = 12.0 * weighted_duration(ltc_hours_gap); //EN Cumulated hours LTC general gap
double ltc_cumh_formal_gap = 12.0 * weighted_duration(ltc_hours_formal_gap); //EN Cumulated hours LTC formal care supply gap
double ltc_cumh_other_gap = 12.0 * weighted_duration(ltc_hours_other_gap); //EN Cumulated hours LTC other care supply gap
double ltc_cum_lifetime = weighted_duration(ltc_in_longitudinal_sample); //EN Life expectancy 65+
double ltc_cum_partnertime = weighted_duration(ltc_in_longitudinal_sample, TRUE, has_spouse); //EN Time 65+ lived with a spouse
//EN Has spouse while in ltc_in_longitudinal_sample
logical ltc_longitudinal_has_spouse = (ltc_in_longitudinal_sample && has_spouse) ? TRUE : FALSE;
logical ltc_in_longitudinal_sample = { FALSE }; //EN Person in longitudinal LTC sample
logical ltc_longitudinal_has_partner = { FALSE }; //EN Person has partner at 65
//EN Family background
ADULT_FAMILY_TYPE adult_family_type =
(!has_spouse && number_children2 == NC2_0) ? AFT_SINGLE_0 :
(!has_spouse && number_children2 == NC2_1) ? AFT_SINGLE_1 :
(!has_spouse && number_children2 == NC2_2P) ? AFT_SINGLE_2P :
(has_spouse && number_children2 == NC2_0) ? AFT_COUPLE_0 :
(has_spouse && number_children2 == NC2_1) ? AFT_COUPLE_1 : AFT_COUPLE_2P;
logical ltc_hours_65p = (ltc_hours > 65.0); //EN LT Care more than 65 hours
logical ltc_hours_180p = (ltc_hours > 180.0); //EN LT Care more than 120 hours
int ltc_int_age = { 0 }; //EN Care age
LTC_AGE ltc_age = COERCE(LTC_AGE, ltc_int_age); //EN Care age
LTC_AGE ltc_real_age = COERCE(LTC_AGE, integer_age); //EN Care age
//EN Care provided to others than partner
double ltc_care_provided = (WITHIN(AGE15P, integer_age)) ?
LtcCareGivingHours[RANGE_POS(AGE15P,integer_age)][sex] : 0.0;
TIME time_next_ltc_age_update = { TIME_INFINITE }; //EN Time next ltv age update
event timeLtcAgeUpdateEvent, LtcAgeUpdateEvent; //EN LTC age update event
void InitTimeNextLtcAgeUpdate(); //EN Init next LTC age update
//hook InitTimeNextLtcAgeUpdate, SetAliveEvent, 5; //EN Hook init next care update
void doUpdateLtcNeeds(); //EN Update LTC needs
void doUpdateLtcPreliminaryMix(); //EN Update LTC mix - preliminary
void doUpdateLtcFinalMix(); //EN Update LTC mix - final
};
actor Observer
{
double ltc_initial_educ_composition[SEX][LTC_AGE][EDUC_LEVEL3]; //EN Initial education composition
// Initial LTC supply
double ltc_scaling_factor_others = { 1.0 }; //EN Scaling foctor others
double ltc_initial_supply_formal = { 0.0 }; //EN Initial LTC supply formal
double ltc_initial_supply_nursing = { 0.0 }; //EN Initial LTC supply nursing
double ltc_initial_supply_spouse = { 0.0 }; //EN Initial LTC supply spouse
double ltc_initial_supply_unmet = { 0.0 }; //EN Initial LTC care gap
logical ltc_initial_supply_is_set = { FALSE }; //EN Initial supply is set
// Current LTC demand & supply (patterns as of today)
double ltc_supply_others = { 0.0 }; //EN Current supply others
double ltc_demand_others = { 0.0 }; //EN Current demand others
double ltc_demand_formal = { 0.0 }; //EN Current demand formal
// Proportion LTC demand currently met
double ltc_propmet_other = { 0.0 }; //EN Proportion met demand informal others
double ltc_propmet_formal = { 0.0 }; //EN Proportion met demand formal
// Functions
void UpdateLongTermCare(); //EN Update longterm care needs
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Maintain LTC age (ltc_int_age)
void Person::InitTimeNextLtcAgeUpdate()
{
ltc_int_age = 0;
if (time < MIN(SIM_YEAR) || integer_age < LtcSlowerAgeingPara[SAL_FROM_AGE]) time_next_ltc_age_update = WAIT(1.0);
else time_next_ltc_age_update = WAIT(LtcSlowerAgeingPara[SAL_DURATION]);
}
TIME Person::timeLtcAgeUpdateEvent() { return time_next_ltc_age_update; }
void Person::LtcAgeUpdateEvent()
{
// increment care age
if (ltc_int_age < MAX(LTC_AGE)) ltc_int_age++;
// set next event
if (time < MIN(SIM_YEAR) || integer_age < LtcSlowerAgeingPara[SAL_FROM_AGE]) time_next_ltc_age_update = WAIT(1.0);
else time_next_ltc_age_update = WAIT(LtcSlowerAgeingPara[SAL_DURATION]);
}
// Monthly update
void Observer::UpdateLongTermCare()
{
/////////////////////////////////////////////////
// Initiate distributional table for education
/////////////////////////////////////////////////
if (!ltc_initial_supply_is_set && LtcSwitchEducationCompositionEffectsOff)
{
for (int nAge = MIN(LTC_AGE); nAge <= MAX(LTC_AGE); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
double dPop = asResidentsAgeSex[nAge][nSex]->Count();
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
ltc_initial_educ_composition[nSex][RANGE_POS(LTC_AGE, nAge)][nEduc] = 0.0;
double nCell = asResidentsAgeSexEduc[nAge][nSex][nEduc]->Count();
if (dPop > 0.0) ltc_initial_educ_composition[nSex][RANGE_POS(LTC_AGE, nAge)][nEduc] = nCell / dPop;
}
}
}
}
/////////////////////////////////////////////////
// Care needs
/////////////////////////////////////////////////
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++) asAllPerson->Item(nJ)->doUpdateLtcNeeds();
/////////////////////////////////////////////////
// Institutionalisation
/////////////////////////////////////////////////
// if not aligned - or initial supply not set - use prevalences based on parameter
if (!LtcAlignSupply[LAT_INST] || !ltc_initial_supply_is_set)
{
ltc_initial_supply_nursing = 0.0;
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
if (RandUniform(56) < ptrPerson->ltc_institution_prevalence)
{
ptrPerson->in_care_home = TRUE;
ptrPerson->ltc_hours_institution = ptrPerson->ltc_hours;
ltc_initial_supply_nursing++;
}
}
}
// else fill available spaces
else
{
double dSupply = ltc_initial_supply_nursing * LtcSupply[LAT_INST][RANGE_POS(SIM_YEAR, observer_year)];
while (dSupply > 0.5 && asPeopleWaitingForNursingHome->Count() > 0)
{
Person_ptr ptrPerson = asPeopleWaitingForNursingHome->Item(0);
ptrPerson->in_care_home = TRUE;
ptrPerson->ltc_hours_institution = ptrPerson->ltc_hours;
dSupply--;
}
}
/////////////////////////////////////////////////
// Preliminary mix given institutionalisation
/////////////////////////////////////////////////
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++) asAllPerson->Item(nJ)->doUpdateLtcPreliminaryMix();
/////////////////////////////////////////////////
// Calculate available care
/////////////////////////////////////////////////
ltc_propmet_other = 1.0;
ltc_propmet_formal = 1.0;
// determine initial supply if not done yet
if (!ltc_initial_supply_is_set)
{
ltc_initial_supply_formal = 0.0;
ltc_scaling_factor_others = 1.0;
double dInitialDemandOthers = 0.0;
double dInitialSupplyOthers = 0.0;
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
dInitialSupplyOthers = dInitialSupplyOthers + ptrPerson->ltc_care_provided;
dInitialDemandOthers = dInitialDemandOthers + ptrPerson->ltc_hours_other;
ltc_initial_supply_formal = ltc_initial_supply_formal + ptrPerson->ltc_hours_formal;
}
ltc_scaling_factor_others = dInitialDemandOthers / dInitialSupplyOthers;
}
// calculate current demand and informal supply by others (as of today)
ltc_supply_others = 0.0;
ltc_demand_others = 0.0;
ltc_demand_formal = 0.0;
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
ltc_supply_others = ltc_supply_others + ptrPerson->ltc_care_provided;
ltc_demand_others = ltc_demand_others + ptrPerson->ltc_hours_other;
ltc_demand_formal = ltc_demand_formal + ptrPerson->ltc_hours_formal;
}
ltc_supply_others = ltc_supply_others * ltc_scaling_factor_others;
// Set avalable proportion of formal care and informal care by others
if (LtcAlignSupply[LAT_OTHER])
{
ltc_propmet_other = (ltc_supply_others * LtcSupply[LAT_OTHER][RANGE_POS(SIM_YEAR, observer_year)]) / ltc_demand_others;
}
if (LtcAlignSupply[LAT_FORMAL])
{
ltc_propmet_formal = (ltc_initial_supply_formal * LtcSupply[LAT_FORMAL][RANGE_POS(SIM_YEAR, observer_year)]) / ltc_demand_formal;
}
// update LTC final mix (accounting fo supply) for all
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++) asAllPerson->Item(nJ)->doUpdateLtcFinalMix();
ltc_initial_supply_is_set = TRUE;
}
// Individual care needs
void Person::doUpdateLtcNeeds()
{
// reset
needs_ltc = FALSE;
ltc_hours = 0.0;
ltc_hours_institution = 0.0;
ltc_hours_formal = 0.0;
ltc_hours_other = 0.0;
ltc_hours_partner = 0.0;
ltc_hours_gap = 0.0;
ltc_institution_prevalence = 0.0;
ltc_institution_wait = TIME_INFINITE;
in_care_home = FALSE;
ltc_hours_formal_excess = 0.0;
ltc_hours_formal_gap = 0.0;
ltc_hours_other_excess = 0.0;
ltc_hours_other_gap = 0.0;
ltc_hours_partner_additional = 0.0 ;
// update need for care
if ( WITHIN(LTC_AGE, ltc_int_age) && is_resident )
{
EDUC_LEVEL3 cCareEduc = educ_level3;
// re-sample education in case of "no composition effects" scenario
if (LtcSwitchEducationCompositionEffectsOff)
{
double dRandom = RandUniform(59);
if (dRandom < lObserver->ltc_initial_educ_composition[sex][RANGE_POS(LTC_AGE, integer_age)][EL3_LOW]) cCareEduc = EL3_LOW;
else if (dRandom < lObserver->ltc_initial_educ_composition[sex][RANGE_POS(LTC_AGE, integer_age)][EL3_LOW]
+ lObserver->ltc_initial_educ_composition[sex][RANGE_POS(LTC_AGE, integer_age)][EL3_MEDIUM]) cCareEduc = EL3_MEDIUM;
else cCareEduc = EL3_HIGH;
}
// modify education in case of convergence scenario
if (RandUniform(57) < LtcConvergenceEducation[RANGE_POS(SIM_YEAR, calendar_year)]) cCareEduc = EL3_HIGH;
// decide if any care needed
if (RandUniform(82) < LtcAnyHours[sex][RANGE_POS(LTC_AGE, ltc_age)][cCareEduc]) needs_ltc = TRUE;
else needs_ltc = FALSE;
// decide hours of care needed
if (needs_ltc)
{
int nDecile = int(RandUniform(83) * 10.0);
ltc_hours = LtcHoursDecileMeans[sex][cCareEduc][RANGE_POS(LTC_AGE, ltc_age)][nDecile];
if (ltc_hours < 0.0) ltc_hours = 0.0;
}
else ltc_hours = 0.0;
// prevalence of institutionalisation as in parameter and derived waiting time for ranking
ltc_institution_prevalence = LtcNursingHome[sex][(PARTNER)(int)has_spouse][number_children2][RANGE_POS(LTC_AGE, ltc_age)][SPLIT(ltc_hours, LTC_NEED)];
if (ltc_institution_prevalence > 0.99) ltc_institution_prevalence = 0.99;
if (ltc_institution_prevalence > 0.0)
{
ltc_institution_wait = log(RandUniform(54)) / log(1.0 - ltc_institution_prevalence);
}
else ltc_institution_wait = TIME_INFINITE;
}
}
// Individual care mix after institutionalisation is decided - preliminary
void Person::doUpdateLtcPreliminaryMix()
{
if (needs_ltc && !in_care_home)
{
// has a partner able to care
if (has_careable_partner) receives_any_home_care = TRUE;
// has no such partner
else
{
CHILDREN cChildren = CHILD_NO;
if (number_children2 != NC2_0) cChildren = CHILD_YES;
double dProbReceiveCare = LtcAnyHomeCareReceivedNoPartner[(LTC_NEEDHOURS)COERCE(LTC_NEEDHOURS, ltc_hours)][cChildren];
if (RandUniform(55) < dProbReceiveCare) receives_any_home_care = TRUE;
else
{
receives_any_home_care = FALSE;
ltc_hours_gap = ltc_hours;
}
}
}
else receives_any_home_care = FALSE;
// home care mix
if (receives_any_home_care)
{
CHILDREN_GROUPED cChildrenGrouped = CHILD_01;
if (number_children2 == NC2_2P) cChildrenGrouped = CHILD_2P;
CARINGPARTNER cCaringPartner = (CARINGPARTNER)(int)has_careable_partner;
ltc_hours_formal = ltc_hours * LtcHomeCareMix[cCaringPartner][cChildrenGrouped][SPLIT(ltc_hours, LTC_NEED)][LCA_FORMAL]; // Formal Home Care
ltc_hours_other = ltc_hours * LtcHomeCareMix[cCaringPartner][cChildrenGrouped][SPLIT(ltc_hours, LTC_NEED)][LCA_OTHER]; // Informal other than partner
ltc_hours_partner = ltc_hours * LtcHomeCareMix[cCaringPartner][cChildrenGrouped][SPLIT(ltc_hours, LTC_NEED)][LCA_SPOUSE]; // Partner
ltc_hours_gap = ltc_hours * LtcHomeCareMix[cCaringPartner][cChildrenGrouped][SPLIT(ltc_hours, LTC_NEED)][LCA_GAP]; // Gap
}
}
// Individual care mix after institutionalisation is decided - final
void Person::doUpdateLtcFinalMix()
{
// final home care mix
if (receives_any_home_care)
{
if (lObserver->ltc_propmet_formal > 1) ltc_hours_formal_excess = (lObserver->ltc_propmet_formal - 1) * ltc_hours_formal;
else if (lObserver->ltc_propmet_formal < 1)
{
ltc_hours_formal_gap = (1 - lObserver->ltc_propmet_formal) * ltc_hours_formal;
ltc_hours_formal = lObserver->ltc_propmet_formal * ltc_hours_formal;
}
if (lObserver->ltc_propmet_other > 1) ltc_hours_other_excess = (lObserver->ltc_propmet_other - 1) * ltc_hours_other;
else if (lObserver->ltc_propmet_other < 1)
{
ltc_hours_other_gap = (1 - lObserver->ltc_propmet_other) * ltc_hours_other;
ltc_hours_other = lObserver->ltc_propmet_other * ltc_hours_other;
}
if (has_careable_partner) ltc_hours_partner_additional = ltc_hours_other_gap + ltc_hours_formal_gap;
// Option partner fills supply gaps
if (LtcPartnerFillsGaps && has_careable_partner)
{
ltc_hours_partner = ltc_hours_partner + ltc_hours_partner_additional;
ltc_hours_other_gap = 0.0;
ltc_hours_formal_gap = 0.0;
}
}
}
Health.mpp
This module implements a binary health status, as well as health transitions between good and bad health and to death. The explanatory variables are age, sex and education. Death probabilities from the health transition parameter are used indirectely by the Mortality module to account for health status once the age, sex and education of the next person to die have been determined. (This module maintaines the information of which person has the shortest random waiting time to death from the pool of individuals with these characteristics.) While deaths occur in continuous time, the health status of all surviving individuals is updated yearly on their birthday. The initial health status of people from the starting population is read from the starting population file.
Parameters:
Age-specific health transition probabilities by initial health status, sex and education level. These probabilities refer to three possible outcomes: good health, poor health and death.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Persons by age, sex, and education ordered by mortality waiting time according to health status
actor_set Person asMortalsByAgeSexEducation[integer_age][sex][educ_level3]
filter is_alive && is_resident && in_projected_time
order wait_death_health_model;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification HEALTH_CAT //EN Health status
{
HC_GOOD, //EN Good
HC_BAD //EN Bad
};
classification HEALTH_TRANSITION_OUTCOME //EN Health transition outcome
{
HTO_GOOD, //EN Good health
HTO_BAD, //EN Bad health
HTO_DEAD //EN Dead
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_HEALTH //EN Health Status
{
HealthTransition
};
parameters
{
//EN Health transitions
double HealthTransition[SEX][EDUC_LEVEL3][HEALTH_CAT][AGE_RANGE][HEALTH_TRANSITION_OUTCOME];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
HEALTH_CAT health_cat = { HC_GOOD }; //EN Health category
void UpdateHealth(); //EN Update health at birthdays
double wait_death_health_model = { TIME_INFINITE }; //EN Waiting time to death (health model)
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::UpdateHealth()
{
if (calendar_year >= MIN(SIM_YEAR) - 1 && is_resident)
{
// Update health status
if (in_projected_time && integer_age > 0)
{
if (health_cat == HC_GOOD)
{
double dProbBad = HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_BAD] /
( HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_GOOD] +
HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_BAD]);
if (RandUniform(80) < dProbBad) health_cat = HC_BAD;
}
else
{
double dProbGood = HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_GOOD] /
( HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_GOOD] +
HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_BAD]);
if (RandUniform(90) < dProbGood) health_cat = HC_GOOD;
}
}
// Update death random waiting time according to current health status
double dDeathProbability = HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_DEAD];
if (dDeathProbability > 0 && dDeathProbability < 1.0) wait_death_health_model = -log(RandUniform(91)) / -log(1 - dDeathProbability);
else if (dDeathProbability <= 0) wait_death_health_model = TIME_INFINITE;
else wait_death_health_model = 0.0;
}
}
NTTA-Childcare.mpp
Childcare in minutes provided by parent(s)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification CC_SINGLE_ANY //EN Any childcare single parents
{
CSA_FEMALE, //EN Female
CSA_EDU2, //EN Medium education
CSA_EDU3, //EN High education
CSA_0, //EN Number children 0 years
CSA_1TO3, //EN Number children 1-3 years
CSA_4TO8, //EN Number children 4-8 years
CSA_9TO13, //EN Number children 9-13 years
CSA_14TO17, //EN Number children 14-17 years
CSA_CONST //EN Constant
};
classification CC_SINGLE_HOURS //EN Hours childcare single parents
{
CSH_FEMALE, //EN Female
CSH_EDU2, //EN Medium education
CSH_EDU3, //EN High education
CSH_0, //EN Number children 0 years
CSH_1TO3, //EN Number children 1-3 years
CSH_4TO8, //EN Number children 4-8 years
CSH_9TO13, //EN Number children 9-13 years
CSH_14TO17, //EN Number children 14-17 years
CSH_2P, //EN Two or more children
CSH_CONST, //EN Constant
CSH_CORR_FACTOR //EN Correction factor
};
classification CC_COUPLE_ANY //EN Any childcare couple parents
{
CCA_EDU2_MOTHER, //EN Medium education mother
CCA_EDU3_MOTHER, //EN High education mother
CCA_EDU2_FATHER, //EN Medium education father
CCA_EDU3_FATHER, //EN High education father
CCA_0, //EN Number children 0 years
CCA_1TO3, //EN Number children 1-3 years
CCA_4TO8, //EN Number children 4-8 years
CCA_9TO13, //EN Number children 9-13 years
CCA_14TO17, //EN Number children 14-17 years
CCA_CONST //EN Constant
};
classification CC_COUPLE_WHO //EN Childcare provider - any
{
CCW_NON, //EN Non
CCW_MOTHER, //EN Mother only
CCW_FATHER //EN Father only
};
classification CC_COUPLE1_HOURS //EN Hours childcare couple one parent cares
{
CC1H_FEMALE, //EN Mother is the caregiver
CC1H_EDU2, //EN Medium education
CC1H_EDU3, //EN High education
CC1H_0, //EN Number children 0 years
CC1H_1TO3, //EN Number children 1-3 years
CC1H_4TO8, //EN Number children 4-8 years
CC1H_9TO13, //EN Number children 9-13 years
CC1H_14TO17, //EN Number children 14-17 years
CC1H_2P, //EN Two or more children
CC1H_CONST, //EN Constant
CC1H_CORR_FACTOR //EN Correction factor
};
classification CC_COUPLE2_HOURS //EN Hours childcare couple both parents care
{
CC2H_EDU2, //EN Medium education
CC2H_EDU3, //EN High education
CC2H_0, //EN Number children 0 years
CC2H_1TO3, //EN Number children 1-3 years
CC2H_4TO8, //EN Number children 4-8 years
CC2H_9TO13, //EN Number children 9-13 years
CC2H_14TO17, //EN Number children 14-17 years
CC2H_2P, //EN Two or more children
CC2H_CONST, //EN Constant
CC2H_CORR_FACTOR //EN Correction factor
};
classification CC_COUPLE_PARENT //EN Parents
{
CCP_MOTHER, //EN Mother
CCP_FATHER //EN Father
};
range CC_NUMBER_RESID{ 0, 99 }; //EN Number
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_CHILDCARE //EN Childcare
{
ChildCareSingleAny,
ChildCareSingleHours,
ChildCareCoupleAny,
ChildCareCoupleHoursOne,
ChildCareCoupleHoursBoth,
ChildCareCoupleHoursResid
};
parameters
{
double ChildCareSingleAny[CC_SINGLE_ANY]; //EN Any childcare single parent
double ChildCareSingleHours[CC_SINGLE_HOURS]; //EN Childcare hours single parent
double ChildCareCoupleAny[CC_COUPLE_ANY][CC_COUPLE_WHO]; //EN Any childcare couple parents
double ChildCareCoupleHoursOne[CC_COUPLE1_HOURS]; //EN Hours childcare couple one cares
double ChildCareCoupleHoursBoth[CC_COUPLE2_HOURS][CC_COUPLE_PARENT]; //EN Hours childcare couple both cares
double ChildCareCoupleHoursResid[CC_NUMBER_RESID][CC_COUPLE_PARENT]; //EN Hours childcare random residuals
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Child 0-17
logical child_0to17 = (lives_with_parents && integer_age <= 17) ? TRUE : FALSE;
//EN Child age 0
logical child_0 = (lives_with_parents && integer_age == 0) ? TRUE : FALSE;
//EN Child age 1-3
logical child_1to3 = (lives_with_parents && integer_age >= 1 && integer_age <= 3) ? TRUE : FALSE;
//EN Child age 4-8
logical child_4to8 = (lives_with_parents && integer_age >= 4 && integer_age <= 8) ? TRUE : FALSE;
//EN Child age 9-13
logical child_9to13 = (lives_with_parents && integer_age >= 9 && integer_age <= 13) ? TRUE : FALSE;
//EN Child age 14-17
logical child_14to17 = (lives_with_parents && integer_age >= 14 && integer_age <= 17) ? TRUE : FALSE;
//EN Number children 0-17
int nchild_0to17 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_0to17) : sum_over(mlCurrentMotherChildren, child_0to17);
//EN Children 0-17 in family
logical has_child_0to17 = (nchild_0to17 > 0) ? TRUE : FALSE;
//EN Number children age 0
int nchild_0 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_0) : sum_over(mlCurrentMotherChildren, child_0);
//EN Number children age 1-3
int nchild_1to3 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_1to3) : sum_over(mlCurrentMotherChildren, child_1to3);
//EN Number children age 4-8
int nchild_4to8 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_4to8) : sum_over(mlCurrentMotherChildren, child_4to8);
//EN Number children age 9-13
int nchild_9to13 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_9to13) : sum_over(mlCurrentMotherChildren, child_9to13);
//EN Number children age 14-17
int nchild_14to17 = (sex == MALE) ? sum_over(mlCurrentFatherChildren, child_14to17) : sum_over(mlCurrentMotherChildren, child_14to17);
//EN Childcare hours provided
double childcare_hours_provided = { 0.0 };
//EN Any childcare provided
logical any_childcare_provided = (childcare_hours_provided > 0.0) ? TRUE : FALSE;
//EN Update childcare hours
void doUpdateChildcareHours();
};
actor Observer
{
void UpdateChildcareHours(); //EN Update childcare hours
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateChildcareHours()
{
// Reset childcare hours
for (long nJ = 0; nJ < asAllPerson->Count(); nJ++) asAllPerson->Item(nJ)->childcare_hours_provided = 0.0;
// Update childcare hours
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateChildcareHours();
}
void Person::doUpdateChildcareHours()
{
// Single parents
if (!has_spouse && nchild_0to17 > 0)
{
double dLhs = 0.0;
double dOdds = 0.0;
double dProb = 0.0;
// Any care?
dLhs = ChildCareSingleAny[CSA_CONST]
+ ChildCareSingleAny[CSA_0] * nchild_0
+ ChildCareSingleAny[CSA_1TO3] * nchild_1to3
+ ChildCareSingleAny[CSA_4TO8] * nchild_4to8
+ ChildCareSingleAny[CSA_9TO13] * nchild_9to13
+ ChildCareSingleAny[CSA_14TO17] * nchild_14to17;
if (sex == FEMALE) dLhs = dLhs + ChildCareSingleAny[CSA_FEMALE];
if (educ_level3 == EL3_MEDIUM) dLhs = dLhs + ChildCareSingleAny[CSA_EDU2];
if (educ_level3 == EL3_HIGH) dLhs = dLhs + ChildCareSingleAny[CSA_EDU3];
dOdds = exp(dLhs);
dProb = dOdds / (1.0 + dOdds);
// Hours of care
if (RandUniform(37) < dProb)
{
dLhs = ChildCareSingleHours[CSH_CONST]
+ ChildCareSingleHours[CSH_0] * nchild_0
+ ChildCareSingleHours[CSH_1TO3] * nchild_1to3
+ ChildCareSingleHours[CSH_4TO8] * nchild_4to8
+ ChildCareSingleHours[CSH_9TO13] * nchild_9to13
+ ChildCareSingleHours[CSH_14TO17] * nchild_14to17
+ ChildCareSingleHours[CSH_CORR_FACTOR]; // Correction factor for log-transformation
if (sex == FEMALE) dLhs = dLhs + ChildCareSingleHours[CSH_FEMALE];
if (educ_level3 == EL3_MEDIUM) dLhs = dLhs + ChildCareSingleHours[CSH_EDU2];
if (educ_level3 == EL3_HIGH) dLhs = dLhs + ChildCareSingleHours[CSH_EDU3];
if (nchild_0to17 >= 2) dLhs = dLhs + ChildCareSingleHours[CSH_2P];
childcare_hours_provided = exp(dLhs);
}
}
// Couple parents
else if (nchild_0to17 > 0)
{
// Any care and who?
double dLhs[SIZE(CC_COUPLE_WHO)];
double dProbNon, dProbMother, dProbFather;
double dRandom = RandUniform(38);
double dLhs1 = 0.0;
for (int nIndex = 0; nIndex < SIZE(CC_COUPLE_WHO); nIndex++)
{
dLhs[nIndex] = ChildCareCoupleAny[CCA_CONST][nIndex]
+ ChildCareCoupleAny[CCA_0][nIndex] * nchild_0
+ ChildCareCoupleAny[CCA_1TO3][nIndex] * nchild_1to3
+ ChildCareCoupleAny[CCA_4TO8][nIndex] * nchild_4to8
+ ChildCareCoupleAny[CCA_9TO13][nIndex] * nchild_9to13
+ ChildCareCoupleAny[CCA_14TO17][nIndex] * nchild_14to17;
if (educ_level3 == EL3_MEDIUM) dLhs[nIndex] = dLhs[nIndex] + ChildCareCoupleAny[CCA_EDU2_MOTHER][nIndex];
if (educ_level3 == EL3_HIGH) dLhs[nIndex] = dLhs[nIndex] + ChildCareCoupleAny[CCA_EDU3_MOTHER][nIndex];
if (lSpouse->educ_level3 == EL3_MEDIUM) dLhs[nIndex] = dLhs[nIndex] + ChildCareCoupleAny[CCA_EDU2_FATHER][nIndex];
if (lSpouse->educ_level3 == EL3_HIGH) dLhs[nIndex] = dLhs[nIndex] + ChildCareCoupleAny[CCA_EDU3_FATHER][nIndex];
}
dProbNon = exp(dLhs[CCW_NON]) / (1.0 + exp(dLhs[CCW_NON]) + exp(dLhs[CCW_MOTHER]) + exp(dLhs[CCW_FATHER]));
dProbMother = exp(dLhs[CCW_MOTHER]) / (1.0 + exp(dLhs[CCW_NON]) + exp(dLhs[CCW_MOTHER]) + exp(dLhs[CCW_FATHER]));
dProbFather = exp(dLhs[CCW_FATHER]) / (1.0 + exp(dLhs[CCW_NON]) + exp(dLhs[CCW_MOTHER]) + exp(dLhs[CCW_FATHER]));
// Care hours
// (1) Only mother cares
if (dRandom < dProbMother)
{
dLhs1 = ChildCareCoupleHoursOne[CC1H_CONST] + ChildCareCoupleHoursOne[CC1H_FEMALE]
+ ChildCareCoupleHoursOne[CC1H_0] * nchild_0
+ ChildCareCoupleHoursOne[CC1H_1TO3] * nchild_1to3
+ ChildCareCoupleHoursOne[CC1H_4TO8] * nchild_4to8
+ ChildCareCoupleHoursOne[CC1H_9TO13] * nchild_9to13
+ ChildCareCoupleHoursOne[CC1H_14TO17] * nchild_14to17
+ ChildCareCoupleHoursOne[CC1H_CORR_FACTOR]; // Correction factor for log-transformation
if (educ_level3 == EL3_MEDIUM) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_EDU2];
if (educ_level3 == EL3_HIGH) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_EDU3];
if (nchild_0to17 >= 2) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_2P];
childcare_hours_provided = exp(dLhs1);
}
// (2) Only father cares
else if (dRandom < dProbMother + dProbFather)
{
dLhs1 = ChildCareCoupleHoursOne[CC1H_CONST]
+ ChildCareCoupleHoursOne[CC1H_0] * nchild_0
+ ChildCareCoupleHoursOne[CC1H_1TO3] * nchild_1to3
+ ChildCareCoupleHoursOne[CC1H_4TO8] * nchild_4to8
+ ChildCareCoupleHoursOne[CC1H_9TO13] * nchild_9to13
+ ChildCareCoupleHoursOne[CC1H_14TO17] * nchild_14to17
+ ChildCareCoupleHoursOne[CC1H_CORR_FACTOR]; // Correction factor for log-transformation
if (lSpouse->educ_level3 == EL3_MEDIUM) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_EDU2];
if (lSpouse->educ_level3 == EL3_HIGH) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_EDU3];
if (nchild_0to17 >= 2) dLhs1 = dLhs1 + ChildCareCoupleHoursOne[CC1H_2P];
lSpouse->childcare_hours_provided = exp(dLhs1);
}
// (3) Nobody cares
else if (dRandom < dProbMother + dProbFather + dProbNon)
{
// do nothing
}
// (3) Both care
else
{
// pick random terms for residuals
int dIndex = int(RandUniform(62) * SIZE(CC_NUMBER_RESID));
double dResMother = ChildCareCoupleHoursResid[dIndex][CCP_MOTHER];
double dResFather = ChildCareCoupleHoursResid[dIndex][CCP_FATHER];
// Childcare provided by mother
dLhs1 = ChildCareCoupleHoursBoth[CC2H_CONST][CCP_MOTHER] + dResMother
+ ChildCareCoupleHoursBoth[CC2H_0][CCP_MOTHER] * nchild_0
+ ChildCareCoupleHoursBoth[CC2H_1TO3][CCP_MOTHER] * nchild_1to3
+ ChildCareCoupleHoursBoth[CC2H_4TO8][CCP_MOTHER] * nchild_4to8
+ ChildCareCoupleHoursBoth[CC2H_9TO13][CCP_MOTHER] * nchild_9to13
+ ChildCareCoupleHoursBoth[CC2H_14TO17][CCP_MOTHER] * nchild_14to17
+ ChildCareCoupleHoursBoth[CC2H_CORR_FACTOR][CCP_MOTHER]; // Correction factor for log-transformation
if (educ_level3 == EL3_MEDIUM) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_EDU2][CCP_MOTHER];
if (educ_level3 == EL3_HIGH) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_EDU3][CCP_MOTHER];
if (nchild_0to17 >= 2) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_2P][CCP_MOTHER];
childcare_hours_provided = exp(dLhs1);
// Childcare provided by father
dLhs1 = ChildCareCoupleHoursBoth[CC2H_CONST][CCP_FATHER] + dResFather
+ ChildCareCoupleHoursBoth[CC2H_0][CCP_FATHER] * nchild_0
+ ChildCareCoupleHoursBoth[CC2H_1TO3][CCP_FATHER] * nchild_1to3
+ ChildCareCoupleHoursBoth[CC2H_4TO8][CCP_FATHER] * nchild_4to8
+ ChildCareCoupleHoursBoth[CC2H_9TO13][CCP_FATHER] * nchild_9to13
+ ChildCareCoupleHoursBoth[CC2H_14TO17][CCP_FATHER] * nchild_14to17
+ ChildCareCoupleHoursBoth[CC2H_CORR_FACTOR][CCP_FATHER]; // Correction factor for log-transformation
if (lSpouse->educ_level3 == EL3_MEDIUM) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_EDU2][CCP_FATHER];
if (lSpouse->educ_level3 == EL3_HIGH) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_EDU3][CCP_FATHER];
if (nchild_0to17 >= 2) dLhs1 = dLhs1 + ChildCareCoupleHoursBoth[CC2H_2P][CCP_FATHER];
lSpouse->childcare_hours_provided = exp(dLhs1);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition AGE_CHILDCARE{ 20, 65 }; //EN Age group
classification SELECTED_YEAR //EN Year
{
SY_2020, //EN 2020
SY_2030, //EN 2030
SY_2040, //EN 2040
SY_2050, //EN 2050
SY_2060 //EN 2060
};
actor Person
{
//EN In selected year
logical in_selected_year = (calendar_year == 2020 || calendar_year == 2030 || calendar_year == 2040 || calendar_year == 2050 || calendar_year == 2060);
//EN Selected year
SELECTED_YEAR selected_year =
(calendar_year == 2020) ? SY_2020 :
(calendar_year == 2030) ? SY_2030 :
(calendar_year == 2040) ? SY_2040 :
(calendar_year == 2050) ? SY_2050 : SY_2060;
};
table_group TG_CHILDCARE //EN Childcare
{
tabChildcareByAge,
tabChildcareByYear
};
table Person tabChildcareByAge //EN [_NEW] Childcare by age
[is_resident && in_selected_year]
{
sex + *
has_spouse + *
selected_year *
integer_age + *
{
weighted_duration(any_childcare_provided) / duration(), //EN Any childcare decimals=2
weighted_duration(any_childcare_provided) / duration(has_child_0to17,TRUE), //EN Any childcare having children decimals=2
weighted_duration(childcare_hours_provided) / duration(), //EN Average childcare decimals=2
weighted_duration(childcare_hours_provided) / duration(has_child_0to17,TRUE), //EN Average childcare having children decimals=2
weighted_duration(childcare_hours_provided) / duration(any_childcare_provided, TRUE), //EN Average childcare if providing care decimals=2
duration(has_child_0to17, TRUE), //EN Population with childrern
duration(any_childcare_provided, TRUE), //EN Population providing any care
duration() //EN Population
}
};
table Person tabChildcareByYear //EN [_NEW] Childcare by year
[is_resident && in_projected_time]
{
sex + *
split(integer_age,AGE_CHILDCARE) + *
{
weighted_duration(any_childcare_provided) / duration(), //EN Any childcare decimals=2
weighted_duration(any_childcare_provided) / duration(has_child_0to17,TRUE), //EN Any childcare having children decimals=2
weighted_duration(childcare_hours_provided) / duration(), //EN Average childcare decimals=2
weighted_duration(childcare_hours_provided) / duration(has_child_0to17,TRUE), //EN Average childcare having children decimals=2
weighted_duration(childcare_hours_provided) / duration(any_childcare_provided, TRUE), //EN Average childcare if providing care decimals=2
duration(has_child_0to17, TRUE), //EN Population with childrern
duration(any_childcare_provided, TRUE), //EN Population providing any care
duration() //EN Population
}
*sim_year
};
Model Output
TablesDemography.mpp
Demographic tables include tables of the projected population by age, sex and education, tables of demographic events such as births and deaths, and detailed tables on fertility and migration.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table states
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
SIM_TO_2050 yob_sim_to_2050 = COERCE(SIM_TO_2050, year_of_birth); //EN Year of birth
//EN Year of birth
YOB_1930_TO_2050 yob_1930_to_2050 = COERCE(YOB_1930_TO_2050, year_of_birth);
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TG_DEMOGRAPHY //EN General demography
{
tabCohortLifeExpectancy,
tabTotalPopulation,
tabDemographicEvents
};
table_group TG_MIGRATION //EN Migration
{
tabMigrationByAgeSex,
tabUnattendedImmigrants
};
table_group TG_FERTILITY //EN Fertility
{
tabBirthPeriodMeasures,
tabAverageAgeAtBirth,
tabCompletedFertility
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// General demography
table Person tabCohortLifeExpectancy //EN Cohort life expectancy
[creation_type == CT_CHILD && is_resident && WITHIN(SIM_TO_2050,year_of_birth)]
{
sex+ *
{
//EN Life expectancy decimals=2
value_at_transitions(is_alive,TRUE,FALSE,age) / transitions(is_alive,TRUE,FALSE)
}
* yob_sim_to_2050 //EN Year of birth
* educ_level3+ //EN Education
};
table Person tabTotalPopulation //EN Total Population
[in_projected_time && is_resident]
{
sex+ * //EN Sex
{
duration() //EN Population decimals=0
}
* integer_age + //EN Age
* sim_year //EN Year
};
table Person tabDemographicEvents //EN Demographic events
[in_projected_time && (is_resident || (!is_resident && is_alive))]
{
sex+ *
sim_year * //EN Year
{
entrances(is_alive,TRUE), //EN Births decimals=0
entrances(is_alive,FALSE), //EN Deaths decimals=0
entrances(is_resident, TRUE), //EN Immigrants decimals=0
entrances(is_resident, FALSE) //EN Emigrants decimals=0
} //EN Demographic events
};
// Migration
table Person tabMigrationByAgeSex //EN Migration by age and sex
[in_projected_time]
{
sex + * //EN Sex
{
entrances(is_resident,TRUE), //EN Immigrants decimals=0
entrances(is_resident,FALSE), //EN Emigrants decimals=0
entrances(is_resident,TRUE) - entrances(is_resident,FALSE) //EN Net migration
} //EN Displayed measure
* integer_age + //EN Age
* sim_year //EN Year
};
table Person tabUnattendedImmigrants //EN Share unattended immigrants
[in_projected_time && creation_type == CT_IMMIGRANT]
{
{
//EN Share entering unattended decimals=3
value_at_transitions(is_resident,FALSE,TRUE,is_unattended) / transitions(is_resident,FALSE,TRUE),
//EN Share resident immigrants being unattended decimals=3
weighted_duration(is_resident,TRUE,is_unattended) / duration(is_resident,TRUE)
} //EN Displayed measure
* integer_age //EN Age
* sim_year //EN Year
};
// Fertility
table Person tabBirthPeriodMeasures //EN Birth period measures
[sex == FEMALE && is_resident && in_projected_time && WITHIN(FERTILE_AGE,integer_age)]
{
educ_level3+ * //EN Education
{
//EN Birth rate decimals=3
sim_births / duration(),
//EN First birth rate decimals=3
entrances(number_children2,NC2_1) / duration(),
//EN Second birth rate decimals=3
entrances(number_children2,NC2_2P) / duration(),
//EN Third and higher birth rate decimals=3
sim_higher_births / duration(),
//EN First birth hazard decimals=3
entrances(number_children2,NC2_1) / duration(number_children2,NC2_0),
//EN Second birth hazard decimals=3
entrances(number_children2,NC2_2P) / duration(number_children2,NC2_1),
//EN Third and higher birth hazard decimals=3
sim_higher_births / duration(number_children2,NC2_2P),
//EN Births decimals=0
sim_births,
//EN First births decimals=0
entrances(number_children2,NC2_1),
//EN Second births decimals=0
entrances(number_children2,NC2_2P),
//EN Third and higher births decimals=0
sim_higher_births
} //EN Displayed measure
* fertile_age //EN Age
* sim_year //EN Year
};
table Person tabAverageAgeAtBirth //EN Average age at birth
[sex == FEMALE && is_resident && in_projected_time ]
{
{
//EN Average age at birth decimals=2
value_at_changes(sim_births,age) / changes(sim_births),
//EN Average age at first birth decimals=2
value_at_entrances(number_children2, NC2_1 ,age) / entrances(number_children2, NC2_1)
} //EN Displayed measure
* sim_year //EN Year
* educ_level3+ //EN Education
};
table Person tabCompletedFertility //EN Completed fertility
[ is_resident && trigger_entrances(is_alive, FALSE) && WITHIN(YOB_1930_TO_2050, year_of_birth)]
{
sex+ * //EN Sex
educ_level3 + * //EN Education
{
unit //EN Persons decimals=0
}
* yob_1930_to_2050 //EN Year of birth
* number_children2 //EN Children
};
TablesEducation.mpp
Education tables present simulation outputs concerning both own and parents’ education.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TG_EDUCATION //EN Education
{
tabEducationYob,
tabEducationYobParents
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabEducationYob //EN Education by year of birth
[integer_age == 0]
{
parents_educ+ * //EN Parents education
sex+ * //EN Sex
{
duration(educ_level4,EL4_ISCED2) / duration(), //EN ISCED 2 decimals=3
duration(educ_level4,EL4_ISCED3) / duration(), //EN ISCED 3 decimals=3
duration(educ_level4,EL4_ISCED4) / duration(), //EN ISCED 4 decimals=3
duration(educ_level4,EL4_ISCED5) / duration(), //EN ISCED 5+ decimals=3
duration(parents_educ,PED_UNKNOWN) / duration() //EN Parents education unknown decimals=3
} //EN Education characteristics
* year_of_birth //EN Year of birth
};
table Person tabEducationYobParents //EN Parents education by year of birth
[trigger_entrances(is_alive, TRUE)]
{
creation_type + * //EN [V] Creation type
{
unit //EN Persons decimals=0
} //EN Persons by education of parents
* calendar_year //EN Year of birth
* parents_educ //EN Parents education
};
TablesFamily.mpp
Families tables provide output concerning family sizes and other family characteristics.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition TIME_PERIOD { 2025, 2035, 2045, 2055, 2065 }; //EN Time period
partition AGE_P10 { 15, 25, 35, 45, 55, 65, 75, 85 }; //EN Age group
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table states
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN In partnership childless
logical has_spouse_childless = (has_spouse && number_children2 == NC2_0);
//EN In partnership one child
logical has_spouse_child_1 = (has_spouse && number_children2 == NC2_1);
//EN In partnership two+ children
logical has_spouse_child_2p = (has_spouse && number_children2 == NC2_2P);
//EN No partnership childless
logical no_spouse_childless = (!has_spouse && number_children2 == NC2_0);
//EN No partnership one child
logical no_spouse_child_1 = (!has_spouse && number_children2 == NC2_1);
//EN No partnership two+ children
logical no_spouse_child_2p = (!has_spouse && number_children2 == NC2_2P);
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TG_FAMILY //EN Family
{
tabLivingWithParents,
tabFamilySizeAtStart,
tabFamily65p,
tabFamilyAgeGrYear
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabLivingWithParents //EN Living with parents
[in_projected_time && is_resident && WITHIN(AGE_MAX26, integer_age)]
{
sex + * //EN Sex
split(calendar_year, TIME_PERIOD) + * //EN Time period
age_max26 * //EN Age
{
duration(lives_with_father,TRUE) / duration(), //EN Living with father decimals=3
duration(lives_with_mother,TRUE) / duration(), //EN Living with mother decimals=3
duration(lives_with_parents,TRUE) / duration(), //EN Living with any parent decimals=3
duration(lives_with_two_parents,TRUE) / duration() //EN Living with two parents decimals=3
} //EN Proportions
};
table Person tabFamilySizeAtStart //EN Family compositions at start
[trigger_entrances(in_projected_time, TRUE) && is_resident]
{
sex + * //EN Sex
has_spouse + * //EN Has spouse
{
unit //EN Persons decimals=0
}
* integer_age //EN Age
* number_children2 + //EN Number of children
};
table Person tabFamily65p //EN Family background 65+
[in_projected_time && is_resident && WITHIN(AGE_65P, integer_age) ]
{
sex+ * //EN Sex
educ_level3+ * //EN Education
//EN Time period
split(calendar_year, TIME_PERIOD) *
//EN Age
age_65p *
{
duration(has_spouse_childless, TRUE) / duration(), //EN In partnership childless decimals=3
duration(has_spouse_child_1, TRUE) / duration(), //EN In partnership one child decimals=3
duration(has_spouse_child_2p, TRUE) / duration(), //EN In partnership two+ children decimals=3
duration(no_spouse_childless, TRUE) / duration(), //EN No partnership childless decimals=3
duration(no_spouse_child_1, TRUE) / duration(), //EN No partnership one child decimals=3
duration(no_spouse_child_2p, TRUE) / duration() //EN No partnership two+ children decimals=3
} //EN Proportions
};
table Person tabFamilyAgeGrYear //EN Family background age group year
[in_projected_time && is_resident]
{
sex + * //EN Sex
educ_level3 + * //EN Education
split(integer_age, AGE_P10)+ * //EN Age group
{
duration(has_spouse, TRUE) / duration(), //EN In partnership decimals=3
duration(has_spouse_childless, TRUE) / duration(), //EN In partnership childless decimals=3
duration(has_spouse_child_1, TRUE) / duration(), //EN In partnership one child decimals=3
duration(has_spouse_child_2p, TRUE) / duration(), //EN In partnership two+ children decimals=3
duration(no_spouse_childless, TRUE) / duration(), //EN No partnership childless decimals=3
duration(no_spouse_child_1, TRUE) / duration(), //EN No partnership one child decimals=3
duration(no_spouse_child_2p, TRUE) / duration() //EN No partnership two+ children decimals=3
} //EN Proportions
* sim_year
};
TablesLongtermCare.mpp
The LTC tables provide a rich output on LTC needs in hours and the mix of care received by those in need of care. Results are presented from both a period and a longitudinal perspective, the latter accumulating LTC hours by type of care over the life course by birth cohort, sex and education.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition TAB_CARE_HOURS { 30, 60, 90, 120, 150, 180, 210 }; //EN Care hours
partition TAB_DECADES { 2028, 2038, 2048, 2058, 2068, 2078 }; //EN Years
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TG_LTC //EN Long-Term Care
{
tabLtCareAge,
tabLtCareHours,
tabLtCareHoursMix,
tabLtCareProvided,
tabCareFromPartner,
tabCohortCare
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabLtCareAge //EN [LTC_COMP] Average LTC care age by age
[year_of_birth == MIN(SIM_YEAR)]
{
integer_age* //EN Age
{
weighted_duration(ltc_int_age) / duration() //EN LTC age decimals=3
}
};
table Person tabLtCareHours //EN [LTC_COMP] LTC care
[in_projected_time && WITHIN(LTC_AGE, integer_age) && is_resident]
{
sex + * //EN Sex
educ_level3 + * //EN Education
{
weighted_duration(needs_ltc) / duration(), //EN Proportion any LTC needs decimals=2
weighted_duration(ltc_hours_65p) / duration(), //EN Proportion LTC 65+ hours decimals=2
weighted_duration(ltc_hours) / duration(), //EN Average LTC hours all decimals=2
weighted_duration(ltc_hours) / weighted_duration(needs_ltc), //EN Average LTC hours of receivers decimals=2
duration(needs_ltc,TRUE), //EN Number receivers
weighted_duration(ltc_hours) //EN Total needed hours
}
* split(ltc_real_age, PART65PER5) + //EN Age
* sim_year //EN Year
};
table Person tabLtCareHoursMix //EN [LTC_COMP] LTC care mix
[in_projected_time && WITHIN(LTC_AGE, integer_age) && needs_ltc && is_resident]
{
{
//EN Proportion hours in institutions decimals=2
weighted_duration(ltc_hours_institution) / weighted_duration(ltc_hours),
//EN Proportion hours formal home decimals=2
weighted_duration(ltc_hours_formal) / weighted_duration(ltc_hours),
//EN Proportion hours informal home care others decimals=2
weighted_duration(ltc_hours_other) / weighted_duration(ltc_hours),
//EN Proportion hours informal home care partner decimals=2
weighted_duration(ltc_hours_partner) / weighted_duration(ltc_hours),
//EN Proportion hours gap decimals=2
weighted_duration(ltc_hours_gap) / weighted_duration(ltc_hours) ,
//EN Proportion hours unmet formal home decimals=2
weighted_duration(ltc_hours_formal_gap) / weighted_duration(ltc_hours),
//EN Proportion hours excess formal home decimals=2
weighted_duration(ltc_hours_formal_excess) / weighted_duration(ltc_hours),
//EN Proportion hours unmet other home decimals=2
weighted_duration(ltc_hours_other_gap) / weighted_duration(ltc_hours),
//EN Proportion hours excess other home decimals=2
weighted_duration(ltc_hours_other_excess) / weighted_duration(ltc_hours),
weighted_duration(ltc_hours) / duration(), //EN Average needed hours decimals=2
weighted_duration(ltc_hours_institution) , //EN hours in institutions
weighted_duration(ltc_hours_formal) , //EN hours formal home
weighted_duration(ltc_hours_other) , //EN hours informal home other
weighted_duration(ltc_hours_partner) , //EN hours informal home partner
weighted_duration(ltc_hours_gap), //EN hours gap
weighted_duration(ltc_hours), //EN Total hours
weighted_duration(ltc_hours_formal_gap), //EN hours formal unmet (supply gap)
weighted_duration(ltc_hours_formal_excess), //EN hours formal excess (additional supply)
weighted_duration(ltc_hours_other_gap), //EN hours other unmet (supply gap)
weighted_duration(ltc_hours_other_excess), //EN hours excess other (additional ssupply)
weighted_duration(ltc_hours_partner_additional) //EN hours supply gap potentially coverable by partner
} //EN Care mix measures
* sim_year //EN Hours
};
table Person tabLtCareProvided //EN [LTC_COMP] LTC care provided to others than partner
[in_projected_time && is_resident]
{
{
weighted_duration(ltc_care_provided) //EN Total hours of care provided
}
* sim_year //EN Year
* sex + //EN Sex
};
table Person tabCareFromPartner //EN [LTC_COMP] Total care from partner if covering supply gaps
[receives_care_from_partner && in_projected_time && is_resident]
{
sex+ * //EN Sex of care receiver
{
duration() //EN Persons
}
* split(ltc_hours_partner_total,TAB_CARE_HOURS) + //EN Duration
* split(sim_year,TAB_DECADES) //EN Decade
};
table Person tabCohortCare //EN [LTC_COMP] Average care hours over lifecourse
[ltc_in_longitudinal_sample && trigger_entrances(is_alive, FALSE) && is_resident]
{
sex + * //EN Sex
educ_level3 + * //EN Education
{
unit, //EN Persons
value_in(ltc_cumh_institution) / unit, //EN Cumulated hours LTC in institution
value_in(ltc_cumh_formal) / unit, //EN Cumulated hours formal LTC
value_in(ltc_cumh_other) / unit, //EN Cumulated hours informal LTC by others
value_in(ltc_cumh_partner) / unit, //EN Cumulated hours informal LTC by spouse
value_in(ltc_cumh_gap) / unit, //EN Cumulated hours LTC general gap
value_in(ltc_cumh_formal_gap) / unit, //EN Cumulated hours LTC formal care supply gap
value_in(ltc_cumh_other_gap) / unit, //EN Cumulated hours LTC others care supply gap
value_in(ltc_cum_lifetime) / unit //EN Life expectancy 65+
} //EN Cumulative measures
* split(year_of_birth, LTC_COHORT) //EN Birth cohort
};
TablesValidation.mpp
Validation tables present simulation results that can be directly compared to model parameters.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition AGE_20_60 { 20, 60 }; //EN Age group
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table states
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
YOB_BIRTH1 yob_birth1 = COERCE(YOB_BIRTH1, year_of_birth); //EN Year of birth
FERT_PROG fert_prog = COERCE(FERT_PROG,years_since_first_birth); //EN Years since first birth
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TG_VALIDATION //EN Validation
{
tabPartnershipStatusMothers,
tabPartnershipStatusChildless,
tabPartnershipAge,
tabFirstBirthRates,
tabSecondBirthRates
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabFirstBirthRates //EN [V] First birth rates
[sex == FEMALE && in_projected_time && is_resident && number_children2 == NC2_0 && WITHIN(YOB_BIRTH1, year_of_birth)]
{
educ_level3 + * //EN Education
{
entrances(number_children2,NC2_1) / duration() //EN First birth rate decimals=3
}
* fertile_age //EN Age
* yob_birth1 //EN Year of birth
};
table Person tabSecondBirthRates //EN [V] Second birth rates
[sex == FEMALE && in_projected_time && is_resident
&& number_children2 == NC2_1 && WITHIN(YOB_BIRTH1, year_of_birth)
&& WITHIN(FERT_PROG, years_since_first_birth)]
{
educ_level3 + * //EN Education
{
entrances(number_children2,NC2_2P) / duration() //EN Second birth rate decimals=3
}
* fert_prog
* yob_birth1 //EN Year of birth
};
table Person tabPartnershipStatusMothers //EN [V] Partnership Status Mothers
[in_projected_time && sex == FEMALE && children_in_family > 0 && is_resident]
{
educ_level3 + * //EN Education
{
duration(has_spouse,TRUE) / duration() //EN Has spouse decimals=3
}
* moth_agegr //EN Age group mother
* child_agegr //EN Child age group
};
table Person tabPartnershipStatusChildless //EN [V] Partnership Status Childless
[in_projected_time && sex==FEMALE && children_in_family==0 && is_resident && WITHIN(SPOUSE_AGE, integer_age)]
{
{
duration(has_spouse,TRUE) / duration() //EN Has spouse decimals=3
}
* partnership_age //EN Age
* educ_level3 + //EN Education
};
table Person tabPartnershipAge //EN [V] Partner age distribution after 2050
[sim_year > 2050 && sex == FEMALE && has_spouse && is_resident && WITHIN(SPOUSE_AGE, integer_age)]
{
{
duration() //EN Persons decimals=0
}
* partnership_age //EN Age
* partnership_spouse_age //EN Spouse age
};
table Person tabTaxStart //EN [V] TAX START
[is_resident && trigger_entrances(in_projected_time,TRUE)]
{
family_role+ *
{
value_in(earnings)/unit, //EN Average earnings
value_in(pension) / unit, //EN Average pension
value_in(unemployment_benefit) / unit, //EN Average unemployment benefit
value_in(parental_benefit) / unit, //EN Average childleave benefit
value_in(si_contribution_own) / unit, //EN Average social insurance own
value_in(si_contribution_employer) / unit, //EN Average social insurance employer
value_in(income_tax) / unit, //EN Average employment income tax
value_in(education_benefit) / unit, //EN Average education benefit
value_in(family_benefit) / unit, //EN Average family benefit
value_in(oldage_benefit) / unit, //EN Average oldage benefit
value_in(social_benefit) / unit, //EN Average social benefit
max_value_in(unemployment_benefit), //EN Max unemployment benefit
unit //EN Persons
}
* activity_start+
};
table Person tabBenefitStart //EN [V] TOTAL BEN START
[is_resident && trigger_entrances(in_projected_time, TRUE)]
{
{
value_in(earnings), //EN Total earnings
value_in(pension) , //EN Total pension
value_in(unemployment_benefit), //EN Unemployment benefit
value_in(parental_benefit), //EN Childleave benefit
value_in(si_contribution_own), //EN Total social insurance own
value_in(si_contribution_employer) , //EN Total social insurance employer
value_in(income_tax) , //EN Total employment income tax
value_in(education_benefit) , //EN Total education benefit
value_in(family_benefit) , //EN Total family benefit
value_in(oldage_benefit) , //EN Total oldage benefit
value_in(social_benefit) //EN Total social benefit
}
*activity_start +
};
table Person tabAvtSimYear //EN [V] ACtivity
[is_resident && in_projected_time]
{
split(integer_age,AGE_20_60)+ * //EN Age group
sex+ *
{
duration(activity,ACT_NEVER) / duration(), //EN Proportion never active decimals=3
duration(activity,ACT_EMPLOYED) / duration(), //EN Proportion employed decimals=3
duration(activity,ACT_UNEMPLOYED) / duration(), //EN Proportion unemployed decimals=3
duration(activity,ACT_LEAVE) / duration(), //EN Proportion leave decimals=3
duration(activity,ACT_OUT) / duration(), //EN Proportion out decimals=3
duration(activity,ACT_RETIRED) / duration(), //EN Proportion retired decimals=3
duration(activity,ACT_NEVER), //EN Proportion never active decimals=3
duration(activity,ACT_EMPLOYED), //EN Proportion employed decimals=3
duration(activity,ACT_UNEMPLOYED), //EN Proportion unemployed decimals=3
duration(activity,ACT_LEAVE), //EN Proportion leave decimals=3
duration(activity,ACT_OUT), //EN Proportion out decimals=3
duration(activity,ACT_RETIRED) //EN Proportion retired decimals=3
}
* sim_year
};
table Person tabLeaveBenefitAmount //EN [V] Leave benefir amount
[is_resident && in_projected_time && activity==ACT_LEAVE]
{
{
weighted_duration(parental_benefit) / duration(), //EN Average leave benefit
min_value_in(parental_benefit), //EN Min leave benefit
max_value_in(parental_benefit) //EN Max leave benefit
}
* sim_year
};
table Person tabActivityStatus //EN [V] Activity status
[is_resident && in_selected_year]
{
selected_year *
sex+ *
integer_age*
{
duration(activity, ACT_NEVER) / duration(), //EN Never active decimals=3
duration(activity, ACT_EMPLOYED) / duration(), //EN Employed decimals=3
duration(activity, ACT_UNEMPLOYED) / duration(), //EN Unemployed decimals=3
duration(activity, ACT_LEAVE) / duration(), //EN Leave decimals=3
duration(activity, ACT_OUT) / duration(), //EN Out decimals=3
duration(activity, ACT_RETIRED) / duration() //EN retired decimals=3
}
};
table Person tabDeathRatesByHealth //EN [V TEST] Death rates by health status
[in_projected_time && is_resident && calendar_year >= MIN(SIM_YEAR) && calendar_year < 2030]
{
{
transitions(is_alive,TRUE,FALSE) / duration() //EN death rates decimals=3
}
* integer_age
* health_cat
};