def check_version(expected, die=False, verbose=True): ''' Get current git information and optionally write it to disk. The expected version string may optionally start with '>=' or '<=' (== is implied otherwise), but other operators (e.g. ~=) are not supported. Note that e.g. '>' is interpreted to mean '>='. Args: expected (str): expected version information die (bool): whether or not to raise an exception if the check fails **Example**:: cv.check_version('>=1.7.0', die=True) # Will raise an exception if an older version is used ''' if expected.startswith('>'): valid = 1 elif expected.startswith('<'): valid = -1 else: valid = 0 # Assume == is the only valid comparison expected = expected.lstrip('<=>') # Remove comparator information version = cvv.__version__ compare = sc.compareversions(version, expected) # Returns -1, 0, or 1 relation = ['older', '', 'newer'][compare + 1] # Picks the right string if relation: # Versions mismatch, print warning or raise error string = f'Note: Covasim is {relation} than expected ({version} vs. {expected})' if die and compare != valid: raise ValueError(string) elif verbose: print(string) return compare
def check_scirisweb(die=False): ''' Check that Scirisweb is available and the right version ''' import sciris as sc # Here since one of the purposes of this file is to check if this exists available['scirisweb'] = True # Assume it works import_error = '' version_error = '' # Try imports try: import scirisweb except ModuleNotFoundError: import_error = 'Scirisweb not found; please rerun setup.py or install via "pip install scirisweb"' if not import_error: ver = scirisweb.__version__ minver = min_versions['scirisweb'] if sc.compareversions(ver, minver) < 0: version_error = f'You have Scirisweb {ver} but {minver} is required; please upgrade via "pip install --upgrade scirisweb"' # Handle consequences if die: if import_error: raise ModuleNotFoundError(import_error) elif version_error: raise ImportError(version_error) else: if import_error: print('Warning: scirisweb was not found; webapp functionality is not available (you can install with "pip install scirisweb")') elif version_error: print(f'Warning: scirisweb is version {ver} but {minver} is required; webapp is disabled (fix with "pip install --upgrade scirisweb")') if import_error or version_error: available['scirisweb'] = False return
def check_sciris(): ''' Check that Sciris is available and the right version ''' try: import sciris as sc except ModuleNotFoundError: errormsg = 'Sciris is a required dependency but is not found; please install via "pip install sciris"' raise ModuleNotFoundError(errormsg) ver = sc.__version__ minver = min_versions['sciris'] if sc.compareversions(ver, minver) < 0: errormsg = f'You have Sciris {ver} but {minver} is required; please upgrade via "pip install --upgrade sciris"' raise ImportError(errormsg) return
def get_version_pars(version, verbose=True): ''' Function for loading parameters from the specified version. Parameters will be loaded for Covasim 'as at' the requested version i.e. the most recent set of parameters that is <= the requested version. Available parameter values are stored in the regression folder. If parameters are available for versions 1.3, and 1.4, then this function will return the following - If parameters for version '1.3' are requested, parameters will be returned from '1.3' - If parameters for version '1.3.5' are requested, parameters will be returned from '1.3', since Covasim at version 1.3.5 would have been using the parameters defined at version 1.3. - If parameters for version '1.4' are requested, parameters will be returned from '1.4' Args: version (str): the version to load parameters from Returns: Dictionary of parameters from that version ''' # Construct a sorted list of available parameters based on the files in the regression folder regression_folder = sc.thisdir(__file__, 'regression', aspath=True) available_versions = [ x.stem.replace('pars_v', '') for x in regression_folder.iterdir() if x.suffix == '.json' ] available_versions = sorted(available_versions, key=LooseVersion) # Find the highest parameter version that is <= the requested version version_comparison = [ sc.compareversions(version, v) >= 0 for v in available_versions ] try: target_version = available_versions[sc.findlast(version_comparison)] except IndexError: errormsg = f"Could not find a parameter version that was less than or equal to '{version}'. Available versions are {available_versions}" raise ValueError(errormsg) # Load the parameters pars = sc.loadjson(filename=regression_folder / f'pars_v{target_version}.json', folder=regression_folder) if verbose: print(f'Loaded parameters from {target_version}') return pars
def check_version(expected, die=False, verbose=True, **kwargs): ''' Get current git information and optionally write it to disk. Args: expected (str): expected version information die (bool): whether or not to raise an exception if the check fails ''' version = cvver.__version__ compare = sc.compareversions(version, expected) # Returns -1, 0, or 1 relation = ['older', '', 'newer'][compare + 1] # Picks the right string if relation: # Not empty, print warning string = f'Note: Covasim is {relation} than expected ({version} vs. {expected})' if die: raise ValueError(string) elif verbose: print(string) return compare
def make_pars(set_prognoses=False, prog_by_age=True, version=None, **kwargs): ''' Create the parameters for the simulation. Typically, this function is used internally rather than called by the user; e.g. typical use would be to do sim = cv.Sim() and then inspect sim.pars, rather than calling this function directly. Args: set_prognoses (bool): whether or not to create prognoses (else, added when the population is created) prog_by_age (bool): whether or not to use age-based severity, mortality etc. kwargs (dict): any additional kwargs are interpreted as parameter names version (str): if supplied, use parameters from this Covasim version Returns: pars (dict): the parameters of the simulation ''' pars = {} # Population parameters pars[ 'pop_size'] = 20e3 # Number of agents, i.e., people susceptible to SARS-CoV-2 pars['pop_infected'] = 20 # Number of initial infections pars[ 'pop_type'] = 'random' # What type of population data to use -- 'random' (fastest), 'synthpops' (best), 'hybrid' (compromise) pars[ 'location'] = None # What location to load data from -- default Seattle # Simulation parameters pars['start_day'] = '2020-03-01' # Start day of the simulation pars['end_day'] = None # End day of the simulation pars['n_days'] = 60 # Number of days to run, if end_day isn't specified pars['rand_seed'] = 1 # Random seed, if None, don't reset pars[ 'verbose'] = cvo.verbose # Whether or not to display information during the run -- options are 0 (silent), 0.1 (some; default), 1 (default), 2 (everything) # Rescaling parameters pars[ 'pop_scale'] = 1 # Factor by which to scale the population -- e.g. pop_scale=10 with pop_size=100e3 means a population of 1 million pars[ 'scaled_pop'] = None # The total scaled population, i.e. the number of agents times the scale factor pars[ 'rescale'] = True # Enable dynamic rescaling of the population -- starts with pop_scale=1 and scales up dynamically as the epidemic grows pars[ 'rescale_threshold'] = 0.05 # Fraction susceptible population that will trigger rescaling if rescaling pars[ 'rescale_factor'] = 1.2 # Factor by which the population is rescaled on each step pars[ 'frac_susceptible'] = 1.0 # What proportion of the population is susceptible to infection # Network parameters, generally initialized after the population has been constructed pars[ 'contacts'] = None # The number of contacts per layer; set by reset_layer_pars() below pars[ 'dynam_layer'] = None # Which layers are dynamic; set by reset_layer_pars() below pars[ 'beta_layer'] = None # Transmissibility per layer; set by reset_layer_pars() below # Basic disease transmission parameters pars['beta_dist'] = dict( dist='neg_binomial', par1=1.0, par2=0.45, step=0.01 ) # Distribution to draw individual level transmissibility; dispersion from https://www.researchsquare.com/article/rs-29548/v1 pars['viral_dist'] = dict( frac_time=0.3, load_ratio=2, high_cap=4 ) # The time varying viral load (transmissibility); estimated from Lescure 2020, Lancet, https://doi.org/10.1016/S1473-3099(20)30200-0 pars[ 'beta'] = 0.016 # Beta per symptomatic contact; absolute value, calibrated pars[ 'asymp_factor'] = 1.0 # Multiply beta by this factor for asymptomatic cases; no statistically significant difference in transmissibility: https://www.sciencedirect.com/science/article/pii/S1201971220302502 # Parameters that control settings and defaults for multi-variant runs pars[ 'n_imports'] = 0 # Average daily number of imported cases (actual number is drawn from Poisson distribution) pars[ 'n_variants'] = 1 # The number of variants circulating in the population # Parameters used to calculate immunity pars[ 'use_waning'] = False # Whether to use dynamically calculated immunity pars['nab_init'] = dict( dist='normal', par1=0, par2=2 ) # Parameters for the distribution of the initial level of log2(nab) following natural infection, taken from fig1b of https://doi.org/10.1101/2021.03.09.21252641 pars['nab_decay'] = dict(form='nab_growth_decay', growth_time=22, decay_rate1=np.log(2) / 100, decay_time1=250, decay_rate2=np.log(2) / 3650, decay_time2=365) pars[ 'nab_kin'] = None # Constructed during sim initialization using the nab_decay parameters pars[ 'nab_boost'] = 1.5 # Multiplicative factor applied to a person's nab levels if they get reinfected. # TODO: add source pars['nab_eff'] = dict( alpha_inf=3.5, beta_inf=1.219, alpha_symp_inf=-1.06, beta_symp_inf=0.867, alpha_sev_symp=0.268, beta_sev_symp=3.4) # Parameters to map nabs to efficacy pars['rel_imm_symp'] = dict( asymp=0.85, mild=1, severe=1.5 ) # Relative immunity from natural infection varies by symptoms pars[ 'immunity'] = None # Matrix of immunity and cross-immunity factors, set by init_immunity() in immunity.py # Variant-specific disease transmission parameters. By default, these are set up for a single variant, but can all be modified for multiple variants pars['rel_beta'] = 1.0 # Relative transmissibility varies by variant pars['rel_imm_variant'] = 1.0 # Relative own-immmunity varies by variant # Duration parameters: time for disease progression pars['dur'] = {} pars['dur']['exp2inf'] = dict( dist='lognormal_int', par1=4.5, par2=1.5 ) # Duration from exposed to infectious; see Lauer et al., https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7081172/, appendix table S2, subtracting inf2sym duration pars['dur']['inf2sym'] = dict( dist='lognormal_int', par1=1.1, par2=0.9 ) # Duration from infectious to symptomatic; see Linton et al., https://doi.org/10.3390/jcm9020538, from Table 2, 5.6 day incubation period - 4.5 day exp2inf from Lauer et al. pars['dur']['sym2sev'] = dict( dist='lognormal_int', par1=6.6, par2=4.9 ) # Duration from symptomatic to severe symptoms; see Linton et al., https://doi.org/10.3390/jcm9020538, from Table 2, 6.6 day onset to hospital admission (deceased); see also Wang et al., https://jamanetwork.com/journals/jama/fullarticle/2761044, 7 days (Table 1) pars['dur']['sev2crit'] = dict( dist='lognormal_int', par1=1.5, par2=2.0 ) # Duration from severe symptoms to requiring ICU; average of 1.9 and 1.0; see Chen et al., https://www.sciencedirect.com/science/article/pii/S0163445320301195, 8.5 days total - 6.6 days sym2sev = 1.9 days; see also Wang et al., https://jamanetwork.com/journals/jama/fullarticle/2761044, Table 3, 1 day, IQR 0-3 days; std=2.0 is an estimate # Duration parameters: time for disease recovery pars['dur']['asym2rec'] = dict( dist='lognormal_int', par1=8.0, par2=2.0 ) # Duration for asymptomatic people to recover; see Wölfel et al., https://www.nature.com/articles/s41586-020-2196-x pars['dur']['mild2rec'] = dict( dist='lognormal_int', par1=8.0, par2=2.0 ) # Duration for people with mild symptoms to recover; see Wölfel et al., https://www.nature.com/articles/s41586-020-2196-x pars['dur']['sev2rec'] = dict( dist='lognormal_int', par1=18.1, par2=6.3 ) # Duration for people with severe symptoms to recover, 24.7 days total; see Verity et al., https://www.thelancet.com/journals/laninf/article/PIIS1473-3099(20)30243-7/fulltext; 18.1 days = 24.7 onset-to-recovery - 6.6 sym2sev; 6.3 = 0.35 coefficient of variation * 18.1; see also https://doi.org/10.1017/S0950268820001259 (22 days) and https://doi.org/10.3390/ijerph17207560 (3-10 days) pars['dur']['crit2rec'] = dict( dist='lognormal_int', par1=18.1, par2=6.3 ) # Duration for people with critical symptoms to recover; as above (Verity et al.) pars['dur']['crit2die'] = dict( dist='lognormal_int', par1=10.7, par2=4.8 ) # Duration from critical symptoms to death, 18.8 days total; see Verity et al., https://www.thelancet.com/journals/laninf/article/PIIS1473-3099(20)30243-7/fulltext; 10.7 = 18.8 onset-to-death - 6.6 sym2sev - 1.5 sev2crit; 4.8 = 0.45 coefficient of variation * 10.7 # Severity parameters: probabilities of symptom progression pars[ 'rel_symp_prob'] = 1.0 # Scale factor for proportion of symptomatic cases pars[ 'rel_severe_prob'] = 1.0 # Scale factor for proportion of symptomatic cases that become severe pars[ 'rel_crit_prob'] = 1.0 # Scale factor for proportion of severe cases that become critical pars[ 'rel_death_prob'] = 1.0 # Scale factor for proportion of critical cases that result in death pars[ 'prog_by_age'] = prog_by_age # Whether to set disease progression based on the person's age pars[ 'prognoses'] = None # The actual arrays of prognoses by age; this is populated later # Efficacy of protection measures pars[ 'iso_factor'] = None # Multiply beta by this factor for diagnosed cases to represent isolation; set by reset_layer_pars() below pars[ 'quar_factor'] = None # Quarantine multiplier on transmissibility and susceptibility; set by reset_layer_pars() below pars[ 'quar_period'] = 14 # Number of days to quarantine for; assumption based on standard policies # Events and interventions pars['interventions'] = [ ] # The interventions present in this simulation; populated by the user pars['analyzers'] = [] # Custom analysis functions; populated by the user pars['timelimit'] = None # Time limit for the simulation (seconds) pars[ 'stopping_func'] = None # A function to call to stop the sim partway through # Health system parameters pars[ 'n_beds_hosp'] = None # The number of hospital (adult acute care) beds available for severely ill patients (default is no constraint) pars[ 'n_beds_icu'] = None # The number of ICU beds available for critically ill patients (default is no constraint) pars[ 'no_hosp_factor'] = 2.0 # Multiplier for how much more likely severely ill people are to become critical if no hospital beds are available pars[ 'no_icu_factor'] = 2.0 # Multiplier for how much more likely critically ill people are to die if no ICU beds are available # Handle vaccine and variant parameters pars['vaccine_pars'] = { } # Vaccines that are being used; populated during initialization pars['vaccine_map'] = {} #Reverse mapping from number to vaccine key pars['variants'] = [ ] # Additional variants of the virus; populated by the user, see immunity.py pars['variant_map'] = { 0: 'wild' } # Reverse mapping from number to variant key pars['variant_pars'] = dict(wild={}) # Populated just below for sp in cvd.variant_pars: if sp in pars.keys(): pars['variant_pars']['wild'][sp] = pars[sp] # Update with any supplied parameter values and generate things that need to be generated pars.update(kwargs) reset_layer_pars(pars) if set_prognoses: # If not set here, gets set when the population is initialized pars['prognoses'] = get_prognoses( pars['prog_by_age'], version=version) # Default to age-specific prognoses # If version is specified, load old parameters if version is not None: version_pars = cvm.get_version_pars(version, verbose=pars['verbose']) for key in pars.keys(): # Only loop over keys that have been populated if key in version_pars: # Only replace keys that exist in the old version pars[key] = version_pars[key] # Handle code change migration if sc.compareversions( version, '2.1.0') == -1 and 'migrate_lognormal' not in pars: cvm.migrate_lognormal(pars, verbose=pars['verbose']) return pars
def migrate(obj, update=True, verbose=True, die=False): ''' Define migrations allowing compatibility between different versions of saved files. Usually invoked automatically upon load, but can be called directly by the user to load custom objects, e.g. lists of sims. Currently supported objects are sims, multisims, scenarios, and people. Args: obj (any): the object to migrate update (bool): whether to update version information to current version after successful migration verbose (bool): whether to print warnings if something goes wrong die (bool): whether to raise an exception if something goes wrong Returns: The migrated object **Example**:: sims = cv.load('my-list-of-sims.obj') sims = [cv.migrate(sim) for sim in sims] ''' from . import base as cvb from . import run as cvr from . import interventions as cvi # Migrations for simulations if isinstance(obj, cvb.BaseSim): sim = obj # Migration from <2.0.0 to 2.0.0 if sc.compareversions(sim.version, '2.0.0') == -1: # Migrate from <2.0 to 2.0 if verbose: print( f'Migrating sim from version {sim.version} to version {cvv.__version__}' ) # Add missing attribute if not hasattr(sim, '_default_ver'): sim._default_ver = None # Recursively migrate people if needed if sim.people: sim.people = migrate(sim.people, update=update) # Rename intervention attribute tps = sim.get_interventions(cvi.test_prob) for tp in tps: # pragma: no cover try: tp.sensitivity = tp.test_sensitivity del tp.test_sensitivity except: pass # Migrations for People elif isinstance(obj, cvb.BasePeople): # pragma: no cover ppl = obj if not hasattr(ppl, 'version'): # For people prior to 2.0 if verbose: print( f'Migrating people from version <2.0 to version {cvv.__version__}' ) cvb.set_metadata(ppl) # Set all metadata # Migrations for MultiSims -- use recursion elif isinstance(obj, cvr.MultiSim): msim = obj msim.base_sim = migrate(msim.base_sim, update=update) msim.sims = [migrate(sim, update=update) for sim in msim.sims] if not hasattr(msim, 'version'): # For msims prior to 2.0 if verbose: print( f'Migrating multisim from version <2.0 to version {cvv.__version__}' ) cvb.set_metadata(msim) # Set all metadata msim.label = None # Migrations for Scenarios elif isinstance(obj, cvr.Scenarios): scens = obj scens.base_sim = migrate(scens.base_sim, update=update) for key, simlist in scens.sims.items(): scens.sims[key] = [migrate(sim, update=update) for sim in simlist] # Nested loop if not hasattr(scens, 'version'): # For scenarios prior to 2.0 if verbose: print( f'Migrating scenarios from version <2.0 to version {cvv.__version__}' ) cvb.set_metadata(scens) # Set all metadata scens.label = None # Unreconized object type else: errormsg = f'Object {obj} of type {type(obj)} is not understood and cannot be migrated: must be a sim, multisim, scenario, or people object' if die: raise TypeError(errormsg) elif verbose: # pragma: no cover print(errormsg) return # If requested, update the stored version to the current version if update: obj.version = cvv.__version__ return obj
def test_metadata(): sp.logger.info("Testing that the version is greater than 1.5.0") pop = sp.Pop(n=100) assert sc.compareversions( pop.version, '1.5.0' ) == 1 # to check that the version of synthpops is higher than 1.5.0
def migrate(obj, update=True, verbose=True, die=False): ''' Define migrations allowing compatibility between different versions of saved files. Usually invoked automatically upon load, but can be called directly by the user to load custom objects, e.g. lists of sims. Currently supported objects are sims, multisims, scenarios, and people. Args: obj (any): the object to migrate update (bool): whether to update version information to current version after successful migration verbose (bool): whether to print warnings if something goes wrong die (bool): whether to raise an exception if something goes wrong Returns: The migrated object **Example**:: sims = cv.load('my-list-of-sims.obj') sims = [cv.migrate(sim) for sim in sims] ''' from . import base as cvb # To avoid circular imports from . import run as cvr from . import interventions as cvi unknown_version = '1.9.9' # For objects without version information, store the "last" version before 2.0.0 # Migrations for simulations if isinstance(obj, cvb.BaseSim): sim = obj # Recursively migrate people if needed if sim.people: sim.people = migrate(sim.people, update=update) # Migration from <2.0.0 to 2.0.0 if sc.compareversions(sim.version, '<2.0.0'): # Migrate from <2.0 to 2.0 if verbose: print(f'Migrating sim from version {sim.version} to version {cvv.__version__}') # Add missing attribute if not hasattr(sim, '_default_ver'): sim._default_ver = None # Rename intervention attribute tps = sim.get_interventions(cvi.test_prob) for tp in tps: # pragma: no cover try: tp.sensitivity = tp.test_sensitivity del tp.test_sensitivity except: pass # Migration from <2.1.0 to 2.1.0 if sc.compareversions(sim.version, '<2.1.0'): if verbose: print(f'Migrating sim from version {sim.version} to version {cvv.__version__}') print('Note: updating lognormal stds to restore previous behavior; see v2.1.0 changelog for details') migrate_lognormal(sim.pars, verbose=verbose) # Migration from <3.0.0 to 3.0.0 if sc.compareversions(sim.version, '<3.0.0'): if verbose: print(f'Migrating sim from version {sim.version} to version {cvv.__version__}') print('Adding variant parameters') migrate_variants(sim.pars, verbose=verbose) # Migration from <3.1.1 to 3.1.1 if sc.compareversions(sim.version, '<3.1.1'): sim._legacy_trans = True # Migrations for People elif isinstance(obj, cvb.BasePeople): # pragma: no cover ppl = obj # Migration from <2.0.0 to 2.0 if not hasattr(ppl, 'version'): # For people prior to 2.0 if verbose: print(f'Migrating people from version <2.0 to "unknown version" ({unknown_version})') cvb.set_metadata(ppl, version=unknown_version) # Set all metadata # # Migration from <3.1.2 to 3.1.2 if sc.compareversions(ppl.version, '<3.1.2'): if verbose: print(f'Migrating people from version {ppl.version} to version {cvv.__version__}') print('Adding infected_initialized') if not hasattr(ppl, 'infected_initialized'): ppl.infected_initialized = True # Migrations for MultiSims -- use recursion elif isinstance(obj, cvr.MultiSim): msim = obj msim.base_sim = migrate(msim.base_sim, update=update) msim.sims = [migrate(sim, update=update) for sim in msim.sims] if not hasattr(msim, 'version'): # For msims prior to 2.0 if verbose: print(f'Migrating multisim from version <2.0 to "unknown version" ({unknown_version})') cvb.set_metadata(msim, version=unknown_version) # Set all metadata msim.label = None # Migrations for Scenarios elif isinstance(obj, cvr.Scenarios): scens = obj scens.base_sim = migrate(scens.base_sim, update=update) for key,simlist in scens.sims.items(): scens.sims[key] = [migrate(sim, update=update) for sim in simlist] # Nested loop if not hasattr(scens, 'version'): # For scenarios prior to 2.0 if verbose: print(f'Migrating scenarios from version <2.0 to "unknown version" ({unknown_version})') cvb.set_metadata(scens, version=unknown_version) # Set all metadata scens.label = None # Unreconized object type else: errormsg = f'Object {obj} of type {type(obj)} is not understood and cannot be migrated: must be a sim, multisim, scenario, or people object' warn(errormsg, errtype=TypeError, verbose=verbose, die=die) if die: raise TypeError(errormsg) elif verbose: # pragma: no cover print(errormsg) return # If requested, update the stored version to the current version if update: obj.version = cvv.__version__ return obj