Step 12: Partner Matching

Overview

At this step we refine the module PartnerMatching.mpp. Partners are matched by distributional tables by age and education. Also, the model accounts for the consequences of childlessness on partnership bahavior.

The refined PartnerMatching.mpp Module

Partner matching is modeled by age, education and childlessness. Currently we only model heterosexual couples. Concerning age differences, we follow a “photo approach”, meaning that we assume that the patterns in observed age differences of couples by age persist over time. One difficulty in assigning a partner lies in the changing distribution of age differences by age of union formation. For example, a young man cannot have a much younger spouse (or vice versa), while the spread of observed age differences is widening with age. As the information on union duration is typically not available in most surveys, and admin data exist only for marriages, we follow an indirect approach oriented on the observed age patterns in existing partnerships. The algorithm is as follows:

  • Based on a parameter on the distribution of the age differences of couples for a given age of the searching female and the cohort size of women in partnerships in the simulation, we calculate the expected number of partners by age.
  • We then calculate the number of existing partners by age in the simulated population.
  • By comparing the expected with the observed numbers, we identify the age with the largest negative gap for which at least one available male spouse is available.

After having identified the pool of available partners, a second criterion is education. In contrast to age, we use the education patterns of currently young couples (age 25-45) for deciding on the spouse’s education. It is assumed, that current patterns are quite persistent and maintainable over time. If no match is possible for a draw based on these probabilities, the experiment is repeated; in contrast, if more than one man stays in the pool, the search moves to its third step.

The remaining criterium is male cohort childlessness. If the pool contains both men flagged as cohort childless and men who are supposed to become fathers at some point in time, it is the latter who are chosen first. To maintain consistency concerning male childlessness, in the case of birth the cohort childless flag is tried to be passed to an un-flagged single male of the same age and sex.

This module is highly experimental. At each step, it can be tracked if a match can be made or the experiment must be repeated. Refinements are possible especially concerning the order of the applied selection criteria which can be changed or follow a random distribution.



link Person.lPartner;                                   //EN Link between spouses

////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
////////////////////////////////////////////////////////////////////////////////////////////////////

//EN Males not in a partnership by age
actor_set Person asAvailableMale[integer_age]
    filter is_alive && sex == MALE && !has_partner && in_projected_time
    && WITHIN(MALE_PART_AGE_RANGE, integer_age);

//EN Males not in a partnership by age and education
actor_set Person asAvailableMaleEduc[integer_age][educ_fate]
    filter is_alive && sex == MALE && !has_partner && in_projected_time
    && WITHIN(MALE_PART_AGE_RANGE, integer_age);

//EN Males not in a partnership by age, education and cohort childlessness
actor_set Person asAvailableMaleEducChildless[integer_age][educ_fate][never_father]
    filter is_alive && sex == MALE && !has_partner && in_projected_time
    && WITHIN(MALE_PART_AGE_RANGE, integer_age);

//EN Women in a partnership
actor_set Person asFamInUnion[integer_age][integer_age_partner]
    filter is_alive && sex == FEMALE && in_projected_time && WITHIN(PART_AGE_RANGE, integer_age)
    && has_partner;

//EN Single males not never_father
actor_set Person asSingleMaleEverFatherEduc[integer_age][educ_fate]
    filter is_alive && sex == MALE && !has_partner && !never_father && in_projected_time;

//EN Couple males not never_father
actor_set Person asCoupleMaleEverFatherEduc[integer_age][educ_fate]
    filter is_alive && sex == MALE && has_partner && !never_father && in_projected_time && !is_blocked;



////////////////////////////////////////////////////////////////////////////////////////////////////
// Dimensions
////////////////////////////////////////////////////////////////////////////////////////////////////

range       MALE_PART_AGE_RANGE { 15, 105 };                 //EN Age

//MARS TEST
classification FATHER_TYPE                                   //EN Father type
{
    FT_NONO,        //EN No Father, never father
    FT_NOYES,       //EN No Father, could be father
    FT_YESNO,       //EN Father, never father
    FT_YESYES       //EN Father, can be father
};



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

