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. MicroWELT 3.0 is still work in progress. This documentation was generated on June 29 2026 and is subject to further updates
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. Observations are created as internal representations of the records in the starting population file. They are used to create Person actors for 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.
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 calculated. The starting population file is a CSV file with a header row containing variable names. Both the file name and the simulation size 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 an 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 the head.)
Currently attending school: 0 no, 1 yes
Activity status: 0 never active, 1 employed, 2 unemployed, 3 family leave, 4 out of the labor force, 5 retired
Employment type: 0 not employed, 1 part-time, 2 full-time
Health limitation: 0 none, 1 limited health
Wage
Place in any wage distribution: location in the empirical residual distribution
Pension
Years worked
Invalidity
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 and 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 × 400,000.
/* NOTE(ActorObservation, EN)
The Observation actor module contains the basic information that defines the Observation actor. Observations are created as internal representations of the records in the starting population file. They are used to create Person actors for 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.
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 calculated. The starting population file is a CSV file with a header row containing variable names. Both the file name and the simulation size 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 an 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 the head.)
- Currently attending school: 0 no, 1 yes
- Activity status: 0 never active, 1 employed, 2 unemployed, 3 family leave, 4 out of the labor force, 5 retired
- Employment type: 0 not employed, 1 part-time, 2 full-time
- Health limitation: 0 none, 1 limited health
- Wage
- Place in any wage distribution: location in the empirical residual distribution
- Pension
- Years worked
- Invalidity
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 and 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 × 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
PMC_INVALID //EN Invalidity
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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).
/* NOTE(ActorPerson, EN)
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;
//EN Residets by age sex and detailed education
actor_set Person asResidentsAgeSexEduc4[integer_age][sex][educ_level4] 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];
if (contribution_time_start > 0 && MIN(SIM_YEAR) - contribution_time_start >= time)
{
time_start_work_ue = MIN(SIM_YEAR) - contribution_time_start;
}
if ((int)prObs->pmc[PMC_INVALID] == 1) invalid = TRUE;
}
// 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, for example, 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. These are used to call functions that are 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, such as 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 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.
/* NOTE(ActorObserver, EN)
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, for example, 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. These are used to call functions that are 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, such as 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 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
TIME next_observer_endmonth = { TIME_INFINITE }; //EN Time next end month event
event timeObserverEndMonthEvent, ObserverEndMonthEvent; //EN End-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; }
TIME Observer::timeObserverEndMonthEvent() { return next_observer_endmonth; }
void Observer::ObserverEndMonthEvent()
{
// Transfers - Consume - Saving decisions
if (observer_year >= MIN(SIM_YEAR)) UpdateTransfersConsumptionSaving();
// not at yearend which is handled separately at year start
if (time - int(time) < 0.95 && observer_year >= MIN(SIM_YEAR))
{
UpdateOldAgeRetiremetEligibility();
UpdateHomeOwnership();
AlignStudentsLivingWithParents();
UpdateAllBenefits();
}
// set clock
if (time - int(time) > 0.9) next_observer_endmonth = TIME_INFINITE; // skip December which is called in yearend
else next_observer_endmonth = WAIT(1.0 / 12.0);
}
void Observer::ObserverYearEndEvent()
{
// before coming to last actions in year, close December
ObserverEndMonthEvent();
// now call year end of Persons
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();
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();
}
if (observer_year >= MIN(SIM_YEAR))
{
TempFixMakeSick();
UpdateOldAgeRetiremetEligibility();
UpdateHomeOwnership();
AlignStudentsLivingWithParents();
UpdateAllBenefits();
}
// Set clock
next_observer_year_start = TIME_INFINITE;
next_observer_endmonth = WAIT(1.0 / 12.0);
}
void Observer::ObserverMidMonthEvent()
{
if (observer_year >= MIN(SIM_YEAR))
{
UpdatePartnershipStatus();
UpdateLongTermCare();
UpdateInvalidity();
UpdateChildcareHours();
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.
/* NOTE(Fertility, EN)
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 Partnership
actor_set Person asWomenByEducFamilySize[year_of_birth][educ_level3][has_spouse][number_children2]
filter sex == FEMALE && is_alive && is_resident;
//EN Women by education and family size
actor_set Person asWomenByAgeEducFamilySize[first_birth_is_imputed][integer_age][educ_level3][number_children2]
filter sex == FEMALE && is_alive && is_resident && !in_projected_time;
//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()
{
for (int nAge = MIN(SEC_BIR_RANGE); nAge <= MAX(SEC_BIR_RANGE); nAge++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
int nWomenTwo = asWomenByAgeEducFamilySize[FALSE][nAge][nEduc][NC2_2P]->Count()
+ asWomenByAgeEducFamilySize[TRUE][nAge][nEduc][NC2_2P]->Count();
int nWomenAll = nWomenTwo
+ asWomenByAgeEducFamilySize[FALSE][nAge][nEduc][NC2_0]->Count()
+ asWomenByAgeEducFamilySize[TRUE][nAge][nEduc][NC2_0]->Count()
+ asWomenByAgeEducFamilySize[FALSE][nAge][nEduc][NC2_1]->Count()
+ asWomenByAgeEducFamilySize[TRUE][nAge][nEduc][NC2_1]->Count();
int nExpectedTwo = round(double(nWomenAll) * mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAge)]);
while (nExpectedTwo > nWomenTwo && asWomenByAgeEducFamilySize[FALSE][nAge][nEduc][NC2_1]->Count() > 0)
{
nWomenTwo++;
Person_ptr ptrPerson = asWomenByAgeEducFamilySize[FALSE][nAge][nEduc][NC2_1]->GetRandom(RandUniform(22));
ptrPerson->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++;
ActDecideMaternityLeave();
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;
}
range SEC_BIR_RANGE { 37, 49 }; //EN Second births alignment range
parameters
{
model_generated double mgParity2pTargets[EDUC_LEVEL3][SEC_BIR_RANGE]; //EN parity 2+ targets at start
};
void PreSimulation()
{
long nIterations = 100000;
for (int nAgeAtStart = MIN(SEC_BIR_RANGE); nAgeAtStart <= MAX(SEC_BIR_RANGE); nAgeAtStart++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAgeAtStart)] = 0.0;
int nCohort = MIN(SIM_YEAR) - nAgeAtStart - 1;
for (long nIndex = 1; nIndex <= nIterations; nIndex++)
{
int nParity = 0;
int nAgeAtFirstBirth = 99;
for (int nAge = 15; nAge <= nAgeAtStart; nAge++)
{
double dProbFirstBirth = 1.0 - exp(-FirstBirthCohortRates[nEduc][RANGE_POS(FERTILE_AGE, nAge)][RANGE_POS(YOB_BIRTH1, nCohort)]);
if (nParity == 0 && RandUniform(66) < dProbFirstBirth)
{
nParity++;
nAgeAtFirstBirth = nAge;
}
double dProbSecondBirth = 1.0 - exp(-ParityProgressionSecond[nEduc][RANGE_POS(YOB_BIRTH1, nCohort)][COERCE(FERT_PROG, nAge - nAgeAtFirstBirth)]);
if (nParity == 1 && RandUniform(94) < dProbSecondBirth)
{
nParity++;
}
}
if (nParity == 2)
{
mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAgeAtStart)]
= mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAgeAtStart)] + 1.0;
}
}
mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAgeAtStart)]
= mgParity2pTargets[nEduc][RANGE_POS(SEC_BIR_RANGE, nAgeAtStart)] / nIterations;
}
}
}
Mortality.mpp
The mortality module simulates mortality by age, sex, education, and, optionally, health status. It is designed for contexts where mortality projections by education are not readily available and must instead be derived by combining three sources of information:
Period mortality rates by age and sex, typically from official population projections.
Data and scenarios on remaining life expectancy at ages 25 and 65 by education and sex.
Current mortality rates by education, age, and sex.
The module distinguishes three levels of educational attainment:
Low education: ISCED 2 and below.
Medium education: ISCED 3 and 4.
High education: ISCED 5 and above.
Health status can be incorporated in a final adjustment step using the health transition model. This feature can also be switched off.
Users can choose among four mortality modelling options.
[A] Base model: The base model does not differentiate mortality by education or health status. It simply applies aggregate period mortality rates by age and sex, usually taken from official population projections. This option is useful as a benchmark scenario, especially when comparing detailed microsimulation outcomes with macro-level population projections that typically do not account for mortality differences by education or health.
[B] Detailed model based on scenarios of remaining life expectancy at ages 25 and 65 by education: This option models mortality by education using target remaining life expectancies at ages 25 and 65. For each year and education level, the current education-specific period mortality rates are calibrated to produce education-specific period life tables consistent with the specified life expectancy targets. This calibration is performed in the module’s pre-simulation function. If the health adjustment is activated, health status is taken into account after the age, sex, and education of the next person to die have been determined. Among individuals with these characteristics, the person with the shortest random waiting time to death, accounting for health status, is selected. This option is useful for projections based on scenarios of life expectancy by education, without alignment to existing population projections.
[C] Model based on current education-specific mortality aligned to existing population projections: This option models mortality by education using the current relative risk structure of mortality by education, while ensuring consistency with aggregate mortality targets from the base model, such as official population projections. It is useful as a baseline scenario for projections that account for education-specific mortality differences while assuming that current mortality patterns by education remain persistent over time. As in option [B], the additional adjustment for health status can be activated.
[D] Detailed model aligned to the base model: This option combines scenario-based modelling of education-specific mortality differentials with alignment to the outcomes of the base model, such as official population projections. It is useful when the user wants to model a changing impact of education on mortality while preserving consistency with aggregate projection targets. As in option [B], the additional adjustment for health status can be activated.
## Parameters
Model option: Specifies which of the four mortality modelling options is used: [A], [B], [C], or [D].
Period mortality by age and sex: Aggregate mortality rates by age, sex, and period. These are usually taken from official population projections and are used directly in the base model and as alignment targets in selected detailed models.
Remaining life expectancy at ages 25 and 65 by education, sex, and period. Target values for remaining life expectancy by education, sex, and period. These values can be based on recent empirical estimates from the literature and can be used to construct scenarios on the future evolution of educational mortality differences, such as convergence or divergence scenarios.
Current age-specific mortality by education and sex: used to capture the baseline age pattern of relative mortality risks by education.
Health-status adjustment switch: Determines whether additional mortality differences by health status are accounted for. When switched on, the module uses information from the health transition model to adjust the selection of the individual who dies within a given age, sex, and education group. When switched off, mortality is simulated without health-status adjustment.
/* NOTE(Mortality, EN)
The mortality module simulates mortality by age, sex, education, and, optionally, health status. It is designed for contexts where mortality projections by education are not readily available and must instead be derived by combining three sources of information:
1. Period mortality rates by age and sex, typically from official population projections.
2. Data and scenarios on remaining life expectancy at ages 25 and 65 by education and sex.
3. Current mortality rates by education, age, and sex.
The module distinguishes three levels of educational attainment:
- Low education: ISCED 2 and below.
- Medium education: ISCED 3 and 4.
- High education: ISCED 5 and above.
Health status can be incorporated in a final adjustment step using the health transition model. This feature can also be switched off.
Users can choose among four mortality modelling options.
- [A] Base model: The base model does not differentiate mortality by education or health status. It simply applies aggregate period mortality rates by age and sex, usually taken from official population projections. This option is useful as a benchmark scenario, especially when comparing detailed microsimulation outcomes with macro-level population projections that typically do not account for mortality differences by education or health.
- [B] Detailed model based on scenarios of remaining life expectancy at ages 25 and 65 by education: This option models mortality by education using target remaining life expectancies at ages 25 and 65. For each year and education level, the current education-specific period mortality rates are calibrated to produce education-specific period life tables consistent with the specified life expectancy targets. This calibration is performed in the module’s pre-simulation function. If the health adjustment is activated, health status is taken into account after the age, sex, and education of the next person to die have been determined. Among individuals with these characteristics, the person with the shortest random waiting time to death, accounting for health status, is selected. This option is useful for projections based on scenarios of life expectancy by education, without alignment to existing population projections.
- [C] Model based on current education-specific mortality aligned to existing population projections: This option models mortality by education using the current relative risk structure of mortality by education, while ensuring consistency with aggregate mortality targets from the base model, such as official population projections. It is useful as a baseline scenario for projections that account for education-specific mortality differences while assuming that current mortality patterns by education remain persistent over time. As in option [B], the additional adjustment for health status can be activated.
- [D] Detailed model aligned to the base model: This option combines scenario-based modelling of education-specific mortality differentials with alignment to the outcomes of the base model, such as official population projections. It is useful when the user wants to model a changing impact of education on mortality while preserving consistency with aggregate projection targets. As in option [B], the additional adjustment for health status can be activated.
## Parameters
- Model option: Specifies which of the four mortality modelling options is used: [A], [B], [C], or [D].
- Period mortality by age and sex: Aggregate mortality rates by age, sex, and period. These are usually taken from official population projections and are used directly in the base model and as alignment targets in selected detailed models.
- Remaining life expectancy at ages 25 and 65 by education, sex, and period. Target values for remaining life expectancy by education, sex, and period. These values can be based on recent empirical estimates from the literature and can be used to construct scenarios on the future evolution of educational mortality differences, such as convergence or divergence scenarios.
- Current age-specific mortality by education and sex: used to capture the baseline age pattern of relative mortality risks by education.
- Health-status adjustment switch: Determines whether additional mortality differences by health status are accounted for. When switched on, the module uses information from the health transition model to adjust the selection of the individual who dies within a given age, sex, and education group. When switched off, mortality is simulated without health-status adjustment.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN All mortal persons
actor_set Person asAllMortalPersonsForMortalityAlignment[sex][integer_age]
filter in_projected_time && is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
classification MORTALITY_MODEL //EN Mortality model selection
{
MOM_BASE, //EN Base
MOM_DETAIL, //EN Detailed: life expectancy by education
MOM_ALIGNED1, //EN By education aligned to base
MOM_ALIGNED2 //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,
MortalityTableEduc,
MortalityByHealth
};
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 MortalityTableEduc[SEX][AGE_25P][EDUC_LEVEL3]; //EN Mortality by education
logical MortalityByHealth; //EN Apply differences by health
//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();
// Survivor pension
if (has_pension && has_spouse) lSpouse->doSetSurvivorPension();
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 (MortalityByHealth && asMortalsByAgeSexEducation[integer_age][sex][educ_level3]->Count() > 0)
{
asMortalsByAgeSexEducation[integer_age][sex][educ_level3]->Item(0)->Death();
}
else Death();
}
// 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 = prPers;
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 (MortalityByHealth && 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;
// Mortality 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++)
{
if (SelectedMortalityModel == MOM_DETAIL || SelectedMortalityModel == MOM_ALIGNED2)
{
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(-MortalityTableEduc[nSex][RANGE_POS(AGE_25P, nAge)][nGroup] * dCenter));
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;
}
}
else dCenter = 1.0;
// applying the solution to the mortality parameter
for (int nAge = 65; nAge < SIZE(AGE_RANGE); nAge++)
{
MortalityDetailedHazard[nSex][nGroup][nAge][nYear] = MortalityTableEduc[nSex][RANGE_POS(AGE_25P, nAge)][nGroup] * dCenter;
}
}
}
}
// Mortality 25-64
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++)
{
if (SelectedMortalityModel == MOM_DETAIL || SelectedMortalityModel == MOM_ALIGNED2)
{
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(-MortalityTableEduc[nSex][RANGE_POS(AGE_25P, nAge)][nGroup] * dCenter));
}
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;
}
}
else dCenter = 1.0;
// applying the solution to the mortality parameter
for (int nAge = 25; nAge < 65; nAge++)
{
MortalityDetailedHazard[nSex][nGroup][nAge][nYear] = MortalityTableEduc[nSex][RANGE_POS(AGE_25P, nAge)][nGroup] * dCenter;
}
}
}
}
// 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];
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range TAB_FIRST_YEARS { 2018, 2026 }; //EN Year
actor Person
{
//EN Year
TAB_FIRST_YEARS tab_first_years = COERCE(TAB_FIRST_YEARS, calendar_year);
//EN Detailed mortality rate
double tab_mortality_detailed = MortalityDetailedHazard[sex][educ_level3][integer_age][RANGE_POS(SIM_YEAR,calendar_year)];
};
table Person tabValidationDetailedMortality //EN [VAL-MORT] Detailed mortality (from pre-simulation)
[WITHIN(TAB_FIRST_YEARS,calendar_year) && is_resident]
{
sex * //EN Sex
tab_first_years * //EN Year
{
weighted_duration(tab_mortality_detailed) / duration() //EN Mortality decimals=5
}
* integer_age //EN Age
* educ_level3 //EN Education
};
table Person tavValidationDeathRates2018Detailed //EN [VAL_MORT] Mortality hazards
[ WITHIN(TAB_FIRST_YEARS, calendar_year) && is_resident ]
{
sex + * //EN Sex
health_cat+ * //EN Health
tab_first_years+ * //EN Year
{
transitions(is_alive,TRUE,FALSE) / duration() //EN Death rates decimals=5
}
* integer_age
* educ_level3+
};
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
/* NOTE(Migration, EN)
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;
}
}
}
}
// current school enrolment
if (asResidentsAgeSexEduc4[integer_age][sex][educ_level4]->Count() > 0)
{
Person_ptr ptrHost = asResidentsAgeSexEduc4[integer_age][sex][educ_level4]->GetRandom(RandUniform(100));
in_educ = ptrHost->in_educ;
current_educ4 = ptrHost->current_educ4;
if (current_educ4 != educ_level4 && !in_educ && integer_age > 10)
{
in_educ = TRUE;
}
}
else // this should happen only rarely
{
in_educ = FALSE;
current_educ4 = educ_level4;
}
}
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.
/* NOTE(Partnerships, EN)
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 relationships between family members. MicroWELT represents families as nuclear families, consisting of:
a household head;
a spouse, if present; and
dependent children.
Each person is assigned one of three family roles: head, spouse, or child. In a couple, the female partner is designated as the family head. The model distinguishes four types of family links:
Spousal links: Links between spouses are maintained throughout the partnership and removed when the union dissolves.
Links to first parents: These links identify a person’s biological parents or, when biological parenthood is unknown, the first recorded mother and father in the initial population. A link is maintained for as long as the respective parent is alive.
Links to most recent parents: These links identify the persons who most recently assumed a parental role, such as stepparents. A link is maintained for as long as the respective parent is alive.
Links to co-residing parents: These links identify the most recent parents with whom a child currently lives. A link is removed when the child leaves the parental home.
The module contains functions that create, update, or remove family links in response to specific life-course events.
Death: When a parent dies, children without another parent or guardian in the family are assigned a new guardian whenever possible. For each affected child, the module searches for a living biological mother or father and, if neither is available, for a living grandmother or grandfather. The child is linked to the selected guardian and, where applicable, to the guardian’s spouse as an additional social parent.
Partnership formation: When two persons form a partnership, they are linked as spouses and establish a new nuclear family. The family links of all children affected by the partnership formation are then updated.
Union dissolution: Before a partnership is dissolved, each child is assigned to the household of one of the separating parents. This decision is governed by a set of rules and by a parameter representing the probability of remaining with the mother. When only one partner is the child’s biological parent, the child remains with that parent. Otherwise, the parent with whom the child remains is selected probabilistically. After the assignment, the child’s family and co-residence links are updated accordingly.
For persons in the starting population, the module establishes links between spouses; and between children and their recorded mothers and fathers. The parents observed in the initial population are treated as both the child’s first parents and most recent parents.
Parameters
Probability of living with the mother after parental union dissolution: The probability that a child remains in the mother’s household when both separating partners have an equivalent parental status.
/* NOTE(Family, EN)
The family module manages and maintains relationships between family members. MicroWELT represents families as **nuclear families**, consisting of:
- a household head;
- a spouse, if present; and
- dependent children.
Each person is assigned one of three family roles: head, spouse, or child. In a couple, the female partner is designated as the family head. The model distinguishes four types of family links:
- Spousal links: Links between spouses are maintained throughout the partnership and removed when the union dissolves.
- Links to first parents: These links identify a person's biological parents or, when biological parenthood is unknown, the first recorded mother and father in the initial population. A link is maintained for as long as the respective parent is alive.
- Links to most recent parents: These links identify the persons who most recently assumed a parental role, such as stepparents. A link is maintained for as long as the respective parent is alive.
- Links to co-residing parents: These links identify the most recent parents with whom a child currently lives. A link is removed when the child leaves the parental home.
The module contains functions that create, update, or remove family links in response to specific life-course events.
- Death: When a parent dies, children without another parent or guardian in the family are assigned a new guardian whenever possible. For each affected child, the module searches for a living biological mother or father and, if neither is available, for a living grandmother or grandfather. The child is linked to the selected guardian and, where applicable, to the guardian's spouse as an additional social parent.
- Partnership formation: When two persons form a partnership, they are linked as spouses and establish a new nuclear family. The family links of all children affected by the partnership formation are then updated.
- Union dissolution: Before a partnership is dissolved, each child is assigned to the household of one of the separating parents. This decision is governed by a set of rules and by a parameter representing the probability of remaining with the mother. When only one partner is the child's biological parent, the child remains with that parent. Otherwise, the parent with whom the child remains is selected probabilistically. After the assignment, the child's family and co-residence links are updated accordingly.
For persons in the starting population, the module establishes links between spouses; and between children and their recorded mothers and fathers. The parents observed in the initial population are treated as both the child's first parents and most recent parents.
**Parameters**
- Probability of living with the mother after parental union dissolution: The probability that a child remains in the mother's household when both separating partners have an equivalent parental status.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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);
}
}
UpdateHomeOwnershipAtUnionFormation();
}
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);
}
}
}
UpdateHomeOwnershipAtUnionDissolution();
// 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;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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
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_group TGV_FAMILY //EN [V] Family.mpp
{
tabLivingWithParents,
tabFamilySizeAtStart,
tabFamily65p,
tabFamilyAgeGrYear
};
table Person tabLivingWithParents //EN [V FAM] 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 [V FAM] 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 [V FAM] 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 Time
//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 [V FAM] 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 //EN Year
};
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
/* NOTE(MaleFamily, EN)
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. Children are assumed to leave home and are therefore considered a separate nuclear family when they turn 18, or earlier if they enter a partnership or become parents themselves. While in education, a proportion of students, set by a parameter by age and sex, is flagged as living with their parents. To be flagged as living with parents, a student must be enrolled in school, single, not a parent, and have known parent links with at least one parent alive. Technically, this requires actor links established either from the starting population and/or created and maintained within the simulation. If any requirement for living with parents is no longer met, students immediately lose this status. In addition, at the beginning of each month, the model checks whether the desired proportion of students by age and sex is living with parents. If the proportion is too high, students are randomly selected to move out. Conversely, if too many students have lost this status and the proportion falls below the desired level, eligible students are selected to move back in with their parents.
Parameters:
Maximum age of students living at home: This is the maximum age up to which tax-benefit systems consider students dependent on their parents and eligible for child benefits.
Proportion of students living at home by age and sex: This parameter applies only to the age range from 18 to the maximum age of students living at home.
/* NOTE(LeavingHome, EN)
The leaving home module deals with the economic emancipation of children. Children are assumed to leave home and are therefore considered a separate nuclear family when they turn 18, or earlier if they enter a partnership or become parents themselves. While in education, a proportion of students, set by a parameter by age and sex, is flagged as living with their parents. To be flagged as living with parents, a student must be enrolled in school, single, not a parent, and have known parent links with at least one parent alive. Technically, this requires actor links established either from the starting population and/or created and maintained within the simulation. If any requirement for living with parents is no longer met, students immediately lose this status. In addition, at the beginning of each month, the model checks whether the desired proportion of students by age and sex is living with parents. If the proportion is too high, students are randomly selected to move out. Conversely, if too many students have lost this status and the proportion falls below the desired level, eligible students are selected to move back in with their parents.
**Parameters:**
- Maximum age of students living at home: This is the maximum age up to which tax-benefit systems consider students dependent on their parents and eligible for child benefits.
- Proportion of students living at home by age and sex: This parameter applies only to the age range from 18 to the maximum age of students living at home.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Students by living with parents status
actor_set Person asStudents[integer_age][sex][student_living_with_parents]
filter is_resident && WITHIN(AGE_STUDENTS, integer_age) && in_educ;
//EN Students potentially living with parents again
actor_set Person asStudentsCouldLiveWithParents[integer_age][sex]
filter is_resident && WITHIN(AGE_STUDENTS, integer_age) && in_educ && !student_living_with_parents
&& knows_recent_parents && !has_spouse && children_in_family == 0;
//EN Heads of families with students 18+ still living at home
actor_set Person asHeadsWithStudentsLivingAtHome
filter is_resident && family_role == FR_HEAD && students_live_in_this_family;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range AGE_STUDENTS { 18, 29 }; //EN Age
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_LEAVING_HOME //EN Leaving home
{
MaxAgeLeavingHomeStudents,
ProportionStudentsLivingWithParents
};
parameters
{
int MaxAgeLeavingHomeStudents; //EN Max age living at home
double ProportionStudentsLivingWithParents[AGE_STUDENTS][SEX]; //EN Proportion students living at home
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Has and knows recent parents
logical knows_recent_parents = (lRecentFather || lRecentMother) ? TRUE : FALSE;
event timeLeavingHomeEvent, LeavingHomeEvent; //EN Leaving home
event timeStopStudentLivingAtHomeEvent, StopStudentLivingAtHomeEvent; //EN Stop being a student living with parents
logical student_living_with_parents = { FALSE }; //EN Student living with parents
//EN Students 18+ are living in this family (heads only)
logical students_live_in_this_family = (family_role == FR_HEAD &&
sum_over(mlRecentFatherChildren, student_living_with_parents) > 0
|| sum_over(mlRecentMotherChildren, student_living_with_parents) > 0) ? TRUE : FALSE;
};
actor Observer
{
void AlignStudentsLivingWithParents(); //EN Align students living with parents
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeStopStudentLivingAtHomeEvent()
{
if (student_living_with_parents && (!knows_recent_parents || !in_educ || integer_age > MaxAgeLeavingHomeStudents || has_spouse || children_in_family > 0))
{
return WAIT(0.0);
}
else return TIME_INFINITE;
}
void Person::StopStudentLivingAtHomeEvent()
{
student_living_with_parents = FALSE;
}
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;
if (in_educ && (lRecentFather || lRecentMother) && !has_spouse && children_in_family == 0) student_living_with_parents = TRUE;
}
void Observer::AlignStudentsLivingWithParents()
{
if (observer_year >= MIN(SIM_YEAR))
{
for (int nAge = MIN(AGE_STUDENTS); nAge <= MAX(AGE_STUDENTS); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
int nStudentsHome = asStudents[nAge][nSex][TRUE]->Count();
int nStudentsNotHome = asStudents[nAge][nSex][FALSE]->Count();
int nStudentsExpectedHome = int(double(nStudentsHome + nStudentsNotHome) * ProportionStudentsLivingWithParents[RANGE_POS(AGE_STUDENTS, nAge)][nSex]);
int nMover = nStudentsHome - nStudentsExpectedHome;
if (nAge > MaxAgeLeavingHomeStudents) nMover = nStudentsHome;
if (nMover > 0)
{
while (nMover > 0 && asStudents[nAge][nSex][TRUE]->Count() > 0)
{
nMover--;
Person_ptr ptrPerson = asStudents[nAge][nSex][TRUE]->GetRandom(RandUniform(102));
ptrPerson->student_living_with_parents = FALSE;
}
}
else if (nMover < 0)
{
while (nMover < 0 && asStudentsCouldLiveWithParents[nAge][nSex]->Count() > 0)
{
nMover++;
Person_ptr ptrPerson = asStudentsCouldLiveWithParents[nAge][nSex]->GetRandom(RandUniform(103));
ptrPerson->student_living_with_parents = TRUE;
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Student who could potentially move back to paremts
logical student_who_could_move_back_to_parents = (is_resident && WITHIN(AGE_STUDENTS, integer_age)
&& in_educ && !student_living_with_parents && knows_recent_parents
&& !has_spouse && children_in_family == 0) ? TRUE : FALSE;
AGE_STUDENTS age_student = COERCE(AGE_STUDENTS,integer_age); //EN Age
};
table_group TGV_LEAVING_HOME //EN EN [V] LeavingHome.mpp
{
tabStudentsLivingAtHomeByAge
};
table Person tabStudentsLivingAtHomeByAge //EN [V LEAHO] Students living at home
[is_resident && in_projected_time && WITHIN(AGE_STUDENTS, integer_age)]
{
sex+ * //EN Sex
{
duration(in_educ,TRUE), //EN Number of students
duration(student_living_with_parents,TRUE), //EN Number of students living at home
duration(in_educ,TRUE) / duration(), //EN Share students in population decimals=3
duration(student_living_with_parents,TRUE)
/ duration(in_educ,TRUE), //EN Share of students living at home decimals=3
duration(student_who_could_move_back_to_parents,TRUE)
/ duration(in_educ,TRUE) //EN Share of students who could move to parents decimals=3
}
* age_student //EN Age
* sim_year //EN Year
};
ParentsEducation.mpp
Parental education is used to model the intergenerational transmission of educational attainment. It is defined as the highest level of education attained by either parent. The model distinguishes four categories of parental education:
Low: ISCED level 2 or below
Medium: ISCED levels 3 or 4
High: ISCED level 5 or above
Unknown: the educational attainment of the parents is unavailable
A child’s future educational attainment is modelled using odds ratios associated with parental education. At the same time, the model must reproduce the target educational distribution for the child’s birth cohort. This requires information on the distribution of parental education among the parents of children born in the same period. Parental education is assigned at birth. An observer function continuously records the parental-education distribution of births occurring during the preceding 12 months. The distribution is updated after each birth and can be retrieved when assigning educational outcomes to members of the corresponding birth cohort.
/* NOTE(ParentsEducation, EN)
Parental education is used to model the intergenerational transmission of educational attainment. It is defined as the highest level of education attained by either parent. The model distinguishes four categories of parental education:
- Low: ISCED level 2 or below
- Medium: ISCED levels 3 or 4
- High: ISCED level 5 or above
- Unknown: the educational attainment of the parents is unavailable
A child’s future educational attainment is modelled using odds ratios associated with parental education. At the same time, the model must reproduce the target educational distribution for the child’s birth cohort. This requires information on the distribution of parental education among the parents of children born in the same period. Parental education is assigned at birth. An observer function continuously records the parental-education distribution of births occurring during the preceding 12 months. The distribution is updated after each birth and can be retrieved when assigning educational outcomes to members of the corresponding birth cohort.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_PARENTS_EDUCATION //EN [V] ParentsEducation.mpp
{
tabParentsEducationYob
};
table Person tabParentsEducationYob //EN [V PARED] Parents Education
[ integer_age == 0 ]
{
creation_type+ *
educ_level3+ *
{
duration(parents_educ,PED_LOW) / duration(), //EN ISCED 3 decimals=3
duration(parents_educ,PED_MEDIUM) / duration(), //EN ISCED 4 decimals=3
duration(parents_educ,PED_HIGH) / duration(), //EN ISCED 5+ decimals=3
duration(parents_educ,PED_UNKNOWN) / duration() //EN Parents education unknown decimals=3
}
* year_of_birth //EN Year of birth
};
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.
/* NOTE(Education, EN)
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);
}
}
// current education
if (creation_type == CT_START)
{
current_educ4 = educ_start;
if ((int)current_educ4 < (int)educ_level4 && !in_educ_start && year_of_birth < MIN(SIM_YEAR) - 10) in_educ_start = TRUE;
}
else current_educ4 = EL4_ISCED2;
}
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_group TGV_EDUCATION //EN [V] Education.mpp
{
tabEducAtStart,
tabEducationYob
};
table Person tabEducAtStart //EN [V EDUC] 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 //EN Age
* educ_level4 //EN Education
};
table Person tabEducationYob //EN [V EDUC] 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
};
EducationEnrolment.mpp
This module handles educational enrolment based on the enrolment patterns observed in the starting population. It assumes that age-specific enrolment rates for a given educational outcome remain at their current levels. Consequently, the dynamics are driven entirely by composition effects resulting from educational expansion.
The enrolment rates used in the simulation are implemented as a state of the observer actor and initialised by the InitializeEnrolmentAtStart() function. This function is called before the simulation begins, once the educational outcomes of all members of the initial population have been determined based on their highest level of education, enrolment status, parental education, and expected cohort outcomes, as defined by the scenario parameters.
In addition to enrolment status, this module implements current highest education. Although the eventual educational outcome is already determined at birth, current highest education is maintained according to the patterns of highest education and school enrolment observed in the initial population, combined with imputed highest education. The module parameterises itself using the enrolment information contained in the initial population file and currently does not allow any alternative scenarios.
/* NOTE(EducationEnrolment, EN)
This module handles educational enrolment based on the enrolment patterns observed in the starting population. It assumes that age-specific enrolment rates for a given educational outcome remain at their current levels. Consequently, the dynamics are driven entirely by composition effects resulting from educational expansion.
The enrolment rates used in the simulation are implemented as a state of the observer actor and initialised by the InitializeEnrolmentAtStart() function. This function is called before the simulation begins, once the educational outcomes of all members of the initial population have been determined based on their highest level of education, enrolment status, parental education, and expected cohort outcomes, as defined by the scenario parameters.
In addition to enrolment status, this module implements current highest education. Although the eventual educational outcome is already determined at birth, current highest education is maintained according to the patterns of highest education and school enrolment observed in the initial population, combined with imputed highest education. The module parameterises itself using the enrolment information contained in the initial population file and currently does not allow any alternative scenarios.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Residents by enrolment status
actor_set Person asResidentsByEnromentStatus[sex][integer_age][educ_level4][in_educ]
filter is_alive && is_resident;
//EN Enroled residents by highest current education
actor_set Person asEnroledResidentsByCurrentEducation[sex][integer_age][educ_level4][current_educ4]
filter is_alive && is_resident && in_educ;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical in_educ = { FALSE }; //EN In education (enroled)
EDUC_LEVEL4 current_educ4 = { EL4_ISCED2 }; //EN Current highest education
};
actor Observer
{
double EnrolmentRates[SEX][AGE_RANGE][EDUC_LEVEL4]; //EN Enrolment rates (in Startpop)
double CurrentLevelEnroled[SEX][AGE_RANGE][EDUC_LEVEL4][EDUC_LEVEL4]; //EN Shares current level by final level
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;
// create "paramater" of current eductation levels for enroled by education fate
double dSum = 0.0;
for (int nCurrentEduc = 0; nCurrentEduc < SIZE(EDUC_LEVEL4); nCurrentEduc++)
{
CurrentLevelEnroled[nSex][nAge][nEduc][nCurrentEduc] =
(double)asEnroledResidentsByCurrentEducation[nSex][nAge][nEduc][nCurrentEduc]->Count();
dSum = dSum + CurrentLevelEnroled[nSex][nAge][nEduc][nCurrentEduc];
}
for (int nCurrentEduc = 0; nCurrentEduc < SIZE(EDUC_LEVEL4) && dSum > 0.0; nCurrentEduc++)
{
CurrentLevelEnroled[nSex][nAge][nEduc][nCurrentEduc] = CurrentLevelEnroled[nSex][nAge][nEduc][nCurrentEduc] / dSum;
}
}
}
}
}
void Observer::UpdateEnrolment()
{
// Align enrolment
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;
ptrPerson->current_educ4 = ptrPerson->educ_level4; // assume highest is reached at first exit
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;
}
}
}
}
// Adjust current education level
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 dSum = 0.0;
// sum up
for (int nCurrent = 0; nCurrent < SIZE(EDUC_LEVEL4); nCurrent++)
{
dSum = dSum + (double)asEnroledResidentsByCurrentEducation[nSex][nAge][nEduc][nCurrent]->Count();
}
// move people to higher education
for (int nCurrent = 0; nCurrent < nEduc; nCurrent++)
{
double dExpected = dSum * CurrentLevelEnroled[nSex][nAge][nEduc][nCurrent];
while (asEnroledResidentsByCurrentEducation[nSex][nAge][nEduc][nCurrent]->Count() > int(dExpected))
{
Person_ptr ptrPerson = asEnroledResidentsByCurrentEducation[nSex][nAge][nEduc][nCurrent]->GetRandom(RandUniform(101));
ptrPerson->current_educ4 = (EDUC_LEVEL4)(nCurrent + 1);
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_EDUCATION_ENROLMENT //EN [V] EducationEnrolment.mpp
{
tabSchoolEnrolemnt,
tabSchoolEnrolemntCurrentHighest2018,
tabSchoolEnrolemntCurrentHighest2020,
tabSchoolEnrolemntCurrentHighest2030
};
table Person tabSchoolEnrolemnt //EN [V EDEN] School enrolment
[in_projected_time && is_resident]
{
educ_level4 + * //EN Education fate
{
duration(in_educ,TRUE) / duration() //EN Education enrolment rate decimals=3
}
* integer_age //EN Age
* sim_year //EN Year
};
table Person tabSchoolEnrolemntCurrentHighest2018 //EN [V EDEN] 2018 Enrolment, current, highest educ
[calendar_year == 2018 && is_resident]
{
creation_type + * //EN Creation type
in_educ + * //EN Enroled
educ_level4 + * //EN Education fate
integer_age* //EN Age
{
duration(current_educ4,EL4_ISCED2) / duration(), //EN Current ISCED 2 decimals=3
duration(current_educ4,EL4_ISCED3) / duration(), //EN Current ISCED 3 decimals=3
duration(current_educ4,EL4_ISCED4) / duration(), //EN Current ISCED 4 decimals=3
duration(current_educ4,EL4_ISCED5) / duration(), //EN Current ISCED 5 decimals=3
duration(current_educ4,EL4_ISCED2), //EN ISCED 2 decimals=0
duration(current_educ4,EL4_ISCED3), //EN ISCED 3 decimals=0
duration(current_educ4,EL4_ISCED4), //EN ISCED 4 decimals=0
duration(current_educ4,EL4_ISCED5) //EN ISCED 5 decimals=0
}
};
table Person tabSchoolEnrolemntCurrentHighest2020 //EN [V EDEN] 2020 Enrolment, current, highest educ
[calendar_year == 2020 && is_resident]
{
creation_type + * //EN Creation type
in_educ + * //EN Enroled
educ_level4 + * //EN Education fate
integer_age* //EN Age
{
duration(current_educ4,EL4_ISCED2) / duration(), //EN Current ISCED 2 decimals=3
duration(current_educ4,EL4_ISCED3) / duration(), //EN Current ISCED 3 decimals=3
duration(current_educ4,EL4_ISCED4) / duration(), //EN Current ISCED 4 decimals=3
duration(current_educ4,EL4_ISCED5) / duration(), //EN Current ISCED 5 decimals=3
duration(current_educ4,EL4_ISCED2), //EN ISCED 2 decimals=0
duration(current_educ4,EL4_ISCED3), //EN ISCED 3 decimals=0
duration(current_educ4,EL4_ISCED4), //EN ISCED 4 decimals=0
duration(current_educ4,EL4_ISCED5) //EN ISCED 5 decimals=0
}
};
table Person tabSchoolEnrolemntCurrentHighest2030 //EN [V EDEN] 2030 Enrolment, current, highest educ
[calendar_year == 2030 && is_resident]
{
creation_type + * //EN Creation type
in_educ + * //EN Enroled
educ_level4 + * //EN Education fate
integer_age* //EN Age
{
duration(current_educ4,EL4_ISCED2) / duration(), //EN Current ISCED 2 decimals=3
duration(current_educ4,EL4_ISCED3) / duration(), //EN Current ISCED 3 decimals=3
duration(current_educ4,EL4_ISCED4) / duration(), //EN Current ISCED 4 decimals=3
duration(current_educ4,EL4_ISCED5) / duration(), //EN Current ISCED 5 decimals=3
duration(current_educ4,EL4_ISCED2), //EN ISCED 2 decimals=0
duration(current_educ4,EL4_ISCED3), //EN ISCED 3 decimals=0
duration(current_educ4,EL4_ISCED4), //EN ISCED 4 decimals=0
duration(current_educ4,EL4_ISCED5) //EN ISCED 5 decimals=0
}
};
Economic activity
Figure: Activity transitions
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 labor 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
As the model is not intended to predict future unemployment rates, a crucial model feature is the ability to align longitudinal careers with scenario-based unemployment rates. This alignment is performed in two steps, allowing rich scenario support, such as convergence scenarios between specific population groups, as well as meeting overall unemployment targets. 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 workforce, 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, which, in addition to the covariates of the alignment model, include state duration.
In the same way, labor force participation can optionally be aligned to a logistic model of 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 among the elderly, people with health limitations, or women. The alignment routine modifies the process of leaving the workforce 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. Unlike the transition models for leaving the labor force, the alignment model also includes the age of the youngest child.
The initial state durations are determined by sampling simulated duration spells created within the simulation. This solution was chosen because records of current spell durations are not available and/or were found to be unreliable in comparative microdata. Donor characteristics are produced using the transition models that already drive activity careers 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.
Three activity transitions are implemented in separate modules, namely retirement (invalidity and old-age pension), first entry into the labor force, and family leave (maternity and parental leave). The module is also complemented by a module for full-time/part-time choice among employed persons.
Parameters:
Activity transitions: collection of hazard regressions for the six modelled processes, accounting for age, state duration, education, and health; separately by sex
Unemployment alignment options: [1] no alignment. [2] Alignment to prevalence scenario. [3] Additional alignment to overall rates.
Probability of unemployment: logistic regression coefficients accounting for age, education, and health; separately by sex; used for optional alignment and for scenarios
Unemployment alignment targets: overall rates by sex and year
Labor force participation alignment options: [1] no alignment. [2] Alignment to prevalence scenario.
Probability of labor force participation: logistic regression coefficients accounting for age, education, health, and age of youngest child; separately by sex; used for optional alignment and for scenarios
Figure: two-step alignment of unemployment
/* NOTE(ActivityTransitions, EN)
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 labor 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
As the model is not intended to predict future unemployment rates, a crucial model feature is the ability to align longitudinal careers with scenario-based unemployment rates. This alignment is performed in two steps, allowing rich scenario support, such as convergence scenarios between specific population groups, as well as meeting overall unemployment targets. 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 workforce, 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, which, in addition to the covariates of the alignment model, include state duration.
In the same way, labor force participation can optionally be aligned to a logistic model of 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 among the elderly, people with health limitations, or women. The alignment routine modifies the process of leaving the workforce 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. Unlike the transition models for leaving the labor force, the alignment model also includes the age of the youngest child.
The initial state durations are determined by sampling simulated duration spells created within the simulation. This solution was chosen because records of current spell durations are not available and/or were found to be unreliable in comparative microdata. Donor characteristics are produced using the transition models that already drive activity careers 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.
Three activity transitions are implemented in separate modules, namely retirement (invalidity and old-age pension), first entry into the labor force, and family leave (maternity and parental leave). The module is also complemented by a module for full-time/part-time choice among employed persons.
**Parameters:**
- Activity transitions: collection of hazard regressions for the six modelled processes, accounting for age, state duration, education, and health; separately by sex
- Unemployment alignment options: [1] no alignment. [2] Alignment to prevalence scenario. [3] Additional alignment to overall rates.
- Probability of unemployment: logistic regression coefficients accounting for age, education, and health; separately by sex; used for optional alignment and for scenarios
- Unemployment alignment targets: overall rates by sex and year
- Labor force participation alignment options: [1] no alignment. [2] Alignment to prevalence scenario.
- Probability of labor force participation: logistic regression coefficients accounting for age, education, health, and age of youngest child; separately by sex; 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
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_LONACT //EN Activity transitions
{
ActEmpUnempAlignment,
ActUnempAlignmentTargets,
ActUnemploymentOdds,
ActLfpAlignment,
ActLfpOdds,
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 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 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
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
{
//EN Last Period Unemployed
double last_period_unemployed[EDUC_LEVEL4][HEALTH_CAT][SEX][ACT_AGE_UNEMP_INDEX];
logical last_period_unemployed_init = { FALSE }; //EN Last Period Unemployed ever initiated
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 dLeave = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_LEAVE]->Count();
double dUnemployed = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_UNEMPLOYED]->Count();
if (!last_period_unemployed_init)
{
last_period_unemployed[nEduc][nHealth][nSex][nAge] = dUnemployed;
}
// 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);
double dMovers = dTargetUnemployed - dUnemployed;
double dLastUnemployedDelta = (last_period_unemployed[nEduc][nHealth][nSex][nAge] - dUnemployed) / 2.0;
if (dLastUnemployedDelta > dMovers / 2.0) dLastUnemployedDelta = dMovers / 2.0;
int nMovers = int(dMovers + dLastUnemployedDelta);
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--;
}
last_period_unemployed[nEduc][nHealth][nSex][nAge] = asActUnemploymentGroup[nEduc][nHealth][nSex][nAge][ACT_UNEMPLOYED]->Count();
}
}
}
}
last_period_unemployed_init = TRUE;
}
}
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)
{
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);
// If on leave initiale type
if (activity_start == ACT_LEAVE)
{
// leave type
if (sex == MALE)
{
time_act_end_parental_leave = MIN(SIM_YEAR) + 0.15;
leave_type = LET_PARENTAL;
gets_contributory_parental = TRUE;
}
else
{
time_act_end_maternity_leave = MIN(SIM_YEAR) + 0.15;
leave_type = LET_MATERNITY;
gets_contributory_maternity = TRUE;
}
}
block_activity_transitions_until_start = TRUE;
time_impute_activity_start = TIME_INFINITE;
}
//////////////////////////
// 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 -> Unemployed
//////////////////////////
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;
}
// for output tables
SetStatesAtEntryUnemployment();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range YOB_RANGE_ACT_TAB { 2018, 2027 }; //EN Birth cohorts 2018-2027
partition LABOR_AGE_PART_TAB { 30, 60 }; //EN Age category
actor Person
{
//EN Age
ACT_UNEMP_AGE_RANGE tab_ue_age = COERCE(ACT_UNEMP_AGE_RANGE, integer_age);
};
table_group TGV_ACTIVITY_TRANSITIONS //EN [V] ActivityTransitions.mpp
{
tabActivityAgeCohort2018to27,
tabActivityDistributionYear,
tabUnemploymentRateAgeYear,
tabActivityTransitions,
tabActivityHeterogeneity
};
table Person tabActivityAgeCohort2018to27 //EN [V ACT] 2018-27 Cohort activity pattern
[WITHIN(YOB_RANGE_ACT_TAB,year_of_birth) && is_resident]
{
sex+ * //EN sex
integer_age * //EN 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 tabActivityDistributionYear //EN [V ACT] Activity status distribution
[in_projected_time && is_resident]
{
sex+ * //EN Sex
{
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 //EN Year
};
table Person tabUnemploymentRateAgeYear //EN [V ACT] Unemployment rate
[ in_projected_time && is_resident
&& WITHIN(ACT_UNEMP_AGE_RANGE, integer_age) ]
{
//sex + * //EN Sex
{
//duration(activity, ACT_UNEMPLOYED)
/// ( duration(activity, ACT_EMPLOYED)
// + duration(activity, ACT_UNEMPLOYED)), //EN Unemployment rate excl leaves decimals=3
duration(activity,ACT_UNEMPLOYED)
/ ( duration(activity,ACT_UNEMPLOYED)
+ duration(activity,ACT_EMPLOYED)
+ duration(activity,ACT_LEAVE)) //EN Unemployment rate incl leaves decimals=3
}
* sim_year //EN Year
* sex+ //EN Sex
//* tab_ue_age+ //EN Age
};
table Person tabActivityTransitions //EN [V ACT] Activity transitions
[in_projected_time && is_resident && in_selected_year]
{
selected_year * //EN Year
sex+ * //EN Sex
integer_age+ * //EN Age
{
exits(activity, ACT_NEVER), //EN Exits from never worked
transitions(activity,ACT_NEVER,ACT_EMPLOYED), //EN Never -> Employed
transitions(activity,ACT_NEVER,ACT_UNEMPLOYED), //EN Never -> Unemployed
transitions(activity,ACT_NEVER,ACT_LEAVE), //EN Never -> Leave
transitions(activity,ACT_NEVER,ACT_OUT), //EN Never -> Out
transitions(activity,ACT_NEVER,ACT_RETIRED), //EN Never -> Retired
entrances(activity,ACT_EMPLOYED), //EN Entrances Employed
transitions(activity,ACT_NEVER,ACT_EMPLOYED), //EN Never -> Employed
transitions(activity,ACT_UNEMPLOYED,ACT_EMPLOYED), //EN Unemployed -> Employed
transitions(activity,ACT_LEAVE,ACT_EMPLOYED), //EN Leave -> Employed
transitions(activity,ACT_OUT,ACT_EMPLOYED), //EN Out -> Employed
transitions(activity,ACT_RETIRED,ACT_EMPLOYED), //EN Retired -> Employed
entrances(activity,ACT_UNEMPLOYED), //EN Entrances Unemployed
transitions(activity,ACT_NEVER,ACT_UNEMPLOYED), //EN Never -> Unemployed
transitions(activity,ACT_EMPLOYED,ACT_UNEMPLOYED), //EN Employed -> Unemployed
transitions(activity,ACT_LEAVE,ACT_UNEMPLOYED), //EN Leave -> Unemployed
transitions(activity,ACT_OUT,ACT_UNEMPLOYED), //EN Out -> Unemployed
transitions(activity,ACT_RETIRED,ACT_UNEMPLOYED), //EN Retired -> Unemployed
entrances(activity,ACT_LEAVE), //EN Entrances Leave
transitions(activity,ACT_NEVER,ACT_LEAVE), //EN Never -> Leave
transitions(activity,ACT_EMPLOYED,ACT_LEAVE), //EN Employed -> Leave
transitions(activity,ACT_UNEMPLOYED,ACT_LEAVE), //EN Unemployed -> Leave
transitions(activity,ACT_OUT,ACT_LEAVE), //EN Out -> Leave
transitions(activity,ACT_RETIRED,ACT_LEAVE), //EN Retired -> Leave
entrances(activity,ACT_OUT), //EN Entrances Out
transitions(activity,ACT_NEVER,ACT_OUT), //EN Never -> Out
transitions(activity,ACT_EMPLOYED,ACT_OUT), //EN Employed -> Out
transitions(activity,ACT_UNEMPLOYED,ACT_OUT), //EN Unemployed -> Out
transitions(activity,ACT_LEAVE,ACT_OUT), //EN Leave -> Out
transitions(activity,ACT_RETIRED,ACT_OUT), //EN Retired -> Out
entrances(activity,ACT_RETIRED), //EN Retirements
transitions(activity,ACT_NEVER,ACT_RETIRED), //EN Never -> Retired
transitions(activity,ACT_EMPLOYED,ACT_RETIRED), //EN Employed -> Retired
transitions(activity,ACT_UNEMPLOYED,ACT_RETIRED), //EN Unemployed -> Retired
transitions(activity,ACT_LEAVE,ACT_RETIRED), //EN Leave -> Retired
transitions(activity,ACT_OUT,ACT_RETIRED) //EN Out -> Retired
}
};
table Person tabActivityHeterogeneity //EN [V ACT] Activity heterogeneity
[ in_projected_time && is_resident && in_selected_year
&& WITHIN(LABOR_AGE,integer_age) ]
{
selected_year * //EN Year
sex+ * //EN Sex
split(integer_age,LABOR_AGE_PART_TAB) * //EN Age group
{
duration(activity,ACT_UNEMPLOYED)
/ (duration(activity,ACT_UNEMPLOYED)
+ duration(activity,ACT_EMPLOYED)
+ duration(activity,ACT_LEAVE)), //EN Unemployment rate decimals=3
duration(activity,ACT_LEAVE) / duration(), //EN Proportion on leave decimals=3
(duration(activity,ACT_UNEMPLOYED)
+ duration(activity,ACT_EMPLOYED)
+ duration(activity,ACT_LEAVE)) / duration(), //EN LFP rate including retired decimals=3
(duration(activity,ACT_UNEMPLOYED)
+ duration(activity,ACT_EMPLOYED)
+ duration(activity,ACT_LEAVE)) /
(duration() - duration(activity,ACT_RETIRED)) //EN LFP rate excluding retired decimals=3
}
* health_cat+ //EN Health status
* age_lfp_child+ //EN Age of youngest child
};
ActivityFirstEntry.mpp
The first entry into the labour force is modelled using entry hazards by age, gender, and education, estimated from retrospective data, i.e. when the highest level of education is known. These rates can optionally be adjusted to achieve a target proportion of individuals, by education, year of birth, and gender, who are assumed never to enter the labour market. In this case, the entry hazards are re-scaled proportionally in the pre-simulation phase to match the target proportion. The algorithm accounts for educational attendance, prioritising those not enrolled in education. It is assumed that people who ever enter the labour force do so between ages 15 and 30.
Parameters:
First entry into the labour force: hazards by age, gender, and education group. These hazards are estimated using retrospective event-history data at the age at which the highest level of education is known.
The share of people by year of birth, education, and gender who are assumed never to enter the labour force. This optional parameter provides alignment targets for birth cohorts younger than 15, or not yet born, at the start of the simulation. If alignment is enabled, the age-specific first-entry hazards are calibrated by a factor determined in the pre-simulation, by year of birth, gender, and highest level of education ever reached.
Alignment of the share of persons never entering the labour force: on/off.
/* NOTE(ActivityFirstEntry, EN)
The first entry into the labour force is modelled using entry hazards by age, gender, and education, estimated from retrospective data, i.e. when the highest level of education is known. These rates can optionally be adjusted to achieve a target proportion of individuals, by education, year of birth, and gender, who are assumed never to enter the labour market. In this case, the entry hazards are re-scaled proportionally in the pre-simulation phase to match the target proportion. The algorithm accounts for educational attendance, prioritising those not enrolled in education. It is assumed that people who ever enter the labour force do so between ages 15 and 30.
**Parameters:**
- First entry into the labour force: hazards by age, gender, and education group. These hazards are estimated using retrospective event-history data at the age at which the highest level of education is known.
- The share of people by year of birth, education, and gender who are assumed never to enter the labour force. This optional parameter provides alignment targets for birth cohorts younger than 15, or not yet born, at the start of the simulation. If alignment is enabled, the age-specific first-entry hazards are calibrated by a factor determined in the pre-simulation, by year of birth, gender, and highest level of education ever reached.
- Alignment of the share of persons never entering the labour force: on/off.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Residents never entered labor force by enrolment status
actor_set Person asResidentsNeverEnteredByEnromentStatus[sex][integer_age][educ_level4][is_resident][in_educ]
filter is_alive && is_resident && activity == ACT_NEVER;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range AGE_ENTRY { 15, 30 }; //EN Age
range YOB_ENTRY_ALIGN { 2003, 2070 }; //EN Year of birth
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_ACT_FIRST_ENTRY //EN First labor-force entry
{
FirstEntryHazard,
AlignFirstEntryNever,
ShareNeverEntered
};
parameters
{
double FirstEntryHazard[SEX][EDUC_LEVEL4][AGE_ENTRY]; //EN First labor entry
double ShareNeverEntered[SEX][YOB_ENTRY_ALIGN][EDUC_LEVEL4]; //EN Share never entering labor force
logical AlignFirstEntryNever; //EN Align first entry never in labor force
//EN First labor entry aligned to never enter targets
model_generated double ActFirstEntryYob[SEX][YOB_ENTRY_ALIGN][AGE_ENTRY][EDUC_LEVEL4];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
event timeActFirstEntryEvent, ActFirstEntryEvent; //EN Event never -> employed
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TIME Person::timeActFirstEntryEvent()
{
double dEventTime = TIME_INFINITE;
// At risk?
if (activity == ACT_NEVER && WITHIN(AGE_ENTRY, integer_age))
{
// unaligned hazard
double dHazard = FirstEntryHazard[sex][educ_level4][RANGE_POS(AGE_ENTRY, integer_age)];
// use aligned hazard if alignment activated
if (AlignFirstEntryNever && year_of_birth >= MIN(YOB_ENTRY_ALIGN))
{
dHazard = ActFirstEntryYob[sex][RANGE_POS(YOB_ENTRY_ALIGN, year_of_birth)][RANGE_POS(AGE_ENTRY, integer_age)][educ_level4];
}
// Event time
if (dHazard > 0.0) dEventTime = WAIT(-log(RandUniform(67)) / dHazard);
}
return dEventTime;
}
void Person::ActFirstEntryEvent()
{
// if enrolled in education try to find somebody else to enter employment
if ( in_educ && asResidentsNeverEnteredByEnromentStatus[sex][integer_age][educ_level4][is_resident][FALSE]->Count() > 0 )
{
Person_ptr ptrPerson = asResidentsNeverEnteredByEnromentStatus[sex][integer_age][educ_level4][is_resident][FALSE]->GetRandom(RandUniform(96));
ptrPerson->doEnterAct(ACT_EMPLOYED);
}
// otherwise enter employment
else
{
doEnterAct(ACT_EMPLOYED);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Simulation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
// Initialisation of ActFirstEntryYob[nSex][nYob][nAge][nEduc]
// copy over
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYob = 0; nYob < SIZE(YOB_ENTRY_ALIGN); nYob++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nAge = 0; nAge < SIZE(AGE_ENTRY); nAge++)
{
ActFirstEntryYob[nSex][nYob][nAge][nEduc] = FirstEntryHazard[nSex][nEduc][nAge];
}
}
}
}
// align if alignment activated
if (AlignFirstEntryNever)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYob = 0; nYob < SIZE(YOB_ENTRY_ALIGN); nYob++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
// Target
double dTarget = ShareNeverEntered[nSex][nYob][nEduc];
// for very small targets close to 0 force virtually everybody into labor force at max entry age
if (dTarget < 0.0005) ActFirstEntryYob[nSex][nYob][SIZE(AGE_ENTRY) - 1][nEduc] = 999;
// else find alignment factor
else
{
double dSurvival = 1.0;
double dUpper = 100.0;
double dLower = 0.01;
double dFactor = 1.0;
int nMaxIterations = 10000;
for (int nAge = 0; nAge < SIZE(AGE_ENTRY); nAge++)
{
dSurvival = dSurvival * exp(-ActFirstEntryYob[nSex][nYob][nAge][nEduc] * dFactor);
}
while (nMaxIterations > 0 && abs(dSurvival - dTarget) > 0.0001)
{
nMaxIterations--;
if (dSurvival > dTarget) dLower = dFactor;
else dUpper = dFactor;
dFactor = (dLower + dUpper) / 2.0;
dSurvival = 1.0;
for (int nAge = 0; nAge < SIZE(AGE_ENTRY); nAge++)
{
dSurvival = dSurvival * exp(-ActFirstEntryYob[nSex][nYob][nAge][nEduc] * dFactor);
}
}
// apply solution
for (int nAge = 0; nAge < SIZE(AGE_ENTRY); nAge++)
{
ActFirstEntryYob[nSex][nYob][nAge][nEduc] = ActFirstEntryYob[nSex][nYob][nAge][nEduc] * dFactor;
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_ACTIVITY_FIRST_ENTRY //EN [V] ActivityFirstEntry.mpp
{
tabFirstEntryLabor2018P
};
table Person tabFirstEntryLabor2018P //EN [V ENTRY] Ever entered labor force
[is_resident && in_projected_time]
{
creation_type + * //EN Creation type
sex + * //EN Sex
{
duration(activity,ACT_NEVER) / duration(), //EN Proportion never in labor force decimals=4
transitions(activity,ACT_NEVER,ACT_EMPLOYED)
/ duration(activity,ACT_NEVER) //EN Hazard never worked employed decimals=4
}
* integer_age //EN Age
* educ_level4 + //EN Education
};
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 and age of children in the family, education level, and age. The status is updated when a person enters new employment and is maintained monthly. Because people tend to remain in the same status for extended periods rather than switching frequently between full-time and part-time work, the model aims to keep individuals in their current status while simultaneously matching the modelled probabilities based on their individual characteristics. Since no longitudinal data are available for modelling transitions between full-time and part-time work, transitions are modelled using a heuristic procedure designed to preserve status persistence while matching the predicted probabilities. Individuals 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, each actor attempts to find another person within the same risk group who has been flagged for a transition in the opposite direction. If such a person is found, both transitions are cancelled. If no such person is found, the actor’s status changes. Mobility between states can be increased through a parameter that determines the probability that an actor attempts to remain in the current state.
Parameters:
Probability of part-time work: coefficients from the logistic regression.
Mobility between full-time and part-time states: probability that an actor attempts to remain in the current state.
/* NOTE(ActivityParttime, EN)
This module covers part-time work. The probability of part-time employment is calculated using logistic regression and takes into account gender, the presence and age of children in the family, education level, and age. The status is updated when a person enters new employment and is maintained monthly. Because people tend to remain in the same status for extended periods rather than switching frequently between full-time and part-time work, the model aims to keep individuals in their current status while simultaneously matching the modelled probabilities based on their individual characteristics. Since no longitudinal data are available for modelling transitions between full-time and part-time work, transitions are modelled using a heuristic procedure designed to preserve status persistence while matching the predicted probabilities. Individuals 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, each actor attempts to find another person within the same risk group who has been flagged for a transition in the opposite direction. If such a person is found, both transitions are cancelled. If no such person is found, the actor’s status changes. Mobility between states can be increased through a parameter that determines the probability that an actor attempts to remain in the current state.
**Parameters:**
- Probability of part-time work: coefficients from the logistic regression.
- Mobility between full-time and part-time states: probability that an actor attempts 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
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
partition PART_TIME_TAB_PART { 0.25, 0.5, 0.75, 1, 2, 4, 8, 12 }; //EN Duration parttime
table_group TGV_ACTIVITYPARTTIME //EN [V] ActivityParttime.mpp
{
tabParttime,
tabParttimeQuantile,
tabParttimeChanges,
tabParttimeDurations2030
};
table Person tabParttime //EN [V PART] Parttime
[in_projected_time && activity==ACT_EMPLOYED && is_resident]
{
sex + * //EN Sex
{
duration(full_part_time,FPT_PART) / duration() //EN Proportion part-time decimals=3
}
* integer_age + //EN Age
* sim_year //EN Sex
};
table Person tabParttimeQuantile //EN [V PART] Part-time quantile
[in_projected_time && activity == ACT_EMPLOYED && is_resident]
{
sim_year * //EN Year
quantile_part_time + * //EN Quantile
{
weighted_duration(prob_part_time) / duration(), //EN Model probability parttime decimals=3
duration(full_part_time,FPT_PART) / duration() //EN Simulation probability parttime decimals=3
}
};
table Person tabParttimeChanges //EN [V PART] Part-time changes
[in_projected_time && activity == ACT_EMPLOYED && is_resident]
{
sex+ * //EN Sex
{
changes(full_part_time), //EN Number changes
changes(full_part_time) / duration() //EN Hazard changes decimals=4
}
* integer_age+ //EN Age
* sim_year //EN Year
};
table Person tabParttimeDurations2030 //EN [V PART] Part-time durations 2030
[activity == ACT_EMPLOYED && trigger_entrances(calendar_year, 2030) && is_resident && full_part_time == FPT_PART]
{
sex + * //EN Sex
{
unit //EN Persons
}
* self_scheduling_split(active_spell_duration(full_part_time,FPT_PART),PART_TIME_TAB_PART) + //EN Duration
};
Retirement.mpp
This module implements the old-age retirement decision. It is based on the eligibility criteria for both early and regular old-age retirement. A minimum social insurance contribution period and a minimum age are set for both pathways into retirement. When reaching the maximum working age, people are forced to retire.
Scenarios:
Base: everybody retires when eligible.
Parameters:
Regular retirement age by year of birth and sex
Regular retirement minimum contribution period by year of birth and sex
Early retirement age by year of birth and sex
Early retirement contribution period by year of birth and sex
/* NOTE(Retirement, EN)
This module implements the old-age retirement decision. It is based on the eligibility criteria for both early and regular old-age retirement. A minimum social insurance contribution period and a minimum age are set for both pathways into retirement. When reaching the maximum working age, people are forced to retire.
**Scenarios:**
Base: everybody retires when eligible.
**Parameters:**
- Regular retirement age by year of birth and sex
- Regular retirement minimum contribution period by year of birth and sex
- Early retirement age by year of birth and sex
- Early retirement contribution period by year of birth and sex
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range YOB_RET_RANGE { 1900, 2054 }; //EN Year of birth
classification BIRTH_MONTH //EN Birth month
{
BM_00, //EN Jan-June
BM_01 //EN July-Dez
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_RETIREMENT //EN Retirement
{
RegRetirementAge,
RegRetirementContribution,
EarlyRetirementAge,
EarlyRetirementContribution
};
parameters
{
double RegRetirementAge[SEX][YOB_RET_RANGE][BIRTH_MONTH]; //EN Regular retirement age
double RegRetirementContribution[SEX][YOB_RET_RANGE]; //EN Regular retirement contribution years
double EarlyRetirementAge[SEX][YOB_RET_RANGE][BIRTH_MONTH]; //EN Early retirement age
double EarlyRetirementContribution[SEX][YOB_RET_RANGE]; //EN Early retirement contribution years
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Birth month
BIRTH_MONTH birth_month = (time_of_birth - int(time_of_birth) < 0.5) ? BM_00 : BM_01;
int sim_year_of_retirement = { 0 }; //EN Year of retirement in simulation
logical can_retire = { FALSE }; //EN Can retire
logical can_retire_reg = { FALSE }; //EN Can retire regular old age
logical can_retire_early = { FALSE }; //EN Can retire early old age
void doUpdateOldAgeRetiremetEligibility(); //EN Update old-age retirement eligibility
event timeActRetireEvent, ActRetireEvent; //EN Retirement event
};
actor Observer
{
void UpdateOldAgeRetiremetEligibility(); //EN Update old-age retirement eligibility
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateOldAgeRetiremetEligibility()
{
long nPersons = asAllPerson->Count();
for (long nJ = 0; nJ < nPersons; nJ++)
{
Person_ptr ptrPerson = asAllPerson->Item(nJ);
ptrPerson->doUpdateOldAgeRetiremetEligibility();
}
}
void Person::doUpdateOldAgeRetiremetEligibility()
{
if (activity != ACT_RETIRED && in_projected_time)
{
// eligible for regulat old-age retirement
if ( age >= RegRetirementAge[sex][RANGE_POS(YOB_RET_RANGE, year_of_birth)][birth_month] &&
pension_contribution_time >= RegRetirementContribution[sex][RANGE_POS(YOB_RET_RANGE, year_of_birth)])
{
can_retire = TRUE;
can_retire_reg = TRUE;
}
// eligible for early old-age retirement
else if ( age >= EarlyRetirementAge[sex][RANGE_POS(YOB_RET_RANGE, year_of_birth)][birth_month] &&
pension_contribution_time >= EarlyRetirementContribution[sex][RANGE_POS(YOB_RET_RANGE, year_of_birth)])
{
can_retire = TRUE;
can_retire_early = TRUE;
}
}
else if (activity == ACT_RETIRED && in_projected_time) can_retire = TRUE; // for test output
}
TIME Person::timeActRetireEvent()
{
// retire as soon as possible TODO allow scenarios
if (can_retire && activity != ACT_RETIRED) return WAIT(0.0);
// forced retirement at old age
else if (in_projected_time && integer_age > MAX(LABOR_AGE) && activity != ACT_RETIRED) return WAIT(0.0);
// do not retire
else return TIME_INFINITE;
}
void Person::ActRetireEvent()
{
doEnterAct(ACT_RETIRED);
doSetPension();
sim_year_of_retirement = calendar_year;
can_retire = TRUE;
if (in_projected_time) doTabPensionStates(); // Pension table states Table-Paper-Pension.mpp
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VALIDATION
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabCanRetireAndActivities //EN [ACT] Can retire and activities by age
[in_projected_time && is_resident]
{
sex+ * //EN Sex
{
duration(can_retire, TRUE) / duration(), //EN Can retire decimals=3
duration(activity, ACT_RETIRED) / duration(), //EN Retired decimals=3
duration(activity, ACT_UNEMPLOYED)
/ (duration(activity, ACT_EMPLOYED)
+ duration(activity, ACT_UNEMPLOYED)), //EN Unemployment rate decimals=3
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 Family leave decimals=3
duration(activity, ACT_OUT) / duration() //EN Out of labor force decimals=3
}
* integer_age + //EN Age
* sim_year //EN Year
};
Tax-Benefit System
TaxBen-General.mpp
This module implements general tax- and benefit-related functionality used across the microWELT model. It does not encode the rules of specific taxes or benefits directly. Instead, it provides shared states, classifications, and lookup structures that prepare simulated individuals and nuclear families for tax–benefit calculations handled in subject-specific modules, such as personal income tax, social insurance contributions, and a series of benefit categories. The module defines the family, income, and activity-state typologies required to link simulated families to pre-calculated tax–benefit parameters. These parameters are derived from a synthetic-grid tax–benefit calculator based on EUROMOD’s Hypothetical Household Tool (HHoT). The approach supports fast deterministic lookups during continuous-time simulation.
Tax–benefit modelling in microWELT is organised into two broad groups.
The first group consists of taxes and benefits that can be represented using the synthetic-grid tax–benefit calculator. These include personal income tax, employee social insurance contributions, employer social insurance contributions, family benefits, social benefits, education-related benefits during compulsory education, and old-age benefits. For these components, EUROMOD HHoT is used to generate tax and benefit outcomes for a large synthetic population covering relevant combinations of family structure, income sources, and income levels. The resulting outputs are transformed into multidimensional parameter tables that can be queried directly by the dynamic model.
The second group consists of benefits that cannot be adequately covered by the grid calculator because eligibility or entitlement depends on characteristics not represented in EUROMOD HHoT. Examples include housing benefits, which require information on home ownership or housing costs; health benefits, which depend on health limitations or service use; and long-term care benefits, which depend on care needs. These components are modelled separately in dedicated modules.
The central purpose of this module is to classify each simulated nuclear family into the categories required for deterministic lookup in the synthetic tax–benefit parameter tables. The term synthetic grid refers to a synthetic database of model families designed to cover the policy-relevant state space. Rather than searching for a similar household in a survey-based database, each simulated family is mapped to a predefined cell described by family type, income category, activity status, and income level. The corresponding tax–benefit parameters can then be retrieved directly. The synthetic database contains all relevant combinations of:
partnership status: single or couple;
number and age composition of dependent children;
activity and income type of each adult;
income level, expressed relative to the national average wage;
benefit or tax category.
The classifications defined in this module are used in two main ways.
First, partnership status and adult activity combinations are used to split up the synthetic database into parameters, avoiding all un-necessary combinations of characteristics within a group. For example, single-adult families require only one adult income type and one income level, whereas couple families require combinations of both partners’ activity states and income levels.
Second, family type and income levels are used as dimensions within the selected parameter table. Depending on the parameter, the lookup may vary by child composition, one or both adults’ income levels, and the relevant income category.
Tax–benefit-relevant characteristics can change at any time due to employment transitions, unemployment spells, retirement, childbirth, partnership formation or dissolution, children ageing into new categories, or changes in income. Whenever such a change occurs, this module updates the family’s classification. The retrieved parameters are then used by the relevant tax–benefit accounting modules to update current rates and benefit amounts. Updates are triggered either immediately, for example in the case of income tax and social insurance contributions, or monthly, as for most benefits.
/* NOTE(TaxBen-General, EN)
This module implements general tax- and benefit-related functionality used across the microWELT model. It does not encode the rules of specific taxes or benefits directly. Instead, it provides shared states, classifications, and lookup structures that prepare simulated individuals and nuclear families for tax–benefit calculations handled in subject-specific modules, such as personal income tax, social insurance contributions, and a series of benefit categories. The module defines the family, income, and activity-state typologies required to link simulated families to pre-calculated tax–benefit parameters. These parameters are derived from a synthetic-grid tax–benefit calculator based on EUROMOD’s Hypothetical Household Tool (HHoT). The approach supports fast deterministic lookups during continuous-time simulation.
Tax–benefit modelling in microWELT is organised into two broad groups.
- The first group consists of taxes and benefits that can be represented using the synthetic-grid tax–benefit calculator. These include personal income tax, employee social insurance contributions, employer social insurance contributions, family benefits, social benefits, education-related benefits during compulsory education, and old-age benefits. For these components, EUROMOD HHoT is used to generate tax and benefit outcomes for a large synthetic population covering relevant combinations of family structure, income sources, and income levels. The resulting outputs are transformed into multidimensional parameter tables that can be queried directly by the dynamic model.
- The second group consists of benefits that cannot be adequately covered by the grid calculator because eligibility or entitlement depends on characteristics not represented in EUROMOD HHoT. Examples include housing benefits, which require information on home ownership or housing costs; health benefits, which depend on health limitations or service use; and long-term care benefits, which depend on care needs. These components are modelled separately in dedicated modules.
The central purpose of this module is to classify each simulated nuclear family into the categories required for deterministic lookup in the synthetic tax–benefit parameter tables. The term synthetic grid refers to a synthetic database of model families designed to cover the policy-relevant state space. Rather than searching for a similar household in a survey-based database, each simulated family is mapped to a predefined cell described by family type, income category, activity status, and income level. The corresponding tax–benefit parameters can then be retrieved directly. The synthetic database contains all relevant combinations of:
- partnership status: single or couple;
- number and age composition of dependent children;
- activity and income type of each adult;
- income level, expressed relative to the national average wage;
- benefit or tax category.
The classifications defined in this module are used in two main ways.
- First, partnership status and adult activity combinations are used to split up the synthetic database into parameters, avoiding all un-necessary combinations of characteristics within a group. For example, single-adult families require only one adult income type and one income level, whereas couple families require combinations of both partners’ activity states and income levels.
- Second, family type and income levels are used as dimensions within the selected parameter table. Depending on the parameter, the lookup may vary by child composition, one or both adults’ income levels, and the relevant income category.
Tax–benefit-relevant characteristics can change at any time due to employment transitions, unemployment spells, retirement, childbirth, partnership formation or dissolution, children ageing into new categories, or changes in income. Whenever such a change occurs, this module updates the family’s classification. The retrieved parameters are then used by the relevant tax–benefit accounting modules to update current rates and benefit amounts. Updates are triggered either immediately, for example in the case of income tax and social insurance contributions, or monthly, as for most benefits.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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
};
classification CHILD_AGE_GROUP //EN Child age group
{
CAG_0, //EN Baby age 0
CAG_1TO5, //EN 1 to 5
CAG_6TO14, //EN 6 to 14
CAG_15P //EN 15+
};
classification EQUI_PERSON_TYPE //EN Equivalence person type
{
EPG_HEAD, //EN Head
EPG_SPOUSE, //EN Spouse
EPG_0, //EN Baby age 0
EPG_1TO5, //EN Child 1 to 5
EPG_6TO14, //EN Child 6 to 14
EPG_15P //EN Child 15+
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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_leave_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 15-18
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_04) ? 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_14) ? 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;
// Alternative fam type ( plus one student 18 - minus if already 3 children)
TAX_FAM_TYPE alt_fam_type =
(tax_fam_type == TFT_00) ? TFT_04 : //EN No children
(tax_fam_type == TFT_01) ? TFT_08 : //EN One child [0]
(tax_fam_type == TFT_02) ? TFT_11 : //EN One child [1-5]
(tax_fam_type == TFT_03) ? TFT_13 : //EN One child [6-14]
(tax_fam_type == TFT_04) ? TFT_14 : //EN One child [15-18]
(tax_fam_type == TFT_05) ? TFT_18 : //EN Two children [0][0]
(tax_fam_type == TFT_06) ? TFT_21 : //EN Two children [0][1-5]
(tax_fam_type == TFT_07) ? TFT_23 : //EN Two children [0][6-14]
(tax_fam_type == TFT_08) ? TFT_24 : //EN Two children [0][15-18]
(tax_fam_type == TFT_09) ? TFT_27 : //EN Two children [1-5][1-5]
(tax_fam_type == TFT_10) ? TFT_29 : //EN Two children [1-5][6-14]
(tax_fam_type == TFT_11) ? TFT_30 : //EN Two children [1-5][15-18]
(tax_fam_type == TFT_12) ? TFT_32 : //EN Two children [6-14][6-14]
(tax_fam_type == TFT_13) ? TFT_33 : //EN Two children [6-14][15-18]
(tax_fam_type == TFT_14) ? TFT_34 : //EN Two children [15-18][15-18]
(tax_fam_type == TFT_15) ? TFT_05 : //EN Three children [0][0][0]
(tax_fam_type == TFT_16) ? TFT_05 : //EN Three children [0][0][1-5]
(tax_fam_type == TFT_17) ? TFT_05 : //EN Three children [0][0][6-14]
(tax_fam_type == TFT_18) ? TFT_05 : //EN Three children [0][0][15-18]
(tax_fam_type == TFT_19) ? TFT_06 : //EN Three children [0][1-5][1-5]
(tax_fam_type == TFT_20) ? TFT_06 : //EN Three children [0][1-5][6-14]
(tax_fam_type == TFT_21) ? TFT_06 : //EN Three children [0][1-5][15-18]
(tax_fam_type == TFT_22) ? TFT_07 : //EN Three children [0][6-14][6-14]
(tax_fam_type == TFT_23) ? TFT_07 : //EN Three children [0][6-14][15-18]
(tax_fam_type == TFT_24) ? TFT_08 : //EN Three children [0][15-18][15-18]
(tax_fam_type == TFT_25) ? TFT_09 : //EN Three children [1-5][1-5][1-5]
(tax_fam_type == TFT_26) ? TFT_09 : //EN Three children [1-5][1-5][6-14]
(tax_fam_type == TFT_27) ? TFT_09 : //EN Three children [1-5][1-5][15-18]
(tax_fam_type == TFT_28) ? TFT_10 : //EN Three children [1-5][6-14][6-14]
(tax_fam_type == TFT_29) ? TFT_10 : //EN Three children [1-5][6-14][15-18]
(tax_fam_type == TFT_30) ? TFT_11 : //EN Three children [1-5][15-18][15-18]
(tax_fam_type == TFT_31) ? TFT_12 : //EN Three children [6-14][6-14][6-14]
(tax_fam_type == TFT_32) ? TFT_12 : //EN Three children [6-14][6-14][15-18]
(tax_fam_type == TFT_33) ? TFT_13 : TFT_14; //EN Three children [6-14][15-18][15-18]
//EN ALternative family type with parental leave
TAX_FAM_TYPE_PARENTAL alt_fam_type_parental =
(alt_fam_type <= TFT_04) ? TFTP_01 :
(alt_fam_type == TFT_05) ? TFTP_05 :
(alt_fam_type == TFT_06) ? TFTP_06 :
(alt_fam_type == TFT_07) ? TFTP_07 :
(alt_fam_type <= TFT_14) ? TFTP_08 :
(alt_fam_type == TFT_15) ? TFTP_15 :
(alt_fam_type == TFT_16) ? TFTP_16 :
(alt_fam_type == TFT_17) ? TFTP_17 :
(alt_fam_type == TFT_18) ? TFTP_18 :
(alt_fam_type == TFT_19) ? TFTP_19 :
(alt_fam_type == TFT_20) ? TFTP_20 :
(alt_fam_type == TFT_21) ? TFTP_21 :
(alt_fam_type == TFT_22) ? TFTP_22 :
(alt_fam_type == TFT_23) ? TFTP_23 : TFTP_24;
//EN Alternative family type couple pension pension
TAX_FAM_TYPE_PENPEN alt_fam_type_penpen =
(alt_fam_type == TFT_00) ? TFTPP_00 :
(alt_fam_type <= TFT_04) ? TFTPP_04 :
(alt_fam_type <= TFT_14) ? TFTPP_14 : TFTPP_34;
//EN Alternative family type single pensioner
TAX_FAM_TYPE_SINGPEN alt_fam_type_singpen =
(alt_fam_type == TFT_00) ? TFTSP_00 :
(alt_fam_type <= TFT_04) ? TFTSP_04 :
(alt_fam_type <= TFT_14) ? TFTSP_14 : TFTSP_34;
};
actor Observer
{
//EN Update all benefits
void UpdateAllBenefits();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateAllBenefits()
{
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)->family_benefit_student = 0.0;
asAllPerson->Item(nJ)->education_benefit = 0.0;
asAllPerson->Item(nJ)->social_benefit = 0.0;
asAllPerson->Item(nJ)->social_benefit_student = 0.0;
if (asAllPerson->Item(nJ)->family_role != FR_HEAD) asAllPerson->Item(nJ)->social_benefit_eligible_months = 0;
}
UpdateOldageBenefit();
UpdateFamilyBenefit();
UpdateEducationBenefit();
UpdateSocialBenefit();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_TAX_BEN_GENERAL //EN [V] TaxBen-General.mpp
{
tabTaxFamilyTypeAge,
tabTaxFamilyTypeCombinatios
};
table Person tabTaxFamilyTypeAge //EN [V TAXGEN] Tax-Ben family type by age (heads)
[in_projected_time && is_resident && family_role == FR_HEAD && in_selected_year]
{
selected_year *
{
duration() //EN Number families
}
* integer_age
* tax_fam_type+
};
table Person tabTaxFamilyTypeCombinatios //EN [V TAXGEN] Tax-Ben family type combinations
[in_projected_time && is_resident && family_role == FR_HEAD && in_selected_year]
{
selected_year * //EN Year
student_living_with_parents+ * //EN Student living with parents
{
duration() //EN Number families
}
* tax_fam_type + //EN Family type
* activity+ //EN Activity
};
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.
/* NOTE(TaxBen-Accounts, EN)
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
ACI_LTC_ALLOWANCE, //EN LTC allowance
ACI_HEALTH_BENEFIT, //EN Health benefit
ACI_EDUC_PRIVATE, //EN Private education consumption
ACI_EDUC_PUBLIC, //EN Public education consumption
ACI_HEALTH_PRIVATE, //EN Private health consumption
ACI_HEALTH_PUBLIC, //EN Public health consumption
ACI_LTC_PRIVATE, //EN Private health consumption
ACI_LTC_PUBLIC, //EN Public health consumption
ACI_SAVINGS, //EN Savings
ACI_CONSUMPTION //EN Other private consumption
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
void doInitAccount(); //EN Initialise accounts
void doUpdateAccount(); //EN Update accounts
void doUpdateMonthlyAccumFlows(); //EN Update monthly accumulated flows individual
void doAggregateMonthlyFamilyAccumFlows(); //EN Update monthly accumulated flows family
double account[AGE_RANGE][ACCOUNT_ITEMS]; //EN Personal account
double this_period[ACCOUNT_ITEMS]; //EN Value in this period
double this_period_family[ACCOUNT_ITEMS]; //EN Values aggregated over family in this period
double last_accum_account[ACCOUNT_ITEMS]; //EN Most recent accumulated value within year
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
double year_end_ltc_allowance = { 0.0 }; //EN LTC allowance
double year_end_educ_private = { 0.0 }; //EN Private education consumption
double year_end_educ_public = { 0.0 }; //EN Public education consumption
double year_end_health_private = { 0.0 }; //EN Private health consumption
double year_end_health_public = { 0.0 }; //EN Public health consumption
double year_end_ltc_private = { 0.0 }; //EN Private health consumption
double year_end_ltc_public = { 0.0 }; //EN Public health consumption
double year_end_health_benefits = { 0.0 }; //EN HEalth benefits
double year_end_consumption = { 0.0 }; //EN Other consumption
double year_end_savings = { 0.0 }; //EN Savings
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::doAggregateMonthlyFamilyAccumFlows()
{
// double-check that only resident family heads
if (family_role == FR_HEAD && is_resident)
{
for (int nItem = 0; nItem < SIZE(ACCOUNT_ITEMS); nItem++)
{
// copy over own
this_period_family[nItem] = this_period[nItem];
// add spouse
if (has_spouse) this_period_family[nItem] = this_period_family[nItem] + lSpouse->this_period[nItem];
// add children
Person_ptr prChild = NULL;
int nIndex = 0;
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(0, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(0, &nIndex);
while (prChild)
{
// add values
this_period_family[nItem] = this_period_family[nItem] + prChild->this_period[nItem];
// link to next child
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(nIndex + 1, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
}
void Person::doUpdateMonthlyAccumFlows()
{
this_period[ACI_EARNINGS] = accum_earnings - last_accum_account[ACI_EARNINGS];
this_period[ACI_UE_BENEFITS] = accum_unemployment_benefit - last_accum_account[ACI_UE_BENEFITS];
this_period[ACI_LEAVE_BENEFITS] = accum_leave_benefit - last_accum_account[ACI_LEAVE_BENEFITS];
this_period[ACI_PENSION] = accum_pension - last_accum_account[ACI_PENSION];
this_period[ACI_SI_OWN] = si_accum_contribution_own - last_accum_account[ACI_SI_OWN];
this_period[ACI_SI_EMPLOYER] = si_accum_contribution_employer - last_accum_account[ACI_SI_EMPLOYER];
this_period[ACI_INCOME_TAX] = accum_income_tax - last_accum_account[ACI_INCOME_TAX];
this_period[ACI_OLDAGE_BENEFIT] = accum_oldage_benefit - last_accum_account[ACI_OLDAGE_BENEFIT];
this_period[ACI_FAMILY_BENEFIT] = accum_family_benefit - last_accum_account[ACI_FAMILY_BENEFIT];
this_period[ACI_EDUCATION_BENEFIT] = accum_education_benefit - last_accum_account[ACI_EDUCATION_BENEFIT];
this_period[ACI_SOCIAL_BENEFIT] = accum_social_benefit - last_accum_account[ACI_SOCIAL_BENEFIT];
this_period[ACI_LTC_ALLOWANCE] = accum_ltc_allowance - last_accum_account[ACI_LTC_ALLOWANCE];
this_period[ACI_EDUC_PRIVATE] = accum_consumption_education_private - last_accum_account[ACI_EDUC_PRIVATE];
this_period[ACI_EDUC_PUBLIC] = accum_consumption_education_public - last_accum_account[ACI_EDUC_PUBLIC];
this_period[ACI_HEALTH_PRIVATE] = accum_consumption_health_private - last_accum_account[ACI_HEALTH_PRIVATE];
this_period[ACI_HEALTH_PUBLIC] = accum_consumption_health_public - last_accum_account[ACI_HEALTH_PUBLIC];
this_period[ACI_LTC_PRIVATE] = accum_consumption_ltc_private - last_accum_account[ACI_LTC_PRIVATE];
this_period[ACI_LTC_PUBLIC] = accum_consumption_ltc_public - last_accum_account[ACI_LTC_PUBLIC];
this_period[ACI_HEALTH_BENEFIT] = accum_health_benefits - last_accum_account[ACI_HEALTH_BENEFIT];
last_accum_account[ACI_EARNINGS] = accum_earnings;
last_accum_account[ACI_UE_BENEFITS] = accum_unemployment_benefit;
last_accum_account[ACI_LEAVE_BENEFITS] = accum_leave_benefit;
last_accum_account[ACI_PENSION] = accum_pension;
last_accum_account[ACI_SI_OWN] = si_accum_contribution_own;
last_accum_account[ACI_SI_EMPLOYER] = si_accum_contribution_employer;
last_accum_account[ACI_INCOME_TAX] = accum_income_tax;
last_accum_account[ACI_OLDAGE_BENEFIT] = accum_oldage_benefit;
last_accum_account[ACI_FAMILY_BENEFIT] = accum_family_benefit;
last_accum_account[ACI_EDUCATION_BENEFIT] = accum_education_benefit;
last_accum_account[ACI_SOCIAL_BENEFIT] = accum_social_benefit;
last_accum_account[ACI_LTC_ALLOWANCE] = accum_ltc_allowance;
last_accum_account[ACI_EDUC_PRIVATE] = accum_consumption_education_private;
last_accum_account[ACI_EDUC_PUBLIC] = accum_consumption_education_public;
last_accum_account[ACI_HEALTH_PRIVATE] = accum_consumption_health_private;
last_accum_account[ACI_HEALTH_PUBLIC] = accum_consumption_health_public;
last_accum_account[ACI_LTC_PRIVATE] = accum_consumption_ltc_private;
last_accum_account[ACI_LTC_PUBLIC] = accum_consumption_ltc_public;
last_accum_account[ACI_HEALTH_BENEFIT] = accum_health_benefits;
// reset family transfers
current_transfer_in = 0.0;
current_transfer_out = 0.0;
};
void Person::doInitAccount()
{
for (int nItem = 0; nItem < SIZE(ACCOUNT_ITEMS); nItem++)
{
last_accum_account[nItem] = 0.0;
this_period[nItem] = 0.0;
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
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_leave_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;
account[integer_age][ACI_LTC_ALLOWANCE] = accum_ltc_allowance;
account[integer_age][ACI_EDUC_PRIVATE] = accum_consumption_education_private;
account[integer_age][ACI_EDUC_PUBLIC] = accum_consumption_education_public;
account[integer_age][ACI_HEALTH_PRIVATE] = accum_consumption_health_private;
account[integer_age][ACI_HEALTH_PUBLIC] = accum_consumption_health_public;
account[integer_age][ACI_LTC_PRIVATE] = accum_consumption_ltc_private;
account[integer_age][ACI_LTC_PUBLIC] = accum_consumption_ltc_public;
account[integer_age][ACI_HEALTH_BENEFIT] = accum_health_benefits;
year_end_earnings = accum_earnings;
year_end_ue_benefits = accum_unemployment_benefit;
year_end_leave_benefits = accum_leave_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;
year_end_ltc_allowance = accum_ltc_allowance;
year_end_educ_private = accum_consumption_education_private;
year_end_educ_public = accum_consumption_education_public;
year_end_health_private = accum_consumption_health_private;
year_end_health_public = accum_consumption_health_public;
year_end_ltc_private = accum_consumption_ltc_private;
year_end_ltc_public = accum_consumption_ltc_public;
year_end_health_benefits = accum_health_benefits;
year_end_consumption = account[integer_age][ACI_CONSUMPTION];
year_end_savings = account[integer_age][ACI_SAVINGS];
// reset accumulated values within the year to 0
for (int nItem = 0; nItem < SIZE(ACCOUNT_ITEMS); nItem++)
{
last_accum_account[nItem] = 0.0;
};
};
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabYearlyAccountTotals2025 //EN [__A] Yearly accounts start 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
value_in(year_end_ltc_allowance) / unit, //EN LTC allowance
value_in(year_end_health_benefits) / unit, //EN Health benefits
value_in(year_end_educ_private) / unit, //EN Consumption education private
value_in(year_end_educ_public) / unit, //EN Consumption education public
value_in(year_end_health_private) / unit, //EN Consumption health private
value_in(year_end_health_public) / unit, //EN Consumption health public
value_in(year_end_ltc_private) / unit, //EN Consumption LTC private
value_in(year_end_ltc_public) / unit, //EN Consumption LTC public
value_in(year_end_consumption) / unit, //EN Consumption
value_in(year_end_savings) / unit //EN Savings
}
* 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
/* NOTE(TaxBen-IncomeTax, EN)
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) ? leave_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) ? leave_benefit * IncomeTaxCoupleParentalEmployed[tax_fam_type_parental][earn_index_parental][earn_index_employed_spouse] :
(has_spouse && activity == ACT_LEAVE && activity_spouse == ACT_UNEMPLOYED) ? leave_benefit * IncomeTaxCoupleParentalUnemployed[tax_fam_type_parental][earn_index_parental][earn_index_unemployed_spouse] :
(has_spouse && activity == ACT_LEAVE && activity_spouse == ACT_RETIRED) ? leave_benefit * IncomeTaxCoupleParentalPension[tax_fam_type_parental][earn_index_parental][earn_index_pension_spouse] :
(has_spouse && activity == ACT_LEAVE) ? leave_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 according to an equivalence schemy by age of children. 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
EducationBenefitWeights: equivalence scale of education benefits
/*NOTE(TaxBen-Education, EN)
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 according to an equivalence schemy by age of children. 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
- EducationBenefitWeights: equivalence scale of education benefits
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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,
EducationBenefitWeights
};
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];
//EN Education benefit weights
double EducationBenefitWeights[CHILD_AGE_GROUP];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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];
}
// Assign education benefits to children
double dEducationBenefits = dBenefit * lObserver->average_earnings;
double dTotalWeight = children_0 * EducationBenefitWeights[CAG_0]
+ children_1to5 * EducationBenefitWeights[CAG_1TO5]
+ children_6to14 * EducationBenefitWeights[CAG_6TO14]
+ children_15to18 * EducationBenefitWeights[CAG_15P];
if (dTotalWeight > 0.0)
{
Person_ptr prChild = NULL;
int nIndex = 0;
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(0, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(0, &nIndex);
while (prChild)
{
// assign benefits
if (prChild->integer_age == 0) prChild->education_benefit = EducationBenefitWeights[CAG_0] * dEducationBenefits / dTotalWeight;
else if (prChild->integer_age <= 5) prChild->education_benefit = EducationBenefitWeights[CAG_1TO5] * dEducationBenefits / dTotalWeight;
else if (prChild->integer_age <= 14) prChild->education_benefit = EducationBenefitWeights[CAG_6TO14] * dEducationBenefits / dTotalWeight;
else prChild->education_benefit = EducationBenefitWeights[CAG_15P] * dEducationBenefits / dTotalWeight;
// link to next child
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(nIndex + 1, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
TaxBen-Family.mpp
This module implements family benefits using parameter tables generated with the EUROMOD Hypothetical Household Tool (HHoT). The tables are multidimensional, with parameters defined by family type and fine-grained income categories for earnings, unemployment benefits, pensions, and maternity or parental leave. Family types are defined by partnership status and family composition, including the number and ages of children. Separate parameters are provided for single-parent and couple families. For couples, parameters distinguish combinations of income types for both partners. For example, the parameter “Family benefit couple employed × unemployed” applies to couples in which one partner is employed and the other receives unemployment benefits. Benefit amounts are retrieved at the family level. The family head then distributes the benefit to children according to an age-based equivalence scheme. Family benefits are updated monthly.
At each update round, family heads also calculate a potential additional family benefit that the family would receive if a student aged 18 or older were added to the observed family. This potential benefit is calculated as the difference between (1) the family benefit that would be received by the current family plus an additional dependent child in the oldest child age category distinguished by the model; and (2) the family benefit received by the actual nuclear family without this additional family member. After all regular family benefits have been calculated and distributed among family members, students aged 18 or older claim a family benefit if they are eligible. Eligibility requires that the student satisfies the relevant age restrictions defined in the Leaving Home module, is single, and is not a parent. Students flagged as living with their parents, and therefore retaining a link to the parental family, directly retrieve their potential family benefit from the parents’ family. Other eligible students sample their family benefit from families that have students aged 18 or older living with them.
Parameters:
Parameters for family benefits are expressed relative to the average wage and vary by family type, defined by the number and age composition of children, as well as by fine-grained income brackets. Separate parameters are provided for single-parent and couple families, further distinguished by (combinations of) income sources.
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
Family benefits are distributed among all dependent children according to an equivalence scale:
Family Benefit Weights: eqivalence scale for children
/* NOTE(TaxBen-Family, EN)
This module implements family benefits using parameter tables generated with the EUROMOD Hypothetical Household Tool (HHoT). The tables are multidimensional, with parameters defined by family type and fine-grained income categories for earnings, unemployment benefits, pensions, and maternity or parental leave. Family types are defined by partnership status and family composition, including the number and ages of children. Separate parameters are provided for single-parent and couple families. For couples, parameters distinguish combinations of income types for both partners. For example, the parameter **“Family benefit couple employed × unemployed”** applies to couples in which one partner is employed and the other receives unemployment benefits. Benefit amounts are retrieved at the family level. The family head then distributes the benefit to children according to an age-based equivalence scheme. Family benefits are updated monthly.
At each update round, family heads also calculate a potential additional family benefit that the family would receive if a student aged 18 or older were added to the observed family. This potential benefit is calculated as the difference between (1) the family benefit that would be received by the current family plus an additional dependent child in the oldest child age category distinguished by the model; and (2) the family benefit received by the actual nuclear family without this additional family member. After all regular family benefits have been calculated and distributed among family members, students aged 18 or older claim a family benefit if they are eligible. Eligibility requires that the student satisfies the relevant age restrictions defined in the Leaving Home module, is single, and is not a parent. Students flagged as living with their parents, and therefore retaining a link to the parental family, directly retrieve their potential family benefit from the parents’ family. Other eligible students sample their family benefit from families that have students aged 18 or older living with them.
**Parameters:**
Parameters for family benefits are expressed relative to the average wage and vary by family type, defined by the number and age composition of children, as well as by fine-grained income brackets. Separate parameters are provided for single-parent and couple families, further distinguished by (combinations of) income sources.
- 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
Family benefits are distributed among all dependent children according to an equivalence scale:
- Family Benefit Weights: eqivalence scale for children
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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,
FamilyBenefitWeights
};
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];
//EN Family benefit weights
double FamilyBenefitWeights[CHILD_AGE_GROUP];
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
double family_benefit = { 0.0 }; //EN Family benefit
double family_benefit_student = { 0.0 }; //EN Potential student family benefit
//EN Accumulated family benefits in current year (own)
double accum_family_benefit = active_spell_weighted_duration(year_spell, TRUE, family_benefit);
void doUpdateFamilyBenefit(); //EN Update family benefit
void doUpdateFamilyBenefitStudent(); //EN Update family benefit student 18+
};
actor Observer
{
void UpdateFamilyBenefit(); //EN Update family benefit
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::UpdateFamilyBenefit()
{
// Update family benefit
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateFamilyBenefit();
// Update family benefits for students 18+ flagged living with parents
for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateFamilyBenefitStudent();
}
void Person::doUpdateFamilyBenefitStudent()
{
// students living with parents
if (student_living_with_parents)
{
if (lRecentMother && lRecentMother->family_benefit_student > 0.0) family_benefit = lRecentMother->family_benefit_student;
else if (lRecentFather && lRecentFather->family_benefit_student > 0.0) family_benefit = lRecentFather->family_benefit_student;
else if (lRecentFather && lRecentFather->has_spouse && lRecentFather->lSpouse->family_benefit_student>0.0) family_benefit = lRecentFather->lSpouse->family_benefit_student;
}
// other students
else if (is_resident && WITHIN(AGE_STUDENTS, integer_age) && in_educ && family_role == FR_HEAD && integer_age <= MaxAgeLeavingHomeStudents && !has_spouse && children_in_family == 0)
{
if (asHeadsWithStudentsLivingAtHome->Count() > 0)
{
Person_ptr ptrPerson = asHeadsWithStudentsLivingAtHome->GetRandom(RandUniform(104));
family_benefit = ptrPerson->family_benefit_student;
}
}
}
void Person::doUpdateFamilyBenefit()
{
double dBenefit = 0.0;
double dAltBenefit = 0.0;
bool bAlt = FALSE;
for (int n = 0; n <= 1; n++) // run twice, first with alternative family type, then with the real one
{
TAX_FAM_TYPE cTaxFamType = tax_fam_type;
TAX_FAM_TYPE_PARENTAL cTaxFamTypeParental = tax_fam_type_parental;
TAX_FAM_TYPE_PENPEN cTaxFamTypePenPen = tax_fam_type_penpen;
TAX_FAM_TYPE_SINGPEN cTaxFamTypeSingPen = tax_fam_type_singpen;
if (n == 0) // fist run with alternative fam type ( +/- one student)
{
cTaxFamType = alt_fam_type;
cTaxFamTypeParental = alt_fam_type_parental;
cTaxFamTypePenPen = alt_fam_type_penpen;
cTaxFamTypeSingPen = alt_fam_type_singpen;
}
else dBenefit = 0.0; // reset for calculation of benefit
// Singles
if (!has_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = FamilyBenefitSingleEmployed[cTaxFamType][earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitSingleUnemployed[cTaxFamType][earn_index_unemployed];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitSingleParental[cTaxFamTypeParental][earn_index_parental];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitSingleRetired[cTaxFamTypeSingPen][earn_index_pension];
else dBenefit = FamilyBenefitSingleOut[cTaxFamType];
}
// Couple symmetric
else if (activity == activity_spouse)
{
if (activity == ACT_EMPLOYED) dBenefit = FamilyBenefitCoupleEmployedEmployed[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_employed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleUnemployedUnemployed[cTaxFamType][earn_index_unemployed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCouplePensionPension[cTaxFamTypePenPen][earn_index_pension][(int)lSpouse->earn_index_pension];
else dBenefit = FamilyBenefitCoupleOutOut[cTaxFamType];
}
// combinations somebody employed
else if (activity == ACT_EMPLOYED || activity_spouse == ACT_EMPLOYED)
{
if (activity_spouse == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleEmployedUnemployed[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_unemployed];
else if (activity == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleEmployedUnemployed[cTaxFamType][(int)lSpouse->earn_index_employed][earn_index_unemployed];
else if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleEmployedParental[cTaxFamTypeParental][earn_index_employed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitCoupleEmployedParental[cTaxFamTypeParental][(int)lSpouse->earn_index_employed][earn_index_parental];
else if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleEmployedPension[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleEmployedPension[cTaxFamType][(int)lSpouse->earn_index_employed][earn_index_pension];
else if (activity_spouse == ACT_EMPLOYED) dBenefit = FamilyBenefitCoupleEmployedOut[cTaxFamType][(int)lSpouse->earn_index_employed];
else dBenefit = FamilyBenefitCoupleEmployedOut[cTaxFamType][earn_index_employed];
}
// remaining combinations somebody unemployed
else if (activity == ACT_UNEMPLOYED || activity_spouse == ACT_UNEMPLOYED)
{
if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleUnemployedParental[cTaxFamTypeParental][earn_index_unemployed][(int)lSpouse->earn_index_parental];
else if (activity == ACT_LEAVE) dBenefit = FamilyBenefitCoupleUnemployedParental[cTaxFamTypeParental][(int)lSpouse->earn_index_unemployed][earn_index_parental];
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleUnemployedPension[cTaxFamType][earn_index_unemployed][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleUnemployedPension[cTaxFamType][(int)lSpouse->earn_index_unemployed][earn_index_pension];
else if (activity_spouse == ACT_UNEMPLOYED) dBenefit = FamilyBenefitCoupleUnemployedOut[cTaxFamType][(int)lSpouse->earn_index_unemployed];
else dBenefit = dBenefit = FamilyBenefitCoupleUnemployedOut[cTaxFamType][earn_index_unemployed];
}
// remaining combinations somebody parental leave
else if (activity == ACT_LEAVE || activity_spouse == ACT_LEAVE)
{
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCoupleParentalPension[cTaxFamTypeParental][earn_index_parental][(int)lSpouse->earn_index_pension];
else if (activity == ACT_RETIRED) dBenefit = FamilyBenefitCoupleParentalPension[cTaxFamTypeParental][(int)lSpouse->earn_index_parental][earn_index_pension];
else if (activity_spouse == ACT_LEAVE) dBenefit = FamilyBenefitCoupleParentalOut[cTaxFamTypeParental][(int)lSpouse->earn_index_parental];
else dBenefit = dBenefit = FamilyBenefitCoupleParentalOut[cTaxFamTypeParental][earn_index_parental];
}
// remaining combinations somebody pension
else if (activity == ACT_RETIRED || activity_spouse == ACT_RETIRED)
{
if (activity_spouse == ACT_RETIRED) dBenefit = FamilyBenefitCouplePensionOut[cTaxFamType][(int)lSpouse->earn_index_pension];
else dBenefit = dBenefit = FamilyBenefitCouplePensionOut[cTaxFamType][earn_index_pension];
}
if (n == 0) dAltBenefit = dBenefit; // first run is with alternative fam type
}
// set potential family benefit for student living with parents
if (dAltBenefit > dBenefit) family_benefit_student = (dAltBenefit - dBenefit) * lObserver->average_earnings;
else family_benefit_student = (dBenefit - dAltBenefit) * lObserver->average_earnings;
// Assign family benefits to children
double dFamilyBenefits = dBenefit * lObserver->average_earnings;
double dTotalWeight = children_0 * FamilyBenefitWeights[CAG_0]
+ children_1to5 * FamilyBenefitWeights[CAG_1TO5]
+ children_6to14 * FamilyBenefitWeights[CAG_6TO14]
+ children_15to18 * FamilyBenefitWeights[CAG_15P];
if (dTotalWeight > 0.0)
{
Person_ptr prChild = NULL;
int nIndex = 0;
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(0, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(0, &nIndex);
while (prChild)
{
// assign benefits
if (prChild->integer_age == 0) prChild->family_benefit = FamilyBenefitWeights[CAG_0] * dFamilyBenefits / dTotalWeight;
else if (prChild->integer_age <= 5) prChild->family_benefit = FamilyBenefitWeights[CAG_1TO5] * dFamilyBenefits / dTotalWeight;
else if (prChild->integer_age <= 14) prChild->family_benefit = FamilyBenefitWeights[CAG_6TO14] * dFamilyBenefits / dTotalWeight;
else prChild->family_benefit = FamilyBenefitWeights[CAG_15P] * dFamilyBenefits / dTotalWeight;
// link to next child
if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(nIndex + 1, &nIndex);
else prChild = mlCurrentMotherChildren->GetNext(nIndex + 1, &nIndex);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range SIM_YEAR_2030 { 2018, 2030 }; //EN Year
actor Person
{
SIM_YEAR_2030 sim_year_30 = COERCE(SIM_YEAR_2030, calendar_year); //EN Year
logical in_sim_year_30 = WITHIN(SIM_YEAR_2030, calendar_year);
//EN Students not living at home eligible for benefits
logical tab_student_not_with_parents = (!student_living_with_parents && is_resident
&& WITHIN(AGE_STUDENTS, integer_age) && in_educ && family_role == FR_HEAD
&& integer_age <= MaxAgeLeavingHomeStudents && !has_spouse && children_in_family == 0);
//EN Other students
logical tab_other_students = (in_educ && !tab_student_not_with_parents && !student_living_with_parents);
};
table_group TGV_TAX_BEN_FAMILY //EN [V] TaxBen-Family.mpp
{
tabFamilyBenefitAgeYearGroup,
tabFamilyBenefitsTotals
};
table Person tabFamilyBenefitAgeYearGroup //EN [V FAMBEN] Family benefits
[in_sim_year_30 && is_resident]
{
sex+ *
in_educ+ *
{
duration(), //EN Persons
weighted_duration(family_benefit) / duration(), //EN Average family benefit
duration(student_living_with_parents,TRUE), //EN Students living with parents
weighted_duration(student_living_with_parents,TRUE,family_benefit)
/ duration(student_living_with_parents,TRUE), //EN Average family benefit students living with parents
duration(tab_student_not_with_parents,TRUE), //EN Students eligible not living with parents
weighted_duration(tab_student_not_with_parents,TRUE,family_benefit)
/ duration(tab_student_not_with_parents,TRUE), //EN Average family benefit eligible students not living with parents
duration(tab_other_students,TRUE), //EN Other students
weighted_duration(tab_other_students,TRUE,family_benefit)
/ duration(tab_other_students,TRUE), //EN Average family benefit other students
duration(tab_other_students,TRUE) / duration(in_educ,TRUE) //EN Sharer of other students among students decimals=3
}
* integer_age +
* sim_year_30
};
table Person tabFamilyBenefitsTotals //EN [V FAMBEN] Total family benefits
[in_sim_year_30 && is_resident]
{
sim_year_30* //EN Year
{
weighted_duration(family_benefit), //EN Total family benefits
weighted_duration(student_living_with_parents, TRUE,family_benefit), //EN Total family benefits students 18+ living with parents
weighted_duration(tab_student_not_with_parents, TRUE,family_benefit) //EN Total family benefits students 18+ not living with parents
}
};
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
TODO retired should be 67+ not active to include those not receiving a pension
/* NOTE(TaxBen-OldAge, EN)
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
TODO retired should be 67+ not active to include those not receiving a 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.
/* NOTE(LongtermCare, EN)
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 <https://www.wifo.ac.at/wp-content/uploads/upload-5916/wp_2025_693.pdf>`_. 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 <https://www.wifo.ac.at/wp-content/uploads/upload-5916/wp_2025_693.pdf>`_
**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
//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. The explanatory variables are age, sex, and education. Death probabilities from the health transition parameter are optionally used 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 maintains information on which person, separately by age, sex, and education, has the shortest random waiting time to death. 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 in the starting population is read from the starting population file. People can be flagged as blocked from recovery from bad health, which is useful, for example, in the case of permanent invalidity. In this case, another person of the same age, sex, and education who has bad health and is not blocked from recovery is searched for and recovers instead.
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.
/* NOTE(Health, EN)
This module implements a binary health status, as well as health transitions between good and bad health. The explanatory variables are age, sex, and education. Death probabilities from the health transition parameter are optionally used 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 maintains information on which person, separately by age, sex, and education, has the shortest random waiting time to death. 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 in the starting population is read from the starting population file. People can be flagged as blocked from recovery from bad health, which is useful, for example, in the case of permanent invalidity. In this case, another person of the same age, sex, and education who has bad health and is not blocked from recovery is searched for and recovers instead.
**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 && MortalityByHealth
order wait_death_health_model;
//EN Persons with bad health who can recover
actor_set Person asResidentsBadHealthWhoCanRecover[integer_age][sex][educ_level3]
filter is_alive && is_resident && in_projected_time && health_cat == HC_BAD && health_can_recover;
//EN Persons by health status (for temp fix)
//EN Persons by age, sex, and education ordered by mortality waiting time according to health status
actor_set Person asResidentsByAgeSexEducationHealth[integer_age][sex][educ_level3][health_cat]
filter is_alive && is_resident;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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,
TempHealthStatus,
TempUseHealthStatus
};
parameters
{
//EN Health transitions
double HealthTransition[SEX][EDUC_LEVEL3][HEALTH_CAT][AGE_RANGE][HEALTH_TRANSITION_OUTCOME];
//EN Health status probability bad health (temporary fix)
double TempHealthStatus[SEX][AGE_RANGE][EDUC_LEVEL3];
//EN Use health status (temporary fix)
logical TempUseHealthStatus;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
HEALTH_CAT health_cat = { HC_GOOD }; //EN Health category
logical health_can_recover = { TRUE }; //EN Can recover from bad health
void UpdateHealth(); //EN Update health at birthdays
double wait_death_health_model = { TIME_INFINITE }; //EN Waiting time to death (health model)
};
actor Observer
{
logical temp_fix_ini = { FALSE };
void TempFixMakeSick();
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Observer::TempFixMakeSick()
{
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL3); nEduc++)
{
int nHealthy = asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_GOOD]->Count();
int nSick = asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_BAD]->Count();
int nPop = nHealthy + nSick;
int nTargetSick = int(double(nPop) * TempHealthStatus[nSex][nAge][nEduc]);
if (!temp_fix_ini)
{
while (asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_BAD]->Count() > nTargetSick && asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_BAD]->Count() > 0)
{
Person_ptr ptrPerson = asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_BAD]->GetRandom(RandUniform(93));
ptrPerson->health_cat = HC_GOOD;
}
}
while (asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_BAD]->Count() < nTargetSick && asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_GOOD]->Count() > 0)
{
Person_ptr ptrPerson = asResidentsByAgeSexEducationHealth[nAge][nSex][nEduc][HC_GOOD]->GetRandom(RandUniform(105));
ptrPerson->health_cat = HC_BAD;
}
}
}
}
temp_fix_ini = TRUE;
}
void Person::UpdateHealth()
{
if (in_projected_time && is_resident)
{
// Update health status
// Transition model
if (!TempUseHealthStatus)
{
if (HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_GOOD] +
HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_BAD] > 0.0)
{
if (health_cat == HC_GOOD)
{
double dProbBad = HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_BAD] /
(HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_GOOD] +
HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_BAD]);
if (RandUniform(80) < dProbBad) health_cat = HC_BAD;
}
else
{
double dProbGood = HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_GOOD] /
(HealthTransition[sex][educ_level3][health_cat][integer_age - 1][HTO_GOOD] +
HealthTransition[sex][educ_level3][health_cat][integer_age][HTO_BAD]);
if (RandUniform(90) < dProbGood)
{
// if blocked from recovery (e.g. permanent invalidity) find somebody else
if (!health_can_recover && asResidentsBadHealthWhoCanRecover[integer_age][sex][educ_level3]->Count() > 0)
{
Person_ptr ptrPerson = asResidentsBadHealthWhoCanRecover[integer_age][sex][educ_level3]->GetRandom(RandUniform(81));
ptrPerson->health_cat = HC_GOOD;
}
else health_cat = HC_GOOD;
}
}
}
}
// Temporary quick-fix: prevalence from parameter
else
{
//HANDLED BY OBSERVER
//if (RandUniform(93) < TempHealthStatus[sex][integer_age][educ_level3]) health_cat = HC_BAD;
//else health_cat = HC_GOOD;
}
// Update death random waiting time according to current health status
if (MortalityByHealth)
{
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;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_HEALTH //EN [V] Health.mpp
{
tabHealthStatus,
tabHealthStatusStart
};
table Person tabHealthStatus //EN [_VAL] Health Status 2020-2029
[calendar_year <= 2029 && calendar_year>=2020 && is_resident]
{
sex+ * //EN Sex
{
duration(health_cat,HC_BAD) / duration() //EN Proportion bad health decimals=3
}
* integer_age //EN Age
* educ_level3+ //EN Education
};
table Person tabHealthStatusStart //EN [_VAL] Health Status Start
[trigger_entrances(in_projected_time, TRUE) && is_resident]
{
sex + * //EN Sex
{
value_in(health_cat) / unit //EN Proportion bad health decimals=3
}
* integer_age //EN Age
* educ_level3 + //EN Education
};
Invalidity.mpp
This module implements permanent invalidity. It is based on the currently observed prevalence by age, sex, and education. Persons entering invalidity are assumed to retire from the labor force. The initial status is obtained from the starting population. Subject to eligibility, an invalidity pension is modeled in the Income-Pensions module. It is assumed that only persons with health limitations can enter invalidity. Once flagged as permanently invalid, persons cannot recover from health limitations.
Parameters:
Invalidity: prevalence by age, sex, and education.
/* NOTE(Invalidity, EN)
This module implements permanent invalidity. It is based on the currently observed prevalence by age, sex, and education. Persons entering invalidity are assumed to retire from the labor force. The initial status is obtained from the starting population. Subject to eligibility, an invalidity pension is modeled in the Income-Pensions module. It is assumed that only persons with health limitations can enter invalidity. Once flagged as permanently invalid, persons cannot recover from health limitations.
**Parameters:**
- Invalidity: prevalence by age, sex, and education.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor-Sets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Persons by invalidity
actor_set Person asInvalitity[sex][educ_level4][integer_age][invalid] filter is_resident && in_projected_time;
// Persons with health limitations but no invalidity
actor_set Person asBadHealthNoInvalidity[sex][educ_level4][integer_age]
filter is_resident && in_projected_time && !invalid && health_cat == HC_BAD;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range INVAL_AGERANGE { 15, 74 }; //EN Age
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
parameter_group PG_INVALIDITY //EN Invalidity
{
InvalidityRates
};
parameters
{
double InvalidityRates[SEX][EDUC_LEVEL4][INVAL_AGERANGE]; //EN Prevalence of invalidity
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical invalid = { FALSE }; //EN Permanent invalidity
void StartInvalidity(); //EN Start invalidity
};
actor Observer
{
void UpdateInvalidity(); //EN Update invalidity
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::StartInvalidity()
{
invalid = TRUE;
health_cat = HC_BAD;
health_can_recover = FALSE;
if (activity != ACT_RETIRED)
{
doUpdateOldAgeRetiremetEligibility();
ActRetireEvent();
}
}
void Observer::UpdateInvalidity()
{
// called in ObserverMidMonthEvent()
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nEduc = 0; nEduc < SIZE(EDUC_LEVEL4); nEduc++)
{
for (int nAge = MIN(INVAL_AGERANGE); nAge <= MAX(INVAL_AGERANGE); nAge++)
{
int nInvalid = asInvalitity[nSex][nEduc][nAge][TRUE]->Count();
int nNotInvalid = asInvalitity[nSex][nEduc][nAge][FALSE]->Count();
int nExpected = int(InvalidityRates[nSex][nEduc][RANGE_POS(INVAL_AGERANGE, nAge)] * double(nInvalid + nNotInvalid));
while (nNotInvalid > 0 && nExpected > nInvalid)
{
Person_ptr ptrPerson = NULL;
if (asBadHealthNoInvalidity[nSex][nEduc][nAge]->Count() > 0)
{
ptrPerson = asBadHealthNoInvalidity[nSex][nEduc][nAge]->GetRandom(RandUniform(97));
}
else ptrPerson = asInvalitity[nSex][nEduc][nAge][FALSE]->GetRandom(RandUniform(98));
ptrPerson->StartInvalidity();
nInvalid = asInvalitity[nSex][nEduc][nAge][TRUE]->Count();
nNotInvalid = asInvalitity[nSex][nEduc][nAge][FALSE]->Count();
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VALIDATION
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGV_INVALIDITY //EN [V] Invalidity.mpp
{
tabInvaliditySelectedYears
};
table Person tabInvaliditySelectedYears //EN [V INV] Invalidity in selected years
[in_selected_year && is_resident]
{
selected_year *
sex+ * //EN Sex
health_cat+ * //EN Health
{
duration(invalid,TRUE) / duration() //EN Invalidity rate decimals=4
}
* integer_age //EN Age
* educ_level3+ //EN Education
};
NTTA-Childcare.mpp
Childcare in minutes provided by parent(s)
/* NOTE(NTTA-Childcare, EN)
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_STD, //EN Standard deviation
CC2H_COV //EN Covariance
};
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
};
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
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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
double dStdMother = ChildCareCoupleHoursBoth[CC2H_STD][CCP_MOTHER];
double dStdFather = ChildCareCoupleHoursBoth[CC2H_STD][CCP_FATHER];
double dCorrelation = ChildCareCoupleHoursBoth[CC2H_COV][CCP_FATHER] / (dStdMother * dStdFather);
double dRandomMother = RandNormal(62);
double dResMother = dRandomMother * dStdMother;
double dResFather = dRandomMother * dStdFather * dCorrelation + sqrt(1.0 - dCorrelation * dCorrelation) * dStdFather * RandNormal(92);
// 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;
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;
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
Detailed validation tables are part of most modules and contained within the module code. In addition, outputs generated for specific projects and papers is organized in additional modules. The current modules are examples of recent such specific output files.
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
};
Tables-Paper-Pension.mpp
Output tables for a paper on retirement and pensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
range TAB_PENS_COHORT { 1960, 1970 }; //EN Pensionskohorte
range TAB_PENS_YEAR { 2018, 2040 }; //EN Year
range TAB_PENS_AGE { 55, 64 }; //EN Age
partition TAB_BEN_AGEPART { 55, 65.085 }; //EN Age part
classification TAB_PENSION_TYPE //EN Pension type
{
PT_EARLY, //EN Early retirement
PT_REGULAR, //EN Regular retirement
PT_INVAL, //EN Invalidity pension
PT_OTHER //EN Other
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table states
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
void doTabPensionStates(); //EN Set states at retirement
logical tab_retired = { FALSE }; //EN Entered retirement
TAB_PENSION_TYPE tab_pension_type = { PT_OTHER }; //EN Pension type
logical in_tab_pens_cohort = WITHIN(TAB_PENS_COHORT, year_of_birth); //EN Year of birth 1960-1970
TAB_PENS_COHORT tab_pens_cohort = COERCE(TAB_PENS_COHORT, year_of_birth); //EN Cohort
logical in_tab_pens_year = WITHIN(TAB_PENS_YEAR, calendar_year); //EN Year 2018-2040
TAB_PENS_YEAR tab_pens_year = COERCE(TAB_PENS_YEAR, calendar_year); //EN Year
//logical in_tab_pens_age = WITHIN(TAB_PENS_AGE, integer_age); //EN Age 60-64
logical in_tab_pens_age = (self_scheduling_split(age, TAB_BEN_AGEPART) == 1); //EN Age 60-64
TAB_PENS_AGE tab_pens_age = COERCE(TAB_PENS_AGE, integer_age); //EN Age
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::doTabPensionStates()
{
if (can_retire_reg) tab_pension_type = PT_REGULAR;
else if (can_retire_early) tab_pension_type = PT_EARLY;
else if (invalid) tab_pension_type = PT_INVAL;
if (can_retire_reg || can_retire_early || invalid) tab_retired = TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGP_PAPER_PENSION //EN [P] Pension
{
TabPaperPensionCohorts,
TabPaperPension60to64,
tabAverageTotalPensionsTypeCohort,
tabAverageTotalPensionsTypeCohortNoYEAR
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person TabPaperPensionCohorts //EN [P PENS] Pension Cohorts
[in_tab_pens_cohort && is_resident && in_tab_pens_year]
{
sex+ * //EN Sex
{
duration(activity,ACT_EMPLOYED), //EN Employed
duration(activity,ACT_EMPLOYED) / duration(), //EN Employed share of population decimals=3
entrances(tab_pension_type,PT_EARLY), //EN Number new early retirements
entrances(tab_pension_type,PT_REGULAR), //EN Number new regular retirements
entrances(tab_pension_type,PT_INVAL), //EN Number new invalidity retirements
entrances(tab_retired,TRUE) //EN All new retirements
}
* tab_pens_cohort+ //EN Cohort
* tab_pens_year //EN Year
};
table Person TabPaperPension60to64 //EN [P PENS] Pensions age 60-65.08
[in_tab_pens_age && is_resident && in_tab_pens_year]
{
sex + * //EN Sex
{
duration(activity,ACT_EMPLOYED), //EN Number employed
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
duration(activity,ACT_NEVER) / duration(), //EN Never employed decimals=3
value_at_entrances(tab_retired,TRUE,age)
/ entrances(tab_retired,TRUE), //EN Average age at new retirements decimals=2
value_at_entrances(tab_pension_type,PT_EARLY,age)
/ entrances(tab_pension_type,PT_EARLY), //EN Average age at new early retirements decimals=2
value_at_entrances(tab_pension_type,PT_REGULAR,age)
/ entrances(tab_pension_type, PT_REGULAR), //EN Average age at new regular retirements decimals=2
entrances(tab_pension_type,PT_EARLY), //EN Number new early retirements
entrances(tab_pension_type,PT_REGULAR), //EN Number new regular retirements
entrances(tab_pension_type,PT_INVAL), //EN Number new invalidity retirements (age 60-64)
entrances(tab_retired,TRUE), //EN Number new retirements
(duration(activity,ACT_RETIRED) - duration(tab_retired,TRUE))
/ duration(), //EN Share of population which entered retirement before 2018 decimals=3
duration(tab_retired,TRUE) / duration(), //EN Share of population which enered retirement since 2018 decimals=3
entrances(tab_pension_type,PT_EARLY) / entrances(tab_retired,TRUE), //EN Share of early retirements in new retirements decimals=3
entrances(tab_pension_type,PT_REGULAR) / duration(tab_retired,TRUE),//EN Share of regular retirements in new retirements decimals=3
entrances(tab_pension_type,PT_INVAL) / duration(tab_retired,TRUE) //EN Share of invalidity retirements in new retirements decimals=3
}
* tab_pens_year //EN Year
};
table Person tabAverageTotalPensionsTypeCohort //EN [P PENS] Pensions by type and cohort
[in_tab_pens_cohort && is_resident && in_tab_pens_year]
{
sex + * //EN Sex
{
weighted_duration(activity,ACT_RETIRED,pension)
/ duration(activity,ACT_RETIRED), //EN Average pension all retired
weighted_duration(has_pension,TRUE,pension)
/ duration(has_pension,TRUE), //EN Average pension all with pension
duration(has_pension,TRUE) / duration(activity,ACT_RETIRED), //EN Proportion retired with pension decimals=4
weighted_duration(pension), //EN Total pensions
value_at_entrances(has_sim_oldage_pension, TRUE, pension)
/ entrances(has_sim_oldage_pension, TRUE), //EN Average new old-age pension
entrances(has_sim_oldage_pension, TRUE), //EN Number new old-age pensioners
value_at_entrances(has_sim_invalidity_pension, TRUE, pension)
/ entrances(has_sim_invalidity_pension, TRUE), //EN Average new invalidity pension
entrances(has_sim_invalidity_pension, TRUE), //EN Number new invalidity pensioners
value_at_entrances(has_sim_survivor_pension, TRUE, sim_survivor_base_pension)
/ entrances(has_sim_survivor_pension, TRUE), //EN Average new survivor base pension
entrances(has_sim_survivor_pension, TRUE) //EN Number new survivor pensioners
}
* tab_pens_cohort+ //EN Cohort
* tab_pens_year //EN Year
};
table Person tabAverageTotalPensionsTypeCohortNoYEAR //EN [P PENS] Entry Pensions by type and cohort
[in_tab_pens_cohort && is_resident && in_tab_pens_year]
{
sex + * //EN Sex
{
value_at_entrances(has_sim_oldage_pension, TRUE, pension)
/ entrances(has_sim_oldage_pension, TRUE), //EN Average new old-age pension
entrances(has_sim_oldage_pension, TRUE), //EN Number new old-age pensioners
value_at_entrances(has_sim_invalidity_pension, TRUE, pension)
/ entrances(has_sim_invalidity_pension, TRUE), //EN Average new invalidity pension
entrances(has_sim_invalidity_pension, TRUE), //EN Number new invalidity pensioners
value_at_entrances(has_sim_survivor_pension, TRUE, sim_survivor_base_pension)
/ entrances(has_sim_survivor_pension, TRUE), //EN Average new survivor base pension
entrances(has_sim_survivor_pension, TRUE) //EN Number new survivor pensioners
}
* tab_pens_cohort+ //EN Cohort
};
Tables-Paper-Transitions.mpp
This module contains output tables produced for a paper on activity transitions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table dimensions
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Spell length
partition TAB_UE_SPELL{ 0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 1.75, 2.00, 2.25, 2.50, 2.75, 3.00, 3.25, 3.50, 3.75, 4.00 };
////EN Spell range
//range TAB_UE_SPELL_INDEX { 0, 16 };
//EN Evaluated work age range
range TAB_UE_AGE{ 20 , 59 };
//EN Age group
partition TAB_UE_AGEGR { 30, 40, 50 };
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table states
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
logical enter_ue_2018 = { FALSE }; //EN Entering Unemployment 2018
logical exit_ue_2018 = { FALSE }; //EN Exit unemployment started 2018
logical enter_ue_2020 = { FALSE }; //EN Entering Unemployment 2020
logical exit_ue_2020 = { FALSE }; //EN Exit unemployment started 2020
//EN First 4 years after unemployment in 2020 cohort
logical in_first_four_years_ue_2020 = (self_scheduling_split(active_spell_duration(enter_ue_2020, TRUE), TAB_UE_SPELL) < 16);
AGE_RANGE age_enter_ue_18 = { 0 }; //EN Age entering unemployment
HEALTH_CAT health_enter_ue_18 = { HC_GOOD }; //EN Health entering unemployment
AGE_RANGE age_enter_ue_20 = { 0 }; //EN Age entering unemployment
HEALTH_CAT health_enter_ue_20 = { HC_GOOD }; //EN Health entering unemployment
logical female_good = (health_cat == HC_GOOD && sex == FEMALE); //EN Female good health
logical female_bad = (health_cat == HC_BAD && sex == FEMALE); //EN Female bad health
logical male_good = (health_cat == HC_GOOD && sex == MALE); //EN Male bad health
logical male_bad = (health_cat == HC_BAD && sex == MALE); //EN Male bad health
void SetStatesAtEntryUnemployment(); //EN Initialise in doEnterAct
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::SetStatesAtEntryUnemployment()
{
if (is_resident && activity == ACT_UNEMPLOYED && calendar_year == 2018 && !exit_ue_2018)
{
age_enter_ue_18 = integer_age;
health_enter_ue_18 = health_cat;
enter_ue_2018 = TRUE;
}
if (is_resident && activity == ACT_UNEMPLOYED && calendar_year == 2020 && !exit_ue_2020)
{
age_enter_ue_20 = integer_age;
health_enter_ue_20 = health_cat;
enter_ue_2020 = TRUE;
}
if (is_resident && enter_ue_2018 && activity != ACT_UNEMPLOYED) exit_ue_2018 = TRUE;
if (is_resident && enter_ue_2020 && activity != ACT_UNEMPLOYED) exit_ue_2020 = TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGP_TRANSITIONS //EN [P] Transitions
{
tabFollowUnemployed2020,
tabFollowUnemployed2020LeaveInOLF,
tabFollowUnemployed2020Composition,
tabUnemploymentRate
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabFollowUnemployed2020 //EN [P TRANS] Follow unmployed 2020
[enter_ue_2020 && age_enter_ue_20 >= 25 && age_enter_ue_20 <= 45 && is_resident]
{
sex + * //EN Sex
educ_level3 + * //EN Education
health_enter_ue_20 + * //EN Health
{
duration(exit_ue_2020,FALSE), //EN Still unemployed
duration(activity,ACT_UNEMPLOYED) - duration(exit_ue_2020,FALSE), //EN Again unemployed
duration(activity,ACT_EMPLOYED) + duration(activity,ACT_LEAVE), //EN Employed and leave
duration(activity,ACT_OUT), //EN Out of LF
duration(activity,ACT_RETIRED), //EN Retired
duration(is_resident,FALSE) //EN (Emigrants)
}
*self_scheduling_split(active_spell_duration(enter_ue_2020, TRUE), TAB_UE_SPELL) //EN Duration
};
table Person tabFollowUnemployed2020LeaveInOLF //EN [P TRANS] Follow unmployed 2020 Leave as OLF
[enter_ue_2020 && age_enter_ue_20 >= 25 && age_enter_ue_20 <= 45 && is_resident]
{
sex + * //EN Sex
educ_level3 + * //EN Education
health_enter_ue_20 + * //EN Health
{
duration(exit_ue_2020,FALSE), //EN Still unemployed
duration(activity,ACT_UNEMPLOYED) - duration(exit_ue_2020,FALSE), //EN Again unemployed
duration(activity,ACT_EMPLOYED), //EN Employed
duration(activity,ACT_OUT) + duration(activity,ACT_LEAVE), //EN Out of LF incl leave
duration(activity,ACT_RETIRED), //EN Retired
duration(is_resident,FALSE) //EN (Emigrants)
}
*self_scheduling_split(active_spell_duration(enter_ue_2020, TRUE), TAB_UE_SPELL) //EN Duration
};
table Person tabFollowUnemployed2020Composition //EN [P TRANS] Follow unmployed 2020 composition
[enter_ue_2020 && !exit_ue_2020 && age_enter_ue_20 >= 25 && age_enter_ue_20 <= 45 && is_resident]
{
educ_level3 + * //EN Education
{
duration(female_good,TRUE), //EN Female good health
duration(female_bad,TRUE), //EN Female bad health
duration(male_good,TRUE), //EN Male good health
duration(male_bad,TRUE) //EN Male bad health
}
*self_scheduling_split(active_spell_duration(enter_ue_2020, TRUE), TAB_UE_SPELL) //EN Duration
};
table Person tabUnemploymentRate //EN [P TRANS] Unemployment rate
[in_projected_time && is_resident && WITHIN(TAB_UE_AGE, integer_age)]
{
sex + * //EN Sex
health_cat + * //EN Health
{
duration(activity,ACT_UNEMPLOYED) / (duration(activity, ACT_EMPLOYED) + duration(activity, ACT_LEAVE) + duration(activity, ACT_UNEMPLOYED)) //EN Unemployment rate decimals=3
}
* split(integer_age,TAB_UE_AGEGR) + //EN Age group
* sim_year //EN Year
};
Tables-Paper-Taxben.mpp
This module contains output tables for a paper on the tax-benefit system
actor Person
{
logical has_social_benefit = (social_benefit > 0.0) ? TRUE : FALSE;
logical single_olf = (!has_spouse && (activity == ACT_NEVER || activity == ACT_OUT) && family_role != FR_CHILD && !student_living_with_parents) ? TRUE : FALSE;
logical single_student_25p = (!has_spouse && integer_age >= 25 && in_educ) ? TRUE : FALSE;
logical single_pens = (!has_spouse && activity==ACT_RETIRED) ? TRUE : FALSE;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Table groups
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table_group TGP_TAX_BEN //EN [P] Paper Tax-Ben
{
tabSocialBenefitsTotal1824
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
table Person tabSocialBenefitsTotal1824 //EN [P TAXBEN] Social benefits by group
[is_resident && WITHIN(TAB_FIRST_YEARS, calendar_year) && is_resident]
{
{
weighted_duration(activity,ACT_NEVER,social_benefit), //EN Total benefits never active
weighted_duration(activity,ACT_EMPLOYED,social_benefit), //EN Total benefits employed
weighted_duration(activity,ACT_UNEMPLOYED,social_benefit), //EN Total benefits unemployed
weighted_duration(activity,ACT_LEAVE,social_benefit), //EN Total benefits leave
weighted_duration(activity,ACT_OUT,social_benefit), //EN Total benefits Out
weighted_duration(activity,ACT_RETIRED,social_benefit), //EN Total benefits Retired
weighted_duration(activity,ACT_NEVER,has_social_benefit) / duration(activity,ACT_NEVER), //EN Prob receiving never active decimals=3
weighted_duration(activity,ACT_EMPLOYED,has_social_benefit) / duration(activity,ACT_EMPLOYED), //EN Prob receiving employed decimals=3
weighted_duration(activity,ACT_UNEMPLOYED,has_social_benefit) / duration(activity,ACT_UNEMPLOYED), //EN Prob receiving unemployed decimals=3
weighted_duration(activity,ACT_LEAVE,has_social_benefit) / duration(activity,ACT_LEAVE), //EN Prob receiving leave decimals=3
weighted_duration(activity,ACT_OUT,has_social_benefit) / duration(activity,ACT_OUT), //EN Prob receiving Out decimals=3
weighted_duration(activity,ACT_RETIRED,has_social_benefit) / duration(activity,ACT_RETIRED), //EN Prob receiving Retired decimals=3
weighted_duration(activity,ACT_NEVER,social_benefit) / weighted_duration(activity,ACT_NEVER,has_social_benefit), //EN Average benefit if receiver never active
weighted_duration(activity,ACT_EMPLOYED,social_benefit) / weighted_duration(activity,ACT_EMPLOYED,has_social_benefit), //EN Average benefit if receiver employed
weighted_duration(activity,ACT_UNEMPLOYED,social_benefit) / weighted_duration(activity,ACT_UNEMPLOYED,has_social_benefit), //EN Average benefit if receiver unemployed
weighted_duration(activity,ACT_LEAVE,social_benefit) / weighted_duration(activity,ACT_LEAVE,has_social_benefit), //EN Average benefit if receiver leave
weighted_duration(activity,ACT_OUT,social_benefit) / weighted_duration(activity,ACT_OUT,has_social_benefit), //EN Average benefit if receiver Out
weighted_duration(activity,ACT_RETIRED,social_benefit) / weighted_duration(activity,ACT_RETIRED,has_social_benefit), //EN Average benefit if receiver retired
weighted_duration(student_living_with_parents,TRUE,social_benefit), //EN Total benefits Student living with parents
weighted_duration(student_living_with_parents,TRUE,has_social_benefit) / duration(student_living_with_parents,TRUE), //EN Prob receiving Students living with parents decimals=3
weighted_duration(student_living_with_parents,TRUE,social_benefit) / weighted_duration(student_living_with_parents,TRUE,has_social_benefit), //EN Average benefit if receiver Students living with parents
weighted_duration(single_olf,TRUE,social_benefit), //EN Total benefits single adult never or out
weighted_duration(single_olf,TRUE,has_social_benefit) / duration(single_olf,TRUE), //EN Prob receiving single adult never or out decimals=3
weighted_duration(single_olf,TRUE,social_benefit) / weighted_duration(single_olf,TRUE,has_social_benefit), //EN Average benefit if receiver single adult never or out
weighted_duration(single_student_25p,TRUE,social_benefit), //EN Total benefits singles enrolled 25p
weighted_duration(single_student_25p,TRUE,has_social_benefit) / duration(single_student_25p,TRUE), //EN Prob receiving singles enrolled 25p decimals=3
weighted_duration(single_student_25p,TRUE,social_benefit) / weighted_duration(single_student_25p,TRUE,has_social_benefit), //EN Average benefit if receiver singles enrolled 25p
weighted_duration(single_pens,TRUE,social_benefit), //EN Total benefits single pension
weighted_duration(single_pens,TRUE,has_social_benefit) / duration(single_pens,TRUE), //EN Prob receiving single pension decimals=3
weighted_duration(single_pens,TRUE,social_benefit) / weighted_duration(single_pens,TRUE,has_social_benefit) //EN Average benefit if receiver single pension
}
* tab_first_years
};