emod-api 3.0.2__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 (100) hide show
  1. {emod_api-3.0.2 → emod_api-3.2.1}/LICENSE +1 -1
  2. {emod_api-3.0.2/emod_api.egg-info → emod_api-3.2.1}/PKG-INFO +36 -24
  3. {emod_api-3.0.2 → 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.0.2 → emod_api-3.2.1}/emod_api/channelreports/channels.py +13 -14
  7. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/calculators.py +0 -62
  8. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/demographics.py +89 -51
  9. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/demographics_base.py +136 -252
  10. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/implicit_functions.py +7 -7
  11. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/node.py +11 -52
  12. emod_api-3.2.1/emod_api/demographics/overlay_node.py +42 -0
  13. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/properties_and_attributes.py +132 -86
  14. emod_api-3.2.1/emod_api/demographics/service/grid_construction.py +99 -0
  15. emod_api-3.2.1/emod_api/demographics/service/service.py +117 -0
  16. emod_api-3.2.1/emod_api/utils/emod_enum.py +14 -0
  17. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/str_enum.py +3 -0
  18. {emod_api-3.0.2 → emod_api-3.2.1/emod_api.egg-info}/PKG-INFO +36 -24
  19. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api.egg-info/SOURCES.txt +1 -5
  20. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api.egg-info/requires.txt +1 -3
  21. {emod_api-3.0.2 → emod_api-3.2.1}/pyproject.toml +5 -6
  22. emod_api-3.2.1/tests/test_campaign_module.py +440 -0
  23. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_channel_reports.py +7 -58
  24. emod_api-3.2.1/tests/test_demog_from_pop.py +101 -0
  25. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_demographics.py +361 -146
  26. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_node.py +28 -2
  27. emod_api-3.0.2/emod_api/__init__.py +0 -1
  28. emod_api-3.0.2/emod_api/campaign.py +0 -170
  29. emod_api-3.0.2/emod_api/channelreports/icj_to_csv.py +0 -65
  30. emod_api-3.0.2/emod_api/demographics/overlay_node.py +0 -16
  31. emod_api-3.0.2/emod_api/demographics/service/grid_construction.py +0 -143
  32. emod_api-3.0.2/emod_api/demographics/service/service.py +0 -55
  33. emod_api-3.0.2/emod_api/migration/__main__.py +0 -22
  34. emod_api-3.0.2/emod_api/migration/migration.py +0 -782
  35. emod_api-3.0.2/emod_api/weather/__init__.py +0 -0
  36. emod_api-3.0.2/tests/test_campaign_module.py +0 -103
  37. emod_api-3.0.2/tests/test_demog_from_pop.py +0 -74
  38. emod_api-3.0.2/tests/test_migration.py +0 -865
  39. {emod_api-3.0.2 → emod_api-3.2.1}/MANIFEST.in +0 -0
  40. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/channelreports/__init__.py +0 -0
  41. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/channelreports/plot_icj_means.py +0 -0
  42. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/channelreports/plot_prop_report.py +0 -0
  43. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/channelreports/utils.py +0 -0
  44. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/config/__init__.py +0 -0
  45. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/config/default_from_schema.py +0 -0
  46. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/config/default_from_schema_no_validation.py +0 -0
  47. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/config/from_overrides.py +0 -0
  48. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/__init__.py +0 -0
  49. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/age_distribution.py +0 -0
  50. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/base_input_file.py +0 -0
  51. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/demographic_exceptions.py +0 -0
  52. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/demographics_overlay.py +0 -0
  53. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/fertility_distribution.py +0 -0
  54. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/mortality_distribution.py +0 -0
  55. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/service/__init__.py +0 -0
  56. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/susceptibility_distribution.py +0 -0
  57. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/demographics/updateable.py +0 -0
  58. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/legacy/__init__.py +0 -0
  59. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/legacy/plotAllCharts.py +0 -0
  60. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/multidim_plotter.py +0 -0
  61. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/schema_to_class.py +0 -0
  62. {emod_api-3.0.2/emod_api/migration → emod_api-3.2.1/emod_api/serialization}/__init__.py +0 -0
  63. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/serialization/census_and_mod_pop.py +0 -0
  64. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/serialization/dtk_file_support.py +0 -0
  65. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/serialization/dtk_file_tools.py +0 -0
  66. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/serialization/dtk_file_utility.py +0 -0
  67. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/serialization/serialized_population.py +0 -0
  68. {emod_api-3.0.2/emod_api/serialization → emod_api-3.2.1/emod_api/spatialreports}/__init__.py +0 -0
  69. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/spatialreports/__main__.py +0 -0
  70. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/spatialreports/plot_spat_means.py +0 -0
  71. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/spatialreports/spatial.py +0 -0
  72. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/__init__.py +0 -0
  73. {emod_api-3.0.2/emod_api/spatialreports → emod_api-3.2.1/emod_api/utils/distributions}/__init__.py +0 -0
  74. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/base_distribution.py +0 -0
  75. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/bimodal_distribution.py +0 -0
  76. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/constant_distribution.py +0 -0
  77. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/demographic_distribution_flag.py +0 -0
  78. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/distribution_type.py +0 -0
  79. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/dual_constant_distribution.py +0 -0
  80. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/dual_exponential_distribution.py +0 -0
  81. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/exponential_distribution.py +0 -0
  82. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/gaussian_distribution.py +0 -0
  83. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/log_normal_distribution.py +0 -0
  84. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/poisson_distribution.py +0 -0
  85. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/uniform_distribution.py +0 -0
  86. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/utils/distributions/weibull_distribution.py +0 -0
  87. {emod_api-3.0.2/emod_api/utils/distributions → emod_api-3.2.1/emod_api/weather}/__init__.py +0 -0
  88. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api/weather/weather.py +0 -0
  89. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api.egg-info/dependency_links.txt +0 -0
  90. {emod_api-3.0.2 → emod_api-3.2.1}/emod_api.egg-info/top_level.txt +0 -0
  91. {emod_api-3.0.2 → emod_api-3.2.1}/setup.cfg +0 -0
  92. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_config.py +0 -0
  93. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_config_demog.py +0 -0
  94. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_demographics_calculators.py +0 -0
  95. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_distributions.py +0 -0
  96. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_property_reports.py +0 -0
  97. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_schema.py +0 -0
  98. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_serialization.py +0 -0
  99. {emod_api-3.0.2 → emod_api-3.2.1}/tests/test_spatial_reports.py +0 -0
  100. {emod_api-3.0.2 → 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.0.2
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
@@ -20,12 +21,9 @@ Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
21
22
  Requires-Dist: matplotlib
22
23
  Requires-Dist: scipy
23
- Requires-Dist: pandas
24
24
  Requires-Dist: numpy
25
- Requires-Dist: shapely
26
25
  Requires-Dist: pyproj
27
26
  Requires-Dist: geographiclib
28
- Requires-Dist: scikit-learn
29
27
  Requires-Dist: lz4
30
28
  Provides-Extra: docs
31
29
  Requires-Dist: mkdocs-material; extra == "docs"
@@ -33,6 +31,7 @@ Requires-Dist: mkdocs-include-markdown-plugin; extra == "docs"
33
31
  Requires-Dist: mkdocstrings-python; extra == "docs"
34
32
  Requires-Dist: mkdocs-autoapi; extra == "docs"
35
33
  Requires-Dist: mkdocs-glightbox; extra == "docs"
34
+ Requires-Dist: mkdocs-table-reader-plugin; extra == "docs"
36
35
  Provides-Extra: lint
37
36
  Requires-Dist: flake8; extra == "lint"
38
37
  Provides-Extra: test
@@ -47,17 +46,31 @@ Dynamic: license-file
47
46
 
48
47
  # emod-api
49
48
 
49
+ **emod-api** is a set of Python tools for working with EMOD configuration, campaign, and output files.
50
+
50
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)
51
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)
52
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)
53
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
+
54
67
  ## Python Version