parameters
{
    //EN Distribution of partner ages by age of female partner
    double PartnerAgeDistribution[PART_AGE_RANGE][MALE_PART_AGE_RANGE];

    //EN Partner Education
    cumrate PartnerEducation[EDUC_LEVEL3][EDUC_LEVEL3];
};


////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor states, events and functions
////////////////////////////////////////////////////////////////////////////////////////////////////

actor Person
{
    AGE_RANGE   integer_age_partner = (has_partner) ? lPartner->integer_age : 0;
    EDUC_LEVEL3 educ_partner = (has_partner) ? lPartner->educ_fate : EL3_LOW;

    logical     FindPartner();

    //EN Person currently in a union
    logical has_partner = (lPartner) ? TRUE : FALSE;

    void CheckFatherChildlessStatus(); hook CheckFatherChildlessStatus, MakeBaby;

    logical is_blocked = { FALSE };
    logical ever_father_in_sim = { FALSE }; //MARS TEST
    //MARS TEST FATHER TYPE
    FATHER_TYPE father_type =
        (!ever_father_in_sim && never_father) ? FT_NONO :
        (!ever_father_in_sim && !never_father) ? FT_NOYES :
        (ever_father_in_sim && never_father) ? FT_YESNO : FT_YESYES;
};


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


void Person::CheckFatherChildlessStatus()
{
    if (has_partner && lPartner->never_father)
    {
        if (asSingleMaleEverFatherEduc[lPartner->integer_age][lPartner->educ_fate]->Count() > 0)
        {
            auto prPerson = asSingleMaleEverFatherEduc[lPartner->integer_age][lPartner->educ_fate]->GetRandom(RandUniform(30));
            prPerson->never_father = TRUE;
            lPartner->never_father = FALSE;
        }
        else if (asCoupleMaleEverFatherEduc[lPartner->integer_age][lPartner->educ_fate]->Count() > 1)
        {
            lPartner->is_blocked == TRUE;
            auto prPerson = asCoupleMaleEverFatherEduc[lPartner->integer_age][lPartner->educ_fate]->GetRandom(RandUniform(31));
            prPerson->never_father = TRUE;
            lPartner->never_father = FALSE;
            lPartner->is_blocked = FALSE;
        }
    }
    if (has_partner) lPartner->ever_father_in_sim = TRUE; //MARS TEST
}

