From a3e18162ba1ec7f011d8c4194b5c4d2d779784f1 Mon Sep 17 00:00:00 2001 From: pbronka <56582427+pbronka@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:26:08 +0000 Subject: [PATCH 1/4] Bump JAS-mine core version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f759653..ca29211 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ com.github.jasmineRepo JAS-mine-core - 4.3.2 + 4.3.4 com.github.jasmineRepo From 0d52778c23d33c59e0fb84063c220ec573a60070 Mon Sep 17 00:00:00 2001 From: pbronka <56582427+pbronka@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:26:57 +0000 Subject: [PATCH 2/4] Draft partnership alignment class --- input/time_series_factor.xlsx | Bin 55685 -> 56768 bytes src/main/java/simpaths/data/Parameters.java | 10 +- .../simpaths/model/PartnershipAlignment.java | 104 +++++++++++ src/main/java/simpaths/model/Person.java | 114 +++++------- .../java/simpaths/model/SimPathsModel.java | 162 +++++++++++------- .../model/enums/TimeSeriesVariable.java | 3 +- 6 files changed, 257 insertions(+), 136 deletions(-) create mode 100644 src/main/java/simpaths/model/PartnershipAlignment.java diff --git a/input/time_series_factor.xlsx b/input/time_series_factor.xlsx index c978eeb34862f82b00fc9131c55a4baf7c308a31..b54543261f54443efc5f6e1210e8e0546c4e4813 100644 GIT binary patch delta 5682 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 @@ -2399,12 +2397,14 @@ public static void loadTimeSeriesFactorMaps(Country country) { upratingIndexMapInflation = ExcelAssistant.loadCoefficientMap("input/time_series_factor.xlsx", country.toString() + "_inflation", 1, 1); upratingIndexMapWageGrowth = ExcelAssistant.loadCoefficientMap("input/time_series_factor.xlsx", country.toString() + "_wage_growth", 1, 1); socialCareProvisionTimeAdjustment = ExcelAssistant.loadCoefficientMap("input/time_series_factor.xlsx", country.toString() + "_care_adjustment", 1, 1); + partnershipTimeAdjustment = ExcelAssistant.loadCoefficientMap("input/time_series_factor.xlsx", country.toString() + "_cohabitation_adjustment", 1, 1); // rebase indices to base year defined by BASE_PRICE_YEAR rebaseIndexMap(TimeSeriesVariable.GDP); rebaseIndexMap(TimeSeriesVariable.Inflation); rebaseIndexMap(TimeSeriesVariable.WageGrowth); rebaseIndexMap(TimeSeriesVariable.CareProvisionAdjustment, startYear, false); + rebaseIndexMap(TimeSeriesVariable.PartnershipAdjustment, startYear, false); // load year-specific fiscal policy parameters socialCarePolicy = ExcelAssistant.loadCoefficientMap("input/policy parameters.xlsx", "social care", 1, 8); @@ -2466,6 +2466,10 @@ private static MultiKeyCoefficientMap getTimeSeriesValueMap(TimeSeriesVariable v break; case CareProvisionAdjustment: map = socialCareProvisionTimeAdjustment; + break; + case PartnershipAdjustment: + map = partnershipTimeAdjustment; + break; } return map; diff --git a/src/main/java/simpaths/model/PartnershipAlignment.java b/src/main/java/simpaths/model/PartnershipAlignment.java new file mode 100644 index 0000000..43b0355 --- /dev/null +++ b/src/main/java/simpaths/model/PartnershipAlignment.java @@ -0,0 +1,104 @@ +package simpaths.model; + +import microsim.engine.SimulationEngine; +import simpaths.data.IEvaluation; +import simpaths.data.Parameters; + +import java.util.Set; + +/** + * PartnershipAlignment adjusts the probability of individuals forming a union to values observed in the data. + * It modifies the intercept of the "considerCohabitation" probit model. + * + * To find the value by which the intercept should be adjusted, it uses a search routine. + * The routine adjusts probabilities, and performs union matching separate from the real matching in the model. If the + * results differ from the targets based on the data by more than a specified threshold, the adjustment is repeated. + * + * Importantly, the adjustment needs to be only found once. Modified intercepts can then be used in subsequent simulations. + */ + +public class PartnershipAlignment implements IEvaluation { + + private double aggregateShareOfPartneredPersons; + private double targetAggregateShareOfPartneredPersons; + private double partnershipAdjustment; + boolean partnershipAdjustmentChanged; + private Set persons; + private SimPathsModel model; + + public PartnershipAlignment(Set persons, double partnershipAdjustment) { + this.model = (SimPathsModel) SimulationEngine.getInstance().getManager(SimPathsModel.class.getCanonicalName()); + this.persons = persons; + this.partnershipAdjustment = partnershipAdjustment; + targetAggregateShareOfPartneredPersons = 0.38; // TODO: List of targets by year should be provided through Excel file policy parameters.xlsx + } + + /** + * Evaluates the discrepancy between the simulated and target aggregate share of partnered persons and adjusts partnerships if necessary. + * + * This method compares the adjustment parameter 'args[0]' with the current 'partnershipAdjustment'. + * If the absolute difference exceeds a small threshold (1.0E-5), it triggers the adjustment of partnerships. + * + * The error is then calculated as the difference between the target and the actual aggregate share of partnered persons. + * This error value is returned and serves as the stopping condition in root search routines. + * + * @param args An array of parameters, where args[0] represents the adjustment parameter. + * @return The error in the target aggregate share of partnered persons after potential adjustments. + */ + @Override + public double evaluate(double[] args) { + if (Math.abs(args[0] - partnershipAdjustment) > 1.0E-5) { + adjustPartnerships(args[0]); + } + + double error = targetAggregateShareOfPartneredPersons - evalAggregateShareOfPartneredPersons(); + return error; + } + + /** + * Evaluates the aggregate share of persons with partners assigned in a test run of union matching among those eligible for partnership. + * + * This method uses Java streams to count the number of persons who meet the age criteria for cohabitation + * and the number of persons who currently have a test partner. The aggregate share is calculated as the + * ratio of successfully partnered persons to those eligible for partnership, with consideration for potential division by zero. + * + * @return The aggregate share of partnered persons among those eligible, or 0.0 if no eligible persons are found. + */ + private double evalAggregateShareOfPartneredPersons() { + long numPersonsWhoCanHavePartner = persons.stream() + .filter(person -> person.getDag() >= Parameters.MIN_AGE_COHABITATION) + .count(); + + long numPersonsPartnered = persons.stream() + .filter(Person::hasTestPartner) + .count(); + + return numPersonsWhoCanHavePartner > 0 + ? (double) numPersonsPartnered / numPersonsWhoCanHavePartner + : 0.0; + } + + /** + * Adjusts the probit regression used for partnership evaluation and re-evaluates the score for all eligible persons. + * Then, creates "test" unions between individuals. + * + * This method performs the following steps: + * 1. Runs the cohabitation probit model. + * 2. Matches individuals within this method. + * + * @param newPartnershipAdjustment The new adjustment value for the partnership probit regression. + */ + private void adjustPartnerships(double newPartnershipAdjustment) { + persons.stream() + .filter(person -> person.getDag() >= Parameters.MIN_AGE_COHABITATION) + .forEach(person -> person.evaluatePartnership(newPartnershipAdjustment)); + + // "Fake" union matching (not modifying household structure) here + model.unionMatching(true); + model.unionMatchingNoRegion(true); + + partnershipAdjustment = newPartnershipAdjustment; + partnershipAdjustmentChanged = true; + } + +} diff --git a/src/main/java/simpaths/model/Person.java b/src/main/java/simpaths/model/Person.java index 4fac43e..0240032 100644 --- a/src/main/java/simpaths/model/Person.java +++ b/src/main/java/simpaths/model/Person.java @@ -191,6 +191,8 @@ public class Person implements EventListener, IDoubleSource, IIntSource, Weight, @Transient private boolean toBePartnered; @Transient + private boolean hasTestPartner; // Used in partnership alignment process. Indicates that this person has found partner in a test run of union matching. + @Transient private Person partner; @Column(name="idpartner") private Long idPartner; //Note, must not use primitive long, as long cannot hold 'null' value, i.e. if the person has no partner @@ -1234,6 +1236,27 @@ public void evaluateSocialCareProvide(double probitAdjustment) { } } + public void evaluatePartnership(double probitAdjustment) { + toBePartnered = false; + + if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION && partner == null) { + if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { + double score = Parameters.getRegPartnershipU1a().getScore(this, Person.DoublesVariables.class); + double prob = Parameters.getRegPartnershipU1a().getProbability(score + probitAdjustment); + toBePartnered = cohabitInnov.nextDouble() < prob; + } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { + double score = Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class); + double prob = Parameters.getRegPartnershipU1b().getProbability(score + probitAdjustment); + toBePartnered = cohabitInnov.nextDouble() < prob; + } + + if (toBePartnered) { + model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + } + } + } + + protected void inSchool() { //Min age to leave education set to 16 (from 18 previously) but note that age to leave home is 18. @@ -1292,78 +1315,27 @@ protected void leavingSchool() { protected void considerCohabitation() { - toBePartnered = false; - if (model.getCountry().equals(Country.UK)) { - - //Apply only to individuals above age defined in MIN_AGE_MARRIAGE - if (dag >= Parameters.MIN_AGE_COHABITATION) { - //If not in partnership, follow U1a or U1b to determine probability of entering partnership - if (partner == null) { - - //Follow process U1a for individuals aged MIN_AGE_MARRIAGE-29 who are in continuous education: - if (dag <= 29 && les_c4.equals(Les_c4.Student) && leftEducation == false) { - - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1a().getProbability(this, Person.DoublesVariables.class)); - if (toBePartnered) { //If true, look for a partner - model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); //Will look for partner in model's unionMatching process - } - } - //Follow process U1b for individuals who are not in continuous education: - else if ((les_c4.equals(Les_c4.Student) && leftEducation == true) || !les_c4.equals(Les_c4.Student)) { - - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class)); - if (toBePartnered) { - model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); - } - } - } - - //If in partnership, follow U2b to determine the probability of exiting partnership by female member of the couple not in education - //Note: this implies a 0 probability of splitting for couples in which female is in continuous education - else if (partner != null) { - - if (dgn.equals(Gender.Female) && ((les_c4.equals(Les_c4.Student) && leftEducation == true) || !les_c4.equals(Les_c4.Student))) { - - if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU2b().getProbability(this, Person.DoublesVariables.class)) { //If true, leave partner - - leavePartner(); - } - } + if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION) { + if (partner == null) { + if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { + toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1a().getProbability(this, Person.DoublesVariables.class)); + } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { + toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class)); } + if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { + if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU2b().getProbability(this, Person.DoublesVariables.class)) leavePartner(); } - } - else if (model.getCountry().equals(Country.IT)) { - - if (dag >= Parameters.MIN_AGE_COHABITATION) { - - //If not in partnership, follow U1a or U1b to determine probability of entering partnership - if (partner == null) { - - //Follow process U1 for individuals who are not in continuous education: - if ((les_c4.equals(Les_c4.Student) && leftEducation == true) || !les_c4.equals(Les_c4.Student)) { - - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU1().getProbability(this, Person.DoublesVariables.class)); - if (toBePartnered) { - - model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); - } - } - } - - //If in partnership, follow U2 to determine the probability of exiting partnership by female member of the couple not in education - //Note: this implies a 0 probability of splitting for couples in which female is in continuous education - else if (partner != null) { - - if (dgn.equals(Gender.Female) && ((les_c4.equals(Les_c4.Student) && leftEducation == true) || !les_c4.equals(Les_c4.Student))) { - - if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU2().getProbability(this, Person.DoublesVariables.class)) { //If true, leave partner - - leavePartner(); - } - } + } else if (model.getCountry() == Country.IT && dag >= Parameters.MIN_AGE_COHABITATION) { + if (partner == null) { + if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { + toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU1().getProbability(this, Person.DoublesVariables.class)); + if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); } + } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { + if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU2().getProbability(this, Person.DoublesVariables.class)) leavePartner(); } } } @@ -4376,4 +4348,12 @@ public RandomGenerator getSocialCareInnov() { return socialCareInnov; } + public boolean hasTestPartner() { + return hasTestPartner; + } + + public void setHasTestPartner(boolean hasTestPartner) { + this.hasTestPartner = hasTestPartner; + } + } \ No newline at end of file diff --git a/src/main/java/simpaths/model/SimPathsModel.java b/src/main/java/simpaths/model/SimPathsModel.java index d14782c..59f2230 100644 --- a/src/main/java/simpaths/model/SimPathsModel.java +++ b/src/main/java/simpaths/model/SimPathsModel.java @@ -452,9 +452,11 @@ public void buildSchedule() { // B: Consider whether in consensual union (cohabiting) yearlySchedule.addCollectionEvent(persons, Person.Processes.ConsiderCohabitation); - yearlySchedule.addEvent(this, Processes.ConsiderCohabitationAlignment); - // C: Marriage + // TODO: new partnership alignment routine should be here. It will adjust the number of people who want to cohabit, perform test union matching, and repeat until satisfactory number of unions has been created. + // Final adjusted probit will be then used to run consider cohabitation for the final time and perform union matching + + // C: Union matching yearlySchedule.addEvent(this, Processes.UnionMatching); yearlySchedule.addCollectionEvent(benefitUnits, BenefitUnit.Processes.UpdateOccupancy); //yearlySchedule.addEvent(this, Processes.CheckForEmptyHouseholds); @@ -615,10 +617,10 @@ public void onEvent(Enum type) { if(unionMatchingMethod.equals(UnionMatchingMethod.SBAM)) { unionMatchingSBAM(); } else if (unionMatchingMethod.equals(UnionMatchingMethod.Parametric)) { - unionMatching(); + unionMatching(false); } else { - unionMatching(); - unionMatchingNoRegion(); //Run matching again relaxing regions this time + unionMatching(false); + unionMatchingNoRegion(false); //Run matching again relaxing regions this time } if (commentsOn) log.info("Union matching complete."); break; @@ -1531,7 +1533,11 @@ public void match(Person p1, Person p2) { int yearMatches = 0; int unmatchedSize = 0; - private void unionMatching() { + /** + * + * @param alignmentRun If true, real unions will not be formed. Instead, flags will be set for individual to indicate those who would have formed a union. + */ + protected void unionMatching(boolean alignmentRun) { Set matches = new LinkedHashSet(); @@ -1596,33 +1602,43 @@ public Double getValue(Person male, Person female) { @Override public void match(Person p1, Person p2) { //The SimpleMatching.getInstance().matching() assumes the first collection in the argument (males in this case) is also the collection that the first argument of the MatchingClosure.match() is sampled from. - // log.debug("Person " + p1.getKey().getId() + " marries person " + p2.getKey().getId()); - if (!p1.getRegion().equals(p2.getRegion())) { //If persons to match have different regions, move female to male + if (alignmentRun) { + p1.setHasTestPartner(true); + p2.setHasTestPartner(true); + unmatchedMales.remove(p1); + unmatchedFemales.remove(p2); + personsToMatch.get(p1.getDgn()).get(region).remove(p1); + personsToMatch.get(p2.getDgn()).get(region).remove(p2); + matches.add(p1); + } else { - p2.setRegion(p1.getRegion()); - } - if (p1.getDgn().equals(p2.getDgn())) { + if (!p1.getRegion().equals(p2.getRegion())) { //If persons to match have different regions, move female to male - throw new RuntimeException("Error - both parties to match have the same gender!"); - } else { + p2.setRegion(p1.getRegion()); + } + if (p1.getDgn().equals(p2.getDgn())) { - p1.setPartner(p2); - p2.setPartner(p1); - p1.setHousehold_status(Household_status.Couple); - p2.setHousehold_status(Household_status.Couple); - p1.setDcpyy(0); //Set years in partnership to 0 - p2.setDcpyy(0); - p1.setDcpst(Dcpst.Partnered); - p2.setDcpst(Dcpst.Partnered); + throw new RuntimeException("Error - both parties to match have the same gender!"); + } else { - //Update household - p1.setupNewBenefitUnit(true); //All the lines below are executed within the setupNewHome() method for both p1 and p2. Note need to have partner reference before calling setupNewHome! + p1.setPartner(p2); + p2.setPartner(p1); + p1.setHousehold_status(Household_status.Couple); + p2.setHousehold_status(Household_status.Couple); + p1.setDcpyy(0); //Set years in partnership to 0 + p2.setDcpyy(0); + p1.setDcpst(Dcpst.Partnered); + p2.setDcpst(Dcpst.Partnered); - unmatchedMales.remove(p1); //Remove matched people from unmatched sets (but keep those who were not matched so they can try next year) - unmatchedFemales.remove(p2); - personsToMatch.get(p1.getDgn()).get(region).remove(p1); - personsToMatch.get(p2.getDgn()).get(region).remove(p2); - matches.add(p1); + //Update household + p1.setupNewBenefitUnit(true); //All the lines below are executed within the setupNewHome() method for both p1 and p2. Note need to have partner reference before calling setupNewHome! + + unmatchedMales.remove(p1); //Remove matched people from unmatched sets (but keep those who were not matched so they can try next year) + unmatchedFemales.remove(p2); + personsToMatch.get(p1.getDgn()).get(region).remove(p1); + personsToMatch.get(p2.getDgn()).get(region).remove(p2); + matches.add(p1); + } } } } @@ -1640,21 +1656,22 @@ public void match(Person p1, Person p2) { //The SimpleMatching.getInstanc (countAttempts < Parameters.MAXIMUM_ATTEMPTS_MATCHING)); // System.out.println("There are (overall stock of)" + unmatchedMales.size() + " unmatched males and " + unmatchedFemales.size() + " unmatched females at the end. Number of matches made for " + region + " is " + matches.size()); - for (Gender gender : Gender.values()) { - - // Turned off to allow unmatched people try again next year without the need to go through considerCohabitation process - // personsToMatch.get(gender).get(region).clear(); //Nothing happens to unmatched people. The next time they considerCohabitation, they will (probabilistically) have the opportunity to enter the matching pool again. - unmatchedSize += personsToMatch.get(gender).get(region).size(); - } - } + if (!alignmentRun) { + for (Gender gender : Gender.values()) { - yearMatches = matches.size(); - allMatches += matches.size(); - // System.out.println("Total number of matches made in the year " + matches.size() + " and total number of matches in all years is " + allMatches); - if (commentsOn) log.debug("Marriage matched."); - for (BenefitUnit benefitUnit : benefitUnits) { + // Turned off to allow unmatched people try again next year without the need to go through considerCohabitation process + // personsToMatch.get(gender).get(region).clear(); //Nothing happens to unmatched people. The next time they considerCohabitation, they will (probabilistically) have the opportunity to enter the matching pool again. + unmatchedSize += personsToMatch.get(gender).get(region).size(); + } - benefitUnit.updateOccupancy(); + yearMatches = matches.size(); + allMatches += matches.size(); + // System.out.println("Total number of matches made in the year " + matches.size() + " and total number of matches in all years is " + allMatches); + if (commentsOn) log.debug("Marriage matched."); + for (BenefitUnit benefitUnit : benefitUnits) { + benefitUnit.updateOccupancy(); + } + } } } @@ -1662,7 +1679,7 @@ public void match(Person p1, Person p2) { //The SimpleMatching.getInstanc * PROCESS - UNION MATCHING WITH REGION RELAXED * */ - private void unionMatchingNoRegion() { + protected void unionMatchingNoRegion(boolean alignmentRun) { int countAttempts = 0; double initialMalesSize = 0.; @@ -1731,33 +1748,45 @@ public Double getValue(Person male, Person female) { public void match(Person p1, Person p2) { //The SimpleMatching.getInstance().matching() assumes the first collection in the argument (males in this case) is also the collection that the first argument of the MatchingClosure.match() is sampled from. // log.debug("Person " + p1.getKey().getId() + " marries person " + p2.getKey().getId()); Region originalRegionP2 = p2.getRegion(); - if (!p1.getRegion().equals(p2.getRegion())) { //If persons to match have different regions, move female to male - - p2.setRegion(p1.getRegion()); -// System.out.println("Region changed"); - } - if (p1.getDgn().equals(p2.getDgn())) { - - throw new RuntimeException("Error - both parties to match have the same gender!"); - } else { - - p1.setPartner(p2); - p2.setPartner(p1); - p1.setHousehold_status(Household_status.Couple); - p2.setHousehold_status(Household_status.Couple); - p1.setDcpyy(0); //Set years in partnership to 0 - p2.setDcpyy(0); - p1.setDcpst(Dcpst.Partnered); - p2.setDcpst(Dcpst.Partnered); - - //Update household - p1.setupNewBenefitUnit(true); //All the lines below are executed within the setupNewHome() method for both p1 and p2. Note need to have partner reference before calling setupNewHome! + if (alignmentRun) { + p1.setHasTestPartner(true); + p2.setHasTestPartner(true); unmatchedMales.remove(p1); //Remove matched people from unmatched sets (but keep those who were not matched so they can try next year) unmatchedFemales.remove(p2); personsToMatch.get(p1.getDgn()).get(p1.getRegion()).remove(p1); personsToMatch.get(p2.getDgn()).get(originalRegionP2).remove(p2); matches.add(p1); + } else { + + if (!p1.getRegion().equals(p2.getRegion())) { //If persons to match have different regions, move female to male + + p2.setRegion(p1.getRegion()); + // System.out.println("Region changed"); + } + if (p1.getDgn().equals(p2.getDgn())) { + + throw new RuntimeException("Error - both parties to match have the same gender!"); + } else { + + p1.setPartner(p2); + p2.setPartner(p1); + p1.setHousehold_status(Household_status.Couple); + p2.setHousehold_status(Household_status.Couple); + p1.setDcpyy(0); //Set years in partnership to 0 + p2.setDcpyy(0); + p1.setDcpst(Dcpst.Partnered); + p2.setDcpst(Dcpst.Partnered); + + //Update household + p1.setupNewBenefitUnit(true); //All the lines below are executed within the setupNewHome() method for both p1 and p2. Note need to have partner reference before calling setupNewHome! + + unmatchedMales.remove(p1); //Remove matched people from unmatched sets (but keep those who were not matched so they can try next year) + unmatchedFemales.remove(p2); + personsToMatch.get(p1.getDgn()).get(p1.getRegion()).remove(p1); + personsToMatch.get(p2.getDgn()).get(originalRegionP2).remove(p2); + matches.add(p1); + } } } } @@ -1771,8 +1800,9 @@ public void match(Person p1, Person p2) { //The SimpleMatching.getInstanc // System.out.println("unmatched females proportion " + unmatchedFemales.size() / (double) initialFemalesSize); } while ((Math.min((unmatchedMales.size() / (double) initialMalesSize), (unmatchedFemales.size() / (double) initialFemalesSize)) > Parameters.UNMATCHED_TOLERANCE_THRESHOLD) && (countAttempts < Parameters.MAXIMUM_ATTEMPTS_MATCHING)); - - allMatches += matches.size(); + if (!alignmentRun) { + allMatches += matches.size(); + } // System.out.println("There are " + unmatchedMales.size() + " unmatched males and " + unmatchedFemales.size() + " unmatched females at the end. Number of matches made " + matches.size() + " and total number of matches in all years is " + allMatches); } @@ -1792,6 +1822,8 @@ private void socialCareMarketClearning() { } + + /** * * PROCESS - ALIGN THE SHARE OF COHABITING INDIVIDUALS IN THE SIMULATED POPULATION diff --git a/src/main/java/simpaths/model/enums/TimeSeriesVariable.java b/src/main/java/simpaths/model/enums/TimeSeriesVariable.java index e605376..1493236 100644 --- a/src/main/java/simpaths/model/enums/TimeSeriesVariable.java +++ b/src/main/java/simpaths/model/enums/TimeSeriesVariable.java @@ -5,5 +5,6 @@ public enum TimeSeriesVariable { Inflation, WageGrowth, CarerWageRate, - CareProvisionAdjustment; + CareProvisionAdjustment, + PartnershipAdjustment; } From 7fdcaf5fce75a8fdf260e7b2b1d9c966665b9ecb Mon Sep 17 00:00:00 2001 From: pbronka <56582427+pbronka@users.noreply.github.com> Date: Fri, 10 Nov 2023 20:17:31 +0000 Subject: [PATCH 3/4] Further updates to partnership alignment routine --- src/main/java/simpaths/data/RootSearch.java | 14 +++++-- src/main/java/simpaths/data/Statistics2.java | 11 +++++ .../simpaths/model/PartnershipAlignment.java | 9 ++--- src/main/java/simpaths/model/Person.java | 10 ++++- .../java/simpaths/model/SimPathsModel.java | 40 ++++++++++++++++--- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/main/java/simpaths/data/RootSearch.java b/src/main/java/simpaths/data/RootSearch.java index cdbb4f7..35074b9 100644 --- a/src/main/java/simpaths/data/RootSearch.java +++ b/src/main/java/simpaths/data/RootSearch.java @@ -33,11 +33,11 @@ private double[] bisection() { final int ITMAX = 200; double[] xg, xs, xn=null, xp=null; - double fg, fs; + double fg, fs, fn, fp; - xg = target; - fg = function.evaluate(xg); - if (Math.abs(fg) > epsFunction) { + xg = target; // Start with initial values provided + fg = function.evaluate(xg); // Calculate the error using initial values and a provided alignment function, e.g. SocialCareAlignment or PartnershipAlignment + if (Math.abs(fg) > epsFunction) { // If error is larger than allowed threshold, conduct search for a better adjustment // need to conduct search targetAltered = true; @@ -47,8 +47,10 @@ private double[] bisection() { fs = function.evaluate(xs); if (fs < 0.0) { xn = xs; + fn = fs; } else { xp = xs; + fp = fs; } xs = upperBounds; fs = function.evaluate(xs); @@ -56,10 +58,12 @@ private double[] bisection() { if (xn!=null) throw new RuntimeException("Root search supplied boundaries that are both negative"); xn = xs; + fn = fs; } else { if (xp!=null) throw new RuntimeException("Root search supplied boundaries that are both positive"); xp = xs; + fp = fs; } // start search @@ -68,8 +72,10 @@ private double[] bisection() { if (fg < 0.0) { xn = xg; + fn = fg; } else { xp = xg; + fp = fg; } xg = midPoint(xn, xp); fg = function.evaluate(xg); diff --git a/src/main/java/simpaths/data/Statistics2.java b/src/main/java/simpaths/data/Statistics2.java index c40ea7a..2a8ae83 100644 --- a/src/main/java/simpaths/data/Statistics2.java +++ b/src/main/java/simpaths/data/Statistics2.java @@ -148,6 +148,16 @@ public class Statistics2 { @Column(name= "social_care_adj_factor") private double socialCareAdjustmentFactor; + @Column(name = "partnership_adj_factor") + private double partnershipAdjustmentFactor; + + public double getPartnershipAdjustmentFactor() { + return partnershipAdjustmentFactor; + } + + public void setPartnershipAdjustmentFactor(double partnershipAdjustmentFactor) { + this.partnershipAdjustmentFactor = partnershipAdjustmentFactor; + } public double getSocialCareAdjustmentFactor() { return socialCareAdjustmentFactor; } public void setSocialCareAdjustmentFactor(double factor) {socialCareAdjustmentFactor = factor;} @@ -581,5 +591,6 @@ public void update(SimPathsModel model) { setPopulation65to84(popula[2]); setSocialCareAdjustmentFactor(Parameters.getTimeSeriesValue(model.getYear()-1, TimeSeriesVariable.CareProvisionAdjustment)); + setPartnershipAdjustmentFactor(Parameters.getTimeSeriesValue(model.getYear()-1, TimeSeriesVariable.PartnershipAdjustment)); } } diff --git a/src/main/java/simpaths/model/PartnershipAlignment.java b/src/main/java/simpaths/model/PartnershipAlignment.java index 43b0355..e741958 100644 --- a/src/main/java/simpaths/model/PartnershipAlignment.java +++ b/src/main/java/simpaths/model/PartnershipAlignment.java @@ -47,9 +47,8 @@ public PartnershipAlignment(Set persons, double partnershipAdjustment) { */ @Override public double evaluate(double[] args) { - if (Math.abs(args[0] - partnershipAdjustment) > 1.0E-5) { - adjustPartnerships(args[0]); - } + + adjustPartnerships(args[0]); double error = targetAggregateShareOfPartneredPersons - evalAggregateShareOfPartneredPersons(); return error; @@ -80,12 +79,12 @@ private double evalAggregateShareOfPartneredPersons() { /** * Adjusts the probit regression used for partnership evaluation and re-evaluates the score for all eligible persons. - * Then, creates "test" unions between individuals. + * Then, creates "test" unions between individuals. * * This method performs the following steps: * 1. Runs the cohabitation probit model. * 2. Matches individuals within this method. - * + * TODO: the loops over persons calculating cohabitation probability should be paralellised * @param newPartnershipAdjustment The new adjustment value for the partnership probit regression. */ private void adjustPartnerships(double newPartnershipAdjustment) { diff --git a/src/main/java/simpaths/model/Person.java b/src/main/java/simpaths/model/Person.java index 0240032..a58e4e6 100644 --- a/src/main/java/simpaths/model/Person.java +++ b/src/main/java/simpaths/model/Person.java @@ -168,6 +168,8 @@ public class Person implements EventListener, IDoubleSource, IIntSource, Weight, private double drawProvCareIncidence; @Transient private double drawProvCareHours; + @Transient + private double drawPartnership; // Used with the partnership alignment process //Sedex is an indicator for leaving education in that year @Enumerated(EnumType.STRING) @@ -459,6 +461,7 @@ public Person(Long id) { educationInnov = new Random(SimulationEngine.getRnd().nextLong()); labourSupplyInnov = new Random(SimulationEngine.getRnd().nextLong()); labourSupplySingleDraw = labourSupplyInnov.nextDouble(); + drawPartnership = -9; } //For use with creating new people at the minimum Age who enter the simulation during UpdateMaternityStatus after fertility has been aligned @@ -1238,16 +1241,19 @@ public void evaluateSocialCareProvide(double probitAdjustment) { public void evaluatePartnership(double probitAdjustment) { toBePartnered = false; + if (drawPartnership < 0.) { + drawPartnership = cohabitInnov.nextDouble(); + } if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION && partner == null) { if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { double score = Parameters.getRegPartnershipU1a().getScore(this, Person.DoublesVariables.class); double prob = Parameters.getRegPartnershipU1a().getProbability(score + probitAdjustment); - toBePartnered = cohabitInnov.nextDouble() < prob; + toBePartnered = drawPartnership < prob; } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { double score = Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class); double prob = Parameters.getRegPartnershipU1b().getProbability(score + probitAdjustment); - toBePartnered = cohabitInnov.nextDouble() < prob; + toBePartnered = drawPartnership < prob; } if (toBePartnered) { diff --git a/src/main/java/simpaths/model/SimPathsModel.java b/src/main/java/simpaths/model/SimPathsModel.java index 59f2230..5f78c68 100644 --- a/src/main/java/simpaths/model/SimPathsModel.java +++ b/src/main/java/simpaths/model/SimPathsModel.java @@ -16,6 +16,8 @@ import jakarta.persistence.EntityTransaction; import jakarta.persistence.Transient; import org.apache.commons.lang3.ArrayUtils; +import org.jetbrains.annotations.NotNull; +import simpaths.data.IEvaluation; import simpaths.data.MahalanobisDistance; import simpaths.data.RootSearch; import simpaths.experiment.SimPathsCollector; @@ -451,6 +453,7 @@ public void buildSchedule() { yearlySchedule.addCollectionEvent(persons, Person.Processes.UpdatePotentialHourlyEarnings); // B: Consider whether in consensual union (cohabiting) + yearlySchedule.addEvent(this, Processes.CohabitationRegressionAlignment); yearlySchedule.addCollectionEvent(persons, Person.Processes.ConsiderCohabitation); // TODO: new partnership alignment routine should be here. It will adjust the number of people who want to cohabit, perform test union matching, and repeat until satisfactory number of unions has been created. @@ -561,7 +564,7 @@ public enum Processes { //Alignment Processes FertilityAlignment, PopulationAlignment, - ConsiderCohabitationAlignment, + CohabitationRegressionAlignment, // HealthAlignment, InSchoolAlignment, EducationLevelAlignment, @@ -603,9 +606,9 @@ public void onEvent(Enum type) { if (commentsOn) log.info("Population alignment skipped as simulated year exceeds period covered by population projections."); } break; - case ConsiderCohabitationAlignment: - if (alignCohabitation && year == getStartYear()) { - considerCohabitationAlignment(); + case CohabitationRegressionAlignment: + if (alignCohabitation) { + partnershipAlignment(); } if (commentsOn) log.info("Cohabitation alignment complete."); break; @@ -1553,6 +1556,8 @@ protected void unionMatching(boolean alignmentRun) { Set unmatchedFemales = new LinkedHashSet(); unmatchedMales.addAll(personsToMatch.get(Gender.Male).get(region)); unmatchedFemales.addAll(personsToMatch.get(Gender.Female).get(region)); + ageDiffBound = Parameters.AGE_DIFFERENCE_INITIAL_BOUND; + potentialHourlyEarningsDiffBound = Parameters.POTENTIAL_EARNINGS_DIFFERENCE_INITIAL_BOUND; // System.out.println("There are " + unmatchedMales.size() + " unmatched males and " + unmatchedFemales.size() + " unmatched females at the start"); Pair, Set> unmatched = new Pair<>(unmatchedMales, unmatchedFemales); @@ -1645,7 +1650,7 @@ public void match(Person p1, Person p2) { //The SimpleMatching.getInstanc ); // Relax differential bounds for next iteration (in the case where there has not been a high enough proportion of matches) - ageDiffBound *= Parameters.RELAXATION_FACTOR; //TODO: Should the bounds be relaxed permanently as it is the case now? Or only for the current year or year-region for example? + ageDiffBound *= Parameters.RELAXATION_FACTOR; potentialHourlyEarningsDiffBound *= Parameters.RELAXATION_FACTOR; countAttempts++; // System.out.println("unmatched males proportion " + unmatchedMales.size() / (double) initialMalesSize); @@ -1802,6 +1807,13 @@ public void match(Person p1, Person p2) { //The SimpleMatching.getInstanc if (!alignmentRun) { allMatches += matches.size(); + } else { + // Clear set if used within the matching procedure + for (Gender gender : Gender.values()) { + for (Region region : Region.values()) { + personsToMatch.get(gender).get(region).clear(); + } + } } // System.out.println("There are " + unmatchedMales.size() + " unmatched males and " + unmatchedFemales.size() + " unmatched females at the end. Number of matches made " + matches.size() + " and total number of matches in all years is " + allMatches); } @@ -1821,7 +1833,25 @@ private void socialCareMarketClearning() { } } + private void partnershipAlignment() { + double partnershipAdjustment = Parameters.getTimeSeriesValue(getYear(), TimeSeriesVariable.PartnershipAdjustment); // Initial values of adjustment to be applied to considerCohabitation probit + PartnershipAlignment partnershipAlignment = new PartnershipAlignment(persons, partnershipAdjustment); + RootSearch search = getRootSearch(partnershipAdjustment, partnershipAlignment, 5.0E-2, 5.0E-2); // epsOrdinates and epsFunction determine the stopping condition for the search. For partnershipAlignment error term is the difference between target and observed share of partnered individuals. + if (search.isTargetAltered()) { + Parameters.putTimeSeriesValue(getYear(), search.getTarget()[0], TimeSeriesVariable.PartnershipAdjustment); // If adjustment is altered from the initial value, update the map + System.out.println("Adjustment value was " + search.getTarget()[0]); + } + } + @NotNull + private static RootSearch getRootSearch(double initialAdjustment, IEvaluation alignmentClass, double epsOrdinates, double epsFunction) { + double[] startVal = new double[] {initialAdjustment}; // Starting values for the adjustment + double[] lowerBound = new double[] {initialAdjustment - 4}; + double[] upperBound = new double[] {initialAdjustment + 4}; + RootSearch search = new RootSearch(lowerBound, upperBound, startVal, alignmentClass, epsOrdinates, epsFunction); + search.evaluate(); + return search; + } /** From 874c257c50974fec737fdab18b3450233c136e4d Mon Sep 17 00:00:00 2001 From: pbronka <56582427+pbronka@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:28:24 +0000 Subject: [PATCH 4/4] Update partnership alignment process --- input/policy parameters.xlsx | Bin 15751 -> 16834 bytes src/main/java/simpaths/data/Parameters.java | 13 ++- .../simpaths/experiment/SimPathsMultiRun.java | 2 +- .../simpaths/model/PartnershipAlignment.java | 12 +- src/main/java/simpaths/model/Person.java | 110 ++++++++++++------ .../java/simpaths/model/SimPathsModel.java | 11 +- 6 files changed, 100 insertions(+), 48 deletions(-) diff --git a/input/policy parameters.xlsx b/input/policy parameters.xlsx index cc4528535d9bf536bcb42202952dfd371f1681a1..63820b3d93c3fcd68f0e42c30a8d0ff5dc33867a 100644 GIT binary patch delta 4832 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 Loq}R2|3?1>8F2uL diff --git a/src/main/java/simpaths/data/Parameters.java b/src/main/java/simpaths/data/Parameters.java index bd6e5de..43c042b 100644 --- a/src/main/java/simpaths/data/Parameters.java +++ b/src/main/java/simpaths/data/Parameters.java @@ -348,7 +348,7 @@ else if(numberOfChildren <= 5) { //Uprating factor private static MultiKeyCoefficientMap upratingIndexMapGDP, upratingIndexMapInflation, socialCareProvisionTimeAdjustment, partnershipTimeAdjustment,upratingIndexMapWageGrowth, priceMapSavingReturns, priceMapDebtCostLow, priceMapDebtCostHigh, - wageRateFormalSocialCare, socialCarePolicy; + wageRateFormalSocialCare, socialCarePolicy, partneredShare; public static MultiKeyMap upratingFactorsMap = new MultiKeyMap<>(); //Education level projections @@ -2408,6 +2408,8 @@ public static void loadTimeSeriesFactorMaps(Country country) { // load year-specific fiscal policy parameters socialCarePolicy = ExcelAssistant.loadCoefficientMap("input/policy parameters.xlsx", "social care", 1, 8); + partneredShare = ExcelAssistant.loadCoefficientMap("input/policy parameters.xlsx", "partnership", 1, 1); + } public static void loadTimeSeriesFactorForTaxDonor(Country country) { @@ -2667,6 +2669,15 @@ public static void setProjectWealth(boolean val) { projectWealth = val; } + public static double getPartnershipShare(int year) { + + MultiKeyCoefficientMap map = partneredShare; + Object val = map.getValue(year); + if (val == null) + val = extendRateTimeSeries(year, map); + return ((Number) val).doubleValue(); + } + public static double getSocialCarePolicyValue(int year, String param) { Object val = socialCarePolicy.getRowColumnValue(year, param); diff --git a/src/main/java/simpaths/experiment/SimPathsMultiRun.java b/src/main/java/simpaths/experiment/SimPathsMultiRun.java index 4b22ade..2b5833a 100644 --- a/src/main/java/simpaths/experiment/SimPathsMultiRun.java +++ b/src/main/java/simpaths/experiment/SimPathsMultiRun.java @@ -17,7 +17,7 @@ public class SimPathsMultiRun extends MultiRun { public static boolean executeWithGui = true; - private static int maxNumberOfRuns = 3; + private static int maxNumberOfRuns = 50; private static String countryString; private static int startYear; diff --git a/src/main/java/simpaths/model/PartnershipAlignment.java b/src/main/java/simpaths/model/PartnershipAlignment.java index e741958..a3c0ac6 100644 --- a/src/main/java/simpaths/model/PartnershipAlignment.java +++ b/src/main/java/simpaths/model/PartnershipAlignment.java @@ -3,6 +3,7 @@ import microsim.engine.SimulationEngine; import simpaths.data.IEvaluation; import simpaths.data.Parameters; +import simpaths.model.enums.Dcpst; import java.util.Set; @@ -30,7 +31,7 @@ public PartnershipAlignment(Set persons, double partnershipAdjustment) { this.model = (SimPathsModel) SimulationEngine.getInstance().getManager(SimPathsModel.class.getCanonicalName()); this.persons = persons; this.partnershipAdjustment = partnershipAdjustment; - targetAggregateShareOfPartneredPersons = 0.38; // TODO: List of targets by year should be provided through Excel file policy parameters.xlsx + targetAggregateShareOfPartneredPersons = Parameters.getPartnershipShare(model.getYear()); } /** @@ -48,6 +49,11 @@ public PartnershipAlignment(Set persons, double partnershipAdjustment) { @Override public double evaluate(double[] args) { + model.clearPersonsToMatch(); + persons.stream() + .filter(person -> person.getDag() >= Parameters.MIN_AGE_COHABITATION) + .forEach(person -> person.evaluatePartnershipDissolution()); + adjustPartnerships(args[0]); double error = targetAggregateShareOfPartneredPersons - evalAggregateShareOfPartneredPersons(); @@ -69,7 +75,7 @@ private double evalAggregateShareOfPartneredPersons() { .count(); long numPersonsPartnered = persons.stream() - .filter(Person::hasTestPartner) + .filter(person -> (person.hasTestPartner() || (person.getDcpst().equals(Dcpst.Partnered)) && !person.hasLeftPartnerTest())) .count(); return numPersonsWhoCanHavePartner > 0 @@ -90,7 +96,7 @@ private double evalAggregateShareOfPartneredPersons() { private void adjustPartnerships(double newPartnershipAdjustment) { persons.stream() .filter(person -> person.getDag() >= Parameters.MIN_AGE_COHABITATION) - .forEach(person -> person.evaluatePartnership(newPartnershipAdjustment)); + .forEach(person -> person.evaluatePartnershipFormation(newPartnershipAdjustment)); // "Fake" union matching (not modifying household structure) here model.unionMatching(true); diff --git a/src/main/java/simpaths/model/Person.java b/src/main/java/simpaths/model/Person.java index a58e4e6..7d76337 100644 --- a/src/main/java/simpaths/model/Person.java +++ b/src/main/java/simpaths/model/Person.java @@ -169,7 +169,7 @@ public class Person implements EventListener, IDoubleSource, IIntSource, Weight, @Transient private double drawProvCareHours; @Transient - private double drawPartnership; // Used with the partnership alignment process + private double drawPartnershipFormation, drawPartnershipDissolution; // Used with the partnership alignment process //Sedex is an indicator for leaving education in that year @Enumerated(EnumType.STRING) @@ -193,7 +193,9 @@ public class Person implements EventListener, IDoubleSource, IIntSource, Weight, @Transient private boolean toBePartnered; @Transient - private boolean hasTestPartner; // Used in partnership alignment process. Indicates that this person has found partner in a test run of union matching. + private boolean hasTestPartner; + @Transient + private boolean leftPartnerTest; // Used in partnership alignment process. Indicates that this person has found partner in a test run of union matching. @Transient private Person partner; @Column(name="idpartner") @@ -461,7 +463,8 @@ public Person(Long id) { educationInnov = new Random(SimulationEngine.getRnd().nextLong()); labourSupplyInnov = new Random(SimulationEngine.getRnd().nextLong()); labourSupplySingleDraw = labourSupplyInnov.nextDouble(); - drawPartnership = -9; + drawPartnershipFormation = -9; + drawPartnershipDissolution = -9; } //For use with creating new people at the minimum Age who enter the simulation during UpdateMaternityStatus after fertility has been aligned @@ -1239,25 +1242,75 @@ public void evaluateSocialCareProvide(double probitAdjustment) { } } - public void evaluatePartnership(double probitAdjustment) { + protected void considerCohabitation() { toBePartnered = false; - if (drawPartnership < 0.) { - drawPartnership = cohabitInnov.nextDouble(); + double probitAdjustment = 0.; + if (drawPartnershipFormation < 0.) { + drawPartnershipFormation = cohabitInnov.nextDouble(); + } + if (model.isAlignCohabitation()) { + probitAdjustment = Parameters.getTimeSeriesValue(getYear(), TimeSeriesVariable.PartnershipAdjustment); + } + + if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION) { + if (partner == null) { + if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { + double score = Parameters.getRegPartnershipU1a().getScore(this, Person.DoublesVariables.class); + double prob = Parameters.getRegPartnershipU1a().getProbability(score + probitAdjustment); + toBePartnered = drawPartnershipFormation < prob; + } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { + double score = Parameters.getRegPartnershipU1b().getScore(this, Person.DoublesVariables.class); + double prob = Parameters.getRegPartnershipU1b().getProbability(score + probitAdjustment); + toBePartnered = drawPartnershipFormation < prob; + } + if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { + if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU2b().getProbability(this, Person.DoublesVariables.class)) leavePartner(); + } + } else if (model.getCountry() == Country.IT && dag >= Parameters.MIN_AGE_COHABITATION) { + if (partner == null) { + if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { + toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU1().getProbability(this, Person.DoublesVariables.class)); + if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + } + } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { + if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU2().getProbability(this, Person.DoublesVariables.class)) leavePartner(); + } + } + } + + public void evaluatePartnershipFormation(double probitAdjustment) { + toBePartnered = false; // Reset variable indicating if individual wants to find a partner + hasTestPartner = false; // Reset variable indicating if individual has partner for the purpose of matching + if (drawPartnershipFormation < 0.) { + drawPartnershipFormation = cohabitInnov.nextDouble(); } if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION && partner == null) { if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { double score = Parameters.getRegPartnershipU1a().getScore(this, Person.DoublesVariables.class); double prob = Parameters.getRegPartnershipU1a().getProbability(score + probitAdjustment); - toBePartnered = drawPartnership < prob; + toBePartnered = drawPartnershipFormation < prob; } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { - double score = Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class); + double score = Parameters.getRegPartnershipU1b().getScore(this, Person.DoublesVariables.class); double prob = Parameters.getRegPartnershipU1b().getProbability(score + probitAdjustment); - toBePartnered = drawPartnership < prob; + toBePartnered = drawPartnershipFormation < prob; } + } + + if (toBePartnered) { + model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + } + } - if (toBePartnered) { - model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); + public void evaluatePartnershipDissolution() { + leftPartnerTest = false; + if (drawPartnershipDissolution < 0.) { + drawPartnershipDissolution = cohabitInnov.nextDouble(); + } + if ((partner != null) && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { + if (drawPartnershipDissolution < Parameters.getRegPartnershipU2b().getProbability(this, Person.DoublesVariables.class)) { + setLeftPartnerTest(true); } } } @@ -1320,33 +1373,6 @@ protected void leavingSchool() { } - protected void considerCohabitation() { - toBePartnered = false; - - if (model.getCountry() == Country.UK && dag >= Parameters.MIN_AGE_COHABITATION) { - if (partner == null) { - if (dag <= 29 && les_c4 == Les_c4.Student && !leftEducation) { - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1a().getProbability(this, Person.DoublesVariables.class)); - } else if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU1b().getProbability(this, Person.DoublesVariables.class)); - } - if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); - } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { - if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipU2b().getProbability(this, Person.DoublesVariables.class)) leavePartner(); - } - } else if (model.getCountry() == Country.IT && dag >= Parameters.MIN_AGE_COHABITATION) { - if (partner == null) { - if ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student)) { - toBePartnered = (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU1().getProbability(this, Person.DoublesVariables.class)); - if (toBePartnered) model.getPersonsToMatch().get(dgn).get(getRegion()).add(this); - } - } else if (partner != null && dgn == Gender.Female && ((les_c4 == Les_c4.Student && leftEducation) || !les_c4.equals(Les_c4.Student))) { - if (cohabitInnov.nextDouble() < Parameters.getRegPartnershipITU2().getProbability(this, Person.DoublesVariables.class)) leavePartner(); - } - } - } - - private void giveBirth() { //To be called once per year after fertility alignment if(toGiveBirth) { //toGiveBirth is determined by fertility alignment (in the model class) @@ -4362,4 +4388,12 @@ public void setHasTestPartner(boolean hasTestPartner) { this.hasTestPartner = hasTestPartner; } + public boolean hasLeftPartnerTest() { + return leftPartnerTest; + } + + public void setLeftPartnerTest(boolean leftPartnerTest) { + this.leftPartnerTest = leftPartnerTest; + } + } \ No newline at end of file diff --git a/src/main/java/simpaths/model/SimPathsModel.java b/src/main/java/simpaths/model/SimPathsModel.java index 5f78c68..117995e 100644 --- a/src/main/java/simpaths/model/SimPathsModel.java +++ b/src/main/java/simpaths/model/SimPathsModel.java @@ -456,9 +456,6 @@ public void buildSchedule() { yearlySchedule.addEvent(this, Processes.CohabitationRegressionAlignment); yearlySchedule.addCollectionEvent(persons, Person.Processes.ConsiderCohabitation); - // TODO: new partnership alignment routine should be here. It will adjust the number of people who want to cohabit, perform test union matching, and repeat until satisfactory number of unions has been created. - // Final adjusted probit will be then used to run consider cohabitation for the final time and perform union matching - // C: Union matching yearlySchedule.addEvent(this, Processes.UnionMatching); yearlySchedule.addCollectionEvent(benefitUnits, BenefitUnit.Processes.UpdateOccupancy); @@ -1836,7 +1833,7 @@ private void socialCareMarketClearning() { private void partnershipAlignment() { double partnershipAdjustment = Parameters.getTimeSeriesValue(getYear(), TimeSeriesVariable.PartnershipAdjustment); // Initial values of adjustment to be applied to considerCohabitation probit PartnershipAlignment partnershipAlignment = new PartnershipAlignment(persons, partnershipAdjustment); - RootSearch search = getRootSearch(partnershipAdjustment, partnershipAlignment, 5.0E-2, 5.0E-2); // epsOrdinates and epsFunction determine the stopping condition for the search. For partnershipAlignment error term is the difference between target and observed share of partnered individuals. + RootSearch search = getRootSearch(partnershipAdjustment, partnershipAlignment, 1.0E-2, 1.0E-2); // epsOrdinates and epsFunction determine the stopping condition for the search. For partnershipAlignment error term is the difference between target and observed share of partnered individuals. if (search.isTargetAltered()) { Parameters.putTimeSeriesValue(getYear(), search.getTarget()[0], TimeSeriesVariable.PartnershipAdjustment); // If adjustment is altered from the initial value, update the map System.out.println("Adjustment value was " + search.getTarget()[0]); @@ -3224,6 +3221,10 @@ public void setCollector(SimPathsCollector collector) { this.collector = collector; } + public boolean isAlignCohabitation() { + return alignCohabitation; + } + /** * @@ -3342,7 +3343,7 @@ private static void populateTaxdbReferences() { } } - private void clearPersonsToMatch() { + public void clearPersonsToMatch() { for (Gender gender: Gender.values()) { for (Region region: Region.values()) {