../../_images/code1.png

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:

  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.



/* 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

../../_images/activity-transitions.png

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

../../_images/activity-alignment.png

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

../../_images/accounts-flows.png

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-SocialInsurance.mpp

This module implements social insurance contributions (both own and employers’ contributions) 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 by fine-grained income categories of earnings, unemployment benefits, pensions, and maternity and parental leave benefits. There are separate parameters by income types. For instance, the parameter ‘Social insurance rates employed’ applies to person is employmented. Social insurance rates are calculated at the indicidual level. They are updated in continuouse time whenever the tax base changes.



/* NOTE(TaxBen-SocialInsurance, EN)
This module implements social insurance contributions (both own and employers' contributions) 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 by fine-grained income categories of earnings, unemployment benefits, pensions, and maternity and parental leave benefits. There are separate parameters by income types. For instance, the parameter 'Social insurance rates employed' applies to person is employmented. Social insurance rates are calculated at the indicidual level. They are updated in continuouse time whenever the tax base changes.
*/

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

classification SI_EMPLOYEE_EMPLOYER                                         //EN Social insurace contributor
{
    SEE_EMPLOYEE,                                                           //EN Employee
    SEE_EMPLOYER                                                            //EN Employer
};

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

parameter_group PG_SOCIAL_INSURANCE                                         //EN Social insurance
{
    SocialInsuranceRatesEmployed,
    SocialInsuranceRatesUnemployed,
    SocialInsuranceRatesParental,
    SocialInsuranceRatesRetired
};

parameters
{
    //EN Social insurance rates employed
    double SocialInsuranceRatesEmployed[EARN_PART_EMPLOYED][SI_EMPLOYEE_EMPLOYER];

    //EN Social insurance rates unemployed
    double SocialInsuranceRatesUnemployed[EARN_PART_UNEMPLOYED];

    //EN Social insurance parental
    double SocialInsuranceRatesParental[EARN_PART_PARENTAL];

    //EN Social insurance retired
    double SocialInsuranceRatesRetired[EARN_PART_PENSION];
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    //EN Social insurance contribution (own)
    double si_contribution_own =
        (activity == ACT_EMPLOYED) ? SocialInsuranceRatesEmployed[earn_index_employed][SEE_EMPLOYEE] * earnings :
        (activity == ACT_UNEMPLOYED) ? SocialInsuranceRatesUnemployed[earn_index_unemployed] * unemployment_benefit :
        (activity == ACT_LEAVE) ? SocialInsuranceRatesParental[earn_index_parental] * leave_benefit :
        (activity == ACT_RETIRED) ? SocialInsuranceRatesRetired[earn_index_pension] * pension : 0.0;

    //EN Social insurance contribution by employer
    double si_contribution_employer =
        (activity == ACT_EMPLOYED) ? SocialInsuranceRatesEmployed[earn_index_employed][SEE_EMPLOYER] * earnings : 0.0;

    //EN Accumulated social insurance contribution in current year (own)
    double si_accum_contribution_own = active_spell_weighted_duration(year_spell, TRUE, si_contribution_own);

    //EN Accumulated social insurance contribution by employer in current year
    double si_accum_contribution_employer = active_spell_weighted_duration(year_spell, TRUE, si_contribution_employer);
};

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

TaxBen-Social.mpp

This module implements social 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 ‘Social 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 across family members according to an equivalence scheme. If equivalence weights differ between head and spouse (e.g., 1.0 versus 0.8) head and spouse receive equal benefits calculated based on their average weight (e.e., (1.0 + 0.8) / 2 = 0.9). Benefits are updated monthly.

At each update round, family heads also calculate a potential additional social 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 social 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 social benefit received by the actual nuclear family without this additional family member. After all regular social benefits have been calculated and distributed among family members, students aged 18 or older flagged living with parents claim a social 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. Such students, if flagged as living with their parents, and therefore retaining a link to the parental family, retrieve their potential social benefit from the parents’ family.

Parameters:

Parameters for social 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.

  • Social benefit single employed

  • Social benefit single parental

  • Social benefit single retired

  • Social benefit single unemployed

  • Social benefit single out

  • Social benefit couple employed x employed

  • Social benefit couple employed x unemployed

  • Social benefit couple employed x parental

  • Social benefit couple employed x pension

  • Social benefit couple employed x out

  • Social benefit couple unemployed x unemployed

  • Social benefit couple unemployed x parental

  • Social benefit couple unemployed x pension

  • Social benefit couple unemployed x out

  • Social benefit couple parental x pension

  • Social benefit couple parental x out

  • Social benefit couple pension x pension

  • Social benefit couple pension x out

  • Social benefit couple out x out