55
68
 
56
69
  Python 3.13 is the recommended and supported version.
57
70
 
58
71
  ## Documentation
59
72
 
60
- Documentation available at https://emod-hub.github.io/emod-api/.
73
+ Documentation available at https://emod.idmod.org/emod-api/
61
74
 
62
75
  To build the documentation locally, do the following:
63
76
 
@@ -65,17 +78,16 @@ To build the documentation locally, do the following:
65
78
  2. Navigate to the root directory of the repo.
66
79
  ```
67
80
  python -m pip install .[docs]
81
+ mkdocs serve
68
82
  ```
69
83
 
70
84
  ## Dependencies
71
85
 
72
86
  ### Linux
73
87
 
74
- 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).
75
89
 
76
- Ubuntu: ```[sudo] apt install libdev-snappy```
77
-
78
- CentOS: ```[sudo] yum install snappy-devel``` (not yet tested)
90
+ Ubuntu: ```[sudo] apt install libsnappy-dev```
79
91
 
80
92
  NOTE: The python-snappy version needs to be 0.6.1. Newer versions have problems
81
93
  working correctly with emod-api.
@@ -97,14 +109,6 @@ Output
97
109
  - User wants to be able to work easily with serialization files.
98
110
  - User wants to be able to work easily with (pending) events.sql file.
