Source code for emod_api.interventions.utils

from emod_api import schema_to_class as s2c
import json
import copy

cached_NSA = None
cached_NSNL = None


[docs]def do_nodes(schema_path, node_ids: list = None): """ Create and return a NodeSetConfig based on node_ids list. Args: schema_path: Path to schema.json file. node_ids: a list of NodeIDs, defaults to None, which is NodeSetAll Returns: Well-configured NodeSetConfig """ if node_ids and len(node_ids) > 0: global cached_NSNL if cached_NSNL is None: cached_NSNL = s2c.get_class_with_defaults("NodeSetNodeList", schema_path) nodelist = copy.deepcopy(cached_NSNL) nodelist.Node_List = node_ids else: global cached_NSA if cached_NSA is None: cached_NSA = s2c.get_class_with_defaults("NodeSetAll", schema_path) nodelist = copy.deepcopy(cached_NSA) return nodelist
[docs]def get_waning_from_params(schema_path, initial=1.0, box_duration=365, decay_rate=0, decay_time_constant=None): """ Get well-configured waning structure. Default is 1-year full efficacy box. Note that an infinite decay rate (0 or even -1) is same as WaningEffectBox. Note that an infinite box duration (-1) is same as WaningEffectConstant. Note that a zero box duration is same as WaningEffectExponential. Args: schema_path: Path to schema.json file. initial: Initial_Effect value, defaults to 1.0. box_duration: Number of timesteps efficacy remains at Initial_Effect before decay. Defaults to 365. decay_rate: Rate at which Initial_Effect decays after box_duration. Defaults to 0. decay_time_constant: 1/decay_rate. Defaults to None. Use this or decay_rate, not both. If this is specified, decay_rate is ignored. Returns: A well-configured WaningEffect structure .. deprecated:: 2.x Please use function get_waning_from_parameters() or get_waning_from_points(). """ waning = get_waning_from_parameters(schema_path, initial, box_duration, decay_rate, decay_time_constant) return waning
[docs]def get_waning_from_points(schema_path, initial: float = 1.0, times_values=None, expire_at_end: bool = False): """ Get well-configured waning structure. Args: schema_path: Path to schema.json file. initial: Initial_Effect value, defaults to 1.0. times_values: A list of tuples with days and values. The values match the defined linear values that modify the Initial_Effect, e.g. [(day_0, value_0), (day_5, value_5)]. expire_at_end: Set to 1 to have efficacy go to zero and let the intervention expire when the end of the map is reached. Only vaccines and bednet usage currently support this expiration feature. defaults to 0. Returns: A well-configured WaningEffectMapLinear structure """ if times_values is None: raise ValueError(f"ERROR: No points to create a waning function given.") if s2c.uses_old_waning(schema_path): waning = s2c.get_class_with_defaults("WaningEffectMapLinear", schema_path) waning.Expire_At_Durability_Map_End = int(expire_at_end) else: waning = s2c.get_class_with_defaults("WaningEffect", schema_path) times, values = zip(*times_values) waning.Durability_Map.Times = list(times) waning.Durability_Map.Values = list(values) waning.Initial_Effect = initial return waning
[docs]def get_waning_from_parameters(schema_path, initial: float = 1.0, box_duration: float = 365, decay_rate: float = 0, decay_time_constant: float = None): """ Get well-configured waning structure. Default is 1-year full efficacy box. Note that an infinite decay rate (0 or even -1) is same as WaningEffectBox. Note that an infinite box duration (-1) is same as WaningEffectConstant. Note that a zero box duration is same as WaningEffectExponential. Args: schema_path: Path to schema.json file. initial: Initial_Effect value, defaults to 1.0. box_duration: Number of timesteps efficacy remains at Initial_Effect before decay. Defaults to 365. decay_rate: Rate at which Initial_Effect decays after box_duration. Defaults to 0. decay_time_constant: 1/decay_rate. Defaults to None. Use this or decay_rate, not both. If this is specified, decay_rate is ignored. Returns: A well-configured WaningEffect structure """ if s2c.uses_old_waning(schema_path): if box_duration == -1: waning = s2c.get_class_with_defaults("WaningEffectConstant", schema_path) else: if decay_time_constant is None: decay_time_constant = 1.0/decay_rate if decay_rate > 0 else 0 if box_duration == 0 and decay_time_constant > 0: waning = s2c.get_class_with_defaults("WaningEffectExponential", schema_path) waning.Decay_Time_Constant = decay_time_constant elif box_duration > 0 and decay_time_constant == 0: waning = s2c.get_class_with_defaults("WaningEffectBox", schema_path) waning.Box_Duration = box_duration elif box_duration > 0 and decay_time_constant > 0: waning = s2c.get_class_with_defaults("WaningEffectBoxExponential", schema_path) waning.Decay_Time_Constant = decay_time_constant waning.Box_Duration = box_duration else: raise ValueError(f"ERROR: Cannot create a valid waning structure with box_duration={box_duration} and decay_time_constant={decay_time_constant}.") waning.Initial_Effect = initial else: waning = s2c.get_class_with_defaults("WaningEffect", schema_path) waning.Initial_Effect = initial if decay_time_constant is None: decay_time_constant = 1.0 / decay_rate if decay_rate > 0 else 0 waning.Decay_Time_Constant = decay_time_constant if box_duration != -1: waning.Box_Duration = box_duration return waning
def _convert_prs(kvps): """ This function is not intended to be called directly by users but rather by emod_api.interventions.common API functions. It is intended to allow users to target interventions to individuals with specific individual properties without having to worry about the particular data structures that the DTK uses. The permissible structures do need to be documented in the relevant API functions. kvps needs to be one of: - "key:value" - "key=value" - "key1:value1,key2:value2" - "key1=value1,key2=value2" - { "key": "value" } - { "key1": "value1", "key2": "value2" } - [ { "key1": "value1", "key2": "value2" }, { "key3": "value3", "key4": "value4" } ] - [ "key:value" ] - [ "key1:value1", "key2:value2" ] Note that the key-value-pairs _values_ are not currently validated against the selected demographics file. In the current implementation, the return value is prepped as a value for Property_Restrictions_Within_Nodes Args: kvps: any of the above Returns: list of dictionaries of key-value-pairs where each dict can only contain a given key once. E.g., [ { "Risk": "High" } ] """ def parse_list_of_strings(entries): ret_kvps = [] prs_as_dict = dict() for entry in entries: if ":" in entry: key, value = entry.split(':') prs_as_dict[key.strip()] = value.strip() elif "=" in entry: key, value = entry.split('=') prs_as_dict[key.strip()] = value.strip() else: raise ValueError( f"ERROR: malformed property targets: {kvps}. " f"Can't figure out where the keys and values are in at least one of the list entries.") ret_kvps = [prs_as_dict] return ret_kvps ret_kvps = [] if kvps is None or kvps == "": ret_kvps = list() # just happen to know default is empty list, not getting from schema elif type(kvps) is str and len(kvps) > 0: entries = kvps.split(',') ret_kvps = parse_list_of_strings(entries) elif type(kvps) is dict: ret_kvps = [kvps] elif type(kvps) is list: if len(kvps) > 0: if type(kvps[0]) in [dict, set]: ret_kvps = kvps # could check the contents of the list... elif type(kvps[0]) is str: ret_kvps = parse_list_of_strings(kvps) else: raise ValueError( f"ERROR: malformed property targets: {kvps}. The input is a list but not of strings or dicts.") else: raise ValueError(f"ERROR: malformed property targets: {kvps}.") # TBD: Let's keep simple things simple and leave complex solutions for complex cases. # Do not return list of dicts unless complex case. if len(ret_kvps) == 1: string_list = [] for key, value in dict(ret_kvps[0]).items(): string_list.append(f"{key}:{value}") ret_kvps = string_list return ret_kvps