diff --git a/README.md b/README.md index 37d920f..eaa2394 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # emod-api -Python library/utilities/tools for interacting with DTK input and output files +Python library/utilities/tools for interacting with EMOD input and output files Developer note: Any .py file in emod_api will be included in the package. -![](https://github.com/institutefordiseasemodeling/emod-api/workflows/Package%20and%20test%20on%20Ubuntu/badge.svg) -![](https://github.com/institutefordiseasemodeling/emod-api/workflows/Package%20and%20test%20on%20Windows/badge.svg) +[![Package and test on Ubuntu](https://github.com/EMOD-Hub/emod-api/actions/workflows/build_test_ubuntu.yaml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/build_test_ubuntu.yaml) +[![Package and test on windows](https://github.com/EMOD-Hub/emod-api/actions/workflows/build_test_windows.yaml/badge.svg)](https://github.com/EMOD-Hub/emod-api/actions/workflows/build_test_windows.yaml) ## Documentation @@ -27,20 +27,13 @@ To build the documentation locally, do the following: ### Linux -emod-api can use Snappy [de]compression (python-snappy, actually) as necessary if it is installed -which requires libdev-snappy (Debian/Ubuntu) or snappy-devel (RedHat/CentOS) on Linux. +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. Ubuntu: ```[sudo] apt install libdev-snappy``` CentOS: ```[sudo] yum install snappy-devel``` (not yet tested) -### Windows - -If Snappy compression support is desired or needed, consider downloading and installing the latest -python-snappy package for Windows from Christoph Gohlke's python package website: -https://www.lfd.uci.edu/~gohlke/pythonlibs/#python-snappy - -## user stories +## User Stories Input - User wants to be able to create a minimal working config.json for any sim type guaranteed to work with a given Eradication binary. @@ -83,17 +76,13 @@ Output ### Running tests -`python3 tests/channel_reports.py` -`python3 tests/serialization.py` -`python3 tests/spatial_reports.py` -`python3 tests/weather_files.py` +Please see the documentation for [testing](/tests/README.md). # Community The EMOD Community is made up of researchers and software developers, primarily focused on malaria and HIV research. -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: +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: https://forms.office.com/r/sjncGvBjvZ @@ -101,6 +90,5 @@ https://forms.office.com/r/sjncGvBjvZ # Disclaimer The code in this repository was developed by IDM and other collaborators to support our joint research on flexible agent-based modeling. - 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. - You are welcome to create your own fork and modify the code to suit your own modeling needs as permitted under the MIT License. +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. +You are welcome to create your own fork and modify the code to suit your own modeling needs as permitted under the MIT License. diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/examples/config/from_schema.py b/examples/config/from_schema.py deleted file mode 100644 index cce9f98..0000000 --- a/examples/config/from_schema.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/python3 - -import emod_api.config.from_poi_and_binary as s2c - -config = s2c.schema_to_config("schema.json") -config.parameters.Enable_Vital_Dynamics = True -config.parameters.Base_Infectivity_Constant = 2.6 -config.parameters.to_file("my_awesome_config.json") diff --git a/examples/default_from_schema.py b/examples/default_from_schema.py deleted file mode 100644 index f0775be..0000000 --- a/examples/default_from_schema.py +++ /dev/null @@ -1,4 +0,0 @@ -import emod_api.config.default_from_schema_no_validation as dfs - -dfs.write_default_from_schema( "schema.json" ) -dfs.write_default_from_schema( "schema.json", False ) diff --git a/examples/insetchart.py b/examples/insetchart.py deleted file mode 100644 index a582728..0000000 --- a/examples/insetchart.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -import os -from argparse import ArgumentParser -from emod_api.channelreports.channels import ChannelReport - -MATPLOTLIB = True -try: - import matplotlib.pyplot as plt -except ModuleNotFoundError: - print("This example requires the matplotlib package.") - MATPLOTLIB = False - -PANDAS = True -try: - import pandas as pd -except ModuleNotFoundError: - print("This example requires the pandas package.") - PANDAS = False - -SCRIPT_PATH = os.path.realpath(__file__) -WORKING_DIRECTORY = os.path.dirname(SCRIPT_PATH) - - -def main(filename: str): - - icj = ChannelReport(filename) - - plt.xkcd() - - plt.subplots(num=os.path.basename(filename)) - plt.plot(icj["Infected"].data) - plt.title("Infected Over Time from InsetChart Channel") - plt.xlabel("Time Step") - plt.ylabel("#Infected") - plt.show() - - df = icj.as_dataframe() - plt.subplots(num=os.path.basename(filename)) - plt.semilogy(df["Susceptible Population"]) - plt.semilogy(df["Exposed Population"]) - plt.semilogy(df["Infectious Population"]) - plt.semilogy(df["Recovered Population"]) - plt.title("SEIR Channels from Data Frame") - plt.xlabel("Time Step") - plt.ylabel("Fraction of Population") - plt.legend(["Susceptible", "Exposed", "Infectious", "Recovered"], loc="right") - plt.show() - - return - - -def dump_source(): - - with open(SCRIPT_PATH, "r") as file: - print(file.read()) - - return - - -if __name__ == "__main__": - parser = ArgumentParser() - default = os.path.join( - WORKING_DIRECTORY, "..", "tests", "data", "insetcharts", "InsetChart.json" - ) - parser.add_argument( - "-f", "--filename", type=str, default=default, help="inset chart filename" - ) - parser.add_argument( - "-g", - "--get", - default=False, - action="store_true", - help="Write source code for this example to stdout.", - ) - - args = parser.parse_args() - - if args.get: - dump_source() - elif MATPLOTLIB and PANDAS: - main(args.filename) diff --git a/examples/intervention_from_schema.py b/examples/intervention_from_schema.py deleted file mode 100644 index d22c93a..0000000 --- a/examples/intervention_from_schema.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -from emod_api.interventions import outbreak as ob -from emod_api import campaign as camp - -camp.schema_path = "schema.json" # we should do this earlier but the default is schema.json -camp.add( ob.new_intervention( camp, 30, cases = 44 ) ) -camp.save( "campaign_outbreaks.json" ) diff --git a/examples/migration.py b/examples/migration.py deleted file mode 100644 index 54ac575..0000000 --- a/examples/migration.py +++ /dev/null @@ -1,241 +0,0 @@ -#! /usr/bin/env python3 - -from argparse import ArgumentParser -import sys -from collections import namedtuple -from pathlib import Path - -import emod_api.migration as migration # for from_file() -from emod_api.migration import Migration - - -def main(options): - - if options.basic: - create_basic_migration_file() - - if options.age: - create_age_dependent_migration_file() - - if options.gender: - create_gender_dependent_migration_file() - - if options.both: - create_age_and_gender_dependent_migration_file() - - if options.examine: - examine_migration_file(options.examine) - - if options.from_csv: - create_migration_file_from_csv() - - return - - -def create_basic_migration_file(): - - nodes = _get_nxn_nodes(5) - - # Create a migration file with all the defaults. Primarily: - # no age dependency, no gender dependency, IdReference = "Legacy", migration type = LOCAL_MIGRATION - migration_data = Migration() - - # For all pairs of nodes, migration rate is 0.1 / Manhattan distance - for source_id, source in nodes.items(): - for dest_id, dest in nodes.items(): - if dest_id != source_id: - distance = abs(dest.x-source.x) + abs(dest.y-source.y) - rate = 0.1 / distance - migration_data[source_id][dest_id] = rate - - print(f"Created migration at {migration_data.DateCreated:%a %B %d %Y %H:%M}") - print(f"Created migration with {migration_data.NodeCount} nodes.") - print(f"Created migration with {migration_data.DatavalueCount} destinations per node.") - - migration_data.to_file(Path("basic_local_migration.bin")) - - return - - -def _get_nxn_nodes(size: int = 5) -> dict: - - Center = namedtuple("Center", ["x", "y"]) - - # NxN grid, distance between nodes is just Manhattan distance - locations = [] - for y in range(size): - for x in range(size): - locations.append(Center(x, y)) - - # map nodes to their location, index will be used for node_id - nodes = {index: location for index, location in enumerate(locations)} - - return nodes - - -def create_age_dependent_migration_file(): - - migration_data = Migration() - migration_data.AgesYears = [10, 60, 125] - - factors = {10: 0.8, 60: 1.0, 125: 0.67} - - nodes = _get_nxn_nodes(5) - for source_id, source in nodes.items(): - for dest_id, dest in nodes.items(): - if dest_id != source_id: - distance = abs(dest.x-source.x) + abs(dest.y-source.y) - rate = 0.1 / distance - for age in migration_data.AgesYears: - migration_data[source_id:age][dest_id] = factors[age] * rate - - print(f"Created migration at {migration_data.DateCreated:%a %B %d %Y %H:%M}") - print(f"Migration rate from 0 -> 1 [ 0- 10] = {migration_data[0:5][1]:.4}") - print(f"Migration rate from 0 -> 1 (10- 60] = {migration_data[0,40][1]:.4}") - print(f"Migration rate from 0 -> 1 (60-125) = {migration_data[0:75][1]:.4}") - - # migration_data.to_file(Path("age_dependent_local_migration.bin")) - - return - - -def create_gender_dependent_migration_file(): - - migration_data = Migration() - migration_data.GenderDataType = Migration.ONE_FOR_EACH_GENDER - - factors = {Migration.MALE: 1.2, Migration.FEMALE: 0.8} - - nodes = _get_nxn_nodes(5) - for source_id, source in nodes.items(): - for dest_id, dest in nodes.items(): - if dest_id != source_id: - distance = abs(dest.x-source.x) + abs(dest.y-source.y) - rate = 0.1 / distance - for gender, factor in factors.items(): - migration_data[source_id:gender][dest_id] = factor * rate - - print(f"Created migration at {migration_data.DateCreated:%a %B %d %Y %H:%M}") - print(f"Migration rate from 0 -> 1 ( male ) = {migration_data[0:Migration.MALE][1]:.4}") - print(f"Migration rate from 0 -> 1 (female) = {migration_data[0:Migration.FEMALE][1]:.4}") - - # migration_data.to_file(Path("gender_dependent_local_migration.bin")) - - return - - -def create_age_and_gender_dependent_migration_file(): - - migration_data = Migration() - age_factors = {10: 0.8, 60: 1.0, 125: 0.67} - gender_factors = {Migration.MALE: 1.2, Migration.FEMALE: 0.8} - migration_data.AgesYears = sorted(age_factors.keys()) - migration_data.GenderDataType = Migration.ONE_FOR_EACH_GENDER - - nodes = _get_nxn_nodes(5) - for source_id, source in nodes.items(): - for dest_id, dest in nodes.items(): - if dest_id != source_id: - distance = abs(dest.x-source.x) + abs(dest.y-source.y) - rate = 0.1 / distance - for gender, kg in gender_factors.items(): - for age, ka in age_factors.items(): - migration_data[source_id:gender:age][dest_id] = kg * ka * rate - - print(f"Created migration at {migration_data.DateCreated:%a %B %d %Y %H:%M}") - print(f"Migration rate from 0 -> 1 ( male, 5yo) = {migration_data[0:Migration.MALE:5][1]:.4}") - print(f"Migration rate from 0 -> 1 ( male, 25yo) = {migration_data[0,Migration.MALE,25][1]:.4}") - print(f"Migration rate from 0 -> 1 (female, 25yo) = {migration_data[0:Migration.FEMALE:25][1]:.4}") - print(f"Migration rate from 0 -> 1 (female, 75yo) = {migration_data[0,Migration.FEMALE,75][1]:.4}") - - # migration_data.to_file(Path("age_and_gender_dependent_local_migration.bin")) - - return - - -def examine_migration_file(filename): - - migration_file = migration.from_file(filename) - print(f"Author: {migration_file.Author}") - print(f"DatavalueCount: {migration_file.DatavalueCount}") - print(f"DateCreated: {migration_file.DateCreated:%a %B %d %Y %H:%M}") - print(f"GenderDataType: {migration_file.GenderDataType}") - print(f"IdReference: {migration_file.IdReference}") - print(f"InterpolationType: {migration_file.InterpolationType}") - print(f"MigrationType: {migration_file.MigrationType}") - print(f"NodeCount: {migration_file.NodeCount}") - print(f"NodeOffsets: {migration_file.NodeOffsets}") - print(f"Tool: {migration_file.Tool}") - print(f"Nodes: {migration_file.Nodes}") - - nodes = migration_file.Nodes - a = nodes[0] - b = nodes[-1] - print(f"Rate from {a} to {b} is {migration_file[a][b] if b in migration_file[a] else 0}") - print(f"Rate from {b} to {a} is {migration_file[b][a] if a in migration_file[b] else 0}") - - # convert to regional migration - migration_file.MigrationType = "REGIONAL_MIGRATION" - migration_file.MigrationType = Migration.REGIONAL - migration_file.InterpolationType = Migration.LINEAR_INTERPOLATION - # migration_data.to_file(Path("regional_migration.bin")) - - return - - -def create_migration_file_from_csv(): - """ - Creates migration object using from_csv() function and writes out migration file consumable by EMOD - using to_file() function. - - Returns: - Nothing - """ - # filename - path to the csv file you're converting to EMOD-consumable migration file - # the csv file needs to have headings of "source", "destination", and "rate" - # with "source" and "destination" being NodeIDs from the demographics file you'll be using with the migration files - - # id_ref - sets the IdReference metadata parameter. It needs to match the IdReference metadata parameter - # in your demographics file, otherwise simulation will not run. - - # mig_type - sets the MigrationType metadata parameter, use the Migration enum parameters to set - # "LOCAL_MIGRATION": LOCAL - # "AIR_MIGRATION": AIR - # "REGIONAL_MIGRATION": REGIONAL - # "SEA_MIGRATION": SEA - # "FAMILY_MIGRATION": FAMILY - # "INTERVENTION_MIGRATION": INTERVENTION - # or ints 1-6 respectively - - migration_type = Migration.SEA - migration_object = migration.from_csv(filename=Path("migration_csv_example.csv"), - id_ref="Gridded world grump2.5arcmin", - mig_type=migration_type) - - # binaryfile - path and name that will become the name and location of the migration file. - - # metafile - path that will become the name and location of the meta information file for the migration file. - # they should be in the same folder and be named the same (+.json extension). Best to leave this blank. - # It will be automatically created for you - - # value_limit - limit on number of destination values to write for each source node (default = 100) - - migration_object.to_file(binaryfile=r"..\lake_walk_file", - # Path("lake_walk_file") also works for the same location folder - metafile=None, value_limit=15) - - return - -if __name__ == "__main__": - parser = ArgumentParser() - parser.add_argument("-b", "--basic", action="store_true", help="create a basic migration file") - parser.add_argument("-a", "--age", action="store_true", help="create a migration file with age dependency") - parser.add_argument("-g", "--gender", action="store_true", help="create a migration file with gender dependency") - parser.add_argument("--both", action="store_true", help="create a migration file with both age and gender dependency") - parser.add_argument("-e", "--examine", type=Path, help="display metadata for the given file") - parser.add_argument("-c", "--from_csv", action="store_true", help="create migration file from csv file") - args = parser.parse_args() - if len(sys.argv) == 1: - print( "You need to specify one of the arguments. Run -h to see help." ) - else: - main(args) diff --git a/examples/migration_csv_example.csv b/examples/migration_csv_example.csv deleted file mode 100644 index 49d0456..0000000 --- a/examples/migration_csv_example.csv +++ /dev/null @@ -1,7 +0,0 @@ -source,destination,rate -340461476,340461477,0.02 -340461476,340461478,0.01 -340461477,340461476,0.03 -340461477,340461478,0.001 -340461478,340461476,0.05 -340461478,340461477,0.08 diff --git a/examples/property_report_to_csv.py b/examples/property_report_to_csv.py deleted file mode 100644 index 4943bbf..0000000 --- a/examples/property_report_to_csv.py +++ /dev/null @@ -1,57 +0,0 @@ -#! /usr/bin/env python3 - -"""Examples for `property_report_to_csv()`.""" - -from pathlib import Path -from tempfile import mkdtemp - -from emod_api.channelreports.utils import property_report_to_csv -# from emod_api.channelreports.channels import ChannelReport - -report_file = (Path(__file__).parent.parent / "tests" / "data" / "propertyreports" / "propertyReport.json").absolute() -if report_file.exists(): - print(f"Using '{report_file.absolute()}' as example property report.") -else: - print(f"Default property report, '{report_file.absolue()}' not found. Exiting.") - exit(1) - -temp_dir = Path(mkdtemp()) -print(f"Example results going into '{temp_dir}'") - -# "default" usage - all channels, no groupby, do not transpose -# channels and groupby default to None, and transpose defaults to False -csv_file = temp_dir / "01-defaults.csv" -print(f"Writing all channels disaggregated to {csv_file}...") -property_report_to_csv(report_file, csv_file) - -# Note - it is a little more efficient to call the following if using all the defaults: -# report = ChannelReport(str(source_file)) -# report.to_csv(csv_file, channels=None, transpose=transpose) - -# include a subset of available channels -# groupby defaults to None, and transpose defaults to False -csv_file = temp_dir / "02-selected-channels.csv" -print(f"Writing 'Infected' and 'New Infections' channels disaggregated to {csv_file}...") -property_report_to_csv(report_file, csv_file, channels=["Infected", "New Infections"]) - -# for one channel, use groupby to aggregate results -# transpose defaults to False -csv_file = temp_dir / "03-group-by-age.csv" -print(f"Writing 'Infected' channel (and 'Statistical Population' for normalization) grouped by 'Age_Bin' IPs to {csv_file}...") -property_report_to_csv(report_file, csv_file, channels=["Infected", "Statistical Population"], groupby=["Age_Bin"]) - -# for one channel, use groupby to aggregate results -# transpose defaults to False -csv_file = temp_dir / "04-no-stat-pop.csv" -print(f"Writing 'Infected' channel grouped by 'Age_Bin' IPs to {csv_file}...") -property_report_to_csv(report_file, csv_file, channels=["Infected"], groupby=["Age_Bin"]) - -# for one channel, use groupby to aggregate results, and transpose results - timesteps in rows -csv_file = temp_dir / "05-transpose.csv" -print(f"Writing 'Infected' channel grouped by 'Age_Bin' IPs transposed to timesteps in rows to {csv_file}...") -property_report_to_csv(report_file, csv_file, channels=["Infected"], groupby=["Age_Bin"], transpose=True) - -# include "Statistical Population" for normalization purposes, use groupby to aggregate results, and transpose results - timesteps in rows -csv_file = temp_dir / "05-transpose.csv" -print(f"Writing 'Infected' channel (and 'Statistical Population') grouped by 'Age_Bin' IPs transposed to timesteps in rows to {csv_file}...") -property_report_to_csv(report_file, csv_file, channels=["Infected", "Statistical Population"], groupby=["Age_Bin"], transpose=True) diff --git a/examples/serialization/census_and_mod_pop.py b/examples/serialization/census_and_mod_pop.py deleted file mode 100644 index e253a5e..0000000 --- a/examples/serialization/census_and_mod_pop.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -import sys -import random -import emod_api.serialization.CensusAndModPop as CAMP - -def non_modifier_fn( individual ): - """ - This version of the function obviously doesn't modify anything. It just prints to console. - """ - # print( individual ) - print( f'individual {individual["suid"]["id"]} is {individual["m_age"]} days old.' ) - return individual - -def modifier_fn( individual ): - """ - Let'take half the women and give them a random age from 0 to 100. Just because. - """ - if random.random() < 0.5 and individual["m_gender"] == 1: - # print( "Assigning age." ) - individual["m_age"] = random.random() * 365000 - return individual - -if __name__ == "__main__": - if len( sys.argv ) < 1: - print( f"USAGE: {sys.argv[0]} " ) - sys.exit(0) - CAMP.change_ser_pop( sys.argv[1], non_modifier_fn, "new_ser_pop.dtk" ) - CAMP.change_ser_pop( sys.argv[1], modifier_fn, "new_ser_pop.dtk" ) - CAMP.change_ser_pop( "new_ser_pop.dtk", non_modifier_fn, "new_ser_pop.dtk" ) diff --git a/examples/serialized_file_mc_to_sc.py b/examples/serialized_file_mc_to_sc.py deleted file mode 100644 index edd9b64..0000000 --- a/examples/serialized_file_mc_to_sc.py +++ /dev/null @@ -1,91 +0,0 @@ -import argparse -import gc -import json - -# from dtk.tools.serialization.idtkFileTools import read_idtk_file -from emod_api.dtk_tools.serialization.idtkFileTools import read_idtk_file - -""" -This script will take serialization files dumped from a multi-core job -and merge them into a valid file for single-core initialization -""" - - -def serialized_dtk_to_json(filename_format, core_idx=0): - filename = filename_format % core_idx - print("\n%s\nReading: %s\n%s\n" % ("-" * 30, filename, "-" * 30)) - header, payload, contents, data = read_idtk_file(filename) - # print(header) - # TODO: memory cleanup? - del payload - del contents - suid_keys = [k for k in data["simulation"].keys() if "SuidGenerator" in k] - for suid_key in suid_keys: - print("\n%s" % suid_key) - print(" Setting %s.numtasks to 1" % suid_key) - data["simulation"][suid_key]["numtasks"] = 1 - print("\n%d nodes on rank %d" % (len(data["simulation"]["nodes"]), core_idx)) - # Merge other files into rank-0 - for core_idx in range(1, 24): - - filename = "state-00365-%03d.dtk" % core_idx - print("\n%s\nReading: %s\n%s\n" % ("-" * 30, filename, "-" * 30)) - next_header, payload, contents, next_data = read_idtk_file(filename) - del payload - del contents - - print(gc.get_count()) - gc.collect() - - for suid_key in suid_keys: - print("\n%s" % suid_key) - next_suid_value = next_data["simulation"][suid_key]["next_suid"]["id"] - max_suid_value = data["simulation"][suid_key]["next_suid"]["id"] - if next_suid_value > max_suid_value: - print(" Overwriting: %d > %d" % (next_suid_value, max_suid_value)) - data["simulation"][suid_key]["next_suid"]["id"] = next_suid_value - else: - print(" Keeping: %d <= %d" % (next_suid_value, max_suid_value)) - - data["simulation"]["nodes"] += next_data["simulation"]["nodes"] - print( - "\nAppended %d nodes from rank %d --> Total = %d" - % ( - len(next_data["simulation"]["nodes"]), - core_idx, - len(data["simulation"]["nodes"]), - ) - ) - - return data - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="This script will take serialization files dumped from a multi-core " - "job and merge them into a valid file for single-core initialization" - ) - parser.add_argument( - "--start-index", - type=int, - help="Which core index file to start with. Defaults to 0", - default=0, - ) - parser.add_argument( - "core_filename_format_string", - default="state-00365-%03d.dtk", - help='File format string. For example: "state-00365-%%03d.dtk". The "%%03d" would be ' - "replaced with the core_index during reading", - ) - - parser.add_argument( - "output-name", - type=str, - help="Output file name. For example: state-00365-merged.dtk", - ) - args = parser.parse_args() - - data = serialized_dtk_to_json(args.core_filename_format_string, args.start_index) - with open(args.output_name, "w") as fp: - print("\n%s\nWriting: %s\n%s\n" % ("-" * 30, args.output_name, "-" * 30)) - json.dump(data, fp, indent=None, separators=(",", ":")) diff --git a/examples/some_config_i_found.py b/examples/some_config_i_found.py deleted file mode 100644 index 3629a2d..0000000 --- a/examples/some_config_i_found.py +++ /dev/null @@ -1,4 +0,0 @@ -def set( config ): - config.parameters.Enable_Default_Reporting = 1 - config.parameters.This_Param_Does_Not_Exist = "gotcha" - return config diff --git a/examples/spatial_report.py b/examples/spatial_report.py deleted file mode 100644 index 372c686..0000000 --- a/examples/spatial_report.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 - -from collections import namedtuple -import numpy as np -import os -from argparse import ArgumentParser -from emod_api.spatialreports.spatial import SpatialReport - -MATPLOTLIB = True -try: - import matplotlib.pyplot as plt -except ModuleNotFoundError: - print("This example requires the matplotlib package.") - MATPLOTLIB = False - -SCRIPT_PATH = os.path.realpath(__file__) -WORKING_DIRECTORY = os.path.dirname(SCRIPT_PATH) - -Coordinate = namedtuple("Coordinate", ["x", "y"]) - - -def decode(node_id: int): - - x = node_id >> 16 - y = (node_id % 65536) - 1 - - return Coordinate(x, y) - - -def main(filename: str): - - report = SpatialReport(filename) - - plt.xkcd() - - # Show prevalence over time for first node - plt.subplots(num=os.path.basename(filename)) - plt.plot(report.nodes[report.node_ids[0]].data) - plt.title(f"Prevalence for Node {report.node_ids[0]}") - plt.xlabel("Time Step") - plt.ylabel("Prevalence") - plt.show() - - # Show prevalence at time step 180 - coordinates = list(map(decode, report.node_ids)) - min_x = min(map(lambda c: c.x, coordinates)) - max_x = max(map(lambda c: c.x, coordinates)) - min_y = min(map(lambda c: c.y, coordinates)) - max_y = max(map(lambda c: c.y, coordinates)) - - width = max_x - min_x + 1 - height = max_y - min_y + 1 - garki = np.zeros((height, width), dtype=np.float32) - time_step = min(report.time_steps-1, 180) - for node_id in report.node_ids: - x, y = decode(node_id) - garki[y - min_y, x - min_x] = report.nodes[node_id][time_step] - - plt.subplots(num=os.path.basename(filename)) - plt.imshow(garki, origin="lower", cmap="RdYlGn_r") - plt.title(f"Prevalence at Time Step {time_step}") - plt.xlabel("East-West") - plt.ylabel("South-North") - plt.show() - - return - - -def dump_source(): - - with open(SCRIPT_PATH, "r") as file: - print(file.read()) - - return - - -if __name__ == "__main__": - parser = ArgumentParser() - default_filename = os.path.join( - WORKING_DIRECTORY, - "..", - "tests", - "data", - "spatialreports", - "SpatialReport_Prevalence.bin", - ) - parser.add_argument( - "-f", "--filename", default=default_filename, help="spatial report filename" - ) - parser.add_argument( - "-g", - "--get", - default=False, - action="store_true", - help="Write source code for this example to stdout.", - ) - - args = parser.parse_args() - - if args.get: - dump_source() - elif MATPLOTLIB: - main(args.filename) diff --git a/examples/weather_file.py b/examples/weather_file.py deleted file mode 100644 index f62ce32..0000000 --- a/examples/weather_file.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 - -import numpy as np -import os -from argparse import ArgumentParser -from collections import namedtuple -from emod_api.weather.weather import Weather, Metadata, WeatherNode - -MATPLOTLIB = True -try: - import matplotlib.pyplot as plt - from matplotlib.colors import Normalize -except ModuleNotFoundError: - print("This example requires the matplotlib package.") - MATPLOTLIB = False - -SCRIPT_PATH = os.path.realpath(__file__) -WORKING_DIRECTORY = os.path.dirname(SCRIPT_PATH) - - -def main(filename: str, day: int, minimum, maximum, cmap: str): - - weather = Weather(filename) - - plt.xkcd() - - node_id = weather.node_ids[0] - plt.subplots(num=os.path.basename(filename)) - plt.plot(weather.nodes[node_id].data) - plt.title(f"Values for Node {node_id}") - plt.xlabel("Time Step") - plt.ylabel("Value") - plt.show() - - Coordinate = namedtuple("Coordinate", ["x", "y"]) - - def decode(nid): - """Decode node IDs into x and y coordinates (indices).""" - x_index = nid >> 16 - y_index = (nid % 65536) - 1 - - return Coordinate(x_index, y_index) - - coordinates = list(map(decode, weather.node_ids)) - min_x = min(map(lambda c: c.x, coordinates)) - max_x = max(map(lambda c: c.x, coordinates)) - min_y = min(map(lambda c: c.y, coordinates)) - max_y = max(map(lambda c: c.y, coordinates)) - - width = max_x - min_x + 1 - height = max_y - min_y + 1 - grid = np.zeros((height, width), dtype=np.float32) - for node_id in weather.node_ids: - x, y = decode(node_id) - grid[y - min_y, x - min_x] = weather.nodes[node_id][day] - - plt.subplots(num=os.path.basename(filename)) - plt.imshow(grid, origin="lower", cmap=cmap, norm=Normalize(minimum, maximum)) - plt.title(f"Values at Time Step {day}") - plt.xlabel("East-West") - plt.ylabel("South-North") - plt.show() - - return - - -def dump_source(): - - with open(SCRIPT_PATH, "r") as file: - print(file.read()) - - return - - -if __name__ == "__main__": - parser = ArgumentParser() - default_file = os.path.join( - WORKING_DIRECTORY, - "..", - "tests", - "data", - "weatherfiles", - "Kenya_Nairobi_2.5arcmin_air_temperature_daily.bin", - ) - parser.add_argument( - "-f", "--filename", type=str, default=default_file, help="Climate file name" - ) - parser.add_argument( - "-d", "--day", type=int, default=180, help="Day of year to display, default=180" - ) - parser.add_argument( - "-m", - "--map", - type=str, - default="RdYlBu_r", - help="Matplotlib colormap for image", - ) - parser.add_argument( - "-n", "--min", type=float, default=0.0, help="Minimum value for scaling" - ) - # https://en.wikipedia.org/wiki/List_of_weather_records#Highest_temperatures_ever_recorded - parser.add_argument( - "-x", "--max", type=float, default=60.0, help="Maximum value for scaling" - ) - parser.add_argument( - "-g", - "--get", - default=False, - action="store_true", - help="Write source code for this example to stdout.", - ) - - args = parser.parse_args() - - if args.get: - dump_source() - elif MATPLOTLIB: - main(args.filename, args.day, args.min, args.max, args.map) diff --git a/examples/write_config_from_default_and_params.py b/examples/write_config_from_default_and_params.py deleted file mode 100644 index 13cf885..0000000 --- a/examples/write_config_from_default_and_params.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python - -import emod_api.config.default_from_schema_no_validation as dfs -import some_config_i_found -import os -import sys - -# binary -> schema -if not os.path.exists( "schema.json" ): - print( """Do: - \tpython -m emod_api.schema.get_schema /path/to/Eradication - """ ) - sys.exit() - -# schema -> default config -default_config = dfs.write_default_from_schema( "schema.json" ) - -# default and my params -> my_config -dfs.write_config_from_default_and_params( default_config, some_config_i_found.set, "new_config.json" ) diff --git a/pyproject.toml b/pyproject.toml index eedd595..6fba6ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,26 +17,22 @@ authors = [ {name = "Ye Chen", email = "ye.chen@gatesfoundation.org"}] keywords = ['modeling', 'IDM'] dependencies = [ - "numpy!=1.19.4", - "pyyaml", - "pandas", + "matplotlib", "scipy", + "pandas", + "numpy", "shapely", "pyproj", "geographiclib", "scikit-learn", - "lz4", - "prodict", # utility for dictionaries - "graphviz", - "parse", - "matplotlib" -] + "lz4"] license = {text = "MIT"} classifiers = [ "Programming Language :: Python :: 3.9", "Framework:: IDM-Tools :: models", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent"] + [project.urls] Repository="https://github.com/EMOD-Hub/emod-api" Issues="https://github.com/EMOD-Hub/emod-api/issues" @@ -45,8 +41,7 @@ Issues="https://github.com/EMOD-Hub/emod-api/issues" test = [ "emod-common", "emod-generic", - "emod-malaria" -] + "emod-malaria"] packaging = [ "build", "flake8", # for lint check diff --git a/tests/README.md b/tests/README.md index a222870..445a8b0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -55,7 +55,6 @@ if __name__ == '__main__': unittest.main()` ``` - ## Writing Assertions Inside your test methods, you can use various assertion methods provided by `unittest.TestCase` to check the expected behavior of your code. Some commonly used assertions include: @@ -72,4 +71,4 @@ For more information on available assertion methods, refer to the [unittest docu - [Python unittest Documentation](https://docs.python.org/3/library/unittest.html) - [A Guide to Python's unittest Framework](https://realpython.com/python-testing/#unit-tests) -Happy testing! \ No newline at end of file +Happy testing! diff --git a/tests/test_config_demog.py b/tests/test_config_demog.py index ad7b809..5bfcd0b 100644 --- a/tests/test_config_demog.py +++ b/tests/test_config_demog.py @@ -3,7 +3,6 @@ import json import emod_api.demographics.PreDefinedDistributions as Distributions -from prodict import Prodict from emod_api.config import default_from_schema_no_validation as dfs import manifest @@ -15,16 +14,13 @@ def setUp(self) -> None: def get_config_as_object(self): config_file = "default_config_test_config_demog.json" schema_name = manifest.generic_schema_path - dfs.get_default_config_from_schema(schema_name, output_filename=config_file) - with open(config_file, 'r') as c_file: - config_obj = Prodict.from_dict(json.load(c_file)) + config_obj = dfs.get_default_config_from_schema(schema_name, as_rod=True) return config_obj def reset_config(self): self.config = self.get_config_as_object() - # Tests that if overdispersion is set, Enable_Infection_Rate_Overdispersion is True def test_age_dependent_transmission_config(self): for index in range(2): @@ -45,7 +41,6 @@ def test_set_birth_rate_config(self): demog.SetBirthRate(0.7) self.assertEqual(len(demog.implicits), 2) demog.implicits[-1](self.config) - #self.assertEqual(self.config.parameters.Enable_Birth, 1) # This should get set also during finalize self.assertEqual(self.config.parameters.Birth_Rate_Dependence, "POPULATION_DEP_RATE") def test_set_mortality_rate_config(self): @@ -53,12 +48,10 @@ def test_set_mortality_rate_config(self): demog = Demographics.from_template_node() if index: demog.SetMortalityRate(0.75) - # self.assertEqual(len(demog.implicits), 1+index) # why are there 3 implicits? demog.implicits[-1](self.config) def test_set_mortality_distribution(self): demog = Demographics.from_template_node() - self.config.parameters.Death_Rate_Dependence = "DISEASE_MORTALITY" mortality_distribution = Distributions.SEAsia_Diag demog.SetMortalityDistribution(mortality_distribution)