Skip to content

zambia

Zambia

Bases: Country

Zambia country model class. This class is a subclass of the Country class and implements the build_campaign method to build campaign object to be used with EMODTask.

Examples:

from emodpy_hiv.countries.zambia import Zambia
from emodpy.emod_task import EMODTask
import manifest
from functools import partial
# Define a campaign_builder function to pass in the required arguments for the Zambia.build_campaign() method.
def campaign_builder():
    return Zambia.build_campaign(schema_path=manifest.schema_file, base_year=Zambia.base_year)
# Alternatively, you can use the partial function to pass in the required arguments for the
# Zambia.build_campaign() method. For example:
# campaign_builder = partial(Zambia.build_campaign, schema_path=manifest.schema_file, base_year=Zambia.base_year)
# Create an EMODTask object using the campaign_builder function
task = EMODTask.from_defaults(config_path="config.json",
                              eradication_path=manifest.eradication_path,
                              campaign_builder=campaign_builder, ...)
Source code in emodpy_hiv/countries/zambia/zambia.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
class Zambia(Country):
    """
    Zambia country model class. This class is a subclass of the Country class and implements the build_campaign method
    to build campaign object to be used with EMODTask.

    Examples:
        ```
        from emodpy_hiv.countries.zambia import Zambia
        from emodpy.emod_task import EMODTask
        import manifest
        from functools import partial
        # Define a campaign_builder function to pass in the required arguments for the Zambia.build_campaign() method.
        def campaign_builder():
            return Zambia.build_campaign(schema_path=manifest.schema_file, base_year=Zambia.base_year)
        # Alternatively, you can use the partial function to pass in the required arguments for the
        # Zambia.build_campaign() method. For example:
        # campaign_builder = partial(Zambia.build_campaign, schema_path=manifest.schema_file, base_year=Zambia.base_year)
        # Create an EMODTask object using the campaign_builder function
        task = EMODTask.from_defaults(config_path="config.json",
                                      eradication_path=manifest.eradication_path,
                                      campaign_builder=campaign_builder, ...)
        ```
    """

    country_name = "Zambia"
    _historical_vmmc_data_file = resources.files(zambia_data).joinpath("historical_vmmc_data.csv")
    _inital_demog_cache = None

    @classmethod
    def initialize_config(cls, schema_path: Union[str, Path]) -> ReadOnlyDict:
        """
        Build the Zambia-calibrated EMOD configuration.

        Sets all HIV model parameters calibrated for Zambia: disease progression, infectivity,
        sexual debut, relationship formation, and memory thresholds. Override in a subclass to
        adjust specific parameters after calling super().

        Args:
            schema_path: Path to the EMOD schema file.

        Returns:
            ReadOnlyDict: Populated EMOD configuration object.
        """
        config = super().initialize_config(schema_path=schema_path)

        config.parameters.Simulation_Type = "HIV_SIM"
        config.parameters.Simulation_Duration = 99.5 * 365
        config.parameters.Simulation_Timestep = 30.4166666666667

        # This is the current "production scale" population scaling factor for Zambia the model is calibrated to
        config.parameters.x_Base_Population = 0.05
        # x_Base_Population = 0.05 needs more than 8GB memory, so we're giving it here
        config.parameters.Memory_Usage_Halting_Threshold_Working_Set_MB = 12000
        config.parameters.Memory_Usage_Warning_Threshold_Working_Set_MB = 11000

        config.parameters.Start_Time = 0
        config.parameters.AIDS_Duration_In_Months = 9
        config.parameters.AIDS_Stage_Infectivity_Multiplier = 4.5
        config.parameters.Acute_Duration_In_Months = 2.9
        config.parameters.Acute_Stage_Infectivity_Multiplier = 26
        config.parameters.Age_Initialization_Distribution_Type = "DISTRIBUTION_COMPLEX"
        config.parameters.Base_Individual_Sample_Rate = 1
        config.parameters.Base_Infectivity = 0.00065805
        config.parameters.Base_Year = 1960.5
        config.parameters.Birth_Rate_Dependence = "INDIVIDUAL_PREGNANCIES_BY_AGE_AND_YEAR"
        config.parameters.Birth_Rate_Time_Dependence = "NONE"
        config.parameters.CD4_At_Death_LogLogistic_Heterogeneity = 0.7
        config.parameters.CD4_At_Death_LogLogistic_Scale = 2.96
        config.parameters.CD4_Post_Infection_Weibull_Heterogeneity = 0.2756
        config.parameters.CD4_Post_Infection_Weibull_Scale = 560.43
        config.parameters.Coital_Dilution_Factor_2_Partners = 0.75
        config.parameters.Coital_Dilution_Factor_3_Partners = 0.6
        config.parameters.Coital_Dilution_Factor_4_Plus_Partners = 0.45
        config.parameters.Condom_Transmission_Blocking_Probability = 0.8
        config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Heterogeneity = 0.5
        config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Scale = 618.341625
        config.parameters.Death_Rate_Dependence = "NONDISEASE_MORTALITY_BY_YEAR_AND_AGE_FOR_EACH_GENDER"
        config.parameters.Enable_Aging = 1
        config.parameters.Enable_Birth = 1
        config.parameters.Enable_Coital_Dilution = 1
        config.parameters.Enable_Demographics_Reporting = 0  # Demographics reporting is on by default, turning it off
        config.parameters.Enable_Infectivity_Reservoir = 0
        config.parameters.Enable_Maternal_Protection = 0
        config.parameters.Enable_Natural_Mortality = 1
        config.parameters.HIV_Adult_Survival_Scale_Parameter_Intercept = 21.182
        config.parameters.HIV_Adult_Survival_Scale_Parameter_Slope = -0.2717
        config.parameters.HIV_Adult_Survival_Shape_Parameter = 2
        config.parameters.HIV_Age_Max_for_Adult_Age_Dependent_Survival = 50
        config.parameters.HIV_Age_Max_for_Child_Survival_Function = 15
        config.parameters.HIV_Child_Survival_Rapid_Progressor_Fraction = 0.57
        config.parameters.HIV_Child_Survival_Rapid_Progressor_Rate = 1.52
        config.parameters.HIV_Child_Survival_Slow_Progressor_Scale = 16
        config.parameters.HIV_Child_Survival_Slow_Progressor_Shape = 2.7
        config.parameters.Heterogeneous_Infectiousness_LogNormal_Scale = 0
        config.parameters.Incubation_Period_Constant = 0
        config.parameters.Incubation_Period_Distribution = "CONSTANT_DISTRIBUTION"
        config.parameters.Individual_Sampling_Type = "FIXED_SAMPLING"
        config.parameters.Infection_Updates_Per_Timestep = 1
        config.parameters.Male_To_Female_Relative_Infectivity_Ages = [0, 15, 25]
        config.parameters.Male_To_Female_Relative_Infectivity_Multipliers = [
            2.26154624,
            2.26154624,
            1.112477558
        ]
        config.parameters.Maternal_Infection_Transmission_Probability = 0.3
        config.parameters.Maternal_Transmission_ART_Multiplier = 0.03334
        config.parameters.Migration_Model = "NO_MIGRATION"
        config.parameters.Min_Days_Between_Adding_Relationships = 0
        config.parameters.Node_Grid_Size = 0.009
        config.parameters.PFA_Burnin_Duration_In_Days = 5475
        config.parameters.PFA_Cum_Prob_Selection_Threshold = 0.2
        config.parameters.Population_Density_Infectivity_Correction = "CONSTANT_INFECTIVITY"
        config.parameters.Population_Scale_Type = "FIXED_SCALING"
        config.parameters.Run_Number = 1
        config.parameters.STI_Coinfection_Acquisition_Multiplier = 5.5
        config.parameters.STI_Coinfection_Transmission_Multiplier = 5.5
        config.parameters.Serialized_Population_Reading_Type = "NONE"
        config.parameters.Serialized_Population_Writing_Type = "NONE"
        config.parameters.Sexual_Debut_Age_Setting_Type = "WEIBULL"
        config.parameters.Sexual_Debut_Age_Female_Weibull_Heterogeneity = 0.22108015674142
        config.parameters.Sexual_Debut_Age_Female_Weibull_Scale = 19.9609856656853
        config.parameters.Sexual_Debut_Age_Male_Weibull_Heterogeneity = 0.165108885953777
        config.parameters.Sexual_Debut_Age_Male_Weibull_Scale = 16.8039276633964
        config.parameters.Sexual_Debut_Age_Min = 13

        config.parameters["logLevel_InfectionHIV"] = "ERROR"
        config.parameters["logLevel_Instrumentation"] = "INFO"
        config.parameters["logLevel_Memory"] = "INFO"
        config.parameters["logLevel_OutbreakIndividual"] = "ERROR"
        config.parameters["logLevel_Simulation"] = "INFO"
        config.parameters["logLevel_SusceptibilityHIV"] = "ERROR"
        config.parameters["logLevel_AntiretroviralTherapy"] = "ERROR"
        config.parameters["logLevel_default"] = "WARNING"

        return config

    @classmethod
    def initialize_campaign(cls, schema_path: Union[str, Path]) -> emod_api.campaign:
        """
        Initialize an empty campaign object with the correct schema.

        Args:
            schema_path: Path to the EMOD schema file.

        Returns:
            emod_api.campaign: Empty campaign object ready for intervention additions.
        """
        campaign = super().initialize_campaign(schema_path=schema_path)
        return campaign

    #
    # Campaign building below
    #

    @classmethod
    def add_male_circumcision_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for all male circumcision interventions.

        Includes three components: historical VMMC NChooser events (from calibrated historical
        data), traditional male circumcision at province-level coverages, and VMMC reference
        tracking.

        Returns:
            List[ParameterizedCall]: ParameterizedCalls for male circumcision interventions.
        """
        parameterized_calls = []

        # ---- Add historical vmmc NChooser events to campaign ----
        non_hp = {'historical_vmmc_data_filepath': cls._historical_vmmc_data_file}
        hp = {'historical_vmmc_reduced_acquire': None}
        pc = ParameterizedCall(func=cls.add_historical_vmmc_nchooser, non_hyperparameters=non_hp, hyperparameters=hp)
        parameterized_calls.append(pc)

        # ---- Add traditional male circumcision to campaign ----
        traditional_male_circumcision_coverage_by_node = {
            1: 0.054978651,
            2: 0.139462861,
            3: 0.028676043,
            4: 0.091349358,
            5: 0.12318707,
            6: 0.039308099,
            7: 0.727917322,
            8: 0.041105263,
            9: 0.044388102,
            10: 0.398239794
        }

        for node_id, coverage in traditional_male_circumcision_coverage_by_node.items():
            hp = {
                'traditional_male_circumcision_coverage': coverage,
                'traditional_male_circumcision_reduced_acquire': None
            }
            pc = ParameterizedCall(func=cls.add_traditional_male_circumcision,
                                   non_hyperparameters={'node_ids': [node_id]},
                                   hyperparameters=hp,
                                   label=NODE_ID_TO_NODE_NAME[node_id])
            parameterized_calls.append(pc)

        # ---- Add (VMMC) reference tracking to campaign ----
        pc = ParameterizedCall(func=cls.add_vmmc_reference_tracking,
                               hyperparameters={'tracking_vmmc_reduced_acquire': None})
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_pmtct_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for prevention of mother-to-child transmission (PMTCT).

        Returns:
            List[ParameterizedCall]: ParameterizedCalls covering the TestingOnANC and
                TestingOnChild6w cascade states.
        """
        parameterized_calls = []

        pc = ParameterizedCall(func=cls.add_pmtct)
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_seed_infections_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for seeding HIV infections at calibrated node-level start years.

        Each node receives an OutbreakIndividual intervention with a calibrated start year and
        coverage drawn from historical epidemic data for Zambia.

        Returns:
            List[ParameterizedCall]: One ParameterizedCall per Zambia province node.
        """
        seeding_data = []
        seeding_data.append({"node_id":  1, "start_year": 1977.96490840667, "coverage": 0.00994069137813979}) # noqa: E241
        seeding_data.append({"node_id":  2, "start_year": 1981.21526571588, "coverage": 0.0290879518815339}) # noqa: E241, E202
        seeding_data.append({"node_id":  3, "start_year": 1987.89402149164, "coverage": 0.11194470109949}) # noqa: E241
        seeding_data.append({"node_id":  4, "start_year": 1970.14036061384, "coverage": 0.0222773645719442}) # noqa: E241
        seeding_data.append({"node_id":  5, "start_year": 1970.36530180447, "coverage": 0.35030772723087}) # noqa: E241, E202
        seeding_data.append({"node_id":  6, "start_year": 1984.22541975358, "coverage": 0.0167949011180618}) # noqa: E241, E202
        seeding_data.append({"node_id":  7, "start_year": 1980.54040493867, "coverage": 0.0930776686478119}) # noqa: E241
        seeding_data.append({"node_id":  8, "start_year": 1979.51682308393, "coverage": 0.0099968647453273}) # noqa: E241
        seeding_data.append({"node_id":  9, "start_year": 1984.36462983067, "coverage": 0.0731563905879651}) # noqa: E241
        seeding_data.append({"node_id": 10, "start_year": 1982.02918538299, "coverage": 0.267106173241476})

        parameterized_calls = []
        for data in seeding_data:
            hp = {'seeding_start_year': data["start_year"], 'seeding_coverage': data["coverage"]}
            nhp = {'node_ids': [data["node_id"]]}
            pc = ParameterizedCall(func=cls.seed_infections,
                                   non_hyperparameters=nhp,
                                   hyperparameters=hp,
                                   label=NODE_ID_TO_NODE_NAME[data["node_id"]])
            parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_csw_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for commercial sex worker (CSW) uptake and dropout.

        Splits nodes into two groups with different male and female uptake coverages,
        reflecting regional variation across Zambia.

        Returns:
            List[ParameterizedCall]: ParameterizedCalls for CSW dynamics across node groups.
        """
        parameterized_calls = []

        pc = ParameterizedCall(func=cls.add_csw,
                               non_hyperparameters={'node_ids': [1, 2, 3, 7, 9]},
                               hyperparameters={'csw_male_uptake_coverage': 0.195, 'csw_female_uptake_coverage': 0.125},
                               label='nodeset1')
        parameterized_calls.append(pc)

        pc = ParameterizedCall(func=cls.add_csw,
                               non_hyperparameters={'node_ids': [4, 5, 6, 8, 10]},
                               hyperparameters={'csw_male_uptake_coverage': 0.0781, 'csw_female_uptake_coverage': 0.05},
                               label='nodeset2')
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_post_debut_coinfection_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for STI co-infections post sexual debut.

        Coverage is split by risk group (HIGH, MEDIUM, LOW) and treated as a hyperparameter
        for calibration.

        Returns:
            List[ParameterizedCall]: ParameterizedCalls for post-debut co-infection.
        """
        parameterized_calls = []

        hp = {'coinfection_coverage_HIGH': None, 'coinfection_coverage_MEDIUM': None, 'coinfection_coverage_LOW': None}
        pc = ParameterizedCall(func=cls.add_post_debut_coinfection, hyperparameters=hp)
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_health_care_testing_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for the health care testing (HCT) loop.

        Configures HCT with node-specific delays between tests, covering the HCTUptakeAtDebut,
        HCTUptakePostDebut, and HCTTestingLoop cascade states.

        Returns:
            List[ParameterizedCall]: ParameterizedCalls for the HCT testing loop.
        """
        parameterized_calls = []
        non_hyperparameters = {
            'hct_delay_to_next_test': [730, 365, 1100],
            'hct_delay_to_next_test_node_ids': [[1, 2, 3, 4, 6, 7], [5, 9, 10], [8]],
            'hct_delay_to_next_test_node_names': ['Default', 'Lusaka, Southern, Western', 'Northern']
        }
        pc = ParameterizedCall(func=cls.add_health_care_testing,
                               non_hyperparameters=non_hyperparameters,
                               hyperparameters={'hct_start_year': None})
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def add_ART_cascade_parameterized_calls(cls) -> List[ParameterizedCall]:
        """
        Return ParameterizedCalls for the full ART cascade.

        Covers eight cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging,
        LinkingToPreART, OnPreART, LinkingToART, OnART, and LostForever. Sigmoid linkage
        parameters and retention rates are treated as hyperparameters for calibration.

        Returns:
            List[ParameterizedCall]: ParameterizedCalls for the ART cascade.
        """
        parameterized_calls = []

        hp = {
            'art_cascade_start_year': None,
            'cd4_retention_rate': None,
            'linking_to_pre_art_sigmoid_min': 0.824382156,
            'linking_to_pre_art_sigmoid_max': 0.959334475,
            'linking_to_pre_art_sigmoid_midyear': 2008.506768,
            'linking_to_pre_art_sigmoid_rate': None,
            'pre_staging_retention': None,
            'pre_art_retention': None,
            'linking_to_art_sigmoid_min': None,
            'linking_to_art_sigmoid_max': 0.90041509,
            'linking_to_art_sigmoid_midyear': 2001.053678,
            'linking_to_art_sigmoid_rate': None,
            'art_reenrollment_willingness': None,
            'immediate_art_rate': None
        }
        pc = ParameterizedCall(func=cls.add_ART_cascade, hyperparameters=hp)
        parameterized_calls.append(pc)

        return parameterized_calls

    @classmethod
    def get_campaign_parameterized_calls(cls, campaign: emod_api.campaign) -> List[ParameterizedCall]:
        """
        Assemble all campaign ParameterizedCalls for the Zambia model.

        Calls each `add_*_parameterized_calls` method in order and returns the combined list.
        Override individual `add_*` methods in a subclass to replace specific components, or
        override this method to reorder or exclude components entirely.

        Args:
            campaign: Empty campaign object passed through to each component.

        Returns:
            List[ParameterizedCall]: All ParameterizedCalls for the Zambia campaign.
        """
        parameterized_calls = super().get_campaign_parameterized_calls(campaign=campaign)

        # ---- Add male circumcision to campaign -----
        # This block contains 3 chunk of events: historical vmmc NChooser, traditional male circumcision and VMMC with
        # reference tracking.
        parameterized_calls.extend(cls.add_male_circumcision_parameterized_calls())
        # ---- Add prevention of mother-to-child transmission (PMTCT) to campaign ----
        # This pmtct block contains 2 Cascade states: TestingOnANC and TestingOnChild6w
        parameterized_calls.extend(cls.add_pmtct_parameterized_calls())
        # ---- Add OutbreakIndividual interventions to campaign ----
        parameterized_calls.extend(cls.add_seed_infections_parameterized_calls())
        # ---- Add commercial sex worker (CSW) uptake and dropout (with delays) for men and women to campaign ----
        parameterized_calls.extend(cls.add_csw_parameterized_calls())
        # ---- Add co-infections post sexual debut to campaign ----
        parameterized_calls.extend(cls.add_post_debut_coinfection_parameterized_calls())
        # ---- Add health care testing loops to campaign ----
        # This health care testing block contains 3 Cascade states: HCTUptakeAtDebut, HCTUptakePostDebut and
        # HCTTestingLoop
        parameterized_calls.extend(cls.add_health_care_testing_parameterized_calls())
        # ---- Add Antiretroviral Therapy (ART) Cascade to campaign ----
        # This ART Cascade block contains 8 Cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging
        # LinkingToPreART, OnPreART, LinkingToART, OnART and LostForever
        parameterized_calls.extend(cls.add_ART_cascade_parameterized_calls())

        return parameterized_calls

    #
    # Demographics building below
    #

    @classmethod
    def initialize_demographics(cls) -> HIVDemographics:
        """
        Build the Zambia demographics object.

        Constructs a multi-node HIVDemographics with 10 provincial nodes, UN World Population
        mortality data, age distributions, concurrency parameters, and risk-group assignments.
        The result is cached after the first call to avoid re-reading large data files on
        repeated invocations.

        Returns:
            HIVDemographics: Fully configured demographics object for Zambia.
        """
        import emodpy_hiv.demographics.un_world_pop as unwp

        # ------------------------------------------------------------------------------------
        # --- The spreadsheets are large and reading/loading them can take many seconds.
        # --- Hence, we cache the initial instance of the demographics so that new versions
        # --- don't have re-read the spreadsheets.
        # ------------------------------------------------------------------------------------
        if cls._inital_demog_cache:
            return copy.deepcopy(cls._inital_demog_cache)

        # -------------------------------------------------------
        # --- Initial population has data for different nodes so
        # --- we can get use data from UN World Pop
        # -------------------------------------------------------
        pop_df = pd.DataFrame(NODE_DATA)

        uwp_country = "Zambia"
        uwp_version = "2015"

        total_pop, age_distribution_yar = unwp.extract_population_by_age_and_distribution(country=uwp_country,
                                                                                          version=uwp_version,
                                                                                          year=1960)

        fertility_yar = unwp.extract_fertility(country=uwp_country, version=uwp_version)
        male_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="male")
        female_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="female")

        # -----------------------------------------------------------------------------------------
        # --- Need a longer fitting interval to flatten natural deaths under the peak in HIV deaths
        # -----------------------------------------------------------------------------------------
        male_mortality_yar = infer.infer_natural_mortality(male_mortality_yar, interval_fit=(1950, 1975))
        female_mortality_yar = infer.infer_natural_mortality(female_mortality_yar, interval_fit=(1950, 1975))

        demog = HIVDemographics.from_year_age_rate_data(pop_df=pop_df,
                                                        age_distribution_yar=age_distribution_yar,
                                                        fertility_yar=fertility_yar,
                                                        male_mortality_yar=male_mortality_yar,
                                                        female_mortality_yar=female_mortality_yar,
                                                        society=None)

        cls._inital_demog_cache = copy.deepcopy(demog)

        return demog

    @classmethod
    def _get_concurrency_parameterized_calls(cls,
                                             relationship_type: str,
                                             risk_group: str,
                                             max_simul_rels_male: float,
                                             max_simul_rels_female: float,
                                             prob_xtra_rel_male: float,
                                             prob_xtra_rel_female: float,
                                             node_ids: Union[List[int], None] = None,
                                             label: str = None) -> List[ParameterizedCall]:
        from emodpy_hiv.demographics.library import set_concurrency_parameters

        parameterized_calls = []
        nhp = {'relationship_type': relationship_type, 'risk_group': risk_group, 'node_ids': node_ids}
        hp = {'max_simul_rels_male': max_simul_rels_male, 'max_simul_rels_female': max_simul_rels_female,
              'prob_xtra_rel_male': prob_xtra_rel_male, 'prob_xtra_rel_female': prob_xtra_rel_female}
        if label is None:
            label = cls.generate_label(relationship_type=relationship_type, risk_group=risk_group, node_ids=node_ids)
        pc = ParameterizedCall(func=set_concurrency_parameters, non_hyperparameters=nhp,
                               hyperparameters=hp,
                               label=label)
        parameterized_calls.append(pc)
        return parameterized_calls

    @classmethod
    def _get_pair_formation_parameterized_calls(cls,
                                                relationship_type: str,
                                                risk_assortivity: float,
                                                formation_rate: float,
                                                node_ids: Union[List[int], None] = None,
                                                label: str = None) -> List[ParameterizedCall]:
        from emodpy_hiv.demographics.library import set_pair_formation_parameters

        parameterized_calls = []
        nhp = {'relationship_type': relationship_type, 'node_ids': node_ids}
        hp = {'risk_assortivity': risk_assortivity, 'formation_rate': formation_rate}
        if label is None:
            label = cls.generate_label(relationship_type=relationship_type, node_ids=node_ids)
        pc = ParameterizedCall(func=set_pair_formation_parameters,
                               non_hyperparameters=nhp,
                               hyperparameters=hp,
                               label=label)
        parameterized_calls.append(pc)
        return parameterized_calls

    @classmethod
    def _get_relationship_parameterized_calls(cls, relationship_type: str,
                                              coital_act_rate: float = None,
                                              condom_usage_min: float = None,
                                              condom_usage_mid: float = None,
                                              condom_usage_max: float = None,
                                              condom_usage_rate: float = None,
                                              duration_scale: float = None,
                                              duration_heterogeneity: float = None,
                                              node_ids: Union[List[int], None] = None,
                                              label: str = None) -> List[ParameterizedCall]:
        from emodpy_hiv.demographics.library import set_relationship_parameters
        parameterized_calls = []
        nhp = {'relationship_type': relationship_type, 'node_ids': node_ids}
        hp = {
            'coital_act_rate': coital_act_rate,
            'condom_usage_min': condom_usage_min,
            'condom_usage_mid': condom_usage_mid,
            'condom_usage_max': condom_usage_max,
            'condom_usage_rate': condom_usage_rate,
            'duration_scale': duration_scale,
            'duration_heterogeneity': duration_heterogeneity
        }

        if label is None:
            label = cls.generate_label(relationship_type=relationship_type, node_ids=node_ids)
        pc = ParameterizedCall(func=set_relationship_parameters,
                               non_hyperparameters=nhp,
                               hyperparameters=hp,
                               label=label)
        parameterized_calls.append(pc)
        return parameterized_calls

    @classmethod
    def _get_individual_property_parameterized_calls(cls) -> List[ParameterizedCall]:
        from emodpy_hiv.demographics.library import set_initial_cascade_state_distribution, \
            set_initial_health_care_accessibility_distribution, set_initial_risk_distribution

        risk_data = []
        risk_data.append({"node_id":  1, "low": 0.255498673 }) # noqa: E202, E241
        risk_data.append({"node_id":  2, "low": 0.330078204 }) # noqa: E202, E241
        risk_data.append({"node_id":  3, "low": 0.121868492 }) # noqa: E202, E241
        risk_data.append({"node_id":  4, "low": 0.221650376 }) # noqa: E202, E241
        risk_data.append({"node_id":  5, "low": 0.395151764 }) # noqa: E202, E241
        risk_data.append({"node_id":  6, "low": 0.095060159 }) # noqa: E202, E241
        risk_data.append({"node_id":  7, "low": 0.288686141 }) # noqa: E202, E241
        risk_data.append({"node_id":  8, "low": 0.446287507 }) # noqa: E202, E241
        risk_data.append({"node_id":  9, "low": 0.378176731 }) # noqa: E202, E241
        risk_data.append({"node_id": 10, "low": 0.283319058 }) # noqa: E202

        parameterized_calls = []
        for data in risk_data:
            hp = {'initial_risk_distribution_low': data["low"]}
            nhp = {'node_ids': [data["node_id"]]}
            pc = ParameterizedCall(func=set_initial_risk_distribution,
                                   non_hyperparameters=nhp,
                                   hyperparameters=hp,
                                   label=NODE_ID_TO_NODE_NAME[data["node_id"]])
            parameterized_calls.append(pc)

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # Since we need to set separate Risk distributions per node, the following
        # is working around a JSON overlay challenge where the list of IPs in a node
        # overrides the list of IPs in Defaults. EMOD does not allow you to override
        # specific IPs.
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        distribution = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        pc = ParameterizedCall(set_initial_cascade_state_distribution,
                               non_hyperparameters={
                                   'cascade_state_distribution': distribution,
                                   'node_ids': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
                               })
        parameterized_calls.append(pc)

        pc = ParameterizedCall(set_initial_health_care_accessibility_distribution,
                               hyperparameters={'initial_accessibility': 0.8},
                               non_hyperparameters={'node_ids': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
        parameterized_calls.append(pc)
        return parameterized_calls

    @classmethod
    def get_demographics_parameterized_calls(cls, demographics: HIVDemographics) -> List[ParameterizedCall]:
        parameterized_calls = super().get_demographics_parameterized_calls(demographics=demographics)

        concurrency_parameters = {
            COMMERCIAL: {
                LOW:    {'max_simul_rels_male':  0, 'max_simul_rels_female':  0,     # noqa: E241
                         'prob_xtra_rel_male':   1, 'prob_xtra_rel_female':   1},    # noqa: E241
                MEDIUM: {'max_simul_rels_male':  0, 'max_simul_rels_female':  0,     # noqa: E241
                         'prob_xtra_rel_male':   1, 'prob_xtra_rel_female':   1},    # noqa: E241
                HIGH:   {'max_simul_rels_male': 59, 'max_simul_rels_female': 59,     # noqa: E241
                         'prob_xtra_rel_male':   1, 'prob_xtra_rel_female':   1},    # noqa: E241
            },
            TRANSITORY: {
                LOW:    {'max_simul_rels_male': 1.861181867682,    'max_simul_rels_female': 1.95751180447129,      # noqa: E241
                         'prob_xtra_rel_male':  0.184386490919862, 'prob_xtra_rel_female':  0.0608225887738051},   # noqa: E241
                MEDIUM: {'max_simul_rels_male': 2.48697611966592,  'max_simul_rels_female': 2.38937717475767,      # noqa: E241
                         'prob_xtra_rel_male':  0.287814625075045, 'prob_xtra_rel_female':  0.3116864843045},      # noqa: E241
                HIGH:   {'max_simul_rels_male': 1,                 'max_simul_rels_female': 1,                     # noqa: E241
                         'prob_xtra_rel_male':  1,                 'prob_xtra_rel_female':  1},                    # noqa: E241
            },
            INFORMAL: {
                LOW:    {'max_simul_rels_male': 1.34356628685348,  'max_simul_rels_female': 1.21853602956715,     # noqa: E241
                         'prob_xtra_rel_male':  0.286213622743763, 'prob_xtra_rel_female':  0.289761028337281},   # noqa: E241
                MEDIUM: {'max_simul_rels_male': 2.70230847725607,  'max_simul_rels_female': 2.76579821196633,     # noqa: E241
                         'prob_xtra_rel_male':  0.396354197417793, 'prob_xtra_rel_female':  0.404566634121599},   # noqa: E241
                HIGH:   {'max_simul_rels_male': 1,                 'max_simul_rels_female': 1,                    # noqa: E241
                         'prob_xtra_rel_male':  1,                 'prob_xtra_rel_female':  1},                   # noqa: E241
            },
            MARITAL: {
                LOW:    {'max_simul_rels_male': 1,                'max_simul_rels_female': 1,                # noqa: E241
                         'prob_xtra_rel_male':  0,                'prob_xtra_rel_female':  0},               # noqa: E241
                MEDIUM: {'max_simul_rels_male': 1.86152532283732, 'max_simul_rels_female': 1.7602276432905,  # noqa: E241
                         'prob_xtra_rel_male':  1,                'prob_xtra_rel_female':  1},               # noqa: E241
                HIGH:   {'max_simul_rels_male': 1,                'max_simul_rels_female': 1,                # noqa: E241
                         'prob_xtra_rel_male':  1,                'prob_xtra_rel_female':  1},               # noqa: E241
            },
        }
        for relationship_type, by_risk_group in concurrency_parameters.items():
            for risk_group, parameters in by_risk_group.items():
                pcs = cls._get_concurrency_parameterized_calls(relationship_type=relationship_type,
                                                               risk_group=risk_group,
                                                               **parameters)
                parameterized_calls.extend(pcs)

        pair_formation = {
            # assortivity -1 means a matrix of all 1s
            COMMERCIAL: {'risk_assortivity': -1,                'formation_rate': 0.15                }, # noqa: E241, E202
            TRANSITORY: {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.00135026850353989 }, # noqa: E241, E202
            INFORMAL:   {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.00032670785736624 }, # noqa: E241, E202
            MARITAL:    {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.000151940868159564}, # noqa: E241
        }
        for relationship_type, parameters in pair_formation.items():
            pcs = cls._get_pair_formation_parameterized_calls(relationship_type=relationship_type, **parameters)
            parameterized_calls.extend(pcs)

        # This value is by relationship type and node
        condom_usage_max_by_rel_type_by_node = {
             1: {TRANSITORY: 0.819762848239337,  INFORMAL: 0.203364778711639,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241, E126- Central
             2: {TRANSITORY: 0.397075298252557,  INFORMAL: 0.398597987534942,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Copperbelt
             3: {TRANSITORY: 0.999759873647185,  INFORMAL: 1.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Eastern
             4: {TRANSITORY: 0.929201760347435,  INFORMAL: 0.773399898218325,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Luapula
             5: {TRANSITORY: 0.403033833521129,  INFORMAL: 0.285059471718292,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Lusaka
             6: {TRANSITORY: 1.0000000000000000, INFORMAL: 0.88811421915765,   MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Muchinga
             7: {TRANSITORY: 0.66571668093682,   INFORMAL: 0.367634610613126,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Northwestern
             8: {TRANSITORY: 0.275068086940464,  INFORMAL: 1.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Northern
             9: {TRANSITORY: 0.636776909282425,  INFORMAL: 0.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241      - Southern
            10: {TRANSITORY: 0.401805672308032,  INFORMAL: 0.387841572157711,  MARITAL: 0.247035322003042, COMMERCIAL: 0.85}  # noqa: E241, E131- Western
        }

        # Other relationship parameters are only by relationship type
        relationship_parameters = {
            TRANSITORY: {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 2001.29656235789,
                         'condom_usage_rate': 2.36008597080353, 'duration_scale': 0.956774771214,
                         'duration_heterogeneity': 0.833333333},
            INFORMAL:   {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 2002.789368, # noqa: E241
                         'condom_usage_rate': 0.288440457896589, 'duration_scale': 2.03104913138,
                         'duration_heterogeneity': 0.75},
            MARITAL:    {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 1994.323885, # noqa: E241
                         'condom_usage_rate': 1.66784081403387, 'duration_scale': 22.154455184937,
                         'duration_heterogeneity': 0.666666667},
            COMMERCIAL: {'coital_act_rate': 0.0027397260273972603, 'condom_usage_min': 0.5, 'condom_usage_mid': 1999.5,
                         'condom_usage_rate': 1, 'duration_scale': 0.01917808219,
                         'duration_heterogeneity': 1}
        }
        # NOTE: The all-node versions of hyperparameters defined here override the per-node-id versions. The reason is
        #  that the all-node hyperparameters are added to the returned list LAST (last defined hyperparameter setting
        #  wins in parameterized call application when >1 hyperparameter affects the same model setting).
        # NOTE: due to how demographics sub-objects are built, if we set 1+ items on e.g. relationship parameters,
        #  we need to set them all to prevent bad things. So we apply all-node values directly to individual nodes,
        #  making the parameters all-node by using an across-node-shared label (multi-use, shared parameters)
        for node_id, condom_usage_max_by_rel_type in condom_usage_max_by_rel_type_by_node.items():
            for relationship_type, condom_usage_max in condom_usage_max_by_rel_type.items():
                node_label = cls.generate_label(relationship_type=relationship_type,
                                                node_name=NODE_ID_TO_NODE_NAME[node_id])
                pcs = cls._get_relationship_parameterized_calls(relationship_type=relationship_type,
                                                                condom_usage_max=condom_usage_max,
                                                                node_ids=[node_id],
                                                                label=node_label)
                parameterized_calls.extend(pcs)

                parameters = relationship_parameters[relationship_type]
                all_nodes_label = cls.generate_label(relationship_type=relationship_type)
                pcs = cls._get_relationship_parameterized_calls(relationship_type=relationship_type,
                                                                **parameters,
                                                                node_ids=[node_id],
                                                                label=all_nodes_label)
                parameterized_calls.extend(pcs)

        # setting initial properties
        pcs = cls._get_individual_property_parameterized_calls()
        parameterized_calls.extend(pcs)

        return parameterized_calls

    @classmethod
    def build_reports(cls, reporters: Reporters) -> Reporters:
        """
        Function that creates reports to be used in the experiment. The function must have Reporters
        object as the parameter and return that object. It is assumed that all the reporters come from the
        reporters that are part of EMOD main code. EMOD also supports reporters as custom plug-in dlls,
        however, not through the current emodpy system.

        Args:
            reporters: The Reporters object to add reports to

        Returns:
            Reporters object
        """
        reporters = super().build_reports(reporters=reporters)

        reporters.add(ReportHIVART(reporters_object=reporters))
        reporters.add(ReportHIVInfection(reporters_object=reporters,
                                         report_filter=ReportFilter(start_year=1980,
                                                                    end_year=2050)))
        reporters.add(ReportHIVMortality(reporters_object=reporters))
        reporters.add(ReportTransmission(reporters_object=reporters))
        reporters.add(ReportRelationshipStart(reporters_object=reporters,
                                              include_hiv_disease_statistics=True,         # default
                                              include_other_relationship_statistics=True,  # default
                                              individual_properties=None,                  # default
                                              report_filter=ReportFilter(start_year=1980,
                                                                         end_year=2050,
                                                                         node_ids=None,                # default
                                                                         min_age_years=None,           # default
                                                                         max_age_years=None,           # default
                                                                         must_have_ip_key_value="",    # default
                                                                         must_have_intervention="")))  # default
        reporters.add(InsetChart(reporters_object=reporters,
                                 has_ip=None,  # default
                                 has_interventions=None,      # default
                                 include_pregnancies=False,   # default
                                 include_coital_acts=True,    # default
                                 event_channels_list=["NewInfectionEvent"]))
        # reporting_period of 182.5 is required for calibration because this will cause EMOD to output a mid-year
        # data timepoint. This reported data is INSTANTANEOUS (current state). It is the current-best-approximation to
        # the year-averaged real-world reference data provided used for calibration (in calibration, we compare the
        # instantaneous, mid-year model output to annual-averaged reference data).
        reporters.add(ReportHIVByAgeAndGender(reporters_object=reporters,
                                              collect_gender_data=True,
                                              collect_age_bins_data=[15, 20, 25, 30, 35, 40, 45, 50],
                                              collect_circumcision_data=True,
                                              collect_hiv_data=True,
                                              collect_hiv_stage_data=False,         # default
                                              collect_on_art_data=True,
                                              collect_ip_data=None,                 # default
                                              collect_intervention_data=["Traditional_MC"],
                                              collect_targeting_config_data=None,   # default
                                              add_transmitters=False,               # default
                                              stratify_infected_by_cd4=False,       # default
                                              event_counter_list=["NewInfectionEvent"],
                                              add_relationships=True,
                                              add_concordant_relationships=False,   # default
                                              reporting_period=182.5,
                                              report_filter=ReportFilter(start_year=1980,
                                                                         end_year=2050)))

        return reporters

add_ART_cascade_parameterized_calls() classmethod

Return ParameterizedCalls for the full ART cascade.

Covers eight cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging, LinkingToPreART, OnPreART, LinkingToART, OnART, and LostForever. Sigmoid linkage parameters and retention rates are treated as hyperparameters for calibration.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls for the ART cascade.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_ART_cascade_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for the full ART cascade.

    Covers eight cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging,
    LinkingToPreART, OnPreART, LinkingToART, OnART, and LostForever. Sigmoid linkage
    parameters and retention rates are treated as hyperparameters for calibration.

    Returns:
        List[ParameterizedCall]: ParameterizedCalls for the ART cascade.
    """
    parameterized_calls = []

    hp = {
        'art_cascade_start_year': None,
        'cd4_retention_rate': None,
        'linking_to_pre_art_sigmoid_min': 0.824382156,
        'linking_to_pre_art_sigmoid_max': 0.959334475,
        'linking_to_pre_art_sigmoid_midyear': 2008.506768,
        'linking_to_pre_art_sigmoid_rate': None,
        'pre_staging_retention': None,
        'pre_art_retention': None,
        'linking_to_art_sigmoid_min': None,
        'linking_to_art_sigmoid_max': 0.90041509,
        'linking_to_art_sigmoid_midyear': 2001.053678,
        'linking_to_art_sigmoid_rate': None,
        'art_reenrollment_willingness': None,
        'immediate_art_rate': None
    }
    pc = ParameterizedCall(func=cls.add_ART_cascade, hyperparameters=hp)
    parameterized_calls.append(pc)

    return parameterized_calls

add_csw_parameterized_calls() classmethod

Return ParameterizedCalls for commercial sex worker (CSW) uptake and dropout.

Splits nodes into two groups with different male and female uptake coverages, reflecting regional variation across Zambia.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls for CSW dynamics across node groups.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_csw_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for commercial sex worker (CSW) uptake and dropout.

    Splits nodes into two groups with different male and female uptake coverages,
    reflecting regional variation across Zambia.

    Returns:
        List[ParameterizedCall]: ParameterizedCalls for CSW dynamics across node groups.
    """
    parameterized_calls = []

    pc = ParameterizedCall(func=cls.add_csw,
                           non_hyperparameters={'node_ids': [1, 2, 3, 7, 9]},
                           hyperparameters={'csw_male_uptake_coverage': 0.195, 'csw_female_uptake_coverage': 0.125},
                           label='nodeset1')
    parameterized_calls.append(pc)

    pc = ParameterizedCall(func=cls.add_csw,
                           non_hyperparameters={'node_ids': [4, 5, 6, 8, 10]},
                           hyperparameters={'csw_male_uptake_coverage': 0.0781, 'csw_female_uptake_coverage': 0.05},
                           label='nodeset2')
    parameterized_calls.append(pc)

    return parameterized_calls

add_health_care_testing_parameterized_calls() classmethod

Return ParameterizedCalls for the health care testing (HCT) loop.

Configures HCT with node-specific delays between tests, covering the HCTUptakeAtDebut, HCTUptakePostDebut, and HCTTestingLoop cascade states.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls for the HCT testing loop.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_health_care_testing_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for the health care testing (HCT) loop.

    Configures HCT with node-specific delays between tests, covering the HCTUptakeAtDebut,
    HCTUptakePostDebut, and HCTTestingLoop cascade states.

    Returns:
        List[ParameterizedCall]: ParameterizedCalls for the HCT testing loop.
    """
    parameterized_calls = []
    non_hyperparameters = {
        'hct_delay_to_next_test': [730, 365, 1100],
        'hct_delay_to_next_test_node_ids': [[1, 2, 3, 4, 6, 7], [5, 9, 10], [8]],
        'hct_delay_to_next_test_node_names': ['Default', 'Lusaka, Southern, Western', 'Northern']
    }
    pc = ParameterizedCall(func=cls.add_health_care_testing,
                           non_hyperparameters=non_hyperparameters,
                           hyperparameters={'hct_start_year': None})
    parameterized_calls.append(pc)

    return parameterized_calls

add_male_circumcision_parameterized_calls() classmethod

Return ParameterizedCalls for all male circumcision interventions.

Includes three components: historical VMMC NChooser events (from calibrated historical data), traditional male circumcision at province-level coverages, and VMMC reference tracking.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls for male circumcision interventions.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_male_circumcision_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for all male circumcision interventions.

    Includes three components: historical VMMC NChooser events (from calibrated historical
    data), traditional male circumcision at province-level coverages, and VMMC reference
    tracking.

    Returns:
        List[ParameterizedCall]: ParameterizedCalls for male circumcision interventions.
    """
    parameterized_calls = []

    # ---- Add historical vmmc NChooser events to campaign ----
    non_hp = {'historical_vmmc_data_filepath': cls._historical_vmmc_data_file}
    hp = {'historical_vmmc_reduced_acquire': None}
    pc = ParameterizedCall(func=cls.add_historical_vmmc_nchooser, non_hyperparameters=non_hp, hyperparameters=hp)
    parameterized_calls.append(pc)

    # ---- Add traditional male circumcision to campaign ----
    traditional_male_circumcision_coverage_by_node = {
        1: 0.054978651,
        2: 0.139462861,
        3: 0.028676043,
        4: 0.091349358,
        5: 0.12318707,
        6: 0.039308099,
        7: 0.727917322,
        8: 0.041105263,
        9: 0.044388102,
        10: 0.398239794
    }

    for node_id, coverage in traditional_male_circumcision_coverage_by_node.items():
        hp = {
            'traditional_male_circumcision_coverage': coverage,
            'traditional_male_circumcision_reduced_acquire': None
        }
        pc = ParameterizedCall(func=cls.add_traditional_male_circumcision,
                               non_hyperparameters={'node_ids': [node_id]},
                               hyperparameters=hp,
                               label=NODE_ID_TO_NODE_NAME[node_id])
        parameterized_calls.append(pc)

    # ---- Add (VMMC) reference tracking to campaign ----
    pc = ParameterizedCall(func=cls.add_vmmc_reference_tracking,
                           hyperparameters={'tracking_vmmc_reduced_acquire': None})
    parameterized_calls.append(pc)

    return parameterized_calls

add_pmtct_parameterized_calls() classmethod

Return ParameterizedCalls for prevention of mother-to-child transmission (PMTCT).

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls covering the TestingOnANC and TestingOnChild6w cascade states.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_pmtct_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for prevention of mother-to-child transmission (PMTCT).

    Returns:
        List[ParameterizedCall]: ParameterizedCalls covering the TestingOnANC and
            TestingOnChild6w cascade states.
    """
    parameterized_calls = []

    pc = ParameterizedCall(func=cls.add_pmtct)
    parameterized_calls.append(pc)

    return parameterized_calls

add_post_debut_coinfection_parameterized_calls() classmethod

Return ParameterizedCalls for STI co-infections post sexual debut.

Coverage is split by risk group (HIGH, MEDIUM, LOW) and treated as a hyperparameter for calibration.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: ParameterizedCalls for post-debut co-infection.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_post_debut_coinfection_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for STI co-infections post sexual debut.

    Coverage is split by risk group (HIGH, MEDIUM, LOW) and treated as a hyperparameter
    for calibration.

    Returns:
        List[ParameterizedCall]: ParameterizedCalls for post-debut co-infection.
    """
    parameterized_calls = []

    hp = {'coinfection_coverage_HIGH': None, 'coinfection_coverage_MEDIUM': None, 'coinfection_coverage_LOW': None}
    pc = ParameterizedCall(func=cls.add_post_debut_coinfection, hyperparameters=hp)
    parameterized_calls.append(pc)

    return parameterized_calls

add_seed_infections_parameterized_calls() classmethod

Return ParameterizedCalls for seeding HIV infections at calibrated node-level start years.

Each node receives an OutbreakIndividual intervention with a calibrated start year and coverage drawn from historical epidemic data for Zambia.

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: One ParameterizedCall per Zambia province node.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def add_seed_infections_parameterized_calls(cls) -> List[ParameterizedCall]:
    """
    Return ParameterizedCalls for seeding HIV infections at calibrated node-level start years.

    Each node receives an OutbreakIndividual intervention with a calibrated start year and
    coverage drawn from historical epidemic data for Zambia.

    Returns:
        List[ParameterizedCall]: One ParameterizedCall per Zambia province node.
    """
    seeding_data = []
    seeding_data.append({"node_id":  1, "start_year": 1977.96490840667, "coverage": 0.00994069137813979}) # noqa: E241
    seeding_data.append({"node_id":  2, "start_year": 1981.21526571588, "coverage": 0.0290879518815339}) # noqa: E241, E202
    seeding_data.append({"node_id":  3, "start_year": 1987.89402149164, "coverage": 0.11194470109949}) # noqa: E241
    seeding_data.append({"node_id":  4, "start_year": 1970.14036061384, "coverage": 0.0222773645719442}) # noqa: E241
    seeding_data.append({"node_id":  5, "start_year": 1970.36530180447, "coverage": 0.35030772723087}) # noqa: E241, E202
    seeding_data.append({"node_id":  6, "start_year": 1984.22541975358, "coverage": 0.0167949011180618}) # noqa: E241, E202
    seeding_data.append({"node_id":  7, "start_year": 1980.54040493867, "coverage": 0.0930776686478119}) # noqa: E241
    seeding_data.append({"node_id":  8, "start_year": 1979.51682308393, "coverage": 0.0099968647453273}) # noqa: E241
    seeding_data.append({"node_id":  9, "start_year": 1984.36462983067, "coverage": 0.0731563905879651}) # noqa: E241
    seeding_data.append({"node_id": 10, "start_year": 1982.02918538299, "coverage": 0.267106173241476})

    parameterized_calls = []
    for data in seeding_data:
        hp = {'seeding_start_year': data["start_year"], 'seeding_coverage': data["coverage"]}
        nhp = {'node_ids': [data["node_id"]]}
        pc = ParameterizedCall(func=cls.seed_infections,
                               non_hyperparameters=nhp,
                               hyperparameters=hp,
                               label=NODE_ID_TO_NODE_NAME[data["node_id"]])
        parameterized_calls.append(pc)

    return parameterized_calls

build_reports(reporters) classmethod

Function that creates reports to be used in the experiment. The function must have Reporters object as the parameter and return that object. It is assumed that all the reporters come from the reporters that are part of EMOD main code. EMOD also supports reporters as custom plug-in dlls, however, not through the current emodpy system.

Parameters:

Name Type Description Default
reporters Reporters

The Reporters object to add reports to

required

Returns:

Type Description
Reporters

Reporters object

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def build_reports(cls, reporters: Reporters) -> Reporters:
    """
    Function that creates reports to be used in the experiment. The function must have Reporters
    object as the parameter and return that object. It is assumed that all the reporters come from the
    reporters that are part of EMOD main code. EMOD also supports reporters as custom plug-in dlls,
    however, not through the current emodpy system.

    Args:
        reporters: The Reporters object to add reports to

    Returns:
        Reporters object
    """
    reporters = super().build_reports(reporters=reporters)

    reporters.add(ReportHIVART(reporters_object=reporters))
    reporters.add(ReportHIVInfection(reporters_object=reporters,
                                     report_filter=ReportFilter(start_year=1980,
                                                                end_year=2050)))
    reporters.add(ReportHIVMortality(reporters_object=reporters))
    reporters.add(ReportTransmission(reporters_object=reporters))
    reporters.add(ReportRelationshipStart(reporters_object=reporters,
                                          include_hiv_disease_statistics=True,         # default
                                          include_other_relationship_statistics=True,  # default
                                          individual_properties=None,                  # default
                                          report_filter=ReportFilter(start_year=1980,
                                                                     end_year=2050,
                                                                     node_ids=None,                # default
                                                                     min_age_years=None,           # default
                                                                     max_age_years=None,           # default
                                                                     must_have_ip_key_value="",    # default
                                                                     must_have_intervention="")))  # default
    reporters.add(InsetChart(reporters_object=reporters,
                             has_ip=None,  # default
                             has_interventions=None,      # default
                             include_pregnancies=False,   # default
                             include_coital_acts=True,    # default
                             event_channels_list=["NewInfectionEvent"]))
    # reporting_period of 182.5 is required for calibration because this will cause EMOD to output a mid-year
    # data timepoint. This reported data is INSTANTANEOUS (current state). It is the current-best-approximation to
    # the year-averaged real-world reference data provided used for calibration (in calibration, we compare the
    # instantaneous, mid-year model output to annual-averaged reference data).
    reporters.add(ReportHIVByAgeAndGender(reporters_object=reporters,
                                          collect_gender_data=True,
                                          collect_age_bins_data=[15, 20, 25, 30, 35, 40, 45, 50],
                                          collect_circumcision_data=True,
                                          collect_hiv_data=True,
                                          collect_hiv_stage_data=False,         # default
                                          collect_on_art_data=True,
                                          collect_ip_data=None,                 # default
                                          collect_intervention_data=["Traditional_MC"],
                                          collect_targeting_config_data=None,   # default
                                          add_transmitters=False,               # default
                                          stratify_infected_by_cd4=False,       # default
                                          event_counter_list=["NewInfectionEvent"],
                                          add_relationships=True,
                                          add_concordant_relationships=False,   # default
                                          reporting_period=182.5,
                                          report_filter=ReportFilter(start_year=1980,
                                                                     end_year=2050)))

    return reporters

get_campaign_parameterized_calls(campaign) classmethod

Assemble all campaign ParameterizedCalls for the Zambia model.

Calls each add_*_parameterized_calls method in order and returns the combined list. Override individual add_* methods in a subclass to replace specific components, or override this method to reorder or exclude components entirely.

Parameters:

Name Type Description Default
campaign campaign

Empty campaign object passed through to each component.

required

Returns:

Type Description
List[ParameterizedCall]

List[ParameterizedCall]: All ParameterizedCalls for the Zambia campaign.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def get_campaign_parameterized_calls(cls, campaign: emod_api.campaign) -> List[ParameterizedCall]:
    """
    Assemble all campaign ParameterizedCalls for the Zambia model.

    Calls each `add_*_parameterized_calls` method in order and returns the combined list.
    Override individual `add_*` methods in a subclass to replace specific components, or
    override this method to reorder or exclude components entirely.

    Args:
        campaign: Empty campaign object passed through to each component.

    Returns:
        List[ParameterizedCall]: All ParameterizedCalls for the Zambia campaign.
    """
    parameterized_calls = super().get_campaign_parameterized_calls(campaign=campaign)

    # ---- Add male circumcision to campaign -----
    # This block contains 3 chunk of events: historical vmmc NChooser, traditional male circumcision and VMMC with
    # reference tracking.
    parameterized_calls.extend(cls.add_male_circumcision_parameterized_calls())
    # ---- Add prevention of mother-to-child transmission (PMTCT) to campaign ----
    # This pmtct block contains 2 Cascade states: TestingOnANC and TestingOnChild6w
    parameterized_calls.extend(cls.add_pmtct_parameterized_calls())
    # ---- Add OutbreakIndividual interventions to campaign ----
    parameterized_calls.extend(cls.add_seed_infections_parameterized_calls())
    # ---- Add commercial sex worker (CSW) uptake and dropout (with delays) for men and women to campaign ----
    parameterized_calls.extend(cls.add_csw_parameterized_calls())
    # ---- Add co-infections post sexual debut to campaign ----
    parameterized_calls.extend(cls.add_post_debut_coinfection_parameterized_calls())
    # ---- Add health care testing loops to campaign ----
    # This health care testing block contains 3 Cascade states: HCTUptakeAtDebut, HCTUptakePostDebut and
    # HCTTestingLoop
    parameterized_calls.extend(cls.add_health_care_testing_parameterized_calls())
    # ---- Add Antiretroviral Therapy (ART) Cascade to campaign ----
    # This ART Cascade block contains 8 Cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging
    # LinkingToPreART, OnPreART, LinkingToART, OnART and LostForever
    parameterized_calls.extend(cls.add_ART_cascade_parameterized_calls())

    return parameterized_calls

initialize_campaign(schema_path) classmethod

Initialize an empty campaign object with the correct schema.

Parameters:

Name Type Description Default
schema_path Union[str, Path]

Path to the EMOD schema file.

required

Returns:

Type Description
campaign

emod_api.campaign: Empty campaign object ready for intervention additions.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def initialize_campaign(cls, schema_path: Union[str, Path]) -> emod_api.campaign:
    """
    Initialize an empty campaign object with the correct schema.

    Args:
        schema_path: Path to the EMOD schema file.

    Returns:
        emod_api.campaign: Empty campaign object ready for intervention additions.
    """
    campaign = super().initialize_campaign(schema_path=schema_path)
    return campaign

initialize_config(schema_path) classmethod

Build the Zambia-calibrated EMOD configuration.

Sets all HIV model parameters calibrated for Zambia: disease progression, infectivity, sexual debut, relationship formation, and memory thresholds. Override in a subclass to adjust specific parameters after calling super().

Parameters:

Name Type Description Default
schema_path Union[str, Path]

Path to the EMOD schema file.

required

Returns:

Name Type Description
ReadOnlyDict ReadOnlyDict

Populated EMOD configuration object.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def initialize_config(cls, schema_path: Union[str, Path]) -> ReadOnlyDict:
    """
    Build the Zambia-calibrated EMOD configuration.

    Sets all HIV model parameters calibrated for Zambia: disease progression, infectivity,
    sexual debut, relationship formation, and memory thresholds. Override in a subclass to
    adjust specific parameters after calling super().

    Args:
        schema_path: Path to the EMOD schema file.

    Returns:
        ReadOnlyDict: Populated EMOD configuration object.
    """
    config = super().initialize_config(schema_path=schema_path)

    config.parameters.Simulation_Type = "HIV_SIM"
    config.parameters.Simulation_Duration = 99.5 * 365
    config.parameters.Simulation_Timestep = 30.4166666666667

    # This is the current "production scale" population scaling factor for Zambia the model is calibrated to
    config.parameters.x_Base_Population = 0.05
    # x_Base_Population = 0.05 needs more than 8GB memory, so we're giving it here
    config.parameters.Memory_Usage_Halting_Threshold_Working_Set_MB = 12000
    config.parameters.Memory_Usage_Warning_Threshold_Working_Set_MB = 11000

    config.parameters.Start_Time = 0
    config.parameters.AIDS_Duration_In_Months = 9
    config.parameters.AIDS_Stage_Infectivity_Multiplier = 4.5
    config.parameters.Acute_Duration_In_Months = 2.9
    config.parameters.Acute_Stage_Infectivity_Multiplier = 26
    config.parameters.Age_Initialization_Distribution_Type = "DISTRIBUTION_COMPLEX"
    config.parameters.Base_Individual_Sample_Rate = 1
    config.parameters.Base_Infectivity = 0.00065805
    config.parameters.Base_Year = 1960.5
    config.parameters.Birth_Rate_Dependence = "INDIVIDUAL_PREGNANCIES_BY_AGE_AND_YEAR"
    config.parameters.Birth_Rate_Time_Dependence = "NONE"
    config.parameters.CD4_At_Death_LogLogistic_Heterogeneity = 0.7
    config.parameters.CD4_At_Death_LogLogistic_Scale = 2.96
    config.parameters.CD4_Post_Infection_Weibull_Heterogeneity = 0.2756
    config.parameters.CD4_Post_Infection_Weibull_Scale = 560.43
    config.parameters.Coital_Dilution_Factor_2_Partners = 0.75
    config.parameters.Coital_Dilution_Factor_3_Partners = 0.6
    config.parameters.Coital_Dilution_Factor_4_Plus_Partners = 0.45
    config.parameters.Condom_Transmission_Blocking_Probability = 0.8
    config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Heterogeneity = 0.5
    config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Scale = 618.341625
    config.parameters.Death_Rate_Dependence = "NONDISEASE_MORTALITY_BY_YEAR_AND_AGE_FOR_EACH_GENDER"
    config.parameters.Enable_Aging = 1
    config.parameters.Enable_Birth = 1
    config.parameters.Enable_Coital_Dilution = 1
    config.parameters.Enable_Demographics_Reporting = 0  # Demographics reporting is on by default, turning it off
    config.parameters.Enable_Infectivity_Reservoir = 0
    config.parameters.Enable_Maternal_Protection = 0
    config.parameters.Enable_Natural_Mortality = 1
    config.parameters.HIV_Adult_Survival_Scale_Parameter_Intercept = 21.182
    config.parameters.HIV_Adult_Survival_Scale_Parameter_Slope = -0.2717
    config.parameters.HIV_Adult_Survival_Shape_Parameter = 2
    config.parameters.HIV_Age_Max_for_Adult_Age_Dependent_Survival = 50
    config.parameters.HIV_Age_Max_for_Child_Survival_Function = 15
    config.parameters.HIV_Child_Survival_Rapid_Progressor_Fraction = 0.57
    config.parameters.HIV_Child_Survival_Rapid_Progressor_Rate = 1.52
    config.parameters.HIV_Child_Survival_Slow_Progressor_Scale = 16
    config.parameters.HIV_Child_Survival_Slow_Progressor_Shape = 2.7
    config.parameters.Heterogeneous_Infectiousness_LogNormal_Scale = 0
    config.parameters.Incubation_Period_Constant = 0
    config.parameters.Incubation_Period_Distribution = "CONSTANT_DISTRIBUTION"
    config.parameters.Individual_Sampling_Type = "FIXED_SAMPLING"
    config.parameters.Infection_Updates_Per_Timestep = 1
    config.parameters.Male_To_Female_Relative_Infectivity_Ages = [0, 15, 25]
    config.parameters.Male_To_Female_Relative_Infectivity_Multipliers = [
        2.26154624,
        2.26154624,
        1.112477558
    ]
    config.parameters.Maternal_Infection_Transmission_Probability = 0.3
    config.parameters.Maternal_Transmission_ART_Multiplier = 0.03334
    config.parameters.Migration_Model = "NO_MIGRATION"
    config.parameters.Min_Days_Between_Adding_Relationships = 0
    config.parameters.Node_Grid_Size = 0.009
    config.parameters.PFA_Burnin_Duration_In_Days = 5475
    config.parameters.PFA_Cum_Prob_Selection_Threshold = 0.2
    config.parameters.Population_Density_Infectivity_Correction = "CONSTANT_INFECTIVITY"
    config.parameters.Population_Scale_Type = "FIXED_SCALING"
    config.parameters.Run_Number = 1
    config.parameters.STI_Coinfection_Acquisition_Multiplier = 5.5
    config.parameters.STI_Coinfection_Transmission_Multiplier = 5.5
    config.parameters.Serialized_Population_Reading_Type = "NONE"
    config.parameters.Serialized_Population_Writing_Type = "NONE"
    config.parameters.Sexual_Debut_Age_Setting_Type = "WEIBULL"
    config.parameters.Sexual_Debut_Age_Female_Weibull_Heterogeneity = 0.22108015674142
    config.parameters.Sexual_Debut_Age_Female_Weibull_Scale = 19.9609856656853
    config.parameters.Sexual_Debut_Age_Male_Weibull_Heterogeneity = 0.165108885953777
    config.parameters.Sexual_Debut_Age_Male_Weibull_Scale = 16.8039276633964
    config.parameters.Sexual_Debut_Age_Min = 13

    config.parameters["logLevel_InfectionHIV"] = "ERROR"
    config.parameters["logLevel_Instrumentation"] = "INFO"
    config.parameters["logLevel_Memory"] = "INFO"
    config.parameters["logLevel_OutbreakIndividual"] = "ERROR"
    config.parameters["logLevel_Simulation"] = "INFO"
    config.parameters["logLevel_SusceptibilityHIV"] = "ERROR"
    config.parameters["logLevel_AntiretroviralTherapy"] = "ERROR"
    config.parameters["logLevel_default"] = "WARNING"

    return config

initialize_demographics() classmethod

Build the Zambia demographics object.

Constructs a multi-node HIVDemographics with 10 provincial nodes, UN World Population mortality data, age distributions, concurrency parameters, and risk-group assignments. The result is cached after the first call to avoid re-reading large data files on repeated invocations.

Returns:

Name Type Description
HIVDemographics HIVDemographics

Fully configured demographics object for Zambia.

Source code in emodpy_hiv/countries/zambia/zambia.py
@classmethod
def initialize_demographics(cls) -> HIVDemographics:
    """
    Build the Zambia demographics object.

    Constructs a multi-node HIVDemographics with 10 provincial nodes, UN World Population
    mortality data, age distributions, concurrency parameters, and risk-group assignments.
    The result is cached after the first call to avoid re-reading large data files on
    repeated invocations.

    Returns:
        HIVDemographics: Fully configured demographics object for Zambia.
    """
    import emodpy_hiv.demographics.un_world_pop as unwp

    # ------------------------------------------------------------------------------------
    # --- The spreadsheets are large and reading/loading them can take many seconds.
    # --- Hence, we cache the initial instance of the demographics so that new versions
    # --- don't have re-read the spreadsheets.
    # ------------------------------------------------------------------------------------
    if cls._inital_demog_cache:
        return copy.deepcopy(cls._inital_demog_cache)

    # -------------------------------------------------------
    # --- Initial population has data for different nodes so
    # --- we can get use data from UN World Pop
    # -------------------------------------------------------
    pop_df = pd.DataFrame(NODE_DATA)

    uwp_country = "Zambia"
    uwp_version = "2015"

    total_pop, age_distribution_yar = unwp.extract_population_by_age_and_distribution(country=uwp_country,
                                                                                      version=uwp_version,
                                                                                      year=1960)

    fertility_yar = unwp.extract_fertility(country=uwp_country, version=uwp_version)
    male_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="male")
    female_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="female")

    # -----------------------------------------------------------------------------------------
    # --- Need a longer fitting interval to flatten natural deaths under the peak in HIV deaths
    # -----------------------------------------------------------------------------------------
    male_mortality_yar = infer.infer_natural_mortality(male_mortality_yar, interval_fit=(1950, 1975))
    female_mortality_yar = infer.infer_natural_mortality(female_mortality_yar, interval_fit=(1950, 1975))

    demog = HIVDemographics.from_year_age_rate_data(pop_df=pop_df,
                                                    age_distribution_yar=age_distribution_yar,
                                                    fertility_yar=fertility_yar,
                                                    male_mortality_yar=male_mortality_yar,
                                                    female_mortality_yar=female_mortality_yar,
                                                    society=None)

    cls._inital_demog_cache = copy.deepcopy(demog)

    return demog

ZambiaForTraining

Bases: Zambia

A version of Zambia that is used for training purposes. It has a smaller population and shorter simulation time.

Source code in emodpy_hiv/countries/zambia/zambia.py
class ZambiaForTraining(Zambia):
    """
    A version of Zambia that is used for training purposes.
    It has a smaller population and shorter simulation time.
    """
    @classmethod
    def initialize_config(cls, schema_path: Union[str, Path]) -> ReadOnlyDict:
        config = super().initialize_config(schema_path=schema_path)

        # Set the base population to a small value for training purposes
        # This is to avoid the model running for too long during training.
        config.parameters.x_Base_Population = 0.002

        # Reduce the simulation duration by 10 years to decrease runtime.
        config.parameters.Simulation_Duration = config.parameters.Simulation_Duration - (10 * 365)

        return config