emod-api 3.1.1__tar.gz → 3.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {emod_api-3.1.1 → emod_api-3.2.1}/LICENSE +1 -1
  2. {emod_api-3.1.1/emod_api.egg-info → emod_api-3.2.1}/PKG-INFO +36 -21
  3. {emod_api-3.1.1 → emod_api-3.2.1}/README.md +32 -19
  4. emod_api-3.2.1/emod_api/__init__.py +1 -0
  5. emod_api-3.2.1/emod_api/campaign.py +371 -0
  6. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/demographics.py +9 -7
  7. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/demographics_base.py +135 -63
  8. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/implicit_functions.py +7 -7
  9. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/node.py +11 -52
  10. emod_api-3.2.1/emod_api/demographics/overlay_node.py +42 -0
  11. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/properties_and_attributes.py +132 -86
  12. emod_api-3.2.1/emod_api/utils/emod_enum.py +14 -0
  13. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/str_enum.py +3 -0
  14. {emod_api-3.1.1 → emod_api-3.2.1/emod_api.egg-info}/PKG-INFO +36 -21
  15. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api.egg-info/SOURCES.txt +1 -4
  16. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api.egg-info/requires.txt +1 -0
  17. {emod_api-3.1.1 → emod_api-3.2.1}/pyproject.toml +5 -3
  18. emod_api-3.2.1/tests/test_campaign_module.py +440 -0
  19. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_demographics.py +240 -38
  20. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_node.py +28 -2
  21. emod_api-3.1.1/emod_api/__init__.py +0 -1
  22. emod_api-3.1.1/emod_api/campaign.py +0 -170
  23. emod_api-3.1.1/emod_api/demographics/overlay_node.py +0 -16
  24. emod_api-3.1.1/emod_api/migration/__main__.py +0 -22
  25. emod_api-3.1.1/emod_api/migration/migration.py +0 -782
  26. emod_api-3.1.1/emod_api/weather/__init__.py +0 -0
  27. emod_api-3.1.1/tests/test_campaign_module.py +0 -103
  28. emod_api-3.1.1/tests/test_migration.py +0 -875
  29. {emod_api-3.1.1 → emod_api-3.2.1}/MANIFEST.in +0 -0
  30. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/channelreports/__init__.py +0 -0
  31. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/channelreports/channels.py +0 -0
  32. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/channelreports/plot_icj_means.py +0 -0
  33. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/channelreports/plot_prop_report.py +0 -0
  34. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/channelreports/utils.py +0 -0
  35. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/config/__init__.py +0 -0
  36. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/config/default_from_schema.py +0 -0
  37. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/config/default_from_schema_no_validation.py +0 -0
  38. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/config/from_overrides.py +0 -0
  39. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/__init__.py +0 -0
  40. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/age_distribution.py +0 -0
  41. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/base_input_file.py +0 -0
  42. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/calculators.py +0 -0
  43. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/demographic_exceptions.py +0 -0
  44. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/demographics_overlay.py +0 -0
  45. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/fertility_distribution.py +0 -0
  46. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/mortality_distribution.py +0 -0
  47. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/service/__init__.py +0 -0
  48. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/service/grid_construction.py +0 -0
  49. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/service/service.py +0 -0
  50. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/susceptibility_distribution.py +0 -0
  51. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/demographics/updateable.py +0 -0
  52. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/legacy/__init__.py +0 -0
  53. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/legacy/plotAllCharts.py +0 -0
  54. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/multidim_plotter.py +0 -0
  55. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/schema_to_class.py +0 -0
  56. {emod_api-3.1.1/emod_api/migration → emod_api-3.2.1/emod_api/serialization}/__init__.py +0 -0
  57. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/serialization/census_and_mod_pop.py +0 -0
  58. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/serialization/dtk_file_support.py +0 -0
  59. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/serialization/dtk_file_tools.py +0 -0
  60. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/serialization/dtk_file_utility.py +0 -0
  61. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/serialization/serialized_population.py +0 -0
  62. {emod_api-3.1.1/emod_api/serialization → emod_api-3.2.1/emod_api/spatialreports}/__init__.py +0 -0
  63. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/spatialreports/__main__.py +0 -0
  64. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/spatialreports/plot_spat_means.py +0 -0
  65. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/spatialreports/spatial.py +0 -0
  66. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/__init__.py +0 -0
  67. {emod_api-3.1.1/emod_api/spatialreports → emod_api-3.2.1/emod_api/utils/distributions}/__init__.py +0 -0
  68. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/base_distribution.py +0 -0
  69. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/bimodal_distribution.py +0 -0
  70. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/constant_distribution.py +0 -0
  71. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/demographic_distribution_flag.py +0 -0
  72. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/distribution_type.py +0 -0
  73. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/dual_constant_distribution.py +0 -0
  74. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/dual_exponential_distribution.py +0 -0
  75. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/exponential_distribution.py +0 -0
  76. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/gaussian_distribution.py +0 -0
  77. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/log_normal_distribution.py +0 -0
  78. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/poisson_distribution.py +0 -0
  79. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/uniform_distribution.py +0 -0
  80. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/utils/distributions/weibull_distribution.py +0 -0
  81. {emod_api-3.1.1/emod_api/utils/distributions → emod_api-3.2.1/emod_api/weather}/__init__.py +0 -0
  82. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api/weather/weather.py +0 -0
  83. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api.egg-info/dependency_links.txt +0 -0
  84. {emod_api-3.1.1 → emod_api-3.2.1}/emod_api.egg-info/top_level.txt +0 -0
  85. {emod_api-3.1.1 → emod_api-3.2.1}/setup.cfg +0 -0
  86. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_channel_reports.py +0 -0
  87. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_config.py +0 -0
  88. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_config_demog.py +0 -0
  89. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_demog_from_pop.py +0 -0
  90. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_demographics_calculators.py +0 -0
  91. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_distributions.py +0 -0
  92. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_property_reports.py +0 -0
  93. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_schema.py +0 -0
  94. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_serialization.py +0 -0
  95. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_spatial_reports.py +0 -0
  96. {emod_api-3.1.1 → emod_api-3.2.1}/tests/test_weather_files.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 - 2025 Gates Foundation