99
111
 
100
- ## Dev Tips
101
-
102
- - To build package:
103
- `python -m build --wheel`
104
-
105
- - To install package (fill in actual version number in filename):
106
- `python -m pip install dist/emod_api...whl`
107
-
108
112
  ## Capability Wishlist
109
113
 
110
114
  - Migration files: users should never have to edit migration binary or header files.
@@ -119,13 +123,21 @@ Please see the documentation for [testing](/tests/README.md).
119
123
 
120
124
  ## Community
121
125
 
122
- The EMOD Community is made up of researchers and software developers, primarily focused on malaria and HIV research.
123
- 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
124
130
 
125
- 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.
126
134
 
127
135
  ## Disclaimer
128
136
 
129
- The code in this repository was developed by IDM and other collaborators to support our joint research on flexible agent-based modeling.
130
- 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.
131
- 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
@@ -4,9 +4,9 @@
4
4
 
5
5
  from datetime import datetime
6
6
  import json
7
+ import csv
7
8
  from pathlib import Path
8
9
  from typing import Union
9
- import pandas as pd
10
10
 
11
11
  _CHANNELS = "Channels"
12
12
  _DTK_VERSION = "DTK_Version"
@@ -328,13 +328,6 @@ class ChannelReport(object):
328
328
  """Return Channel object by channel name/title"""
329
329
  return self._channels[item]
330
330
 
331
- def as_dataframe(self) -> pd.DataFrame:
332
- """Return underlying data as a Pandas DataFrame"""
333
- dataframe = pd.DataFrame(
334
- {key: self.channels[key].data for key in self.channel_names}
335
- )
336
- return dataframe
337
-
338
331
  def write_file(self, filename: str, indent: int = 0, separators=(",", ":")) -> None:
339
332
  """Write inset chart to specified text file."""
340
333
 
@@ -423,11 +416,17 @@ class ChannelReport(object):
423
416
  if channel_names is None:
424
417
  channel_names = self.channel_names
425
418
 
426
- if not transpose: # default
427
- data_frame = pd.DataFrame([[channel_name] + list(self[channel_name]) for channel_name in channel_names])
428
- # data_frame = pd.DataFrame(([channel_name] + list(self[channel_name]) for channel_name in channel_names))
429
- data_frame.to_csv(filename, header=False, index=False)
430
- else: # transposed
431
- self.as_dataframe().to_csv(filename, header=True, index=True, index_label="timestep")
419
+ if not transpose: # default
420
+ with open(filename, "w") as g_f:
421
+ csv_obj = csv.writer(g_f, dialect='unix', quoting=csv.QUOTE_MINIMAL)
422
+ for cname in channel_names:
423
+ csv_obj.writerow([cname] + list(self[cname]))
424
+
425
+ else: # transposed
426
+ with open(filename, "w") as g_f:
427
+ csv_obj = csv.writer(g_f, dialect='unix', quoting=csv.QUOTE_MINIMAL)
428
+ csv_obj.writerow(channel_names)
429
+ for row_idx in range(self.num_time_steps):
430
+ csv_obj.writerow([self[cname][row_idx] for cname in channel_names])
432
431
 
433
432
  return