Social benefits are distributed among all family members according to an equivalence scale:

  • Social Benefit Weights: equivalence scale



/* NOTE(TaxBen-Social, EN)
This module implements social 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 'Social 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 across family members according to an equivalence scheme. If equivalence weights differ between head and spouse (e.g., 1.0 versus 0.8) head and spouse receive equal benefits calculated based on their average weight (e.e., (1.0 + 0.8) / 2 = 0.9). Benefits are updated monthly.

At each update round, family heads also calculate a potential additional social 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 social 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 social benefit received by the actual nuclear family without this additional family member. After all regular social benefits have been calculated and distributed among family members, students aged 18 or older flagged living with parents claim a social 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. Such students, if flagged as living with their parents, and therefore retaining a link to the parental family, retrieve their potential social benefit from the parents’ family.

**Parameters:**

Parameters for social 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.

- Social benefit single employed
- Social benefit single parental
- Social benefit single retired
- Social benefit single unemployed
- Social benefit single out
- Social benefit couple employed x employed
- Social benefit couple employed x unemployed
- Social benefit couple employed x parental
- Social benefit couple employed x pension
- Social benefit couple employed x out
- Social benefit couple unemployed x unemployed
- Social benefit couple unemployed x parental
- Social benefit couple unemployed x pension
- Social benefit couple unemployed x out
- Social benefit couple parental x pension
- Social benefit couple parental x out
- Social benefit couple pension x pension
- Social benefit couple pension x out
- Social benefit couple out x out

Social benefits are distributed among all family members according to an equivalence scale:

- Social Benefit Weights: equivalence scale
*/

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

parameter_group PG_SOCIAL_BENEFITS                                                  //EN Social benefits
{
    SocialBenefitSingleEmployed,
    SocialBenefitSingleParental,
    SocialBenefitSingleRetired,
    SocialBenefitSingleUnemployed,
    SocialBenefitSingleOut,
    SocialBenefitCoupleEmployedEmployed,
    SocialBenefitCoupleEmployedUnemployed,
    SocialBenefitCoupleEmployedParental,
    SocialBenefitCoupleEmployedPension,
    SocialBenefitCoupleEmployedOut,
    SocialBenefitCoupleUnemployedUnemployed,
    SocialBenefitCoupleUnemployedParental,
    SocialBenefitCoupleUnemployedPension,
    SocialBenefitCoupleUnemployedOut,
    SocialBenefitCoupleParentalPension,
    SocialBenefitCoupleParentalOut,
    SocialBenefitCouplePensionPension,
    SocialBenefitCouplePensionOut,
    SocialBenefitCoupleOutOut,
    SocialBenefitWeights
};