3
+ Copyright (c) 2010-2026 Gates Foundation
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emod-api
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Core tools for modeling using EMOD
5
5
  Author-email: Sharon Chen <sharon.chen@gatesfoundation.org>, Zhaowei Du <zhaowei.du@gatesfoundation.org>, Clark Kirkman IV <clark.kirkmand@gatesfoundation.org>, Daniel Bridenbecker <daniel.bridenbecker@gatesfoundation.org>, Svetlana Titova <svetlana.titova@gatesfoundation.org>, Ye Chen <ye.chen@gatesfoundation.org>
6
6
  License-Expression: MIT
7
7
  Project-URL: Repository, https://github.com/EMOD-Hub/emod-api
8
- Project-URL: Issues, https://github.com/EMOD-Hub/emod-api/issues
8
+ Project-URL: Documentation, https://emod.idmod.org/emod-api/
9
+ Project-URL: Issues, https://github.com/EMOD-Hub/issues-and-discussions/issues
9
10
  Keywords: modeling,IDM
10
11
  Classifier: Intended Audience :: Science/Research
11
12
  Classifier: Programming Language :: Python :: 3
@@ -30,6 +31,7 @@ Requires-Dist: mkdocs-include-markdown-plugin; extra == "docs"
30
31
  Requires-Dist: mkdocstrings-python; extra == "docs"
31
32
  Requires-Dist: mkdocs-autoapi; extra == "docs"
32
33
  Requires-Dist: mkdocs-glightbox; extra == "docs"
34
+ Requires-Dist: mkdocs-table-reader-plugin; extra == "docs"
33
35
  Provides-Extra: lint
34
36
  Requires-Dist: flake8; extra == "lint"
35
37
  Provides-Extra: test
@@ -44,17 +46,31 @@ Dynamic: license-file
44
46
 
45
47
  # emod-api
46
48
 
