Skip to content

Commit ddbbf25

Browse files
authored
Merge branch 'develop' into fix_default_settings_current_date
2 parents 3119da6 + 87c974b commit ddbbf25

File tree

6 files changed

+414
-387
lines changed

6 files changed

+414
-387
lines changed

tests/conftest.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from datetime import datetime
2+
3+
import pytest
4+
import pandas as pd
5+
6+
from src.penn_chime.parameters import (
7+
Parameters,
8+
Disposition,
9+
Regions,
10+
)
11+
from src.penn_chime.models import SimSirModel
12+
13+
14+
class MockStreamlit:
15+
"""Mock implementation of streamlit
16+
17+
We just want to verify that st _attempted_ to render the right stuff
18+
so we store the input, and make sure that it matches what we expect
19+
"""
20+
21+
def __init__(self):
22+
self.render_store = []
23+
self.markdown = self.just_store_instead_of_rendering
24+
self.latex = self.just_store_instead_of_rendering
25+
self.subheader = self.just_store_instead_of_rendering
26+
27+
def just_store_instead_of_rendering(self, inp, *args, **kwargs):
28+
self.render_store.append(inp)
29+
return None
30+
31+
32+
@pytest.fixture
33+
def mock_st():
34+
return MockStreamlit()
35+
36+
37+
# The defaults in settings will change and break the tests
38+
@pytest.fixture
39+
def DEFAULTS():
40+
return Parameters(
41+
region=Regions(
42+
delaware=564696,
43+
chester=519293,
44+
montgomery=826075,
45+
bucks=628341,
46+
philly=1581000,
47+
),
48+
current_date=datetime(year=2020, month=3, day=28),
49+
current_hospitalized=14,
50+
date_first_hospitalized=datetime(year=2020, month=3, day=7),
51+
doubling_time=4.0,
52+
n_days=60,
53+
market_share=0.15,
54+
relative_contact_rate=0.3,
55+
hospitalized=Disposition(0.025, 7),
56+
icu=Disposition(0.0075, 9),
57+
ventilated=Disposition(0.005, 10),
58+
)
59+
60+
61+
@pytest.fixture
62+
def param():
63+
return Parameters(
64+
current_date=datetime(year=2020, month=3, day=28),
65+
current_hospitalized=100,
66+
doubling_time=6.0,
67+
market_share=0.05,
68+
relative_contact_rate=0.15,
69+
population=500000,
70+
hospitalized=Disposition(0.05, 7),
71+
icu=Disposition(0.02, 9),
72+
ventilated=Disposition(0.01, 10),
73+
n_days=60,
74+
)
75+
76+
77+
@pytest.fixture
78+
def halving_param():
79+
return Parameters(
80+
current_date=datetime(year=2020, month=3, day=28),
81+
current_hospitalized=100,
82+
doubling_time=6.0,
83+
market_share=0.05,
84+
relative_contact_rate=0.7,
85+
population=500000,
86+
hospitalized=Disposition(0.05, 7),
87+
icu=Disposition(0.02, 9),
88+
ventilated=Disposition(0.01, 10),
89+
n_days=60,
90+
)
91+
92+
93+
@pytest.fixture
94+
def model(param):
95+
return SimSirModel(param)
96+
97+
98+
@pytest.fixture
99+
def halving_model(halving_param):
100+
return SimSirModel(halving_param)
101+
102+
103+
@pytest.fixture
104+
def admits_df():
105+
return pd.read_csv(
106+
"tests/by_doubling_time/2020-03-28_projected_admits.csv", parse_dates=["date"]
107+
)
108+
109+
110+
@pytest.fixture
111+
def census_df():
112+
return pd.read_csv(
113+
"tests/by_doubling_time/2020-03-28_projected_census.csv", parse_dates=["date"]
114+
)

