Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Calib uplift #772

Merged
merged 32 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3ed73ee
Making quick progress, but still much to do.
daniel-klein Oct 18, 2024
13ce0e1
Good progress on the API, almost there
daniel-klein Oct 18, 2024
7db30ee
Stopping here for now. Need a general way to know if a parameter is "…
daniel-klein Oct 19, 2024
aac65c5
Starting work on a CalibComponent. WIP
daniel-klein Oct 20, 2024
c71e5d9
WIP on components
daniel-klein Oct 24, 2024
f95ff6b
conforming seems to work
daniel-klein Oct 24, 2024
80af5c0
WIP
daniel-klein Oct 25, 2024
e0b57d7
correcting a typo in a comment
daniel-klein Oct 31, 2024
94c0585
Experimenting with Ax + BoTorch
daniel-klein Nov 4, 2024
072f97c
Merge branch 'rc2.1.0' into calib-uplift
daniel-klein Nov 4, 2024
16ddd5b
Merge branch 'rc2.1.0' into calib-uplift
daniel-klein Nov 6, 2024
7068139
Addressing stochasticity in Births related to dicussion in Unstable V…
daniel-klein Nov 8, 2024
9d8f87c
Updating baselines
daniel-klein Nov 8, 2024
faa8d8d
Addressing match_time_inds fails #750
daniel-klein Nov 8, 2024
8b965fb
Merge branch 'stochasticbirths' into calib-uplift for v2.1.1 compatib…
daniel-klein Nov 8, 2024
05bf41c
Adding Ax+BoTorch example
daniel-klein Nov 8, 2024
ddc0e25
Removing `save_results`
daniel-klein Nov 8, 2024
8e2dd74
Beta-Binomial likelihood working for SIR example
daniel-klein Nov 15, 2024
ee1da91
Beginning work on a calibration tutorial
Nov 16, 2024
acffe50
Continued work on the calibration tutorial
Nov 16, 2024
dd9a184
Needs cleanup and additional comments, but working.
daniel-klein Nov 16, 2024
a601aab
Calib tutorial improvements
daniel-klein Nov 17, 2024
7eaa99a
Adding Gamma Poisson likelihood
daniel-klein Nov 17, 2024
b04f585
Merge branch 'calib-uplift' of github.com:starsimhub/starsim into cal…
daniel-klein Nov 17, 2024
de626cd
Minor text and plotting improvements.
daniel-klein Nov 17, 2024
f696cc9
move ax to devtests
cliffckerr Nov 18, 2024
206106d
start tidying test_calibration
cliffckerr Nov 18, 2024
f75d6ff
refactor to use strings and actual and expected
cliffckerr Nov 18, 2024
3c286b7
update results warnings
cliffckerr Nov 18, 2024
d10b86a
more calibration refactoring
cliffckerr Nov 18, 2024
7527939
change hyperlinks
cliffckerr Nov 18, 2024
82616a3
fix syntax
cliffckerr Nov 18, 2024
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
332 changes: 332 additions & 0 deletions docs/tutorials/tut_calibration.ipynb

Large diffs are not rendered by default.

491 changes: 244 additions & 247 deletions starsim/calibration.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion starsim/demographics.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def get_births(self):

scaled_birth_prob = this_birth_rate * p.rate_units * p.rel_birth * factor
scaled_birth_prob = np.clip(scaled_birth_prob, a_min=0, a_max=1)
n_new = int(sc.randround(sim.people.alive.count() * scaled_birth_prob))
n_new = np.random.binomial(n=sim.people.alive.count(), p=scaled_birth_prob) # Not CRN safe, see issue #404
return n_new

def step(self):
Expand Down
5 changes: 2 additions & 3 deletions starsim/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,12 @@ def init_time(self, force=False):

