Skip to content

Commit

Permalink
Merge pull request #307 from atomicateam/develop
Browse files Browse the repository at this point in the history
Constrain output parameters
  • Loading branch information
RomeshA authored Mar 22, 2019
2 parents 779df2d + 4a11b26 commit 62492c4
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 20 deletions.
Binary file modified atomica/library/diabetes_databook.xlsx
Binary file not shown.
14 changes: 13 additions & 1 deletion atomica/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,16 @@ def model_tidying(proj):
for prog in progset.programs.values():
prog.capacity_constraint = prog.capacity
del prog.capacity
return proj
return proj

@migration('1.0.27', '1.0.28', 'Rename link labels')
def model_tidying(proj):

# Normalize link labels - they should now always derive from their associated parameter
for result in all_results(proj):
for pop in result.model.pops:
for link in pop.links:
link.id = link.id[0:3] + (link.parameter.name + ':flow',)
result.model.set_vars_by_pop()

return proj
28 changes: 18 additions & 10 deletions atomica/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ class Link(Variable):

#
# *** Link values are always dt-based ***
def __init__(self, pop, parameter, source, dest, tag):
def __init__(self, pop, parameter, source, dest, tag=None):
# Note that the Link's name is the transition tag
Variable.__init__(self, pop=pop, id=(pop.name, source.name, dest.name, tag)) # A Link is only uniquely identified by (Pop,Source,Dest,Par)
self.vals = None
Expand Down Expand Up @@ -1015,7 +1015,7 @@ def __init__(self, settings, framework, parset, progset=None, program_instructio
self._vars_by_pop = None # Cache to look up lists of variables by name across populations
self._pop_ids = sc.odict() # Maps name of a population to its position index within populations list.
self._program_cache = None
self._par_list = list(framework.pars.index) # This is a list of all parameters code names in the model
self._par_list = None # This is a list of all parameters code names in the model

self.framework = sc.dcp(framework) # Store a copy of the Framework used to generate this model
self.framework.spreadsheet = None # No need to keep the spreadsheet
Expand Down Expand Up @@ -1078,10 +1078,14 @@ def update_program_cache(self):

def set_vars_by_pop(self):
self._vars_by_pop = defaultdict(list)
par_names = []
for pop in self.pops:
for var in pop.comps + pop.characs + pop.pars + pop.links:
self._vars_by_pop[var.name].append(var)
for par in pop.pars:
par_names.append(par.name)
self._vars_by_pop = dict(self._vars_by_pop) # Stop new entries from appearing in here by accident
self._par_list = list(sc.odict.fromkeys(par_names))

def __getstate__(self):
self.unlink()
Expand Down Expand Up @@ -1148,19 +1152,19 @@ def build(self, settings, parset):
# For each population pair, instantiate a Parameter with the values from the databook
# For each compartment, instantiate a set of Links that all derive from that Parameter
# NB. If a Program somehow targets the transfer parameter, those values will automatically... what?
for trans_type in parset.transfers:
if parset.transfers[trans_type]:
for pop_source in parset.transfers[trans_type]:
for transfer_name in parset.transfers:
if parset.transfers[transfer_name]:
for pop_source in parset.transfers[transfer_name]:

# This contains the data for all of the destination pops.
transfer_parameter = parset.transfers[trans_type][pop_source]
transfer_parameter = parset.transfers[transfer_name][pop_source]

pop = self.get_pop(pop_source)

for pop_target in transfer_parameter.ts:

# Create the parameter object for this link (shared across all compartments)
par_name = trans_type + '_' + pop_source + '_to_' + pop_target # e.g. 'aging_0-4_to_15-64'
par_name = "%s_%s_to_%s" % (transfer_name, pop_source , pop_target) # e.g. 'aging_0-4_to_15-64'
par = Parameter(pop=pop, name=par_name)
par.preallocate(self.t, self.dt)
par.scale_factor = transfer_parameter.y_factor[pop_target] * transfer_parameter.meta_y_factor
Expand All @@ -1175,8 +1179,7 @@ def build(self, settings, parset):
if not (source.tag_birth or source.tag_dead or source.is_junction):
# Instantiate a link between corresponding compartments
dest = target_pop_obj.get_comp(source.name) # Get the corresponding compartment
link_tag = par_name + '_' + source.name + ':flow' # e.g. 'aging_0-4_to_15-64_sus:flow'
link = Link(pop, par, source, dest, link_tag)
link = Link(pop, par, source, dest, par.name + ':flow')
link.preallocate(self.t, self.dt)
pop.links.append(link)
if link.name in pop.link_lookup:
Expand Down Expand Up @@ -1212,7 +1215,12 @@ def process(self):
self.update_junctions()

for pop in self.pops:
[par.update() for par in pop.pars if (par.fcn_str and not (par._is_dynamic or par._precompute))] # Update any remaining parameters

for par in pop.pars:
if par.fcn_str and not (par._is_dynamic or par._precompute):
par.update()
par.constrain()

for charac in pop.characs:
charac._vals = None # Wipe out characteristic vals to save space

Expand Down
16 changes: 13 additions & 3 deletions atomica/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def from_spreadsheet(spreadsheet=None, framework=None, data=None, project=None,
# Load individual sheets
self._read_targeting(workbook['Program targeting'])
self._read_spending(workbook['Spending data'], _allow_missing_data=_allow_missing_data)
self._read_effects(workbook['Program effects'])
self._read_effects(workbook['Program effects'], framework=framework, data=data)

return self

Expand Down Expand Up @@ -732,17 +732,27 @@ def _write_spending(self):

apply_widths(sheet, widths)

def _read_effects(self, sheet):
def _read_effects(self, sheet, framework, data):
# Read the program effects sheet. Here we instantiate a costcov object for every non-empty row

tables, start_rows = read_tables(sheet)
pop_codenames = {v.lower().strip(): x for x, v in self.pops.items()}
par_codenames = {v.lower().strip(): x for x, v in self.pars.items()}
transfer_names = set()
for transfer in data.transfers:
for pops in transfer.ts.keys():
transfer_names.add(('%s_%s_to_%s' % (transfer.code_name,pops[0],pops[1])).lower())

self.covouts = sc.odict()

for table in tables:
par_name = par_codenames[table[0][0].value.strip().lower()] # Code name of the parameter we are working with
full_name = table[0][0].value.strip().lower()
if full_name in par_codenames:
par_name = par_codenames[table[0][0].value.strip().lower()] # Code name of the parameter we are working with
elif full_name in transfer_names:
par_name = table[0][0].value.strip() # Preserve case
else:
raise Exception('Program name "%s" was not found in the framework parameters or in the databook transfers' % (table[0][0].value.strip()))
headers = [x.value.strip() if sc.isstring(x.value) else x.value for x in table[0]]
idx_to_header = {i: h for i, h in enumerate(headers)} # Map index to header

Expand Down
4 changes: 2 additions & 2 deletions atomica/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import sciris as sc

version = "1.0.26"
versiondate = "2019-03-11"
version = "1.0.28"
versiondate = "2019-03-19"
gitinfo = sc.gitinfo(__file__, verbose=False)

6 changes: 3 additions & 3 deletions docs/examples/Uncertainty.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"![image.png](assets/databook_uncertainty.png)\n"
"![image1](assets/databook_uncertainty.png)\n"
]
},
{
Expand All @@ -51,8 +51,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"![image.png](assets/progbook_spending_uncertainty.png)\n",
"![image.png](assets/progbook_outcome_uncertainty.png)\n"
"![image2](assets/progbook_spending_uncertainty.png)\n",
"![image3](assets/progbook_outcome_uncertainty.png)\n"
]
},
{
Expand Down
11 changes: 10 additions & 1 deletion docs/general/programs/Programs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,13 @@ Finally, note that in the case where there are multiple programs, the program co
.. image:: number_outcome_case3.png
:width: 800px

This is depicted visually above, assuming that the coverage interaction is additive, and we correctly recover the expected result that 267 people would be diagnosed.
This is depicted visually above, assuming that the coverage interaction is additive, and we correctly recover the expected result that 267 people would be diagnosed.

Targeting transfers
===================

An advanced feature in the Programs system is the ability to target transfers between populations. This allows movements in and out of key populations to be affected by programs. It is not aimed at general use, and is therefore slightly more involved.

Transfers between populations are handled in the same way as normal transitions - there is a parameter in the `Model` governing the flow rate, with values supplied from the 'Transfers' sheet of the databook, and a set of `Links` between every compartment in the source and destination. These items don't appear explicitly in the Framework (in the transition matrix or in the parameter list) because they are specific to the populations defined in the databook. The key things to be aware of are

- The name of the Parameter is `'<transfer name>_<source_pop>_<dest_pop>'` so for example, if the transfer had code name 'inc' for incarceration from the `15-64` population to the `Prisoners` population,

0 comments on commit 62492c4

Please sign in to comment.