tests/penn_chime/test_charts.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from math import ceil
2+
from datetime import datetime
3+
4+
import altair as alt
5+
import pytest
6+
7+
from src.penn_chime.charts import (
8+
build_admits_chart,
9+
build_census_chart,
10+
build_descriptions,
11+
)
12+
13+
# TODO add test for asterisk
14+
15+
16+
def test_admits_chart(admits_df):
17+
chart = build_admits_chart(alt=alt, admits_df=admits_df)
18+
assert isinstance(chart, (alt.Chart, alt.LayerChart))
19+
assert round(chart.data.iloc[40].icu, 0) == 39
20+
21+
# test fx call with no params
22+
with pytest.raises(TypeError):
23+
build_admits_chart()
24+
25+
26+
def test_build_descriptions(admits_df, param):
27+
chart = build_admits_chart(alt=alt, admits_df=admits_df)
28+
description = build_descriptions(chart=chart, labels=param.labels)
29+
30+
hosp, icu, vent = description.split("\n\n") # break out the description into lines
31+
32+
max_hosp = chart.data["hospitalized"].max()
33+
assert str(ceil(max_hosp)) in hosp
34+
35+
36+
def test_no_asterisk(admits_df, param):
37+
param.n_days = 600
38+
39+
chart = build_admits_chart(alt=alt, admits_df=admits_df)
40+
description = build_descriptions(chart=chart, labels=param.labels)
41+
assert "*" not in description
42+
43+
44+
def test_census(census_df, param):
45+
chart = build_census_chart(alt=alt, census_df=census_df)
46+
description = build_descriptions(chart=chart, labels=param.labels)
47+
48+
assert str(ceil(chart.data["ventilated"].max())) in description
49+
assert str(chart.data["icu"].idxmax()) not in description
50+
assert (
51+
datetime.strftime(chart.data.iloc[chart.data["icu"].idxmax()].date, "%b %d")
52+
in description
53+
)
54+
55+
56+
def test_census_chart(census_df):
57+
chart = build_census_chart(alt=alt, census_df=census_df)
58+
assert isinstance(chart, (alt.Chart, alt.LayerChart))
59+
assert chart.data.iloc[1].hospitalized == 3
60+
assert chart.data.iloc[49].ventilated == 365
61+
62+
# test fx call with no params
63+
with pytest.raises(TypeError):
64+
build_census_chart()