def match_time_inds(self, inds=None):
""" Find the nearest matching sim time indices for the current module """
if inds is None: inds = Ellipsis
self_tvec = self.t.abstvec
sim_tvec = self.sim.t.abstvec
if len(self_tvec) == len(sim_tvec): # Shortcut to avoid doing matching
return inds
return Ellipsis if inds is None else inds
else:
out = sc.findnearest(sim_tvec, [inds])
out = sc.findnearest(sim_tvec, self_tvec)
return out

def start_step(self):
Expand Down
5 changes: 1 addition & 4 deletions starsim/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,10 @@ def append(self, arg, key=None):
result = arg

if not isinstance(result, Result):
warnmsg = f'You are adding a result of type {type(result)} to Results, which is inadvisable.'
warnmsg = f'You are adding a result of type {type(result)} to Results, which is inadvisable; if you intended to add it, use results[key] = value instead'
ss.warn(warnmsg)

if result.module != self._module:
if result.module:
warnmsg = f'You are adding a result from module {result.module} to module {self._module}; check that this is intentional.'
ss.warn(warnmsg)
result.module = self._module

super().append(result, key=key)
Expand Down
46 changes: 24 additions & 22 deletions tests/baseline.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
{
"summary": {
"timevec": 2020.0,
"births_new": 46.84158415841584,
"births_cumulative": 2267.7425742574255,
"births_cbr": 19.93833106141367,
"deaths_new": 9.673267326732674,
"deaths_cumulative": 468.5742574257426,
"deaths_cmr": 4.118825151847284,
"sir_n_susceptible": 2440.029702970297,
"sir_n_infected": 3630.3960396039606,
"sir_n_recovered": 5676.693069306931,
"sir_prevalence": 0.32402568798505815,
"sir_new_infections": 122.34653465346534,
"sir_cum_infections": 12357.0,
"sis_n_susceptible": 4784.306930693069,
"sis_n_infected": 6973.504950495049,
"sis_prevalence": 0.5720906510271361,
"sis_new_infections": 193.5742574257426,
"sis_cum_infections": 19551.0,
"sis_rel_sus": 0.5019197711850157,
"n_alive": 11747.118811881188,
"new_deaths": 10.693069306930694,
"cum_deaths": 1072.0
"births_new": 48.257425742574256,
"births_cumulative": 2343.3069306930693,
"births_cbr": 20.43178207644599,
"deaths_new": 9.712871287128714,
"deaths_cumulative": 470.58415841584156,
"deaths_cmr": 4.112394571867341,
"randomnet_n_edges": 58901.33663366337,
"mfnet_n_edges": 4004.732673267327,
"maternalnet_n_edges": 0.0,
"sir_n_susceptible": 2464.970297029703,
"sir_n_infected": 3658.227722772277,
"sir_n_recovered": 5694.504950495049,
"sir_prevalence": 0.32462009407521675,
"sir_new_infections": 122.81188118811882,
"sir_cum_infections": 12404.0,
"sis_n_susceptible": 4828.3267326732675,
"sis_n_infected": 7000.19801980198,
"sis_prevalence": 0.5702209995778549,
"sis_new_infections": 195.01980198019803,
"sis_cum_infections": 19697.0,
"sis_rel_sus": 0.5033450153204474,
"n_alive": 11817.70297029703,
"new_deaths": 10.821782178217822,
"cum_deaths": 1084.0
}
}
6 changes: 3 additions & 3 deletions tests/benchmark.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"time": {
"initialize": 0.055,
"run": 1.013
"initialize": 0.053,
"run": 0.914
},
"parameters": {
"n_agents": 10000,
"dur": 20,
"dt": 0.2
},
"cpu_performance": 0.9665005580733697
"cpu_performance": 0.8734404449460742
}
146 changes: 146 additions & 0 deletions tests/devtests/devtest_axbo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""
Test calibration
"""

#%% Imports and settings
import sciris as sc
import starsim as ss
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from ax.plot.contour import plot_contour
from ax.plot.trace import optimization_trace_single_method
from ax.service.managed_loop import optimize
from ax.utils.notebook.plotting import init_notebook_plotting, render

do_plot = 1
do_save = 0
n_agents = 2e3

#%% Helper functions

def make_sim():
sir = ss.SIR(
beta = ss.beta(0.9),
dur_inf = ss.lognorm_ex(mean=ss.dur(6)),
init_prev = ss.bernoulli(0.01),
)

#deaths = ss.Deaths(death_rate=15)
#births = ss.Births(birth_rate=15)

random = ss.RandomNet(n_contacts=ss.poisson(4))

sim = ss.Sim(
dt = 1,
unit = 'day',
n_agents = n_agents,
#total_pop = 9980999,
start = sc.date('2024-01-01'),
stop = sc.date('2024-01-31'),
diseases = sir,
networks = random,
#demographics = [deaths, births],
)

return sim


def build_sim(sim, calib_pars, **kwargs):
""" Modify the base simulation by applying calib_pars """

for k, v in calib_pars.items():
if k == 'beta':
sim.diseases.sir.pars['beta'] = ss.beta(v)
elif k == 'dur_inf':
sim.diseases.sir.pars['dur_inf'] = ss.lognorm_ex(mean=ss.dur(v)), #ss.dur(v)
elif k == 'n_contacts':
sim.networks.randomnet.pars.n_contacts = v # Typically a Poisson distribution, but this should set the distribution parameter value appropriately
else:
sim.pars[k] = v # Assume sim pars

return sim

def eval_sim(pars):
sim = make_sim()
sim.init()
sim = build_sim(sim, pars)
sim.run()
#print('pars:', pars, ' --> Final prevalence:', sim.results.sir.prevalence[-1])
fig = sim.plot()
fig.suptitle(pars)
fig.subplots_adjust(top=0.9)
plt.show()

return dict(
prevalence_error = ((sim.results.sir.prevalence[-1] - 0.10)**2, None),
prevalence = (sim.results.sir.prevalence[-1], None),
)


#%% Define the tests
def test_calibration(do_plot=False):
sc.heading('Testing calibration')

# Define the calibration parameters
calib_pars = [
dict(name='beta', type='range', bounds=[0.01, 1.0], value_type='float', log_scale=True),
dict(name='dur_inf', type='range', bounds=[1, 60], value_type='float', log_scale=False),
#dict(name='init_prev', type='range', bounds=[0.01, 0.30], value_type='float', log_scale=False),
dict(name='n_contacts', type='range', bounds=[2, 10], value_type='int', log_scale=False),
]

best_pars, values, exp, model = optimize(
experiment_name = 'starsim',
parameters = calib_pars,
evaluation_function = eval_sim,
objective_name = 'prevalence_error',
minimize = True,
parameter_constraints = None,
outcome_constraints = None,
total_trials = 10,
arms_per_trial = 3,
)

return best_pars, values, exp, model


#%% Run as a script
if __name__ == '__main__':

T = sc.timer()
do_plot = True

best_pars, values, exp, model = test_calibration(do_plot=do_plot)

print('best_pars:', best_pars)
print('values:', values)
print('exp:', exp)
print('model:', model)

render(plot_contour(model=model, param_x='beta', param_y='init_prev', metric_name='prevalence'))

# `plot_single_method` expects a 2-d array of means, because it expects to average means from multiple
# optimization runs, so we wrap out best objectives array in another array.

for trial in exp.trials.values():
print(trial)
print(dir(trial))
print(f"Trial {trial.index} with parameters {trial.arm.parameters} "
f"has objective {trial.objective_mean}.")

best_objectives = np.array(
[[trial.objective_mean for trial in exp.trials.values()]]
)
best_objective_plot = optimization_trace_single_method(
y = np.minimum.accumulate(best_objectives, axis=1),
optimum = 0.10, #hartmann6.fmin,
title = "Model performance vs. # of iterations",
ylabel = "Prevalence",
)
render(best_objective_plot)

plt.show()

T.toc()
Loading