parameters
{
    //EN Social benefits min months eligible for first payout
    int SocialBenefitsMinMonthsEligibleForPayout;

    //EN Social benefit single employed
    double SocialBenefitSingleEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED];

    //EN Social benefit single parental
    double SocialBenefitSingleParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];

    //EN Social benefit single retired
    double SocialBenefitSingleRetired[TAX_FAM_TYPE_SINGPEN][EARN_PART_PENSION];

    //EN Social benefit single unemployed
    double SocialBenefitSingleUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];

    //EN Social benefit single out
    double SocialBenefitSingleOut[TAX_FAM_TYPE];

    //EN Social benefit couple employed x employed
    double SocialBenefitCoupleEmployedEmployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_EMPLOYED];

    //EN Social benefit couple employed x unemployed
    double SocialBenefitCoupleEmployedUnemployed[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_UNEMPLOYED];

    //EN Social benefit couple employed x parental
    double SocialBenefitCoupleEmployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_EMPLOYED][EARN_PART_PARENTAL];

    //EN Social benefit couple employed x pension
    double SocialBenefitCoupleEmployedPension[TAX_FAM_TYPE][EARN_PART_EMPLOYED][EARN_PART_PENSION];

    //EN Social benefit couple employed x out
    double SocialBenefitCoupleEmployedOut[TAX_FAM_TYPE][EARN_PART_EMPLOYED];

    //EN Social benefit couple unemployed x unemployed
    double SocialBenefitCoupleUnemployedUnemployed[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_UNEMPLOYED];

    //EN Social benefit couple unemployed x parental
    double SocialBenefitCoupleUnemployedParental[TAX_FAM_TYPE_PARENTAL][EARN_PART_UNEMPLOYED][EARN_PART_PARENTAL];

    //EN Social benefit couple unemployed x pension
    double SocialBenefitCoupleUnemployedPension[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED][EARN_PART_PENSION];

    //EN Social benefit couple unemployed x out
    double SocialBenefitCoupleUnemployedOut[TAX_FAM_TYPE][EARN_PART_UNEMPLOYED];

    //EN Social benefit couple parental x pension
    double SocialBenefitCoupleParentalPension[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL][EARN_PART_PENSION];

    //EN Social benefit couple parental x out
    double SocialBenefitCoupleParentalOut[TAX_FAM_TYPE_PARENTAL][EARN_PART_PARENTAL];

    //EN Social benefit couple pension x pension
    double SocialBenefitCouplePensionPension[TAX_FAM_TYPE_PENPEN][EARN_PART_PENSION][EARN_PART_PENSION];

    //EN Social benefit couple pension x out
    double SocialBenefitCouplePensionOut[TAX_FAM_TYPE][EARN_PART_PENSION];

    //EN Social benefit couple out x out
    double SocialBenefitCoupleOutOut[TAX_FAM_TYPE];

    //EN Social benefit weights
    double SocialBenefitWeights[EQUI_PERSON_TYPE];
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    //EN Social benefit
    double social_benefit = { 0.0 };                                        //EN Social benefit
    double social_benefit_student = { 0.0 };                                //EN Potential student social benefit

    double equi_workrelincome = { 0.0 };                                    //EN Equivalence work related relative income
    double equi_social_benefit = { 0.0 };                                   //EN Social benefits per equi person


    int social_benefit_eligible_months = { 0 };                             //EN Months of eligibility for social benefits

    //EN Accumulated social benefits in current year
    double accum_social_benefit = active_spell_weighted_duration(year_spell, TRUE, social_benefit);


    void doUpdateSocialBenefit();                                           //EN Update social benefit
    void doUpdateSocialBenefitStudent();                                    //EN Update family benefit student 18+
};

actor Observer
{
    //EN Update social benefit
    void UpdateSocialBenefit();
};

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


void Observer::UpdateSocialBenefit()
{
    // Update family benefit
    for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateSocialBenefit();

    // Update social benefits for students flagged living with parents 18+
    for (long nJ = 0; nJ < asAllResidentHeads->Count(); nJ++) asAllResidentHeads->Item(nJ)->doUpdateSocialBenefitStudent();
}

void Person::doUpdateSocialBenefitStudent()
{
    // Only students living with parents!
    if (student_living_with_parents)
    {
        if (lRecentMother && lRecentMother->social_benefit_student > 0.0) social_benefit = lRecentMother->social_benefit_student;
        else if (lRecentFather && lRecentFather->social_benefit_student > 0.0) social_benefit = lRecentFather->social_benefit_student;
        else if (lRecentFather && lRecentFather->has_spouse && lRecentFather->lSpouse->social_benefit_student > 0.0) social_benefit = lRecentFather->lSpouse->social_benefit_student;
    }
}

