Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
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
    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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
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