49
+ **emod-api** is a set of Python tools for working with EMOD configuration, campaign, and output files.
50
+
47
51
  [![Build docs and deploy to GH Pages](https://github.com/EMOD-Hub/emod-api/actions/workflows/mkdocs_deploy.yml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/mkdocs_deploy.yml)
48
52
  [![Lint](https://github.com/EMOD-Hub/emod-api/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/EMOD-Hub/emod-api/actions/workflows/lint.yml)
49
53
  [![Test and update version](https://github.com/EMOD-Hub/emod-api/actions/workflows/test_and_bump_version.yml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/test_and_bump_version.yml)
50
54
 
55
+ ## Project status
56
+
57
+ EMOD-Hub projects are provided as open source software under the MIT License for
58
+ community use, research, and development.
59
+
60
+ **Unless otherwise noted, these projects are no longer actively maintained or supported
61
+ by IDM or the Gates Foundation.**
62
+
63
+ Community contributions are welcome, and trusted collaborators may review and
64
+ merge pull requests, but no guarantees are made regarding support, pull request
65
+ review, security response, maintenance, or release timelines.
66
+
51
67
  ## Python Version
52
68
 
53
69
  Python 3.13 is the recommended and supported version.
54
70
 
55
71
  ## Documentation
56
72
 
57
- Documentation available at https://emod-hub.github.io/emod-api/.
73
+ Documentation available at https://emod.idmod.org/emod-api/
58
74
 
59
75
  To build the documentation locally, do the following:
60
76
 
@@ -62,17 +78,16 @@ To build the documentation locally, do the following:
62
78
  2. Navigate to the root directory of the repo.
63
79
  ```
64
80
  python -m pip install .[docs]
81
+ mkdocs serve
65
82
  ```
66
83
 
67
84
  ## Dependencies
68
85
 
69
86
  ### Linux
70
87
 
71
- emod-api can use Snappy [de]compression (python-snappy) as necessary if it is installed which requires libdev-snappy (Debian/Ubuntu) or snappy-devel (RedHat/CentOS) on Linux.
88
+ emod-api can use Snappy [de]compression (python-snappy) as necessary if it is installed, which requires libsnappy-dev on Ubuntu 22.04 (Jammy Jellyfish).
72
89
 
73
- Ubuntu: ```[sudo] apt install libdev-snappy```
74
-
75
- CentOS: ```[sudo] yum install snappy-devel``` (not yet tested)
90
+ Ubuntu: ```[sudo] apt install libsnappy-dev```
76
91
 
77
92
  NOTE: The python-snappy version needs to be 0.6.1. Newer versions have problems
78
93
  working correctly with emod-api.
@@ -94,14 +109,6 @@ Output
94
109
  - User wants to be able to work easily with serialization files.
95
110
  - User wants to be able to work easily with (pending) events.sql file.
96
111
 
97
- ## Dev Tips
98
-
99
- - To build package:
100
- `python -m build --wheel`
101
-
102
- - To install package (fill in actual version number in filename):
103
- `python -m pip install dist/emod_api...whl`
104
-
105
112
  ## Capability Wishlist
106
113
 
107
114
  - Migration files: users should never have to edit migration binary or header files.
@@ -116,13 +123,21 @@ Please see the documentation for [testing](/tests/README.md).
116
123
 
117
124
  ## Community
118
125
 
119
- The EMOD Community is made up of researchers and software developers, primarily focused on malaria and HIV research.
120
- We value mutual respect, openness, and a collaborative spirit. If these values resonate with you, we invite you to join our EMOD Slack Community by completing this form:
126
+ Have a question or a comment? Check out our
127
+ [Discussions](https://github.com/orgs/EMOD-Hub/discussions) space.
128
+
129
+ ## Contributing
121
130
 
122
- https://forms.office.com/r/sjncGvBjvZ
131
+ If you have feature requests, issues, or new code, please see our
132
+ [CONTRIBUTING](https://github.com/EMOD-Hub/.github/blob/main/CONTRIBUTING.md)
133
+ page for how to provide your feedback.
123
134
 
124
135
  ## Disclaimer
125
136
 
126
- The code in this repository was developed by IDM and other collaborators to support our joint research on flexible agent-based modeling.
127
- We've made it publicly available under the MIT License to provide others with a better understanding of our research and an opportunity to build upon it for their own work. We make no representations that the code works as intended or that we will provide support, address issues that are found, or accept pull requests.
128
- You are welcome to create your own fork and modify the code to suit your own modeling needs as permitted under the MIT License.
137
+ The code in this repository was developed by IDM and other collaborators to support our
138
+ joint research on flexible agent-based modeling. We've made it publicly available under
139
+ the MIT License to provide others with a better understanding of our research and an
140
+ opportunity to build upon it for their own work. We make no representations that the code
141
+ works as intended or that we will provide support, address issues that are found, or accept
142
+ pull requests. You are welcome to create your own fork and modify the code to suit your own
143
+ modeling needs as permitted under the MIT License.
@@ -1,16 +1,30 @@
1
1
  # emod-api
2
2
 
3
+ **emod-api** is a set of Python tools for working with EMOD configuration, campaign, and output files.
4
+
3
5
  [![Build docs and deploy to GH Pages](https://github.com/EMOD-Hub/emod-api/actions/workflows/mkdocs_deploy.yml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/mkdocs_deploy.yml)
4
6
  [![Lint](https://github.com/EMOD-Hub/emod-api/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/EMOD-Hub/emod-api/actions/workflows/lint.yml)
5
7
  [![Test and update version](https://github.com/EMOD-Hub/emod-api/actions/workflows/test_and_bump_version.yml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/test_and_bump_version.yml)
6
8
 
9
+ ## Project status
10
+
11
+ EMOD-Hub projects are provided as open source software under the MIT License for
12
+ community use, research, and development.
13
+
14
+ **Unless otherwise noted, these projects are no longer actively maintained or supported
15
+ by IDM or the Gates Foundation.**
16
+
17
+ Community contributions are welcome, and trusted collaborators may review and
18
+ merge pull requests, but no guarantees are made regarding support, pull request
19
+ review, security response, maintenance, or release timelines.
20
+
7
21
  ## Python Version
8
22
 
9
23
  Python 3.13 is the recommended and supported version.
10
24
 
11
25
  ## Documentation
12
26
 
13
- Documentation available at https://emod-hub.github.io/emod-api/.
27
+ Documentation available at https://emod.idmod.org/emod-api/
14
28
 
15
29
  To build the documentation locally, do the following:
16
30
 
@@ -18,17 +32,16 @@ To build the documentation locally, do the following:
18
32
  2. Navigate to the root directory of the repo.
19
33
  ```
20
34
  python -m pip install .[docs]
35
+ mkdocs serve
21
36
  ```
22
37
 
23
38
  ## Dependencies
24
39
 
25
40
  ### Linux
26
41
 
27
- emod-api can use Snappy [de]compression (python-snappy) as necessary if it is installed which requires libdev-snappy (Debian/Ubuntu) or snappy-devel (RedHat/CentOS) on Linux.
42
+ emod-api can use Snappy [de]compression (python-snappy) as necessary if it is installed, which requires libsnappy-dev on Ubuntu 22.04 (Jammy Jellyfish).
28
43
 
29
- Ubuntu: ```[sudo] apt install libdev-snappy```
30
-
31
- CentOS: ```[sudo] yum install snappy-devel``` (not yet tested)
44
+ Ubuntu: ```[sudo] apt install libsnappy-dev```
32
45
 
33
46
  NOTE: The python-snappy version needs to be 0.6.1. Newer versions have problems
34
47
  working correctly with emod-api.
@@ -50,14 +63,6 @@ Output
50
63
  - User wants to be able to work easily with serialization files.
51
64
  - User wants to be able to work easily with (pending) events.sql file.
52
65
 
53
- ## Dev Tips
54
-
55
- - To build package:
56
- `python -m build --wheel`
57
-
58
- - To install package (fill in actual version number in filename):
59
- `python -m pip install dist/emod_api...whl`
60
-
61
66
  ## Capability Wishlist
62
67
 
63
68
  - Migration files: users should never have to edit migration binary or header files.
@@ -72,13 +77,21 @@ Please see the documentation for [testing](/tests/README.md).
72
77
 
73
78
  ## Community
74
79
 
75
- The EMOD Community is made up of researchers and software developers, primarily focused on malaria and HIV research.
76
- We value mutual respect, openness, and a collaborative spirit. If these values resonate with you, we invite you to join our EMOD Slack Community by completing this form:
80
+ Have a question or a comment? Check out our
81
+ [Discussions](https://github.com/orgs/EMOD-Hub/discussions) space.
82
+
83
+ ## Contributing
77
84
 
78
- https://forms.office.com/r/sjncGvBjvZ
85
+ If you have feature requests, issues, or new code, please see our
86
+ [CONTRIBUTING](https://github.com/EMOD-Hub/.github/blob/main/CONTRIBUTING.md)
87
+ page for how to provide your feedback.
79
88
 
80
89
  ## Disclaimer
81
90
 
82
- The code in this repository was developed by IDM and other collaborators to support our joint research on flexible agent-based modeling.
83
- We've made it publicly available under the MIT License to provide others with a better understanding of our research and an opportunity to build upon it for their own work. We make no representations that the code works as intended or that we will provide support, address issues that are found, or accept pull requests.
84
- You are welcome to create your own fork and modify the code to suit your own modeling needs as permitted under the MIT License.
91
+ The code in this repository was developed by IDM and other collaborators to support our
92
+ joint research on flexible agent-based modeling. We've made it publicly available under
93
+ the MIT License to provide others with a better understanding of our research and an
94
+ opportunity to build upon it for their own work. We make no representations that the code
95
+ works as intended or that we will provide support, address issues that are found, or accept
96
+ pull requests. You are welcome to create your own fork and modify the code to suit your own
97
+ modeling needs as permitted under the MIT License.
@@ -0,0 +1 @@
1
+ __version__ = "3.2.1"
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env python
2
+ """Simple campaign builder for EMOD simulations.
3
+
4
+ Import this module, add valid campaign events via ``add``, and write the
5
+ campaign file with ``save``.
6
+ """
7
+
8
+ import json
9
+ import warnings
10
+
11
+ from emod_api import schema_to_class as s2c
12
+
13
+ schema_path = None
14
+ _schema_json = None
15
+ campaign_dict = {"Events": [], "Use_Defaults": 1}
16
+ individual_events_listened = []
17
+ individual_events_broadcast = []
18
+ node_events_broadcast = []
19
+ node_events_listened = []
20
+ coordinator_events_broadcast = []
21
+ coordinator_events_listened = []
22
+ use_old_adhoc_handling = False
23
+ unsafe = False
24
+ implicits = list()
25
+ individual_builtin_events = []
26
+ node_builtin_events = []
27
+ coordinator_builtin_events = []
28
+
29
+
30
+ def reset():
31
+ """Reset all campaign state to defaults.
32
+
33
+ Clears accumulated events, signal tracking lists, event mappings,
34
+ and the schema cache.
35
+ """
36
+ campaign_dict["Events"].clear()
37
+
38
+ individual_events_listened.clear()
39
+ individual_events_broadcast.clear()
40
+ node_events_broadcast.clear()
41
+ node_events_listened.clear()
42
+ coordinator_events_broadcast.clear()
43
+ coordinator_events_listened.clear()
44
+ implicits.clear()
45
+ individual_builtin_events.clear()
46
+ node_builtin_events.clear()
47
+ coordinator_builtin_events.clear()
48
+ s2c.clear_schema_cache()
49
+
50
+
51
+ def _find_builtin_events(schema, reporter_key, events_key):
52
+ """Recursively find a builtin events using reporter entry and extract its event list.
53
+
54
+ Walks the schema looking for ``reporter_key`` as a dict key. When
55
+ found, looks up ``events_key`` inside it and returns the
56
+ ``"Built-in"`` list if present, otherwise the ``"enum"`` list (used in EMOD-Generic)
57
+
58
+ Args:
59
+ schema: The schema JSON object (or sub-object) to search.
60
+ reporter_key: The reporter key to find (e.g.
61
+ ``"ReportEventRecorder"``).
62
+ events_key: The events parameter inside the reporter (e.g.
63
+ ``"Report_Event_Recorder_Events"``).
64
+
65
+ Returns:
66
+ A list of event name strings, or ``None`` if the reporter
67
+ or its event list is not found.
68
+ """
69
+ if isinstance(schema, dict):
70
+ if reporter_key in schema:
71
+ events_entry = schema[reporter_key]
72
+ if isinstance(events_entry, dict):
73
+ events_param = events_entry.get(events_key)
74
+ if isinstance(events_param, dict):
75
+ builtin = events_param.get("Built-in")
76
+ if isinstance(builtin, list):
77
+ return builtin
78
+ enum = events_param.get("enum")
79
+ if isinstance(enum, list):
80
+ return enum
81
+ return None
82
+ for value in schema.values():
83
+ result = _find_builtin_events(value, reporter_key, events_key)
84
+ if result is not None:
85
+ return result
86
+ elif isinstance(schema, list):
87
+ for item in schema:
88
+ result = _find_builtin_events(item, reporter_key, events_key)
89
+ if result is not None:
90
+ return result
91
+ return None
92
+
93
+
94
+ def set_schema(schema_path_in):
95
+ """Set the schema file path and reset all campaign state.
96
+
97
+ This is essentially the "start building a campaign" entry point.
98
+ It clears any previously accumulated events and loads the new schema.
99
+ Also extracts built-in event lists for individual, node, and
100
+ coordinator levels by recursively searching for
101
+ ``ReportEventRecorder``, ``ReportEventRecorderNode``, and
102
+ ``ReportEventRecorderCoordinator`` in the schema.
103
+
104
+ Args:
105
+ schema_path_in: Path to a ``schema.json`` file.
106
+ """
107
+ reset()
108
+ global schema_path, _schema_json
109
+
110
+ schema_path = schema_path_in
111
+ with open(schema_path_in) as schema_file:
112
+ _schema_json = json.load(schema_file)
113
+
114
+ found = _find_builtin_events(_schema_json, "ReportEventRecorder", "Report_Event_Recorder_Events")
115
+ if found:
116
+ individual_builtin_events.extend(found)
117
+
118
+ found = _find_builtin_events(_schema_json, "ReportEventRecorderNode", "Report_Node_Event_Recorder_Events")
119
+ if found:
120
+ node_builtin_events.extend(found)
121
+
122
+ found = _find_builtin_events(_schema_json, "ReportEventRecorderCoordinator", "Report_Coordinator_Event_Recorder_Events")
123
+ if found:
124
+ coordinator_builtin_events.extend(found)
125
+
126
+
127
+ def get_schema():
128
+ """Return the loaded schema JSON dictionary.
129
+
130
+ Returns:
131
+ The parsed schema dictionary, or ``None`` if ``set_schema`` has
132
+ not been called.
133
+ """
134
+ return _schema_json
135
+
136
+
137
+ def add(event, note: str = None):
138
+ """Add a complete campaign event to the campaign builder.
139
+
140
+ The event is assumed to be valid and is not validated here.
141
+
142
+ Args:
143
+ event: A complete campaign event object. It must support
144
+ ``finalize()`` and dict-style key assignment.
145
+ note: An optional human-readable note added to the event
146
+ inside the output ``campaign.json`` file.
147
+ """
148
+ event.finalize()
149
+ if note is not None:
150
+ event["Note"] = note
151
+ campaign_dict["Events"].append(event)
152
+
153
+
154
+ def save(filename: str = "campaign.json"):
155
+ """Save the accumulated campaign events to a JSON file.
156
+
157
+ Args:
158
+ filename: Output file path.
159
+
160
+ Returns:
161
+ The filename that was written.
162
+ """
163
+ with open(filename, "w") as camp_file:
164
+ json.dump(campaign_dict, camp_file, sort_keys=True, indent=4)
165
+
166
+ return filename
167
+
168
+
169
+ def _validate_custom_events(listened_list, broadcast_list, builtin_list, level):
170
+ """Validate that listened-to events are broadcast and vice versa.
171
+
172
+ Built-in events are excluded from validation since they are
173
+ handled by the simulation engine and do not need to be explicitly
174
+ broadcast or listened to in the campaign.
175
+
176
+ Args:
177
+ listened_list: List of event names being listened to.
178
+ broadcast_list: List of event names being broadcast.
179
+ builtin_list: List of built-in event names to exclude from
180
+ validation.
181
+ level: Label for the event level (e.g. ``"coordinator"``
182
+ or ``"node"``) used in error/warning messages.
183
+
184
+ Returns:
185
+ A deduplicated list of custom (non-built-in) broadcast event
186
+ name strings.
187
+
188
+ Raises:
189
+ ValueError: If any events are listened to but never broadcast.
190
+ """
191
+ builtins = set(builtin_list)
192
+
193
+ broadcast_matching_builtins = set(broadcast_list) & builtins
194
+ if broadcast_matching_builtins:
195
+ warnings.warn(
196
+ f"The following {level}-level broadcast events mirror built-in {level}-level events, "
197
+ f"therefore these events will be broadcast by the simulation as well as the campaign: "
198
+ f"{sorted(broadcast_matching_builtins)}")
199
+
200
+ listened = set(listened_list) - builtins
201
+ broadcast = set(broadcast_list) - builtins
202
+
203
+ listened_not_broadcast = listened - broadcast
204
+ if listened_not_broadcast:
205
+ raise ValueError(
206
+ f"The following {level}-level events are listened to but never broadcast. This means that any campaign "
207
+ f"interventions that rely on listening to these events will never fire. Please fix the error by either "
208
+ f"broadcasting these events in the campaign or removing the interventions that are listening for them:\n"
209
+ f"{sorted(listened_not_broadcast)}")
210
+
211
+ broadcast_not_listened = broadcast - listened
212
+ if broadcast_not_listened:
213
+ warnings.warn(
214
+ f"The following {level} events are broadcast but nothing is listening to them within "
215
+ f"the campaign: {sorted(broadcast_not_listened)}")
216
+
217
+ return list(broadcast)
218
+
219
+
220
+ def get_custom_coordinator_events():
221
+ """Validate and return deduplicated custom coordinator-level events.
222
+
223
+ Returns:
224
+ A list of unique coordinator event name strings that are broadcast
225
+ in the campaign.
226
+
227
+ Raises:
228
+ ValueError: If any coordinator events are listened to but
229
+ never broadcast.
230
+ """
231
+ return _validate_custom_events(coordinator_events_listened, coordinator_events_broadcast, coordinator_builtin_events, "coordinator")
232
+
233
+
234
+ def get_custom_node_events():
235
+ """Validate and return deduplicated custom node-level events.
236
+
237
+ Returns:
238
+ A list of unique node event name strings that are broadcast
239
+ in the campaign.
240
+
241
+ Raises:
242
+ ValueError: If any node events are listened to but
243
+ never broadcast.
244
+ """
245
+ return _validate_custom_events(node_events_listened, node_events_broadcast, node_builtin_events, "node")
246
+
247
+
248
+ def get_custom_individual_events():
249
+ """Validate and return deduplicated custom individual-level events.
250
+
251
+ Returns:
252
+ A list of unique individual event name strings that are broadcast
253
+ in the campaign.
254
+
255
+ Raises:
256
+ ValueError: If any individual events are listened to but
257
+ never broadcast.
258
+ """
259
+ return _validate_custom_events(individual_events_listened, individual_events_broadcast, individual_builtin_events, "individual")
260
+
261
+
262
+ def get_recv_trigger(trigger, old=use_old_adhoc_handling):
263
+ """Register an individual-level event as listened to.
264
+
265
+ Tracks which individual events are used throughout the simulation
266
+ so that ``get_custom_individual_events`` can validate that every
267
+ listened-to event has a corresponding broadcast.
268
+
269
+ Args:
270
+ trigger: The individual event name string.
271
+ old: Unused. Kept for backwards compatibility.
272
+
273
+ Returns:
274
+ The event name, unchanged.
275
+ """
276
+ if not trigger:
277
+ raise ValueError("Event name must not be None or empty.")
278
+ individual_events_listened.append(trigger)
279
+ return trigger
280
+
281
+
282
+ def set_listened_node_event(event: str) -> str:
283
+ """Register a node-level event as listened to.
284
+
285
+ Tracks which node events are used throughout the simulation so
286
+ that ``get_custom_node_events`` can validate that every listened-to
287
+ event has a corresponding broadcast.
288
+
289
+ Args:
290
+ event: The node event name string.
291
+
292
+ Returns:
293
+ The event name, unchanged.
294
+ """
295
+ if not event:
296
+ raise ValueError("Event name must not be None or empty.")
297
+ node_events_listened.append(event)
298
+ return event
299
+
300
+
301
+ def set_listened_coordinator_event(event: str) -> str:
302
+ """Register a coordinator-level event as listened to.
303
+
304
+ Tracks which coordinator events are used throughout the simulation
305
+ so that ``get_custom_coordinator_events`` can validate that every
306
+ listened-to event has a corresponding broadcast.
307
+
308
+ Args:
309
+ event: The coordinator event name string.
310
+
311
+ Returns:
312
+ The event name, unchanged.
313
+ """
314
+ if not event:
315
+ raise ValueError("Event name must not be None or empty.")
316
+ coordinator_events_listened.append(event)
317
+ return event
318
+
319
+
320
+ def get_send_trigger(trigger, old=use_old_adhoc_handling):
321
+ """Register an individual-level event as broadcast.
322
+
323
+ Args:
324
+ trigger: The individual event name string.
325
+ old: Unused. Kept for backwards compatibility.
326
+
327
+ Returns:
328
+ The event name, unchanged.
329
+ """
330
+ if not trigger:
331
+ raise ValueError("Event name must not be None or empty.")
332
+ individual_events_broadcast.append(trigger)
333
+ return trigger
334
+
335
+
336
+ def set_broadcast_node_event(event: str) -> str:
337
+ """Register a node-level event as broadcast.
338
+
339
+ Tracks which node events are used throughout the simulation so
340
+ that ``get_custom_node_events`` can validate that every broadcast
341
+ event has something listening to it.
342
+
343
+ Args:
344
+ event: The node event name string.
345
+
346
+ Returns:
347
+ The event name, unchanged.
348
+ """
349
+ if not event:
350
+ raise ValueError("Event name must not be None or empty.")
351
+ node_events_broadcast.append(event)
352
+ return event
353
+
354
+
355
+ def set_broadcast_coordinator_event(event: str) -> str:
356
+ """Register a coordinator-level event as broadcast.
357
+
358
+ Tracks which coordinator events are used throughout the simulation
359
+ so that ``get_custom_coordinator_events`` can validate that every
360
+ broadcast event has something listening to it.
361
+
362
+ Args:
363
+ event: The coordinator event name string.
364
+
365
+ Returns:
366
+ The event name, unchanged.
367
+ """
368
+ if not event:
369
+ raise ValueError("Event name must not be None or empty.")
370
+ coordinator_events_broadcast.append(event)
371
+ return event
@@ -7,7 +7,7 @@ from typing import Union
7
7
 
8
8
  from emod_api.demographics.demographics_base import DemographicsBase
9
9
  from emod_api.demographics.node import Node
10
- from emod_api.demographics.properties_and_attributes import NodeAttributes
10
+ from emod_api.demographics.properties_and_attributes import NodeAttributes, NodeProperty, NodeProperties # noqa: F401
11
11
  from emod_api.demographics.service import service
12
12
 
13
13
 
@@ -30,13 +30,9 @@ class Demographics(DemographicsBase):
30
30
  """
31
31
  super().__init__(nodes=nodes, idref=idref, default_node=default_node)
32
32
 
33
- # set some standard EMOD defaults. set_defaults should always be True unless reading from a demographics file,
34
- # as False allows setting default_node.node_attributes exactly as they are in the file. Loading via
35
- # Demographics.from_file() is deprecated, see below.
33
+ # No current default settings
36
34
  if set_defaults:
37
- self.default_node.node_attributes.airport = 1
38
- self.default_node.node_attributes.seaport = 1
39
- self.default_node.node_attributes.region = 1
35
+ pass
40
36
 
41
37
  def to_file(self, path: Union[str, Path] = "demographics.json", indent: int = 4) -> None:
42
38
  """
@@ -96,6 +92,12 @@ class Demographics(DemographicsBase):
96
92
  demographics = cls(nodes=nodes, default_node=default_node, idref=idref, set_defaults=False)
97
93
  demographics.metadata = metadata
98
94
  demographics.implicits.extend(implicit_functions)
95
+
96
+ node_properties_list = demographics_dict.get("NodeProperties")
97
+ if node_properties_list:
98
+ for np_dict in node_properties_list:
99
+ demographics.node_properties.add(NodeProperty.from_dict(np_dict))
100
+
99
101
  return demographics
100
102
 
101
103
  @classmethod