Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
204d0be
fixing up distribution tests from emodpy
ckirkman-IDM Nov 13, 2025
9428b2d
Fixing up IndividualProperties to properly handle a None (no IPs) con…
ckirkman-IDM Nov 13, 2025
119abee
Big cleanup, removed demog templates, old dist objs, reenabling from_…
ckirkman-IDM Nov 19, 2025
0ace70b
restoring migration tests after having restored demog,from_file(). Re…
ckirkman-IDM Nov 19, 2025
a2cb1c8
doc fixes and linting
ckirkman-IDM Nov 19, 2025
d080adf
Demographics.from_file() now sets implicit functions on its return ob…
ckirkman-IDM Nov 20, 2025
f30e770
merging from mercury
ckirkman-IDM Nov 20, 2025
7125f4e
fixing minor merge error, param rename
ckirkman-IDM Nov 20, 2025
7b4948a
Merge branch 'mercury' into 43
ckirkman-IDM Nov 20, 2025
6eb5668
linting
ckirkman-IDM Nov 20, 2025
d680a0f
Simplify schema processing of interventions; add tests (#49)
kfrey-idm Nov 21, 2025
ae4a61e
Bump version: 2.0.36 → 2.0.37
Nov 21, 2025
a93ee9e
added google tag manager for analytics tracking (#53)
JSchripsema-IDM Nov 22, 2025
fcfa59a
removing ability to pass in unverified metadata to demographics objec…
ckirkman-IDM Nov 25, 2025
3c582dd
minor file_name -> filename usage homogenization
ckirkman-IDM Nov 25, 2025
dbebc43
Change tracking_id to property in mkdocs.yml (#54)
JSchripsema-IDM Nov 25, 2025
43703f8
adding note that susceptibility distributions are not compatible with…
ckirkman-IDM Nov 25, 2025
381c0ab
adding note that susceptibility distributions are not compatible with…
ckirkman-IDM Nov 25, 2025
186f242
removing e.g. usage
ckirkman-IDM Nov 25, 2025
c5c159b
minor doc update
ckirkman-IDM Nov 25, 2025
b68ae0f
distribution explanatory note
ckirkman-IDM Nov 25, 2025
8ab7f4e
minor comment fix
ckirkman-IDM Nov 26, 2025
71f93ac
cleanup of cruft and a touch of named parameters for clarity
ckirkman-IDM Nov 26, 2025
5837553
Clean up docs (#56)
kfrey-idm Nov 26, 2025
167b65c
Bump version: 2.0.37 → 2.0.38
Nov 26, 2025
568cb5c
demographics.to_file() indent is now optional, where None means one-l…
ckirkman-IDM Dec 1, 2025
4d420d1
adding Path to demographics.to_file() type hinting
ckirkman-IDM Dec 1, 2025
ab49bbe
review updates
ckirkman-IDM Dec 2, 2025
eded5d2
changing Demgoraphics.from_file() to use 'r' reading instead of 'rb'.…
ckirkman-IDM Dec 2, 2025
9028fd0
further extending Demographics.from_file() warning
ckirkman-IDM Dec 2, 2025
d9b7252
Updating review comments related to hiv/malaria only comment sections…
ckirkman-IDM Dec 2, 2025
e17b450
Setting both a simple and complex distribution for those that allow e…
ckirkman-IDM Dec 4, 2025
5b4cc8f
final cleanup from review comments
ckirkman-IDM Dec 4, 2025
de9668c
linting
ckirkman-IDM Dec 4, 2025
2e97a01
Merge branch 'main' into mercury
ckirkman-IDM Dec 4, 2025
dbe35d3
Merge branch 'mercury' into 43
ckirkman-IDM Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.0.36
current_version = 2.0.38
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[\+a-z]+)\.(?P<build>\d+))?
Expand Down
5 changes: 3 additions & 2 deletions docs/bib.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[emodpy]: https://docs.idmod.org/projects/emodpy/en/latest/index.html
[idmtools]: https://docs.idmod.org/projects/idmtools/en/latest/index.html
[emodpy]: https://docs.idmod.org/projects/emodpy/
[emod-api]: https://docs.idmod.org/projects/emod-api/
[idmtools]: https://docs.idmod.org/projects/idmtools/
[emod-generic]: https://docs.idmod.org/projects/emod-generic/en/latest/parameter-overview.html
[idmod]: https://www.idmod.org/
[idm_pypi]: https://packages.idmod.org/
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{# page partial for GTM analytics code, extends "!page.html"
#}

{% block extrahead %}

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-NK4K647');</script>
<!-- End Google Tag Manager -->

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NK4K647"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

<meta name="google-site-verification" content="L6w2tbHp4xFTm04T0CdrrzqX_T9PH-bpQnGGYuwuAE4" />

{% endblock %}
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ See the [idmtools documentation][idmtools] for a diagram of how each of the rela
The emod-api package is currently available on a [Python package index server][idm_pypi] managed by the [Institute for Disease Modeling][idmod].

```
pip install --extra-index-url=https://packages.idmod.org/api/pypi/pypi-production/simple
pip install emod-api --extra-index-url=https://packages.idmod.org/api/pypi/pypi-production/simple
```

{%
Expand Down
2 changes: 1 addition & 1 deletion emod_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@

For rest of emod-api documentation, please go to https://github.com/InstituteforDiseaseModeling/emod-api
"""
__version__ = "2.0.36"
__version__ = "2.0.38"
59 changes: 0 additions & 59 deletions emod_api/demographics/age_distribution_old.py

This file was deleted.

7 changes: 4 additions & 3 deletions emod_api/demographics/base_input_file.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from abc import ABCMeta, abstractmethod
from datetime import datetime

# from simtools.Utilities.LocalOS import LocalOS
import getpass


class BaseInputFile:
__metaclass__ = ABCMeta

def __init__(self, idref):
self.idref = idref
DEFAULT_ID_REFERENCE = "default_id_reference"

def __init__(self, idref: str = None):
self.idref = self.DEFAULT_ID_REFERENCE if idref is None else idref

@abstractmethod
def generate_file(self, name):
Expand Down
159 changes: 159 additions & 0 deletions emod_api/demographics/calculators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import math
import numpy as np
import pandas as pd
import os

from scipy import sparse as sp
from scipy.sparse import linalg as la
from typing import Union

from emod_api.demographics.age_distribution import AgeDistribution
from emod_api.demographics.mortality_distribution import MortalityDistribution


def generate_equilibrium_age_distribution(birth_rate: float = 40.0, mortality_rate: float = 20.0) -> AgeDistribution:
"""
Create an AgeDistribution object representing an equilibrium for birth and mortality rates.

Args:
birth_rate: (float) The birth rate in units of births/year/1000-women
mortality_rate: (float) The mortality rate in units of deaths/year/1000 people

Returns:
an AgeDistribution object
"""
from emod_api.demographics.age_distribution import AgeDistribution

# convert to daily rate per person, EMOD units
birth_rate = (birth_rate / 1000) / 365 # what is actually used below
mortality_rate = (mortality_rate / 1000) / 365 # what is actually used below

birth_rate = math.log(1 + birth_rate)
mortality_rate = -1 * math.log(1 - mortality_rate)

# It is important for the age distribution computation that the age-spacing be very fine; I've used 30 days here.
# With coarse spacing, the computation in practice doesn't work as well.
age_dist_tuple = _computeAgeDist(birth_rate, [i * 30 for i in range(1200)], 1200 * [mortality_rate], 12 * [1.0])

# The final demographics file, though, can use coarser binning interpolated from the finely-spaced computed distribution.
age_bins = list(range(16)) + [20 + 5 * i for i in range(14)]
cum_pop_fraction = np.interp(age_bins, [i / 365 for i in age_dist_tuple[2]], age_dist_tuple[1]).tolist()
age_bins.extend([90])
cum_pop_fraction.extend([1.0])
distribution = AgeDistribution(ages_years=age_bins, cumulative_population_fraction=cum_pop_fraction)
return distribution


def _computeAgeDist(bval, mvecX, mvecY, fVec, max_yr=90):
"""
Compute equilibrium age distribution given age-specific mortality and crude birth rates

Args:
bval: crude birth rate in births per day per person
mvecX: list of age bins in days
mvecY: List of per day mortality rate for the age bins
fVec: Seasonal forcing per month
max_yr : maximum agent age in years

returns EquilibPopulationGrowthRate, MonthlyAgeDist, MonthlyAgeBins
author: Kurt Frey
"""

bin_size = 30
day_to_year = 365

# Age brackets
avecY = np.arange(0, max_yr * day_to_year, bin_size) - 1

# Mortality sampling
mvecX = [-1] + mvecX + [max_yr * day_to_year + 1]
mvecY = [mvecY[0]] + mvecY + [mvecY[-1]]
mX = np.arange(0, max_yr * day_to_year, bin_size)
mX[0] = 1
mval = 1.0 - np.interp(mX, xp=mvecX, fp=mvecY)
r_n = mval.size

# Matrix construction
BmatRC = (np.zeros(r_n), np.arange(r_n))
Bmat = sp.csr_matrix(([bval * bin_size] * r_n, BmatRC), shape=(r_n, r_n))
Mmat = sp.spdiags(mval[:-1] ** bin_size, -1, r_n, r_n)
Dmat = Bmat + Mmat

# Math
(gR, popVec) = la.eigs(Dmat, k=1, sigma=1.0)
gR = np.abs(gR ** (float(day_to_year) / float(bin_size)))
popVec = np.abs(popVec) / np.sum(np.abs(popVec))

# Apply seasonal forcing
mVecR = [-2.0, 30.5, 30.6, 60.5, 60.6, 91.5, 91.6, 121.5,
121.6, 152.5, 152.6, 183.5, 183.6, 213.5, 213.6, 244.5,
245.6, 274.5, 274.6, 305.5, 305.6, 333.5, 335.6, 364.5]
fVec = np.flipud([val for val in fVec for _ in (0, 1)])
wfVec = np.array([np.mean(np.interp(np.mod(range(val + 1, val + 31), 365),
xp=mVecR, fp=fVec)) for val in avecY]).reshape(-1, 1)
popVec = popVec * wfVec / np.sum(popVec * wfVec)

# Age sampling
avecY[0] = 0
avecX = np.clip(np.around(np.cumsum(popVec), decimals=7), 0.0, 1.0)
avecX = np.insert(avecX, 0, np.zeros(1))

return gR.tolist()[0], avecX[:-1].tolist(), avecY.tolist()


def generate_mortality_over_time_from_data(data_csv: Union[str, os.PathLike],
base_year: int) -> MortalityDistribution:
"""
Generate a MortalityDistribution object from a data csv file.

Args:
data_csv: Path to csv file with the mortality rates by calendar year and age bucket.
base_year: The calendar year the sim is treating as the base.

Returns:
a MortalityDistribution object.
"""
if base_year < 0:
raise ValueError(f"User passed negative value of base_year: {base_year}.")
if base_year > 2050:
raise ValueError(f"User passed too large value of base_year: {base_year}.")

# Load csv. Convert rate arrays into DTK-compatiable JSON structures.
rates = [] # array of arrays, but leave that for a minute
df = pd.read_csv(data_csv)
header = df.columns
year_start = int(header[1]) # someone's going to come along with 1990.5, etc. Sigh.
year_end = int(header[-1])
if year_end <= year_start:
raise ValueError(f"Failed check that {year_end} is greater than {year_start} in csv dataset.")
num_years = year_end - year_start + 1
rel_years = list()
for year in range(year_start, year_start + num_years):
mort_data = list(df[str(year)])
rel_years.append(year - base_year)

age_key = None
for trykey in df.keys():
if trykey.lower().startswith("age"):
age_key = trykey
raw_age_bins = list(df[age_key])

if age_key is None:
raise ValueError("Failed to find 'Age_Bin' (or similar) column in the csv dataset. Cannot process.")

age_bins = list()
try:
for age_bin in raw_age_bins:
left_age = float(age_bin.split("-")[0])
age_bins.append(left_age)

except Exception as ex:
raise ValueError(f"Ran into error processing the values in the Age-Bin column. {ex}")

for idx in range(len(age_bins)): # 18 of these
# mort_data is the array of mortality rates (by year bin) for age_bin
mort_data = list(df.transpose()[idx][1:])
rates.append(mort_data) # 28 of these, 1 for each year, eg

distribution = MortalityDistribution(ages_years=age_bins, mortality_rate_matrix=rates, calendar_years=rel_years)
return distribution
4 changes: 4 additions & 0 deletions emod_api/demographics/demographic_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ class NonMonotonicDistributionException(ValueError):

class InvalidNodeIdException(ValueError):
pass


class ConflictingDistributionsException(ValueError):
pass
Loading