void Person::doUpdateSocialBenefit()
{
    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

        if (!student_living_with_parents)
        {
            // Singles
            if (!has_spouse)
            {
                if (activity == ACT_EMPLOYED) dBenefit = SocialBenefitSingleEmployed[cTaxFamType][earn_index_employed];
                else if (activity == ACT_UNEMPLOYED) dBenefit = SocialBenefitSingleUnemployed[cTaxFamType][earn_index_unemployed];
                else if (activity == ACT_LEAVE) dBenefit = SocialBenefitSingleParental[cTaxFamTypeParental][earn_index_parental];
                else if (activity == ACT_RETIRED) dBenefit = SocialBenefitSingleRetired[cTaxFamTypeSingPen][earn_index_pension];
                else dBenefit = SocialBenefitSingleOut[cTaxFamType];
            }

            // Couple symmetric
            else if (activity == activity_spouse)
            {
                if (activity == ACT_EMPLOYED) dBenefit = SocialBenefitCoupleEmployedEmployed[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_employed];
                else if (activity == ACT_UNEMPLOYED) dBenefit = SocialBenefitCoupleUnemployedUnemployed[cTaxFamType][earn_index_unemployed][(int)lSpouse->earn_index_unemployed];
                else if (activity == ACT_RETIRED) dBenefit = SocialBenefitCouplePensionPension[cTaxFamTypePenPen][earn_index_pension][(int)lSpouse->earn_index_pension];
                else dBenefit = SocialBenefitCoupleOutOut[cTaxFamType];
            }

            // combinations somebody employed
            else if (activity == ACT_EMPLOYED || activity_spouse == ACT_EMPLOYED)
            {
                if (activity_spouse == ACT_UNEMPLOYED) dBenefit = SocialBenefitCoupleEmployedUnemployed[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_unemployed];
                else if (activity == ACT_UNEMPLOYED) dBenefit = SocialBenefitCoupleEmployedUnemployed[cTaxFamType][(int)lSpouse->earn_index_employed][earn_index_unemployed];

                else if (activity_spouse == ACT_LEAVE) dBenefit = SocialBenefitCoupleEmployedParental[cTaxFamTypeParental][earn_index_employed][(int)lSpouse->earn_index_parental];
                else if (activity == ACT_LEAVE) dBenefit = SocialBenefitCoupleEmployedParental[cTaxFamTypeParental][(int)lSpouse->earn_index_employed][earn_index_parental];

                else if (activity_spouse == ACT_RETIRED) dBenefit = SocialBenefitCoupleEmployedPension[cTaxFamType][earn_index_employed][(int)lSpouse->earn_index_pension];
                else if (activity == ACT_RETIRED) dBenefit = SocialBenefitCoupleEmployedPension[cTaxFamType][(int)lSpouse->earn_index_employed][earn_index_pension];

                else if (activity_spouse == ACT_EMPLOYED) dBenefit = SocialBenefitCoupleEmployedOut[cTaxFamType][(int)lSpouse->earn_index_employed];
                else dBenefit = SocialBenefitCoupleEmployedOut[cTaxFamType][earn_index_employed];
            }

            // remaining combinations somebody unemployed
            else if (activity == ACT_UNEMPLOYED || activity_spouse == ACT_UNEMPLOYED)
            {
                if (activity_spouse == ACT_LEAVE) dBenefit = SocialBenefitCoupleUnemployedParental[cTaxFamTypeParental][earn_index_unemployed][(int)lSpouse->earn_index_parental];
                else if (activity == ACT_LEAVE) dBenefit = SocialBenefitCoupleUnemployedParental[cTaxFamTypeParental][(int)lSpouse->earn_index_unemployed][earn_index_parental];

                if (activity_spouse == ACT_RETIRED) dBenefit = SocialBenefitCoupleUnemployedPension[cTaxFamType][earn_index_unemployed][(int)lSpouse->earn_index_pension];
                else if (activity == ACT_RETIRED) dBenefit = SocialBenefitCoupleUnemployedPension[cTaxFamType][(int)lSpouse->earn_index_unemployed][earn_index_pension];

                else if (activity_spouse == ACT_UNEMPLOYED) dBenefit = SocialBenefitCoupleUnemployedOut[cTaxFamType][(int)lSpouse->earn_index_unemployed];
                else dBenefit = dBenefit = SocialBenefitCoupleUnemployedOut[cTaxFamType][earn_index_unemployed];
            }

            // remaining combinations somebody parental leave
            else if (activity == ACT_LEAVE || activity_spouse == ACT_LEAVE)
            {
                if (activity_spouse == ACT_RETIRED) dBenefit = SocialBenefitCoupleParentalPension[cTaxFamTypeParental][earn_index_parental][(int)lSpouse->earn_index_pension];
                else if (activity == ACT_RETIRED) dBenefit = SocialBenefitCoupleParentalPension[cTaxFamTypeParental][(int)lSpouse->earn_index_parental][earn_index_pension];

                else if (activity_spouse == ACT_LEAVE) dBenefit = SocialBenefitCoupleParentalOut[cTaxFamTypeParental][(int)lSpouse->earn_index_parental];
                else dBenefit = dBenefit = SocialBenefitCoupleParentalOut[cTaxFamTypeParental][earn_index_parental];
            }

            // remaining combinations somebody pension
            else if (activity == ACT_RETIRED || activity_spouse == ACT_RETIRED)
            {
                if (activity_spouse == ACT_RETIRED) dBenefit = SocialBenefitCouplePensionOut[cTaxFamType][(int)lSpouse->earn_index_pension];
                else dBenefit = dBenefit = SocialBenefitCouplePensionOut[cTaxFamType][earn_index_pension];
            }

            if (n == 0) dAltBenefit = dBenefit; // first run is with alternative fam type
        }
    }

    // Assign social benefits to family members
    double dSocialBenefits = dBenefit * lObserver->average_earnings;
    double dTotalWeight = SocialBenefitWeights[EPG_HEAD]
        + children_0 * SocialBenefitWeights[EPG_0]
        + children_1to5 * SocialBenefitWeights[EPG_1TO5]
        + children_6to14 * SocialBenefitWeights[EPG_6TO14]
        + children_15to18 * SocialBenefitWeights[EPG_15P];
    if (has_spouse) dTotalWeight = dTotalWeight + SocialBenefitWeights[EPG_SPOUSE];

    if (dSocialBenefits > 50) social_benefit_eligible_months++;
    else social_benefit_eligible_months = 0;

    // set potential social benefit for student living with parents
    if (dSocialBenefits > 50 && social_benefit_eligible_months > SocialBenefitsMinMonthsEligibleForPayout && dAltBenefit > dBenefit) social_benefit_student = (dAltBenefit - dBenefit) * lObserver->average_earnings;
    else if (dSocialBenefits > 50 && social_benefit_eligible_months > SocialBenefitsMinMonthsEligibleForPayout) social_benefit_student = (dBenefit - dAltBenefit) * lObserver->average_earnings;
    else social_benefit_student = 0.0;

    if (dTotalWeight > 0.0 && dSocialBenefits > 50 && social_benefit_eligible_months > SocialBenefitsMinMonthsEligibleForPayout)
    {
        double dWeightAdult = SocialBenefitWeights[EPG_HEAD];
        if (has_spouse) dWeightAdult = 0.5 * (SocialBenefitWeights[EPG_HEAD] + SocialBenefitWeights[EPG_SPOUSE]);
        social_benefit = dWeightAdult * dSocialBenefits / dTotalWeight;
        if (has_spouse) lSpouse->social_benefit = dWeightAdult * dSocialBenefits / dTotalWeight;
        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->social_benefit = SocialBenefitWeights[EPG_0] * dSocialBenefits / dTotalWeight;
            else if (prChild->integer_age <= 5)  prChild->social_benefit = SocialBenefitWeights[EPG_1TO5] * dSocialBenefits / dTotalWeight;
            else if (prChild->integer_age <= 14) prChild->social_benefit = SocialBenefitWeights[EPG_6TO14] * dSocialBenefits / dTotalWeight;
            else                                 prChild->social_benefit = SocialBenefitWeights[EPG_15P] * dSocialBenefits / dTotalWeight;
            // link to next child
            if (sex == MALE) prChild = mlCurrentFatherChildren->GetNext(nIndex + 1, &nIndex);
            else prChild = mlCurrentMotherChildren->GetNext(nIndex + 1, &nIndex);
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    //EN Any student 18+ not living with parents
    logical tab_student_18P_not_living_with_parents = (in_educ && !student_living_with_parents && integer_age >= 18);
};