logical Person::FindPartner()
{
    bool   bFoundSpouse = FALSE;
    double dExpectedPartners[SIZE(MALE_PART_AGE_RANGE)];
    double dObservedPartners[SIZE(MALE_PART_AGE_RANGE)];
    double dSumExpectedPartners = 0;
    double dSumObservedPartners = 0;
    double dGap = 0.0;
    double dLargestGap = 0.0;
    int    nAgePartner;
    int    nEducPartner;

    //(1) Partner Age
    for (long nI = 0; nI < SIZE(MALE_PART_AGE_RANGE); nI++)
    {
        dExpectedPartners[nI] = PartnerAgeDistribution[RANGE_POS(PART_AGE_RANGE, integer_age)][nI];
        dObservedPartners[nI] = asFamInUnion[integer_age][MIN(MALE_PART_AGE_RANGE) + nI]->Count();

        dSumExpectedPartners = dSumExpectedPartners + dExpectedPartners[nI];
        dSumObservedPartners = dSumObservedPartners + dObservedPartners[nI];
    }
    for (long nI = 0; nI < SIZE(MALE_PART_AGE_RANGE); nI++)
    {
        if (dSumObservedPartners == 0.0) dSumObservedPartners = 1.0;
        dExpectedPartners[nI] = 1.001 * dExpectedPartners[nI] / dSumExpectedPartners;
        dObservedPartners[nI] = dObservedPartners[nI] / dSumObservedPartners;
        dGap = dExpectedPartners[nI] - dObservedPartners[nI];
        if (dExpectedPartners[nI] > 0.0 && dGap > dLargestGap && asAvailableMale[MIN(MALE_PART_AGE_RANGE)+nI] -> Count() > 0 )
        {
            dLargestGap = dGap;
            bFoundSpouse = TRUE;
            nAgePartner = MIN(MALE_PART_AGE_RANGE) + nI;
        }
    }
    //(2) Partner Education
    if (bFoundSpouse)
    {
        bool    bFoundSpouseByEduc = FALSE;
        int     nTrials = 15;
        while (!bFoundSpouseByEduc && nTrials > 0)
        {
            Lookup_PartnerEducation(RandUniform(27), (int)educ_fate, &nEducPartner);
            if (asAvailableMaleEduc[nAgePartner][nEducPartner]->Count() > 0)
            {
                bFoundSpouseByEduc = TRUE;
            }
            nTrials--;
        }
        if (!bFoundSpouseByEduc)
        {
            nEducPartner = asAvailableMale[nAgePartner]->GetRandom(RandUniform(28))->educ_fate;
        }

        //(3) Partner Childlessness
        if (asAvailableMaleEducChildless[nAgePartner][nEducPartner][FALSE]->Count() > 0)
        {
            lPartner = asAvailableMaleEducChildless[nAgePartner][nEducPartner][FALSE]->GetRandom(RandUniform(26));
        }
        else if (asAvailableMaleEducChildless[nAgePartner][nEducPartner][TRUE]->Count() > 0)
        {
            lPartner = asAvailableMaleEducChildless[nAgePartner][nEducPartner][TRUE]->GetRandom(RandUniform(29));
        }
    }
    return bFoundSpouse;
}

table Person tabCoupleAges2080
[calendar_year == 2080 && sex==FEMALE]
{
    has_partner *
    {
        duration()
    }
    *integer_age+
    *integer_age_partner+
};

table Person tabCoupleAges2140
[calendar_year == 2140 && sex==FEMALE]
{
    has_partner *
    {
        duration()
    }
    *integer_age+
    *integer_age_partner+
};


table Person tabCoupleAges2140MALE
[calendar_year == 2140 && sex==MALE]
{
    has_partner *
    {
        duration()
    }
    *integer_age+
    *integer_age_partner+
};

//EN UNION STATUS
table Person TabTestPartnershipStatus
{
    sex+ *
    {
        duration(has_partner, TRUE) / duration() //EN Proportion in partnership decimals=4
    }
    *integer_age +
    *calendar_year
};

//EN Partner Education
table Person TabTestPartnershipEduc2080
[ sex == FEMALE && has_partner && calendar_year == 2080 ]
{
    educ_fate+ *
    {
        duration(educ_partner, EL3_LOW) / duration(), //EN Low decimals=4
        duration(educ_partner, EL3_MEDIUM) / duration(), //EN Medium decimals=4
        duration(educ_partner, EL3_HIGH) / duration() //EN High decimals=4
    }
    * integer_age +
};

//EN Male Education
table Person TabTestMaleEduc2080
[ calendar_year == 2080 ]
{
    sex+ *
    {
        duration(educ_fate, EL3_LOW) / duration(), //EN Low decimals=4
        duration(educ_fate, EL3_MEDIUM) / duration(), //EN Medium decimals=4
        duration(educ_fate, EL3_HIGH) / duration() //EN High decimals=4
    }
    * integer_age +
};


//EN Male FATHER TYPE
table Person TabTestMaleFATHER2140
[ calendar_year == 2140 && sex==MALE]
{
    educ_fate+ *
    {
        duration(father_type, FT_NONO) / duration(), //EN NoNo decimals=4
        duration(father_type, FT_NOYES) / duration(), //EN NoYes decimals=4
        duration(father_type, FT_YESNO) / duration(), //EN YesNo decimals=4
        duration(father_type, FT_YESYES) / duration()  //EN YesYes decimals=4
    }
    * integer_age +
};