tests/penn_chime/test_models.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from datetime import date
2+
3+
import pytest
4+
import pandas as pd
5+
import numpy as np
6+
7+
from src.penn_chime.models import (
8+
sir,
9+
sim_sir_df,
10+
get_growth_rate,
11+
)
12+
13+
from src.penn_chime.constants import EPSILON
14+
15+
16+
def test_sir():
17+
"""
18+
Someone who is good at testing, help
19+
"""
20+
sir_test = sir(100, 1, 0, 0.2, 0.5, 1)
21+
assert sir_test == (
22+
0.7920792079207921,
23+
0.20297029702970298,
24+
0.0049504950495049506,
25+
), "This contrived example should work"
26+
27+
assert isinstance(sir_test, tuple)
28+
for v in sir_test:
29+
assert isinstance(v, float)
30+
assert v >= 0
31+
32+
# Certain things should *not* work
33+
with pytest.raises(TypeError) as error:
34+
sir("S", 1, 0, 0.2, 0.5, 1)
35+
assert str(error.value) == "can't multiply sequence by non-int of type 'float'"
36+
37+
with pytest.raises(TypeError) as error:
38+
sir(100, "I", 0, 0.2, 0.5, 1)
39+
assert str(error.value) == "can't multiply sequence by non-int of type 'float'"
40+
41+
with pytest.raises(TypeError) as error:
42+
sir(100, 1, "R", 0.2, 0.5, 1)
43+
assert str(error.value) == "unsupported operand type(s) for +: 'float' and 'str'"
44+
45+
with pytest.raises(TypeError) as error:
46+
sir(100, 1, 0, "beta", 0.5, 1)
47+
assert str(error.value) == "bad operand type for unary -: 'str'"
48+
49+
with pytest.raises(TypeError) as error:
50+
sir(100, 1, 0, 0.2, "gamma", 1)
51+
assert str(error.value) == "unsupported operand type(s) for -: 'float' and 'str'"
52+
53+
with pytest.raises(TypeError) as error:
54+
sir(100, 1, 0, 0.2, 0.5, "N")
55+
assert str(error.value) == "unsupported operand type(s) for /: 'str' and 'float'"
56+
57+
# Zeros across the board should fail
58+
with pytest.raises(ZeroDivisionError):
59+
sir(0, 0, 0, 0, 0, 0)
60+
61+
62+
def test_sim_sir():
63+
"""
64+
Rounding to move fast past decimal place issues
65+
"""
66+
raw_df = sim_sir_df(
67+
5, 6, 7, 0.1, 0, 0.1, 40, # s # i # r # gamma # i_day # beta1 # n_days1
68+
)
69+
70+
first = raw_df.iloc[0, :]
71+
last = raw_df.iloc[-1, :]
72+
73+
assert round(first.susceptible, 0) == 5
74+
assert round(first.infected, 2) == 6
75+
assert round(first.recovered, 0) == 7
76+
assert round(last.susceptible, 2) == 0
77+
assert round(last.infected, 2) == 0.18
78+
assert round(last.recovered, 2) == 17.82
79+
80+
assert isinstance(raw_df, pd.DataFrame)
81+
82+
83+
def test_growth_rate():
84+
assert np.round(get_growth_rate(5) * 100.0, decimals=4) == 14.8698
85+
assert np.round(get_growth_rate(0) * 100.0, decimals=4) == 0.0
86+
assert np.round(get_growth_rate(-4) * 100.0, decimals=4) == -15.9104
87+
88+
89+
def test_model(model, param):
90+
# test the Model
91+
92+
assert round(model.infected, 0) == 45810.0
93+
assert isinstance(model.infected, float) # based off note in models.py
94+
95+
# test the class-calculated attributes
96+
# we're talking about getting rid of detection probability
97+
# assert model.detection_probability == 0.125
98+
assert model.intrinsic_growth_rate == 0.12246204830937302
99+
assert abs(model.beta - 4.21501347256401e-07) < EPSILON
100+
assert model.r_t == 2.307298374881539
101+
assert model.r_naught == 2.7144686763312222
102+
assert model.doubling_time_t == 7.764405988534983
103+
104+
105+
def test_model_raw_start(model, param):
106+
raw_df = model.raw_df
107+
108+
# test the things n_days creates, which in turn tests sim_sir, sir, and get_dispositions
109+
110+
# print('n_days: %s; i_day: %s' % (param.n_days, model.i_day))
111+
assert len(raw_df) == (len(np.arange(-model.i_day, param.n_days + 1))) == 104
112+
113+
first = raw_df.iloc[0, :]
114+
second = raw_df.iloc[1, :]
115+
116+
assert first.susceptible == 499600.0
117+
assert round(second.infected, 0) == 449.0
118+
assert list(model.dispositions_df.iloc[0, :]) == [
119+
-43,
120+
date(year=2020, month=2, day=14),
121+
1.0,
122+
0.4,
123+
0.2,
124+
]
125+
assert round(raw_df.recovered[30], 0) == 7083.0
126+
127+
d, dt, s, i, r = list(model.dispositions_df.iloc[60, :])
128+
assert dt == date(year=2020, month=4, day=14)
129+
assert [round(v, 0) for v in (d, s, i, r)] == [17, 549.0, 220.0, 110.0]
130+
131+
132+
def test_model_conservation(param, model):
133+
raw_df = model.raw_df
134+
135+
assert (0.0 <= raw_df.susceptible).all()
136+
assert (0.0 <= raw_df.infected).all()
137+
assert (0.0 <= raw_df.recovered).all()
138+
139+
diff = raw_df.susceptible + raw_df.infected + raw_df.recovered - param.population
140+
assert (diff < 0.1).all()
141+
142+
assert (raw_df.susceptible <= param.population).all()
143+
assert (raw_df.infected <= param.population).all()
144+
assert (raw_df.recovered <= param.population).all()
145+
146+
147+
def test_model_raw_end(param, model):
148+
raw_df = model.raw_df
149+
last = raw_df.iloc[-1, :]
150+
assert round(last.susceptible, 0) == 83391.0
151+
152+
153+
def test_model_monotonicity(param, model):
154+
raw_df = model.raw_df
155+
156+
# Susceptible population should be non-increasing, and Recovered non-decreasing
157+
assert (raw_df.susceptible[1:] - raw_df.susceptible.shift(1)[1:] <= 0).all()
158+
assert (raw_df.recovered[1:] - raw_df.recovered.shift(1)[1:] >= 0).all()
159+
160+
161+
def test_model_cumulative_census(param, model):
162+
# test that census is being properly calculated
163+
raw_df = model.raw_df
164+
admits_df = model.admits_df
165+
df = pd.DataFrame(
166+
{
167+
"hospitalized": admits_df.hospitalized,
168+
"icu": admits_df.icu,
169+
"ventilated": admits_df.ventilated,
170+
}
171+
)
172+
admits = df.cumsum()
173+
174+
# TODO: is 1.0 for ceil function?
175+
diff = admits.hospitalized[1:-1] - (
176+
0.05 * 0.05 * (raw_df.infected[1:-1] + raw_df.recovered[1:-1]) - 1.0
177+
)
178+
assert (diff.abs() < 0.1).all()

0 commit comments

Comments
 (0)