table_group TGV_TAX_BEN_SOCIAL                                              //EN [V] TaxBen-Social.mpp
{
    tabSocialBenefitAgeYearGroup,
    tabSocialBenefitsTotals
};

table Person tabSocialBenefitAgeYearGroup                                               //EN [V SOCBEN] Social benefits
[in_sim_year_30 && is_resident]
{
    sex + *
    in_educ + *
    {
        duration(),                                                                     //EN Persons
        weighted_duration(social_benefit) / duration(),                                 //EN Average social benefit

        duration(student_living_with_parents,TRUE),                                     //EN Students living with parents
        weighted_duration(student_living_with_parents,TRUE,social_benefit)
            / duration(student_living_with_parents,TRUE),                               //EN Average social benefit students living with parents

        duration(tab_student_not_with_parents,TRUE),                                    //EN Students 18+ not living with parents (eligible for family benefits)
        weighted_duration(tab_student_18P_not_living_with_parents,TRUE,social_benefit)
            / duration(tab_student_18P_not_living_with_parents,TRUE)                    //EN Average social benefit students 18+ not living with parents
    }
    * integer_age +
    * sim_year_30
};

table Person tabSocialBenefitsTotals                                                        //EN [V SOCBEN] Total social benefits
[in_sim_year_30 && is_resident]
{
    sim_year_30*                                                                            //EN Year
    {
        weighted_duration(social_benefit),                                                  //EN Total social benefits
        weighted_duration(student_living_with_parents, TRUE,social_benefit),                //EN Total social benefits students 18+ living with parents
        weighted_duration(tab_student_18P_not_living_with_parents, TRUE,social_benefit)     //EN Total social benefits all students 18+ not living with parents
    }
};

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.

../../_images/ltc1.png


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