From 20406336c786dad0a5537b72a5cfb885843cf805 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Sat, 21 Feb 2026 22:38:42 +0000 Subject: [PATCH 01/10] Get only the columns necessary from the population dataframe --- src/tlo/methods/labour.py | 42 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index ff4e6f88dd..ab0d10cec3 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1206,7 +1206,42 @@ def predict(self, eq, person_id): """ df = self.sim.population.props mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - person = df.loc[[person_id]] + person = df.loc[[person_id], [ + 'ac_iv_anti_htn_treatment', + 'ac_mag_sulph_treatment', + 'ac_received_abx_for_prom', + 'ac_total_anc_visits_current_pregnancy', + 'age_years', + 'is_alive', + 'la_antepartum_haem', + 'la_eclampsia_treatment', + 'la_has_had_hysterectomy', + 'la_maternal_hypertension_treatment', + 'la_obstructed_labour', + 'la_parity', + 'la_placental_abruption', + 'la_postpartum_haem_treatment', + 'la_previous_cs_delivery', + 'la_sepsis', + 'la_sepsis_treatment', + 'la_uterine_rupture', + 'la_uterine_rupture_treatment', + 'li_bmi', + 'li_ed_lev', + 'li_mar_stat', + 'li_urban', + 'li_wealth', + 'nc_hypertension', + 'pn_htn_disorders', + 'ps_antepartum_haemorrhage', + 'ps_chorioamnionitis', + 'ps_htn_disorders', + 'ps_multiple_pregnancy', + 'ps_placenta_praevia', + 'ps_placental_abruption', + 'ps_premature_rupture_of_membranes', + 'un_HAZ_category', + ]] # We define specific external variables used as predictors in the equations defined below has_rbt = mni[person_id]['received_blood_transfusion'] @@ -2707,7 +2742,7 @@ def __init__(self, module, mother_id): def apply(self, mother_id): df = self.sim.population.props - person = df.loc[mother_id] + person = df.loc[mother_id, ['is_alive', 'la_intrapartum_still_birth', 'ps_multiple_pregnancy', 'is_pregnant']] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.module.current_parameters @@ -2747,6 +2782,7 @@ def apply(self, mother_id): else: self.sim.do_birth(mother_id) + # refresh the reference to the dataframe as the do_birth function might have changed the underlying object df = self.sim.population.props # If the mother survived labour but experienced a stillbirth we reset all the relevant pregnancy variables now @@ -2787,7 +2823,7 @@ def apply(self, mother_id): else: # We use a linear model to determine if women without complications will receive any postnatal care prob_pnc = self.module.la_linear_models['postnatal_check'].predict( - df.loc[[mother_id]], + df.loc[[mother_id], ['age_years', 'li_urban', 'li_wealth', 'la_parity', 'ac_total_anc_visits_current_pregnancy']], mode_of_delivery=pd.Series(mni[mother_id]['mode_of_delivery'], index=df.loc[[mother_id]].index), delivery_setting=pd.Series(mni[mother_id]['delivery_setting'], index=df.loc[[mother_id]].index) )[mother_id] From a7be69354b26f7605f2d8896ad5879b403277591 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 15:55:48 +0000 Subject: [PATCH 02/10] Set list of fields on initialise --- src/tlo/methods/labour.py | 91 +++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index ab0d10cec3..362ea21fa6 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -73,6 +73,9 @@ def __init__(self, name=None): # Finally define a dictionary which will hold the required consumables for each intervention self.item_codes_lab_consumables = dict() + # Stores the list of fields used in linear models equations + self._linear_model_fields = [] + INIT_DEPENDENCIES = {'Demography'} OPTIONAL_INIT_DEPENDENCIES = {'Stunting'} @@ -1066,6 +1069,54 @@ def initialise_simulation(self, sim): pregnancy_helper_functions.scale_linear_model_at_initialisation( self, model=model[0], parameter_key=model[1]) + # Set the person properties required for the models + self._linear_model_fields = [ + # demography + 'age_years', + 'is_alive', + # lifestyle + 'li_bmi', + 'li_ed_lev', + 'li_mar_stat', + 'li_urban', + 'li_wealth', + # care of women + 'ac_iv_anti_htn_treatment', + 'ac_mag_sulph_treatment', + 'ac_received_abx_for_prom', + 'ac_total_anc_visits_current_pregnancy', + # labour + 'la_antepartum_haem', + 'la_eclampsia_treatment', + 'la_has_had_hysterectomy', + 'la_maternal_hypertension_treatment', + 'la_obstructed_labour', + 'la_parity', + 'la_placental_abruption', + 'la_postpartum_haem_treatment', + 'la_previous_cs_delivery', + 'la_sepsis', + 'la_sepsis_treatment', + 'la_uterine_rupture', + 'la_uterine_rupture_treatment', + # postnatal supervisor + 'pn_htn_disorders', + # pregnancy supervisor + 'ps_antepartum_haemorrhage', + 'ps_chorioamnionitis', + 'ps_htn_disorders', + 'ps_multiple_pregnancy', + 'ps_placenta_praevia', + 'ps_placental_abruption', + 'ps_premature_rupture_of_membranes', + ] + + if "CardioMetabolicDisorders" in self.sim.modules: + self._linear_model_fields += ['nc_hypertension'] + + if "Stunting" in self.sim.modules: + self._linear_model_fields += ['un_HAZ_category'] + def on_birth(self, mother_id, child_id): df = self.sim.population.props @@ -1206,42 +1257,10 @@ def predict(self, eq, person_id): """ df = self.sim.population.props mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - person = df.loc[[person_id], [ - 'ac_iv_anti_htn_treatment', - 'ac_mag_sulph_treatment', - 'ac_received_abx_for_prom', - 'ac_total_anc_visits_current_pregnancy', - 'age_years', - 'is_alive', - 'la_antepartum_haem', - 'la_eclampsia_treatment', - 'la_has_had_hysterectomy', - 'la_maternal_hypertension_treatment', - 'la_obstructed_labour', - 'la_parity', - 'la_placental_abruption', - 'la_postpartum_haem_treatment', - 'la_previous_cs_delivery', - 'la_sepsis', - 'la_sepsis_treatment', - 'la_uterine_rupture', - 'la_uterine_rupture_treatment', - 'li_bmi', - 'li_ed_lev', - 'li_mar_stat', - 'li_urban', - 'li_wealth', - 'nc_hypertension', - 'pn_htn_disorders', - 'ps_antepartum_haemorrhage', - 'ps_chorioamnionitis', - 'ps_htn_disorders', - 'ps_multiple_pregnancy', - 'ps_placenta_praevia', - 'ps_placental_abruption', - 'ps_premature_rupture_of_membranes', - 'un_HAZ_category', - ]] + person = df.loc[ + [person_id], + self._linear_model_fields + ] # We define specific external variables used as predictors in the equations defined below has_rbt = mni[person_id]['received_blood_transfusion'] From ed6d21f2a793469cd19b56dd5cd63b92cbc656ca Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 16:09:22 +0000 Subject: [PATCH 03/10] Get mother properties once --- src/tlo/methods/labour.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 362ea21fa6..c354a43ea8 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2841,10 +2841,11 @@ def apply(self, mother_id): else: # We use a linear model to determine if women without complications will receive any postnatal care + mother = df.loc[[mother_id], ['age_years', 'li_urban', 'li_wealth', 'la_parity', 'ac_total_anc_visits_current_pregnancy']] prob_pnc = self.module.la_linear_models['postnatal_check'].predict( - df.loc[[mother_id], ['age_years', 'li_urban', 'li_wealth', 'la_parity', 'ac_total_anc_visits_current_pregnancy']], - mode_of_delivery=pd.Series(mni[mother_id]['mode_of_delivery'], index=df.loc[[mother_id]].index), - delivery_setting=pd.Series(mni[mother_id]['delivery_setting'], index=df.loc[[mother_id]].index) + mother, + mode_of_delivery=pd.Series(mni[mother_id]['mode_of_delivery'], index=mother.index), + delivery_setting=pd.Series(mni[mother_id]['delivery_setting'], index=mother.index) )[mother_id] has_comps = False From bb707f4d3055a9463d47617b8478a8da2650166b Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 16:16:48 +0000 Subject: [PATCH 04/10] Fix line too long --- src/tlo/methods/labour.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index c354a43ea8..07a3619045 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2841,7 +2841,10 @@ def apply(self, mother_id): else: # We use a linear model to determine if women without complications will receive any postnatal care - mother = df.loc[[mother_id], ['age_years', 'li_urban', 'li_wealth', 'la_parity', 'ac_total_anc_visits_current_pregnancy']] + mother = df.loc[ + [mother_id], + ['age_years', 'li_urban', 'li_wealth', 'la_parity', 'ac_total_anc_visits_current_pregnancy'] + ] prob_pnc = self.module.la_linear_models['postnatal_check'].predict( mother, mode_of_delivery=pd.Series(mni[mother_id]['mode_of_delivery'], index=mother.index), From 9f359454cd735330ce2d602d68c669848539ab4c Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 22:08:19 +0000 Subject: [PATCH 05/10] Get specific properties for the lm calls --- src/tlo/methods/labour.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 07a3619045..06b070eaed 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1226,7 +1226,7 @@ def set_date_of_labour(self, individual_id): # we determine if she will go into labour post term (42+ weeks) if self.rng.random_sample() < self.la_linear_models['post_term_labour'].predict( - df.loc[[individual_id]])[individual_id]: + df.loc[[individual_id], 'li_bmi'])[individual_id]: df.at[individual_id, 'la_due_date_current_pregnancy'] = \ (df.at[individual_id, 'date_of_last_pregnancy'] + pd.DateOffset( @@ -2508,12 +2508,16 @@ def apply(self, individual_id): # been admitted antenatally for delivery will be delivering in hospital and that is scheduled accordingly if df.at[individual_id, 'ac_admitted_for_immediate_delivery'] == 'none': + individual = df.loc[ + [individual_id], + ['age_years', 'li_urban', 'la_parity', 'li_ed_lev', 'li_wealth', 'li_mar_stat'] + ] # Here we calculate this womans predicted risk of home birth and health centre birth pred_hb_delivery = self.module.la_linear_models['probability_delivery_at_home'].predict( - df.loc[[individual_id]])[individual_id] + individual)[individual_id] pred_hc_delivery = self.module.la_linear_models['probability_delivery_health_centre'].predict( - df.loc[[individual_id]])[individual_id] + individual)[individual_id] pred_hp_delivery = params['probability_delivery_hospital'] # The denominator is calculated From b6f63d5a25b2a721a252f343f4271750dd004879 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 22:19:35 +0000 Subject: [PATCH 06/10] Change access so it returns a dataframe --- src/tlo/methods/labour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 06b070eaed..41101026e1 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1226,7 +1226,7 @@ def set_date_of_labour(self, individual_id): # we determine if she will go into labour post term (42+ weeks) if self.rng.random_sample() < self.la_linear_models['post_term_labour'].predict( - df.loc[[individual_id], 'li_bmi'])[individual_id]: + df.loc[[individual_id], ['li_bmi']])[individual_id]: df.at[individual_id, 'la_due_date_current_pregnancy'] = \ (df.at[individual_id, 'date_of_last_pregnancy'] + pd.DateOffset( From 8699d2cfdb4c5e9a7aac6c7946e4746ba260f318 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Mon, 23 Feb 2026 22:21:44 +0000 Subject: [PATCH 07/10] Get only those properties required for method --- src/tlo/methods/labour.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 41101026e1..76f31ae356 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1303,7 +1303,11 @@ def check_labour_can_proceed(self, individual_id): :returns True/False if labour can proceed """ df = self.sim.population.props - person = df.loc[individual_id] + person = df.loc[ + individual_id, + ['is_alive', 'is_pregnant', 'la_currently_in_labour', 'la_due_date_current_pregnancy', + 'ac_admitted_for_immediate_delivery', 'ps_gestational_age_in_weeks'] + ] # If the mother has died OR has lost her pregnancy OR is already in labour then the labour events wont run if not person.is_alive or not person.is_pregnant or person.la_currently_in_labour: @@ -1313,8 +1317,7 @@ def check_labour_can_proceed(self, individual_id): return False # If she is alive, pregnant, not in labour AND her due date is today then the event will run - if person.is_alive and person.is_pregnant and (person.la_due_date_current_pregnancy == self.sim.date) \ - and not person.la_currently_in_labour: + if person.la_due_date_current_pregnancy == self.sim.date: # If the woman in not currently an inpatient then we assume this is her normal labour if person.ac_admitted_for_immediate_delivery == 'none': @@ -1332,11 +1335,9 @@ def check_labour_can_proceed(self, individual_id): f'at gestation {person.ps_gestational_age_in_weeks}') return True - # If she is alive, pregnant, not in labour BUT her due date is not today, however shes been admitted then we + # If she is alive, pregnant, not in labour BUT her due date is not today, however she's been admitted then we # labour can progress as she requires early delivery - if person.is_alive and person.is_pregnant and not person.la_currently_in_labour and \ - (person.la_due_date_current_pregnancy != self.sim.date) and (person.ac_admitted_for_immediate_delivery != - 'none'): + if person.ac_admitted_for_immediate_delivery != 'none': logger.debug(key='message', data=f'person {individual_id} has just reached LabourOnsetEvent on ' f'{self.sim.date}- they have been admitted for delivery due to ' f'complications in the antenatal period and will now progress into the ' From 26621b9be1655b5d58216d43b4796378ed21aaf5 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Wed, 25 Feb 2026 22:37:45 +0000 Subject: [PATCH 08/10] Make known the person properties needed for the LM predict function - the decorator allows us to list none or more properties needed to evaluate the function - does not change the logic inside the function, but is a bit of metadata that can be used by callers --- src/tlo/methods/labour_lm.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/tlo/methods/labour_lm.py b/src/tlo/methods/labour_lm.py index 83c2360706..f9700e1ba8 100644 --- a/src/tlo/methods/labour_lm.py +++ b/src/tlo/methods/labour_lm.py @@ -23,6 +23,16 @@ def predict_for_dataframe(self, df, rng=None, **externals): import pandas as pd +def person_properties(keys): + """Decorator to specify the person's properties needed to evaluate the function""" + keys_t = tuple(keys) + def decorator(func): + func.person_properties = keys_t + return func + return decorator + + +@person_properties(['li_bmi']) def predict_post_term_labour(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of post term labour. Risk is increased in @@ -40,6 +50,7 @@ def predict_post_term_labour(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['age_years', 'li_mar_stat', 'li_wealth', 'li_ed_lev', 'li_urban']) def predict_parity(self, df, rng=None, **externals): """ Population level linear model (additive) which returns a df containing the predicted parity (previous number of @@ -70,6 +81,7 @@ def predict_parity(self, df, rng=None, **externals): return result +@person_properties(['un_HAZ_category']) def predict_obstruction_cpd_ip(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing obstructed labour @@ -91,6 +103,7 @@ def predict_obstruction_cpd_ip(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['ps_premature_rupture_of_membranes', 'ac_received_abx_for_prom']) def predict_sepsis_chorioamnionitis_ip(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing sepsis secondary @@ -111,6 +124,7 @@ def predict_sepsis_chorioamnionitis_ip(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties([]) def predict_sepsis_endometritis_pp(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing sepsis secondary @@ -128,6 +142,7 @@ def predict_sepsis_endometritis_pp(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties([]) def predict_sepsis_skin_soft_tissue_pp(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing sepsis secondary @@ -145,6 +160,7 @@ def predict_sepsis_skin_soft_tissue_pp(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties([]) def predict_sepsis_urinary_tract_pp(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing sepsis secondary @@ -160,6 +176,7 @@ def predict_sepsis_urinary_tract_pp(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_sepsis_treatment']) def predict_sepsis_death(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of death due to postpartum sepsis. @@ -175,6 +192,8 @@ def predict_sepsis_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_eclampsia_treatment', 'ac_mag_sulph_treatment', 'la_maternal_hypertension_treatment', + 'ac_iv_anti_htn_treatment']) def predict_eclampsia_death(self, df, rng=None, **externals): """ This is an individual level linear model which predicts an individuals probability of death due to eclampsia. @@ -195,6 +214,7 @@ def predict_eclampsia_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_maternal_hypertension_treatment', 'ac_iv_anti_htn_treatment']) def predict_severe_pre_eclamp_death(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of death due to severe @@ -211,6 +231,7 @@ def predict_severe_pre_eclamp_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_previous_cs_delivery', 'ps_htn_disorders']) def predict_placental_abruption_ip(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing placental @@ -230,6 +251,7 @@ def predict_placental_abruption_ip(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['ps_placenta_praevia', 'ps_placental_abruption', 'la_placental_abruption']) def predict_antepartum_haem_ip(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing an antepartum @@ -249,6 +271,7 @@ def predict_antepartum_haem_ip(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties([]) def predict_antepartum_haem_death(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of death due to antepartum @@ -268,6 +291,8 @@ def predict_antepartum_haem_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['pn_htn_disorders', 'nc_hypertension', 'ps_multiple_pregnancy', 'la_placental_abruption', + 'ps_placental_abruption']) def predict_pph_uterine_atony_pp(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing a postpartum haemorrhage due @@ -298,6 +323,7 @@ def predict_pph_uterine_atony_pp(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties([]) def predict_pph_retained_placenta_pp(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of developing a postpartum @@ -313,6 +339,7 @@ def predict_pph_retained_placenta_pp(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_postpartum_haem_treatment']) def predict_postpartum_haem_pp_death(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of death following a postpartum @@ -333,6 +360,7 @@ def predict_postpartum_haem_pp_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['la_parity', 'la_previous_cs_delivery', 'la_obstructed_labour']) def predict_uterine_rupture_ip(self, df, rng=None, **externals): """ Population level linear model to allow for the model to be scaled at initialisation of the simulation. The model @@ -352,6 +380,7 @@ def predict_uterine_rupture_ip(self, df, rng=None, **externals): return result +@person_properties(['la_uterine_rupture_treatment', 'la_has_had_hysterectomy']) def predict_uterine_rupture_death(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of death following a uterine @@ -371,6 +400,9 @@ def predict_uterine_rupture_death(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['is_alive', 'la_uterine_rupture', 'la_obstructed_labour', 'la_antepartum_haem', + 'ps_antepartum_haemorrhage', 'ps_htn_disorders', 'la_sepsis', 'ps_chorioamnionitis', + 'ps_multiple_pregnancy']) def predict_intrapartum_still_birth(self, df, rng=None, **externals): """ Individual level linear model which predicts an individuals probability of experiencing an intrapartum @@ -410,6 +442,7 @@ def predict_intrapartum_still_birth(self, df, rng=None, **externals): return pd.Series(data=[result], index=df.index) +@person_properties(['age_years', 'li_wealth', 'la_parity', 'li_mar_stat', 'li_urban']) def predict_probability_delivery_health_centre(self, df, rng=None, **externals): """ Population level to allow for scaling at the initialisation of the simulation. This model predicts an @@ -444,6 +477,7 @@ def predict_probability_delivery_health_centre(self, df, rng=None, **externals): return result +@person_properties(['age_years', 'li_urban', 'la_parity', 'li_ed_lev', 'li_wealth', 'li_mar_stat']) def predict_probability_delivery_at_home(self, df, rng=None, **externals): """ Population level to allow for scaling at the initialisation of the simulation. This model predicts an @@ -481,6 +515,7 @@ def predict_probability_delivery_at_home(self, df, rng=None, **externals): return result +@person_properties(['age_years', 'li_urban', 'la_parity', 'li_wealth', 'ac_total_anc_visits_current_pregnancy']) def predict_postnatal_check(self, df, rng=None, **externals): """ Population level to allow for scaling at the initialisation of the simulation. This model predicts an From 4a704a1275385289ec538a56c2927efa8ebfc1f3 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Fri, 27 Feb 2026 22:09:32 +0000 Subject: [PATCH 09/10] Only get properties explicitly stated to run the linear model function --- src/tlo/methods/labour.py | 74 +++++++++++++-------------------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 76f31ae356..fff8433764 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -73,8 +73,6 @@ def __init__(self, name=None): # Finally define a dictionary which will hold the required consumables for each intervention self.item_codes_lab_consumables = dict() - # Stores the list of fields used in linear models equations - self._linear_model_fields = [] INIT_DEPENDENCIES = {'Demography'} @@ -1069,53 +1067,21 @@ def initialise_simulation(self, sim): pregnancy_helper_functions.scale_linear_model_at_initialisation( self, model=model[0], parameter_key=model[1]) - # Set the person properties required for the models - self._linear_model_fields = [ - # demography - 'age_years', - 'is_alive', - # lifestyle - 'li_bmi', - 'li_ed_lev', - 'li_mar_stat', - 'li_urban', - 'li_wealth', - # care of women - 'ac_iv_anti_htn_treatment', - 'ac_mag_sulph_treatment', - 'ac_received_abx_for_prom', - 'ac_total_anc_visits_current_pregnancy', - # labour - 'la_antepartum_haem', - 'la_eclampsia_treatment', - 'la_has_had_hysterectomy', - 'la_maternal_hypertension_treatment', - 'la_obstructed_labour', - 'la_parity', - 'la_placental_abruption', - 'la_postpartum_haem_treatment', - 'la_previous_cs_delivery', - 'la_sepsis', - 'la_sepsis_treatment', - 'la_uterine_rupture', - 'la_uterine_rupture_treatment', - # postnatal supervisor - 'pn_htn_disorders', - # pregnancy supervisor - 'ps_antepartum_haemorrhage', - 'ps_chorioamnionitis', - 'ps_htn_disorders', - 'ps_multiple_pregnancy', - 'ps_placenta_praevia', - 'ps_placental_abruption', - 'ps_premature_rupture_of_membranes', - ] - - if "CardioMetabolicDisorders" in self.sim.modules: - self._linear_model_fields += ['nc_hypertension'] - - if "Stunting" in self.sim.modules: - self._linear_model_fields += ['un_HAZ_category'] + def unregister_properties_from_models(module_name, property_name): + """If `module_name` is not registered in sim, then remove `property_name` from linear model's properties""" + # if module is not registered + if module_name not in self.sim.modules: + # iterate over each of the linear models + for name, lm_model in self.la_linear_models.items(): + # if the `person_properties` lists the `property_name` + if property_name in lm_model.predict.person_properties: + # remove it from the list of properties used by the linear model + properties = list(lm_model.predict.person_properties) + properties.remove(property_name) + lm_model.predict.__func__.person_properties = tuple(properties) + + unregister_properties_from_models("CardioMetabolicDisorders", 'nc_hypertension') + unregister_properties_from_models("Stunting", 'un_HAZ_category') def on_birth(self, mother_id, child_id): df = self.sim.population.props @@ -1257,9 +1223,17 @@ def predict(self, eq, person_id): """ df = self.sim.population.props mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info + + # we only need those properties required to evaluate the function + person_properties = eq.predict.person_properties + + if not person_properties: + # we do need to pass a valid dataframe, despite not actually needing any properties :( + person_properties = ['is_alive'] + person = df.loc[ [person_id], - self._linear_model_fields + person_properties ] # We define specific external variables used as predictors in the equations defined below From 37373b264782f4de0c53330d14c6b76f954464f4 Mon Sep 17 00:00:00 2001 From: Asif Tamuri Date: Fri, 27 Feb 2026 23:59:23 +0000 Subject: [PATCH 10/10] Don't modify the original `person_properties` on the function because then new simulations in the same process may fail. --- src/tlo/methods/labour.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index fff8433764..0f07377e52 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1067,22 +1067,6 @@ def initialise_simulation(self, sim): pregnancy_helper_functions.scale_linear_model_at_initialisation( self, model=model[0], parameter_key=model[1]) - def unregister_properties_from_models(module_name, property_name): - """If `module_name` is not registered in sim, then remove `property_name` from linear model's properties""" - # if module is not registered - if module_name not in self.sim.modules: - # iterate over each of the linear models - for name, lm_model in self.la_linear_models.items(): - # if the `person_properties` lists the `property_name` - if property_name in lm_model.predict.person_properties: - # remove it from the list of properties used by the linear model - properties = list(lm_model.predict.person_properties) - properties.remove(property_name) - lm_model.predict.__func__.person_properties = tuple(properties) - - unregister_properties_from_models("CardioMetabolicDisorders", 'nc_hypertension') - unregister_properties_from_models("Stunting", 'un_HAZ_category') - def on_birth(self, mother_id, child_id): df = self.sim.population.props @@ -1227,6 +1211,16 @@ def predict(self, eq, person_id): # we only need those properties required to evaluate the function person_properties = eq.predict.person_properties + # if the simulation is running without stunting or cardio metabolic disorders modules + # then we need to remove the properties + if "Stunting" not in self.sim.modules and "un_HAZ_category" in person_properties: + person_properties = list(person_properties) + person_properties.remove("un_HAZ_category") + + if "CardioMetabolicDisorders" not in self.sim.modules and "nc_hypertension" in person_properties: + person_properties = list(person_properties) + person_properties.remove("nc_hypertension") + if not person_properties: # we do need to pass a valid dataframe, despite not actually needing any properties :( person_properties = ['is_alive']