Module epispot.comps

The compartments module contains pre-built disease compartments for basic modelling and allows for custom user-defined compartments. This module consists of several classes, each representing a specific compartment.

Structure:

  • Susceptible(object)
  • Infected(object)
  • Recovered(object)
  • Exposed(object)
  • Dead(object)
  • Hospitalized(object)
  • Critical(object)
  • Idiom(object)
Expand source code
"""
The `compartments` module contains pre-built disease compartments for basic modelling and allows for
custom user-defined compartments. This module consists of several classes, each representing a specific
compartment.

## Structure:

- Susceptible(object)
- Infected(object)
- Recovered(object)
- Exposed(object)
- Dead(object)
- Hospitalized(object)
- Critical(object)
- Idiom(object)
"""

from . import warnings


class Susceptible(object):
    """
    The Susceptible class is the 'S' of the 'SIR' Model.
    This is the portion of individuals who have not yet been exposed to the disease.
    This class can be used as a beginning state.

    Recovered (?) --> Susceptible --> Infected

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, R_0, gamma, N, p_resusceptibility=None, s_rate=None):
        """
        Initialize the Susceptible class
        
        - layer_index: index of layer in `layers`
        - R_0: the basic reproductive number--
            this is the average number of susceptibles infected by one infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: the infectious period--
            1 / average duration of infectious period\
            implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - N: the total population\
            implemented as a function N(t):
            - t: time
            - return: total population
        - p_resusceptibility: =None, probability of re-susceptibility (0 <= x <= 1)--
           only applicable if individuals can become susceptible again\
           implemented as a function p_resusceptibility(t):
            - t: time
            - return: probability of re-susceptibility
        - s_rate: =None, 1 / average time to become susceptible again--
           only applicable if individuals can become susceptible again\
           implemented as a function s_rate(t):
            - t: time
            - return: susceptiblity rate
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.N = N

        self.p_resusceptibility = p_resusceptibility
        self.s_rate = s_rate

        self.infected_category_indices = []
        self.prev_layer_indices = []
        self.first_layer = True

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for i in range(0, len(layer_names)):
            if layer_names[i] == 'Infected':
                self.infected_category_indices.append(i)

        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.first_layer = False
                    self.prev_layer_indices.append(layer_no)

        # tests
        if not self.first_layer and not self.s_rate:  # pragma: no cover
            warnings.warn('The Susceptible layer at %s is not the first layer and there does not seem \n'
                          'to be any specified susceptibility rate. You can specify this \n'
                          'by passing `s_rate=Value` into this layer.' % self.layer_index)

        for prev_layer_index in self.prev_layer_indices:
            if layer_names[prev_layer_index] != 'Removed' and \
               layer_names[prev_layer_index] != 'Recovered':  # pragma: no cover
                warnings.warn('Previous layer at %s to the Susceptible layer at %s is neither Removed or \n'
                              'Recovered. If you want to create a layer which does this, add a custom \n'
                              'layer through `add_layer`. If not, fix the `layer_map`.' % (prev_layer_index,
                                                                                           self.layer_index))

    def get_deriv(self, time, system):
        """
        Derivative of the Susceptible compartment
        must be the *only* Susceptible compartment which people from other layers may enter

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_infecteds = 0
        for infected_category_index in self.infected_category_indices:
            total_infecteds += system[infected_category_index]

        derivative = - self.gamma(time) * self.R_0(time) * system[self.layer_index] * \
                       total_infecteds / self.N(time)

        if self.first_layer:
            return derivative

        else:
            possible_new_susceptibles = 0
            for prev_layer_index in self.prev_layer_indices:
                possible_new_susceptibles += system[prev_layer_index]

            derivative += self.p_resusceptibility(time) * self.s_rate(time) * possible_new_susceptibles
            return derivative


class Infected(object):
    """
    The Infected class is the 'I' of the 'SIR' Model.
    This is the portion of individuals who are actively spreading the disease.

    Susceptible, Exposed --> Infected --> Recovered, Hospitalized, Critical, Dead

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, N, R_0=None, gamma=None, delta=None, p_recovery=None, recovery_rate=None,
                 p_hospitalized=None, hospital_rate=None, p_critical=None, critical_rate=None,
                 p_death=None, death_rate=None):
        """
        Initialize the Infected class

        - layer_index: index of layer in `layers`
        - N: the total population\
          implemented as a function N(t):
            - t: time
            - return: total population
        - R_0: =None, the basic reproductive number (only applicable if previous layer is Susceptible)--
            this is the average number of Susceptibles infected by one Infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: =None, the infectious period (only applicable if previous layer is Susceptible)--
          1 / average duration of infectious period\
          implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - delta: =None, the incubation period (only applicable if previous layer is Exposed)\
          implemented as a function delta(t)--in most cases this should stay constant
            - t: time
            - return: incubation period
        - p_recovery: =None, probability of recovery--
          (only applicable if next layer is Recovered)\
          implemented as a function p_recovery(t):
            - t: time
            - return: probability of recovery
        - recovery_rate: =None, the recovery rate--different from the standard recovery rate `gamma`--
            measures only 1 / the time it takes to move to the Recovered layer
            (only applicable if next layer is Recovered)\
            implemented as a function recovery_rate(t):
            - t: time
            - return: recovery rate
        - p_hospitalized: =None, probability of hospitalization--
          (only applicable if next layer is Hospitalized)\
          implemented as a function p_hospitalized(t):
            - t: time
            - return: probability of hospitalization
        - hospital_rate: =None, 1 / average time to hospitalization--
          (only applicable if next layer is Hospitalized)\
          implemented as a function hospital_rate(t)
            - t: time
            - return: hospitalization rate
        - p_critical: =None, probability of becoming a critical patient--
          (only applicable if next layer is Critical)\
          implemented as a function p_critical(t)
            - t: time
            - return: critical probability
        - critical_rate: =None, 1 / average time to becoming a critical patient--
          (only applicable if next layer is Critical)\
          implemented as a function critical_rate(t)
            - t: time
            - return: critical rate
        - p_death: =None, probability of death (only applicable if next layer is Dead)\
          implemented as a function p_death(t)
            - t: time
            - return: death probability
        - death_rate: =None, 1 / rate of death (only applicable if next layer is Dead)\
          implemented as a function death_rate(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.delta = delta
        self.N = N
        self.p_recovery = p_recovery
        self.recovery_rate = recovery_rate
        self.p_hospitalized = p_hospitalized
        self.hospital_rate = hospital_rate
        self.p_critical = p_critical
        self.critical_rate = critical_rate
        self.p_death = p_death
        self.death_rate = death_rate

        self.prev_layer_type = None
        self.prev_layer_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_map_no in range(len(layer_map)):
            for next_layer in layer_map[layer_map_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.prev_layer_type = layer_names[layer_map_no]
                    self.prev_layer_indices.append(layer_map_no)
                # warning if there are different input layer types
                if self.prev_layer_type is not None and next_layer.get_layer_index() == self.layer_index and \
                   layer_names[layer_map_no] != self.prev_layer_type:  # pragma: no cover
                    warnings.warn('Not all input layers to the Infected layer at %s are the same. \n'
                                  'Input layers to the Infected layer should either all be Susceptible \n'
                                  'or Exposed. Consider changing the `layer_map`.' %
                                  self.layer_index)

        # warnings
        # undefined parameters
        if self.prev_layer_type == 'Susceptible' and not self.R_0:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                          'basic reproductive number is not defined. Please define as `R_0=Value`.' %
                          self.layer_index)
        if self.prev_layer_type == 'Susceptible' and not self.gamma:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                          'recovery rate is not defined. Please define as `gamma=Value.`' % self.layer_index)

        if self.prev_layer_type == 'Exposed' and not self.delta:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Exposed and the \n'
                          'incubation period is not defined. Please define as `delta=Value`.' % self.layer_index)

        # layer structures
        if self.prev_layer_type != 'Susceptible' and self.prev_layer_type != 'Exposed':  # pragma: no cover
            warnings.warn('Input layer types to the Infected layer at %s must be either \n'
                          'Susceptible or Exposed. Consider changing the Input layers in `layer_map` \n'
                          'or creating a custom Infected layer using `add_layer`.' % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Infected compartment
        all layers feeding into the infected layer must be of the same type and either Susceptible or
        Exposed

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_prev_layer = 0
        for prev_layer_index in self.prev_layer_indices:
            total_prev_layer += system[prev_layer_index]

        if self.prev_layer_type == 'Susceptible':
            derivative = self.gamma(time) * self.R_0(time) * total_prev_layer * \
                   system[self.layer_index] / self.N(time)
        if self.prev_layer_type == 'Exposed':
            derivative = self.delta(time) * total_prev_layer

        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
        if self.p_hospitalized:
            derivative -= self.p_hospitalized(time) * self.hospital_rate(time) * system[self.layer_index]
        if self.p_critical:
            derivative -= self.p_critical(time) * self.critical_rate(time) * system[self.layer_index]
        if self.p_death:
            derivative -= self.p_death(time) * self.death_rate(time) * system[self.layer_index]

        return derivative


class Recovered(object):
    """
    The Recovered class can act like the 'R' of the 'SIR' Model if the recovery and death rates are the same.
    This class actually consists of individuals who have had the disease and recovered (i.e. did not die).
    This class can be used as a terminal state.

    Infected, Critical, Hospitalized --> Recovered --> Susceptible (?)

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, p_from_inf=None, from_inf_rate=None, p_from_cri=None,
                 from_cri_rate=None, p_from_hos=None, from_hos_rate=None, p_resusceptibility=None,
                 s_rate=None):
        """
        Initialize the Recovered class

        - layer_index: index of layer in `layers`
        - p_from_inf: =None, probability of recovery from Infected (only applicable if previous layer is Infected)\
           implemented as a function p_from_inf(t)
            - t: time
            - return: probability of recovery
        - from_inf_rate: =None, 1 / time to recover from Infected (only applicable if previous layer is Infected)\
           implemented as a function from_inf_rate(t)
            - t: time
            - return: recovery rate
        - p_from_cri: =None, probability of recovery from Critical (only applicable if previous layer is Critical)\
           implemented as a function p_from_cri(t)
            - t: time
            - return: probability of recovery
        - from_cri_rate: =None, 1 / time to recover from Critical (only applicable if previous layer is Critical)\
           implemented as a function from_cri_rate(t)
            - t: time
            - return: recovery rate
        - p_from_hos: =None, probability of recovery from Hospitalized--
           (only applicable if previous layer is Hospitalized)\
           implemented as a function p_from_hos(t)
            - t: time
            - return: probability of recovery
        - from_hos_rate: =None, 1 / time to recover from Hospitalized--
           (only applicable if previous layer is Hospitalized)\
           implemented as a function from_hos_rate(t)
            - t: time
            - return: recovery rate
        - p_resusceptibility: =None, probability of resusceptibility (only applicable if next layer is Susceptible)\
           implemented as a function p_resusceptibility(t)
            - t: time
            - return: probability of resusceptibility
        - s_rate: =None, 1 / time to resusceptibility (only applicable if next layer is Susceptible)\
           implemented as a function s_rate(t)
            - t: time
            - return: rate of resusceptibility
        """

        self.layer_index = layer_index
        self.p_from_inf = p_from_inf
        self.from_inf_rate = from_inf_rate
        self.p_from_cri = p_from_cri
        self.from_cri_rate = from_cri_rate
        self.p_from_hos = p_from_hos
        self.from_hos_rate = from_hos_rate
        self.p_resusceptibility = p_resusceptibility
        self.s_rate = s_rate

        self.prev_layer_indices_by_type = [[], [], []]  # in order [infected, critical, hospitalized]

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    if layer_names[layer_no] == 'Infected':
                        self.prev_layer_indices_by_type[0].append(layer_no)
                    elif layer_names[layer_no] == 'Critical':
                        self.prev_layer_indices_by_type[1].append(layer_no)
                    elif layer_names[layer_no] == 'Hospitalized':
                        self.prev_layer_indices_by_type[2].append(layer_no)
                    else:  # pragma: no cover
                        warnings.warn('Previous layer at %s to Recovered layer at %s is not Infected, Critical, or \n'
                                      'Hospitalized. Consider either correcting the `layer_map` if this is not \n'
                                      'supposed to happen, or accomodating for this setup by using a custom \n'
                                      'Recovered layer.' % (layer_no, self.layer_index))

        # warnings
        for next_layer_index in range(len(layer_map[self.layer_index])):
            if layer_names[next_layer_index] != 'Susceptible':  # pragma: no cover
                warnings.warn('The next layer to the Recovered layer at %s must be a Susceptible layer. \n'
                              'Change the `layer_map` to avoid this complication or use a custom layer.'
                              % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Recovered compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        # previous layers
        # infected
        for prev_layer_index in self.prev_layer_indices_by_type[0]:
            derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[prev_layer_index]

        # critical
        for prev_layer_index in self.prev_layer_indices_by_type[1]:
            derivative += self.p_from_cri(time) * self.from_cri_rate(time) * system[prev_layer_index]

        # hospitalized
        for prev_layer_index in self.prev_layer_indices_by_type[2]:
            derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[prev_layer_index]

        # next layers
        # susceptible
        if self.p_resusceptibility:
            derivative -= self.p_resusceptibility(time) * self.s_rate(time) * system[self.layer_index]

        return derivative


class Exposed(object):
    """
    The Exposed class represents the incubation period of the disease.
    This portion of individuals cannot spread the disease but are bound to become infected after some period of time.

    Susceptible --> Exposed --> Infected

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, R_0, gamma, N, delta):
        """
        Initialize the Exposed class

        - layer_index: index of layer in `layers`
        - R_0: the basic reproductive number--
            this is the average number of Susceptibles infected by one Infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: the infectious period--
          1 / average duration of infectious period\
          implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - N: the total population\
          implemented as a function N(t):
            - t: time
            - return: total population
        - delta: the incubation period (only applicable if previous layer is Exposed)\
          implemented as a function delta(t)--in most cases this should stay constant
            - t: time
            - return: incubation period
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.N = N
        self.delta = delta

        self.prev_layer_indices = []
        self.infected_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:

                if next_layer.get_layer_index() == self.layer_index and \
                        layer_names[layer_no] == 'Susceptible':
                    self.prev_layer_indices.append(layer_no)

                # warning
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('It seems like you want to connect the layer at %s to the Exposed layer at %s. \n'
                                  'However, only Susceptible layers can be fed into an Exposed layer. \n'
                                  'Consider creating a custom layer to handle this or remove the connection.' %
                                  (layer_no, self.layer_index))

        for next_layer_index in range(len(layer_map[self.layer_index])):

            if layer_names[layer_map[self.layer_index][next_layer_index].get_layer_index()] == 'Infected':
                self.infected_category_indices.append(layer_map[self.layer_index][next_layer_index].
                                                      get_layer_index())

            # warnings
            else:  # pragma: no cover
                warnings.warn('It seems like you want to connect Exposed layer at %s to the layer at %s. \n'
                              'However, only Infected layers can be placed in front of Exposed layers. \n'
                              'Consider creating a custom layer to handle this or remove the connection.' %
                              (self.layer_index, next_layer_index))

        # warnings
        if len(self.prev_layer_indices) == 0:  # pragma: no cover
            warnings.warn('It seems that the Exposed layer at %s is not in use. Please find a Susceptible \n'
                          'layer to route through this layer or remove this layer altogether.' %
                          self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Exposed compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_susceptibles = 0
        for prev_layer_index in self.prev_layer_indices:
            total_susceptibles += system[prev_layer_index]

        total_infecteds = 0
        for infected_index in self.infected_category_indices:
            total_infecteds += system[infected_index]

        return self.gamma(time) * self.R_0(time) * total_susceptibles * \
               total_infecteds / self.N(time) - self.delta(time) * system[self.layer_index]


class Dead(object):
    """
    The Dead class is a terminal state\
    As is convention with the SIR Model, we assume that this portion of individuals does not significantly
    change the original population ## Structure, and therefore, the total population will remain the same
    regardless of how many people have been classified as Dead.

    Infected, Critical, Hospitalized --> Dead (TERMINAL)

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, rho_inf=None, alpha_inf=None, rho_hos=None, alpha_hos=None, rho_cri=None,
                 alpha_cri=None):
        """
        Initialize the Dead class

        - layer_index: index of layer in `layers`
        - rho_inf: =None, 1 / time until death from Infected (only applicable if previous layer is Infected)\
            implemented as a function rho_inf(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_inf: =None, probability of death from Infected (only applicable if previous layer is Infected)\
          implemented as a function alpha_inf(t)
            - t: time
            - return: probability of death
        - rho_hos: =None, 1 / time until death from Hospitalized (only applicable if previous layer is
            Hospitalized)\
            implemented as a function rho_hos(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_hos: =None, probability of death from Hospitalized (only applicable if previous layer is
          Hospitalized)\
          implemented as a function alpha_hos(t)
            - t: time
            - return: probability of death
        - rho_cri: =None, 1 / time until death from Critical (only applicable if previous layer is Critical)\
            implemented as a function rho_cri(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_cri: =None, probability of death from Critical (only applicable if previous layer is Critical)\
          implemented as a function alpha_cri(t)
            - t: time
            - return: probability of death
        """

        self.layer_index = layer_index
        self.rho_inf = rho_inf
        self.alpha_inf = alpha_inf
        self.rho_hos = rho_hos
        self.alpha_hos = alpha_hos
        self.rho_cri = rho_cri
        self.alpha_cri = alpha_cri

        self.infected_category_indices = []
        self.hospitalized_category_indices = []
        self.critical_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:

                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.infected_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                    self.hospitalized_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Critical':
                    self.critical_category_indices.append(layer_no)

                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('You are trying to connect an incorrect layer type at %s to the Dead layer at %s. \n'
                                  'Previous layers to the Dead layer must be of the Infected, Critical, or \n'
                                  'Hospitalized type.' % (layer_no, self.layer_index))

        # warnings
        if not self.rho_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected an Infected layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_inf=Float` and `alpha_inf=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

        if not self.rho_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected a Hospitalized layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_hos=Float` and `alpha_hos=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

        if not self.rho_cri and len(self.critical_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected a Critical layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_cri=Float` and `alpha_cri=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Dead compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        for infected_category_index in self.infected_category_indices:
            derivative += self.rho_inf(time) * self.alpha_inf(time) * system[infected_category_index]

        for hospitalized_category_index in self.hospitalized_category_indices:
            derivative += self.rho_hos(time) * self.alpha_hos(time) * system[hospitalized_category_index]

        for critical_category_index in self.critical_category_indices:
            derivative += self.rho_cri(time) * self.alpha_cri(time) * system[critical_category_index]

        return derivative


class Hospitalized(object):
    """
    The Hospitalized class represents the portion of individuals currently taking up space in the available
    hospitals. However, this is a distinct category from the Critical portion of individuals, who require
    more resources (ICU beds, ventilators, etc.). This layer supports triage.

    Infected --> Hospitalized --> Critical, Dead

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, hos_rate, p_hos, cri_rate=None, p_cri=None, recovery_rate=None,
                 p_recovery=None, rho=None, alpha=None, maxCap=None, dump_to_layer=None):
        """
        Initialize the Hospitalized class

        - layer_index: index of layer in `layers`
        - hos_rate: 1 / time until hospitalization\
            implemented as a function hos_rate(t)
            - t: time
            - return: hospitalization rate
        - p_hos: probability of hospitalization\
            implemented as a function p_hos(t)
            - t: time
            - return: probability of hospitalization
        - cri_rate: =None, 1 / time until a patient becomes Critical (only applicable if next layer is Critical)\
            implemented as a function cri_rate(t)
            - t: time
            - return: critical rate
        - p_cri: =None, probability of becoming a Critical patient (only applicable if next layer is Critical)\
            implemented as a function p_cri(t)
            - t: time
            - return: probability of becoming Critical
        - recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered)\
            implemented as a function recovery_rate(t)
            - t: time
            - return: recovery rate
        - p_recovery: =None, probability of recovery (only applicable if next layer is Recovered)\
            implemented as a function p_recovery(t)
            - t: time
            - return: probability of recovery
        - rho: =None, 1 / time in hospital until death (only applicable if next layer is Dead)\
            implemented as a function rho(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha: =None, probability of death (only applicable if next layer is Dead)\
            implemented as a function alpha(t)
            - t: time
            - return: probability of death
        - maxCap: =None, maximum hospital capacity to implement triage\
            implemented as a function maxCap(t)
            - t: time
            - return: maximum capacity
        - dump_to_layer: =None, index of the layer to dump patients which do not make the triage
            should be of type int()
        """

        self.layer_index = layer_index
        self.hos_rate = hos_rate
        self.p_hos = p_hos
        self.cri_rate = cri_rate
        self.p_cri = p_cri
        self.recovery_rate = recovery_rate
        self.p_recovery = p_recovery
        self.rho = rho
        self.alpha = alpha
        self.maxCap = maxCap
        self.dump_to_layer = dump_to_layer

        self.prev_layer_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.prev_layer_indices.append(layer_no)
                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('A layer of an unsupported type at %s is being connected to the Infected \n'
                                  'layer at %s. If this is a mistake, remove the connection. Otherwise, try \n'
                                  'using a custom layer to do this.' % (layer_no, self.layer_index))

    def get_deriv(self, time, system):
        """
        Derivative of the Hospitalized compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        # no triage
        for prev_layer_index in self.prev_layer_indices:
            derivative += self.hos_rate(time) * self.p_hos(time) * system[prev_layer_index]

        if self.p_cri:
            derivative -= self.p_cri(time) * self.cri_rate(time) * system[self.layer_index]
        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
        if self.alpha:
            derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]

        # implement triage
        if self.maxCap and system[self.layer_index] > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        # limited triage
        elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        return derivative


class Critical(object):
    """
    The Critical class represents the portion of individuals currently taking up space in the available
    hospitals *and* using limited resources. However, this is a distinct category from the Hospitalized portion of
    individuals, who don't require extra resources (ICU beds, ventilators, etc.). This layer supports triage.

    Hospitalized, Infected --> Critical --> Dead, Recovered

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, p_from_hos=None, from_hos_rate=None, p_from_inf=None, from_inf_rate=None, rho=None,
                 alpha=None, p_recovery=None, recovery_rate=None, maxCap=None, dump_to_layer=None):
        """
        Initialize the Critical class

        - layer_index: index of layer in `layers`
        - p_from_hos: =None, probability of becoming a Critical patient from Hospitalized
           (only applicable if previous layer is Hospitalized)\
           implemented as a function p_from_hos(t)
            - t: time
            - return: Critical probability
        - from_hos_rate: =None, 1 / time to Critical condition from Hospitalized
           (only applicable if previous layer is Hospitalized)\
           implemented as a function from_hos_rate(t)
            - t: time
            - return: Critical rate
        - p_from_inf: =None, probability of becoming a Critical patient from Infected
           (only applicable if previous layer is Infected)\
           implemented as a function p_from_inf(t)
            - t: time
            - return: Critical probability
        - from_inf_rate: =None, 1 / time to Critical condition from Infected
           (only applicable if previous layer is Infected)\
           implemented as a function from_inf_rate(t)
            - t: time
            - return: Critical rate
        - alpha: =None, probability of death (only applicable if next layer is Dead)\
           implemented as a function alpha(t)
            - t: time
            - return: probability of death
        - rho: =None, 1 / time until death from Critical (only applicable if next layer is Dead)\
           implemented as a function rho(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - p_recovery: =None, probability of recovery (only applicable if next layer is Recovered)\
           implemented as a function p_recovery(t)
            - t: time
            - return: probability of recovery
        - recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered)\
           implemented as a function recovery_rate(t)
            - t: time
            - return: recovery rate
        - maxCap: =None, maximum hospital capacity to implement triage\
            implemented as a function maxCap(t)
            - t: time
            - return: maximum capacity
        - dump_to_layer: =None, index of the layer to dump patients which do not make the triage
            should be of type int()
        """

        self.layer_index = layer_index
        self.p_from_hos = p_from_hos
        self.from_hos_rate = from_hos_rate
        self.p_from_inf = p_from_inf
        self.from_inf_rate = from_inf_rate
        self.alpha = alpha
        self.rho = rho
        self.p_recovery = p_recovery
        self.recovery_rate = recovery_rate
        self.maxCap = maxCap
        self.dump_to_layer = dump_to_layer

        self.hospitalized_category_indices = []
        self.infected_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                    self.hospitalized_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.infected_category_indices.append(layer_no)
                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('You are trying to connect a layer to the Critical layer at %s that is neither \n'
                                  'of the Hospitalized or Infected type. Please remove this connection or use a \n'
                                  'custom layer instead of this one.' % self.layer_index)

        # warnings
        if not self.p_from_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
            warnings.warn("You have connected a Hospitalized layer to the Critical layer at %s but \n"
                          "haven't specified a Critical probability. Please do this by writing \n"
                          "`p_from_hos=FLOAT` AND `from_hos_rate=FLOAT` so this can be used.")

        if not self.p_from_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
            warnings.warn("You have connected a Infected layer to the Critical layer at %s but \n"
                          "haven't specified a Critical probability. Please do this by writing \n"
                          "`p_from_inf=FLOAT` AND `from_inf_rate=FLOAT` so this can be used.")

    def get_deriv(self, time, system):
        """
        Derivative of the Critical compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        for hospitalized_category_index in self.hospitalized_category_indices:
            derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[hospitalized_category_index]

        for infected_category_index in self.infected_category_indices:
            derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[infected_category_index]

        if self.alpha:
            derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]
        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]

        # implement triage
        if self.maxCap and system[self.layer_index] > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        # limited triage
        elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        return derivative


class Idiom(object):  # pragma: no cover
    """
    An idiom used to create custom classes. Feed this into `Model.add_layer` to
    be used with any class. Make sure to change `get_deriv` file.
    If you wish, you can change all the other methods as well.
    Pass all parameters as an array in `param_list`

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, param_list=None):
        """
        Initialize the class

        - layer_index: index of layer in `layers`
        - param_list: =[], list of parameters, passed in array format
        """

        self.layer_index = layer_index
        self.param_list = param_list

        self.prev_layer_indices = []
        self.prev_layer_types = []
        self.next_layer_indices = []
        self.next_layer_types = []
        self.test_info = []  # use this to store any test information to be passed on to get_deriv

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.prev_layer_indices.append(layer_no)
                    self.prev_layer_types.append(layer_names[layer_no])

        for next_layer_no in range(len(layer_map[self.layer_index])):
            self.next_layer_indices.append(layer_map[self.layer_index][next_layer_no].get_layer_index())
            self.next_layer_types.append(layer_names[layer_map[self.layer_index][next_layer_no].
                                         get_layer_index()])

    def get_deriv(self):
        """
        Derivative of this compartment
        Setup by changing the function--create a new method with parameters time & system:

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        # warn on no setup
        warnings.warn("The Idiom layer at %s has not been set up yet. Please replace the `get_deriv` method by \n"
                      "adding IDIOM_LAYER_NAME.get_deriv = SOME_FUNCTION(self, time, system). Please see this \n"
                      "function's documentation for more info" % self.layer_index)  # pragma: no cover
        return None

Classes

class Critical (layer_index, p_from_hos=None, from_hos_rate=None, p_from_inf=None, from_inf_rate=None, rho=None, alpha=None, p_recovery=None, recovery_rate=None, maxCap=None, dump_to_layer=None)

The Critical class represents the portion of individuals currently taking up space in the available hospitals and using limited resources. However, this is a distinct category from the Hospitalized portion of individuals, who don't require extra resources (ICU beds, ventilators, etc.). This layer supports triage.

Hospitalized, Infected –> Critical –> Dead, Recovered

Structure:

  • init
  • get_layer_index
  • test
  • get_deriv

Initialize the Critical class

  • layer_index: index of layer in layers
  • p_from_hos: =None, probability of becoming a Critical patient from Hospitalized (only applicable if previous layer is Hospitalized) implemented as a function p_from_hos(t)
    • t: time
    • return: Critical probability
  • from_hos_rate: =None, 1 / time to Critical condition from Hospitalized (only applicable if previous layer is Hospitalized) implemented as a function from_hos_rate(t)
    • t: time
    • return: Critical rate
  • p_from_inf: =None, probability of becoming a Critical patient from Infected (only applicable if previous layer is Infected) implemented as a function p_from_inf(t)
    • t: time
    • return: Critical probability
  • from_inf_rate: =None, 1 / time to Critical condition from Infected (only applicable if previous layer is Infected) implemented as a function from_inf_rate(t)
    • t: time
    • return: Critical rate
  • alpha: =None, probability of death (only applicable if next layer is Dead) implemented as a function alpha(t)
    • t: time
    • return: probability of death
  • rho: =None, 1 / time until death from Critical (only applicable if next layer is Dead) implemented as a function rho(t)–in most cases this should stay constant
    • t: time
    • return: death rate
  • p_recovery: =None, probability of recovery (only applicable if next layer is Recovered) implemented as a function p_recovery(t)
    • t: time
    • return: probability of recovery
  • recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered) implemented as a function recovery_rate(t)
    • t: time
    • return: recovery rate
  • maxCap: =None, maximum hospital capacity to implement triage implemented as a function maxCap(t)
    • t: time
    • return: maximum capacity
  • dump_to_layer: =None, index of the layer to dump patients which do not make the triage should be of type int()
Expand source code
class Critical(object):
    """
    The Critical class represents the portion of individuals currently taking up space in the available
    hospitals *and* using limited resources. However, this is a distinct category from the Hospitalized portion of
    individuals, who don't require extra resources (ICU beds, ventilators, etc.). This layer supports triage.

    Hospitalized, Infected --> Critical --> Dead, Recovered

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, p_from_hos=None, from_hos_rate=None, p_from_inf=None, from_inf_rate=None, rho=None,
                 alpha=None, p_recovery=None, recovery_rate=None, maxCap=None, dump_to_layer=None):
        """
        Initialize the Critical class

        - layer_index: index of layer in `layers`
        - p_from_hos: =None, probability of becoming a Critical patient from Hospitalized
           (only applicable if previous layer is Hospitalized)\
           implemented as a function p_from_hos(t)
            - t: time
            - return: Critical probability
        - from_hos_rate: =None, 1 / time to Critical condition from Hospitalized
           (only applicable if previous layer is Hospitalized)\
           implemented as a function from_hos_rate(t)
            - t: time
            - return: Critical rate
        - p_from_inf: =None, probability of becoming a Critical patient from Infected
           (only applicable if previous layer is Infected)\
           implemented as a function p_from_inf(t)
            - t: time
            - return: Critical probability
        - from_inf_rate: =None, 1 / time to Critical condition from Infected
           (only applicable if previous layer is Infected)\
           implemented as a function from_inf_rate(t)
            - t: time
            - return: Critical rate
        - alpha: =None, probability of death (only applicable if next layer is Dead)\
           implemented as a function alpha(t)
            - t: time
            - return: probability of death
        - rho: =None, 1 / time until death from Critical (only applicable if next layer is Dead)\
           implemented as a function rho(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - p_recovery: =None, probability of recovery (only applicable if next layer is Recovered)\
           implemented as a function p_recovery(t)
            - t: time
            - return: probability of recovery
        - recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered)\
           implemented as a function recovery_rate(t)
            - t: time
            - return: recovery rate
        - maxCap: =None, maximum hospital capacity to implement triage\
            implemented as a function maxCap(t)
            - t: time
            - return: maximum capacity
        - dump_to_layer: =None, index of the layer to dump patients which do not make the triage
            should be of type int()
        """

        self.layer_index = layer_index
        self.p_from_hos = p_from_hos
        self.from_hos_rate = from_hos_rate
        self.p_from_inf = p_from_inf
        self.from_inf_rate = from_inf_rate
        self.alpha = alpha
        self.rho = rho
        self.p_recovery = p_recovery
        self.recovery_rate = recovery_rate
        self.maxCap = maxCap
        self.dump_to_layer = dump_to_layer

        self.hospitalized_category_indices = []
        self.infected_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                    self.hospitalized_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.infected_category_indices.append(layer_no)
                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('You are trying to connect a layer to the Critical layer at %s that is neither \n'
                                  'of the Hospitalized or Infected type. Please remove this connection or use a \n'
                                  'custom layer instead of this one.' % self.layer_index)

        # warnings
        if not self.p_from_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
            warnings.warn("You have connected a Hospitalized layer to the Critical layer at %s but \n"
                          "haven't specified a Critical probability. Please do this by writing \n"
                          "`p_from_hos=FLOAT` AND `from_hos_rate=FLOAT` so this can be used.")

        if not self.p_from_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
            warnings.warn("You have connected a Infected layer to the Critical layer at %s but \n"
                          "haven't specified a Critical probability. Please do this by writing \n"
                          "`p_from_inf=FLOAT` AND `from_inf_rate=FLOAT` so this can be used.")

    def get_deriv(self, time, system):
        """
        Derivative of the Critical compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        for hospitalized_category_index in self.hospitalized_category_indices:
            derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[hospitalized_category_index]

        for infected_category_index in self.infected_category_indices:
            derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[infected_category_index]

        if self.alpha:
            derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]
        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]

        # implement triage
        if self.maxCap and system[self.layer_index] > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        # limited triage
        elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Critical compartment

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Critical compartment

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    derivative = 0

    for hospitalized_category_index in self.hospitalized_category_indices:
        derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[hospitalized_category_index]

    for infected_category_index in self.infected_category_indices:
        derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[infected_category_index]

    if self.alpha:
        derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]
    if self.p_recovery:
        derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]

    # implement triage
    if self.maxCap and system[self.layer_index] > self.maxCap(time):
        system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
        derivative = self.maxCap(time) - system[self.layer_index]

    # limited triage
    elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
        system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
        derivative = self.maxCap(time) - system[self.layer_index]

    return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:
            if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                self.hospitalized_category_indices.append(layer_no)
            elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                self.infected_category_indices.append(layer_no)
            # warnings
            elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                warnings.warn('You are trying to connect a layer to the Critical layer at %s that is neither \n'
                              'of the Hospitalized or Infected type. Please remove this connection or use a \n'
                              'custom layer instead of this one.' % self.layer_index)

    # warnings
    if not self.p_from_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
        warnings.warn("You have connected a Hospitalized layer to the Critical layer at %s but \n"
                      "haven't specified a Critical probability. Please do this by writing \n"
                      "`p_from_hos=FLOAT` AND `from_hos_rate=FLOAT` so this can be used.")

    if not self.p_from_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
        warnings.warn("You have connected a Infected layer to the Critical layer at %s but \n"
                      "haven't specified a Critical probability. Please do this by writing \n"
                      "`p_from_inf=FLOAT` AND `from_inf_rate=FLOAT` so this can be used.")
class Dead (layer_index, rho_inf=None, alpha_inf=None, rho_hos=None, alpha_hos=None, rho_cri=None, alpha_cri=None)

The Dead class is a terminal state As is convention with the SIR Model, we assume that this portion of individuals does not significantly change the original population ## Structure, and therefore, the total population will remain the same regardless of how many people have been classified as Dead.

Infected, Critical, Hospitalized –> Dead (TERMINAL)

Structure:

  • init
  • get_layer_index
  • test
  • get_deriv

Initialize the Dead class

  • layer_index: index of layer in layers
  • rho_inf: =None, 1 / time until death from Infected (only applicable if previous layer is Infected) implemented as a function rho_inf(t)–in most cases this should stay constant
    • t: time
    • return: death rate
  • alpha_inf: =None, probability of death from Infected (only applicable if previous layer is Infected) implemented as a function alpha_inf(t)
    • t: time
    • return: probability of death
  • rho_hos: =None, 1 / time until death from Hospitalized (only applicable if previous layer is Hospitalized) implemented as a function rho_hos(t)–in most cases this should stay constant
    • t: time
    • return: death rate
  • alpha_hos: =None, probability of death from Hospitalized (only applicable if previous layer is Hospitalized) implemented as a function alpha_hos(t)
    • t: time
    • return: probability of death
  • rho_cri: =None, 1 / time until death from Critical (only applicable if previous layer is Critical) implemented as a function rho_cri(t)–in most cases this should stay constant
    • t: time
    • return: death rate
  • alpha_cri: =None, probability of death from Critical (only applicable if previous layer is Critical) implemented as a function alpha_cri(t)
    • t: time
    • return: probability of death
Expand source code
class Dead(object):
    """
    The Dead class is a terminal state\
    As is convention with the SIR Model, we assume that this portion of individuals does not significantly
    change the original population ## Structure, and therefore, the total population will remain the same
    regardless of how many people have been classified as Dead.

    Infected, Critical, Hospitalized --> Dead (TERMINAL)

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, rho_inf=None, alpha_inf=None, rho_hos=None, alpha_hos=None, rho_cri=None,
                 alpha_cri=None):
        """
        Initialize the Dead class

        - layer_index: index of layer in `layers`
        - rho_inf: =None, 1 / time until death from Infected (only applicable if previous layer is Infected)\
            implemented as a function rho_inf(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_inf: =None, probability of death from Infected (only applicable if previous layer is Infected)\
          implemented as a function alpha_inf(t)
            - t: time
            - return: probability of death
        - rho_hos: =None, 1 / time until death from Hospitalized (only applicable if previous layer is
            Hospitalized)\
            implemented as a function rho_hos(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_hos: =None, probability of death from Hospitalized (only applicable if previous layer is
          Hospitalized)\
          implemented as a function alpha_hos(t)
            - t: time
            - return: probability of death
        - rho_cri: =None, 1 / time until death from Critical (only applicable if previous layer is Critical)\
            implemented as a function rho_cri(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha_cri: =None, probability of death from Critical (only applicable if previous layer is Critical)\
          implemented as a function alpha_cri(t)
            - t: time
            - return: probability of death
        """

        self.layer_index = layer_index
        self.rho_inf = rho_inf
        self.alpha_inf = alpha_inf
        self.rho_hos = rho_hos
        self.alpha_hos = alpha_hos
        self.rho_cri = rho_cri
        self.alpha_cri = alpha_cri

        self.infected_category_indices = []
        self.hospitalized_category_indices = []
        self.critical_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:

                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.infected_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                    self.hospitalized_category_indices.append(layer_no)
                elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Critical':
                    self.critical_category_indices.append(layer_no)

                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('You are trying to connect an incorrect layer type at %s to the Dead layer at %s. \n'
                                  'Previous layers to the Dead layer must be of the Infected, Critical, or \n'
                                  'Hospitalized type.' % (layer_no, self.layer_index))

        # warnings
        if not self.rho_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected an Infected layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_inf=Float` and `alpha_inf=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

        if not self.rho_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected a Hospitalized layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_hos=Float` and `alpha_hos=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

        if not self.rho_cri and len(self.critical_category_indices) > 0:  # pragma: no cover
            warnings.warn('You have connected a Critical layer to the Dead layer at %s but \n'
                          'you have not specified a death rate for that layer. Please do this by \n'
                          'passing in parameters `rho_cri=Float` and `alpha_cri=Float` when \n'
                          'the Dead layer is initialized.' % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Dead compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        for infected_category_index in self.infected_category_indices:
            derivative += self.rho_inf(time) * self.alpha_inf(time) * system[infected_category_index]

        for hospitalized_category_index in self.hospitalized_category_indices:
            derivative += self.rho_hos(time) * self.alpha_hos(time) * system[hospitalized_category_index]

        for critical_category_index in self.critical_category_indices:
            derivative += self.rho_cri(time) * self.alpha_cri(time) * system[critical_category_index]

        return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Dead compartment

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Dead compartment

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    derivative = 0

    for infected_category_index in self.infected_category_indices:
        derivative += self.rho_inf(time) * self.alpha_inf(time) * system[infected_category_index]

    for hospitalized_category_index in self.hospitalized_category_indices:
        derivative += self.rho_hos(time) * self.alpha_hos(time) * system[hospitalized_category_index]

    for critical_category_index in self.critical_category_indices:
        derivative += self.rho_cri(time) * self.alpha_cri(time) * system[critical_category_index]

    return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:

            if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                self.infected_category_indices.append(layer_no)
            elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Hospitalized':
                self.hospitalized_category_indices.append(layer_no)
            elif next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Critical':
                self.critical_category_indices.append(layer_no)

            # warnings
            elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                warnings.warn('You are trying to connect an incorrect layer type at %s to the Dead layer at %s. \n'
                              'Previous layers to the Dead layer must be of the Infected, Critical, or \n'
                              'Hospitalized type.' % (layer_no, self.layer_index))

    # warnings
    if not self.rho_inf and len(self.infected_category_indices) > 0:  # pragma: no cover
        warnings.warn('You have connected an Infected layer to the Dead layer at %s but \n'
                      'you have not specified a death rate for that layer. Please do this by \n'
                      'passing in parameters `rho_inf=Float` and `alpha_inf=Float` when \n'
                      'the Dead layer is initialized.' % self.layer_index)

    if not self.rho_hos and len(self.hospitalized_category_indices) > 0:  # pragma: no cover
        warnings.warn('You have connected a Hospitalized layer to the Dead layer at %s but \n'
                      'you have not specified a death rate for that layer. Please do this by \n'
                      'passing in parameters `rho_hos=Float` and `alpha_hos=Float` when \n'
                      'the Dead layer is initialized.' % self.layer_index)

    if not self.rho_cri and len(self.critical_category_indices) > 0:  # pragma: no cover
        warnings.warn('You have connected a Critical layer to the Dead layer at %s but \n'
                      'you have not specified a death rate for that layer. Please do this by \n'
                      'passing in parameters `rho_cri=Float` and `alpha_cri=Float` when \n'
                      'the Dead layer is initialized.' % self.layer_index)
class Exposed (layer_index, R_0, gamma, N, delta)

The Exposed class represents the incubation period of the disease. This portion of individuals cannot spread the disease but are bound to become infected after some period of time.

Susceptible –> Exposed –> Infected

Structure:

  • init
  • get_layer_index
  • test
  • get_deriv

Initialize the Exposed class

  • layer_index: index of layer in layers
  • R_0: the basic reproductive number– this is the average number of Susceptibles infected by one Infected implemented as a function R_0(t):
    • t: time
    • return: R_0 value
  • gamma: the infectious period– 1 / average duration of infectious period implemented as a function gamma(t):
    • t: time
    • return: infectious period
  • N: the total population implemented as a function N(t):
    • t: time
    • return: total population
  • delta: the incubation period (only applicable if previous layer is Exposed) implemented as a function delta(t)–in most cases this should stay constant
    • t: time
    • return: incubation period
Expand source code
class Exposed(object):
    """
    The Exposed class represents the incubation period of the disease.
    This portion of individuals cannot spread the disease but are bound to become infected after some period of time.

    Susceptible --> Exposed --> Infected

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, R_0, gamma, N, delta):
        """
        Initialize the Exposed class

        - layer_index: index of layer in `layers`
        - R_0: the basic reproductive number--
            this is the average number of Susceptibles infected by one Infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: the infectious period--
          1 / average duration of infectious period\
          implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - N: the total population\
          implemented as a function N(t):
            - t: time
            - return: total population
        - delta: the incubation period (only applicable if previous layer is Exposed)\
          implemented as a function delta(t)--in most cases this should stay constant
            - t: time
            - return: incubation period
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.N = N
        self.delta = delta

        self.prev_layer_indices = []
        self.infected_category_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:

                if next_layer.get_layer_index() == self.layer_index and \
                        layer_names[layer_no] == 'Susceptible':
                    self.prev_layer_indices.append(layer_no)

                # warning
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('It seems like you want to connect the layer at %s to the Exposed layer at %s. \n'
                                  'However, only Susceptible layers can be fed into an Exposed layer. \n'
                                  'Consider creating a custom layer to handle this or remove the connection.' %
                                  (layer_no, self.layer_index))

        for next_layer_index in range(len(layer_map[self.layer_index])):

            if layer_names[layer_map[self.layer_index][next_layer_index].get_layer_index()] == 'Infected':
                self.infected_category_indices.append(layer_map[self.layer_index][next_layer_index].
                                                      get_layer_index())

            # warnings
            else:  # pragma: no cover
                warnings.warn('It seems like you want to connect Exposed layer at %s to the layer at %s. \n'
                              'However, only Infected layers can be placed in front of Exposed layers. \n'
                              'Consider creating a custom layer to handle this or remove the connection.' %
                              (self.layer_index, next_layer_index))

        # warnings
        if len(self.prev_layer_indices) == 0:  # pragma: no cover
            warnings.warn('It seems that the Exposed layer at %s is not in use. Please find a Susceptible \n'
                          'layer to route through this layer or remove this layer altogether.' %
                          self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Exposed compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_susceptibles = 0
        for prev_layer_index in self.prev_layer_indices:
            total_susceptibles += system[prev_layer_index]

        total_infecteds = 0
        for infected_index in self.infected_category_indices:
            total_infecteds += system[infected_index]

        return self.gamma(time) * self.R_0(time) * total_susceptibles * \
               total_infecteds / self.N(time) - self.delta(time) * system[self.layer_index]

Methods

def get_deriv(self, time, system)

Derivative of the Exposed compartment

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Exposed compartment

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    total_susceptibles = 0
    for prev_layer_index in self.prev_layer_indices:
        total_susceptibles += system[prev_layer_index]

    total_infecteds = 0
    for infected_index in self.infected_category_indices:
        total_infecteds += system[infected_index]

    return self.gamma(time) * self.R_0(time) * total_susceptibles * \
           total_infecteds / self.N(time) - self.delta(time) * system[self.layer_index]
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:

            if next_layer.get_layer_index() == self.layer_index and \
                    layer_names[layer_no] == 'Susceptible':
                self.prev_layer_indices.append(layer_no)

            # warning
            elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                warnings.warn('It seems like you want to connect the layer at %s to the Exposed layer at %s. \n'
                              'However, only Susceptible layers can be fed into an Exposed layer. \n'
                              'Consider creating a custom layer to handle this or remove the connection.' %
                              (layer_no, self.layer_index))

    for next_layer_index in range(len(layer_map[self.layer_index])):

        if layer_names[layer_map[self.layer_index][next_layer_index].get_layer_index()] == 'Infected':
            self.infected_category_indices.append(layer_map[self.layer_index][next_layer_index].
                                                  get_layer_index())

        # warnings
        else:  # pragma: no cover
            warnings.warn('It seems like you want to connect Exposed layer at %s to the layer at %s. \n'
                          'However, only Infected layers can be placed in front of Exposed layers. \n'
                          'Consider creating a custom layer to handle this or remove the connection.' %
                          (self.layer_index, next_layer_index))

    # warnings
    if len(self.prev_layer_indices) == 0:  # pragma: no cover
        warnings.warn('It seems that the Exposed layer at %s is not in use. Please find a Susceptible \n'
                      'layer to route through this layer or remove this layer altogether.' %
                      self.layer_index)
class Hospitalized (layer_index, hos_rate, p_hos, cri_rate=None, p_cri=None, recovery_rate=None, p_recovery=None, rho=None, alpha=None, maxCap=None, dump_to_layer=None)

The Hospitalized class represents the portion of individuals currently taking up space in the available hospitals. However, this is a distinct category from the Critical portion of individuals, who require more resources (ICU beds, ventilators, etc.). This layer supports triage.

Infected –> Hospitalized –> Critical, Dead

Structure:

- __init__
- get_layer_index
- test
- get_deriv

Initialize the Hospitalized class

  • layer_index: index of layer in layers
  • hos_rate: 1 / time until hospitalization implemented as a function hos_rate(t)
    • t: time
    • return: hospitalization rate
  • p_hos: probability of hospitalization implemented as a function p_hos(t)
    • t: time
    • return: probability of hospitalization
  • cri_rate: =None, 1 / time until a patient becomes Critical (only applicable if next layer is Critical) implemented as a function cri_rate(t)
    • t: time
    • return: critical rate
  • p_cri: =None, probability of becoming a Critical patient (only applicable if next layer is Critical) implemented as a function p_cri(t)
    • t: time
    • return: probability of becoming Critical
  • recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered) implemented as a function recovery_rate(t)
    • t: time
    • return: recovery rate
  • p_recovery: =None, probability of recovery (only applicable if next layer is Recovered) implemented as a function p_recovery(t)
    • t: time
    • return: probability of recovery
  • rho: =None, 1 / time in hospital until death (only applicable if next layer is Dead) implemented as a function rho(t)–in most cases this should stay constant
    • t: time
    • return: death rate
  • alpha: =None, probability of death (only applicable if next layer is Dead) implemented as a function alpha(t)
    • t: time
    • return: probability of death
  • maxCap: =None, maximum hospital capacity to implement triage implemented as a function maxCap(t)
    • t: time
    • return: maximum capacity
  • dump_to_layer: =None, index of the layer to dump patients which do not make the triage should be of type int()
Expand source code
class Hospitalized(object):
    """
    The Hospitalized class represents the portion of individuals currently taking up space in the available
    hospitals. However, this is a distinct category from the Critical portion of individuals, who require
    more resources (ICU beds, ventilators, etc.). This layer supports triage.

    Infected --> Hospitalized --> Critical, Dead

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, hos_rate, p_hos, cri_rate=None, p_cri=None, recovery_rate=None,
                 p_recovery=None, rho=None, alpha=None, maxCap=None, dump_to_layer=None):
        """
        Initialize the Hospitalized class

        - layer_index: index of layer in `layers`
        - hos_rate: 1 / time until hospitalization\
            implemented as a function hos_rate(t)
            - t: time
            - return: hospitalization rate
        - p_hos: probability of hospitalization\
            implemented as a function p_hos(t)
            - t: time
            - return: probability of hospitalization
        - cri_rate: =None, 1 / time until a patient becomes Critical (only applicable if next layer is Critical)\
            implemented as a function cri_rate(t)
            - t: time
            - return: critical rate
        - p_cri: =None, probability of becoming a Critical patient (only applicable if next layer is Critical)\
            implemented as a function p_cri(t)
            - t: time
            - return: probability of becoming Critical
        - recovery_rate: =None, 1 / time to recover (only applicable if next layer is Recovered)\
            implemented as a function recovery_rate(t)
            - t: time
            - return: recovery rate
        - p_recovery: =None, probability of recovery (only applicable if next layer is Recovered)\
            implemented as a function p_recovery(t)
            - t: time
            - return: probability of recovery
        - rho: =None, 1 / time in hospital until death (only applicable if next layer is Dead)\
            implemented as a function rho(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        - alpha: =None, probability of death (only applicable if next layer is Dead)\
            implemented as a function alpha(t)
            - t: time
            - return: probability of death
        - maxCap: =None, maximum hospital capacity to implement triage\
            implemented as a function maxCap(t)
            - t: time
            - return: maximum capacity
        - dump_to_layer: =None, index of the layer to dump patients which do not make the triage
            should be of type int()
        """

        self.layer_index = layer_index
        self.hos_rate = hos_rate
        self.p_hos = p_hos
        self.cri_rate = cri_rate
        self.p_cri = p_cri
        self.recovery_rate = recovery_rate
        self.p_recovery = p_recovery
        self.rho = rho
        self.alpha = alpha
        self.maxCap = maxCap
        self.dump_to_layer = dump_to_layer

        self.prev_layer_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                    self.prev_layer_indices.append(layer_no)
                # warnings
                elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                    warnings.warn('A layer of an unsupported type at %s is being connected to the Infected \n'
                                  'layer at %s. If this is a mistake, remove the connection. Otherwise, try \n'
                                  'using a custom layer to do this.' % (layer_no, self.layer_index))

    def get_deriv(self, time, system):
        """
        Derivative of the Hospitalized compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        # no triage
        for prev_layer_index in self.prev_layer_indices:
            derivative += self.hos_rate(time) * self.p_hos(time) * system[prev_layer_index]

        if self.p_cri:
            derivative -= self.p_cri(time) * self.cri_rate(time) * system[self.layer_index]
        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
        if self.alpha:
            derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]

        # implement triage
        if self.maxCap and system[self.layer_index] > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        # limited triage
        elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
            system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
            derivative = self.maxCap(time) - system[self.layer_index]

        return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Hospitalized compartment

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Hospitalized compartment

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    derivative = 0

    # no triage
    for prev_layer_index in self.prev_layer_indices:
        derivative += self.hos_rate(time) * self.p_hos(time) * system[prev_layer_index]

    if self.p_cri:
        derivative -= self.p_cri(time) * self.cri_rate(time) * system[self.layer_index]
    if self.p_recovery:
        derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
    if self.alpha:
        derivative -= self.alpha(time) * self.rho(time) * system[self.layer_index]

    # implement triage
    if self.maxCap and system[self.layer_index] > self.maxCap(time):
        system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
        derivative = self.maxCap(time) - system[self.layer_index]

    # limited triage
    elif self.maxCap and system[self.layer_index] + derivative > self.maxCap(time):
        system[self.dump_to_layer] += self.maxCap(time) - system[self.layer_index]
        derivative = self.maxCap(time) - system[self.layer_index]

    return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:
            if next_layer.get_layer_index() == self.layer_index and layer_names[layer_no] == 'Infected':
                self.prev_layer_indices.append(layer_no)
            # warnings
            elif next_layer.get_layer_index() == self.layer_index:  # pragma: no cover
                warnings.warn('A layer of an unsupported type at %s is being connected to the Infected \n'
                              'layer at %s. If this is a mistake, remove the connection. Otherwise, try \n'
                              'using a custom layer to do this.' % (layer_no, self.layer_index))
class Idiom (layer_index, param_list=None)

An idiom used to create custom classes. Feed this into Model.add_layer to be used with any class. Make sure to change get_deriv file. If you wish, you can change all the other methods as well. Pass all parameters as an array in param_list

Structure:

- __init__
- get_layer_index
- test
- get_deriv

Initialize the class

  • layer_index: index of layer in layers
  • param_list: =[], list of parameters, passed in array format
Expand source code
class Idiom(object):  # pragma: no cover
    """
    An idiom used to create custom classes. Feed this into `Model.add_layer` to
    be used with any class. Make sure to change `get_deriv` file.
    If you wish, you can change all the other methods as well.
    Pass all parameters as an array in `param_list`

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, param_list=None):
        """
        Initialize the class

        - layer_index: index of layer in `layers`
        - param_list: =[], list of parameters, passed in array format
        """

        self.layer_index = layer_index
        self.param_list = param_list

        self.prev_layer_indices = []
        self.prev_layer_types = []
        self.next_layer_indices = []
        self.next_layer_types = []
        self.test_info = []  # use this to store any test information to be passed on to get_deriv

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.prev_layer_indices.append(layer_no)
                    self.prev_layer_types.append(layer_names[layer_no])

        for next_layer_no in range(len(layer_map[self.layer_index])):
            self.next_layer_indices.append(layer_map[self.layer_index][next_layer_no].get_layer_index())
            self.next_layer_types.append(layer_names[layer_map[self.layer_index][next_layer_no].
                                         get_layer_index()])

    def get_deriv(self):
        """
        Derivative of this compartment
        Setup by changing the function--create a new method with parameters time & system:

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        # warn on no setup
        warnings.warn("The Idiom layer at %s has not been set up yet. Please replace the `get_deriv` method by \n"
                      "adding IDIOM_LAYER_NAME.get_deriv = SOME_FUNCTION(self, time, system). Please see this \n"
                      "function's documentation for more info" % self.layer_index)  # pragma: no cover
        return None

Methods

def get_deriv(self)

Derivative of this compartment Setup by changing the function–create a new method with parameters time & system:

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self):
    """
    Derivative of this compartment
    Setup by changing the function--create a new method with parameters time & system:

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    # warn on no setup
    warnings.warn("The Idiom layer at %s has not been set up yet. Please replace the `get_deriv` method by \n"
                  "adding IDIOM_LAYER_NAME.get_deriv = SOME_FUNCTION(self, time, system). Please see this \n"
                  "function's documentation for more info" % self.layer_index)  # pragma: no cover
    return None
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:
            if next_layer.get_layer_index() == self.layer_index:
                self.prev_layer_indices.append(layer_no)
                self.prev_layer_types.append(layer_names[layer_no])

    for next_layer_no in range(len(layer_map[self.layer_index])):
        self.next_layer_indices.append(layer_map[self.layer_index][next_layer_no].get_layer_index())
        self.next_layer_types.append(layer_names[layer_map[self.layer_index][next_layer_no].
                                     get_layer_index()])
class Infected (layer_index, N, R_0=None, gamma=None, delta=None, p_recovery=None, recovery_rate=None, p_hospitalized=None, hospital_rate=None, p_critical=None, critical_rate=None, p_death=None, death_rate=None)

The Infected class is the 'I' of the 'SIR' Model. This is the portion of individuals who are actively spreading the disease.

Susceptible, Exposed –> Infected –> Recovered, Hospitalized, Critical, Dead

Structure:

- __init__
- get_layer_index
- test
- get_deriv

Initialize the Infected class

  • layer_index: index of layer in layers
  • N: the total population implemented as a function N(t):
    • t: time
    • return: total population
  • R_0: =None, the basic reproductive number (only applicable if previous layer is Susceptible)– this is the average number of Susceptibles infected by one Infected implemented as a function R_0(t):
    • t: time
    • return: R_0 value
  • gamma: =None, the infectious period (only applicable if previous layer is Susceptible)– 1 / average duration of infectious period implemented as a function gamma(t):
    • t: time
    • return: infectious period
  • delta: =None, the incubation period (only applicable if previous layer is Exposed) implemented as a function delta(t)–in most cases this should stay constant
    • t: time
    • return: incubation period
  • p_recovery: =None, probability of recovery– (only applicable if next layer is Recovered) implemented as a function p_recovery(t):
    • t: time
    • return: probability of recovery
  • recovery_rate: =None, the recovery rate–different from the standard recovery rate gamma– measures only 1 / the time it takes to move to the Recovered layer (only applicable if next layer is Recovered) implemented as a function recovery_rate(t):
    • t: time
    • return: recovery rate
  • p_hospitalized: =None, probability of hospitalization– (only applicable if next layer is Hospitalized) implemented as a function p_hospitalized(t):
    • t: time
    • return: probability of hospitalization
  • hospital_rate: =None, 1 / average time to hospitalization– (only applicable if next layer is Hospitalized) implemented as a function hospital_rate(t)
    • t: time
    • return: hospitalization rate
  • p_critical: =None, probability of becoming a critical patient– (only applicable if next layer is Critical) implemented as a function p_critical(t)
    • t: time
    • return: critical probability
  • critical_rate: =None, 1 / average time to becoming a critical patient– (only applicable if next layer is Critical) implemented as a function critical_rate(t)
    • t: time
    • return: critical rate
  • p_death: =None, probability of death (only applicable if next layer is Dead) implemented as a function p_death(t)
    • t: time
    • return: death probability
  • death_rate: =None, 1 / rate of death (only applicable if next layer is Dead) implemented as a function death_rate(t)–in most cases this should stay constant
    • t: time
    • return: death rate
Expand source code
class Infected(object):
    """
    The Infected class is the 'I' of the 'SIR' Model.
    This is the portion of individuals who are actively spreading the disease.

    Susceptible, Exposed --> Infected --> Recovered, Hospitalized, Critical, Dead

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, N, R_0=None, gamma=None, delta=None, p_recovery=None, recovery_rate=None,
                 p_hospitalized=None, hospital_rate=None, p_critical=None, critical_rate=None,
                 p_death=None, death_rate=None):
        """
        Initialize the Infected class

        - layer_index: index of layer in `layers`
        - N: the total population\
          implemented as a function N(t):
            - t: time
            - return: total population
        - R_0: =None, the basic reproductive number (only applicable if previous layer is Susceptible)--
            this is the average number of Susceptibles infected by one Infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: =None, the infectious period (only applicable if previous layer is Susceptible)--
          1 / average duration of infectious period\
          implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - delta: =None, the incubation period (only applicable if previous layer is Exposed)\
          implemented as a function delta(t)--in most cases this should stay constant
            - t: time
            - return: incubation period
        - p_recovery: =None, probability of recovery--
          (only applicable if next layer is Recovered)\
          implemented as a function p_recovery(t):
            - t: time
            - return: probability of recovery
        - recovery_rate: =None, the recovery rate--different from the standard recovery rate `gamma`--
            measures only 1 / the time it takes to move to the Recovered layer
            (only applicable if next layer is Recovered)\
            implemented as a function recovery_rate(t):
            - t: time
            - return: recovery rate
        - p_hospitalized: =None, probability of hospitalization--
          (only applicable if next layer is Hospitalized)\
          implemented as a function p_hospitalized(t):
            - t: time
            - return: probability of hospitalization
        - hospital_rate: =None, 1 / average time to hospitalization--
          (only applicable if next layer is Hospitalized)\
          implemented as a function hospital_rate(t)
            - t: time
            - return: hospitalization rate
        - p_critical: =None, probability of becoming a critical patient--
          (only applicable if next layer is Critical)\
          implemented as a function p_critical(t)
            - t: time
            - return: critical probability
        - critical_rate: =None, 1 / average time to becoming a critical patient--
          (only applicable if next layer is Critical)\
          implemented as a function critical_rate(t)
            - t: time
            - return: critical rate
        - p_death: =None, probability of death (only applicable if next layer is Dead)\
          implemented as a function p_death(t)
            - t: time
            - return: death probability
        - death_rate: =None, 1 / rate of death (only applicable if next layer is Dead)\
          implemented as a function death_rate(t)--in most cases this should stay constant
            - t: time
            - return: death rate
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.delta = delta
        self.N = N
        self.p_recovery = p_recovery
        self.recovery_rate = recovery_rate
        self.p_hospitalized = p_hospitalized
        self.hospital_rate = hospital_rate
        self.p_critical = p_critical
        self.critical_rate = critical_rate
        self.p_death = p_death
        self.death_rate = death_rate

        self.prev_layer_type = None
        self.prev_layer_indices = []

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_map_no in range(len(layer_map)):
            for next_layer in layer_map[layer_map_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.prev_layer_type = layer_names[layer_map_no]
                    self.prev_layer_indices.append(layer_map_no)
                # warning if there are different input layer types
                if self.prev_layer_type is not None and next_layer.get_layer_index() == self.layer_index and \
                   layer_names[layer_map_no] != self.prev_layer_type:  # pragma: no cover
                    warnings.warn('Not all input layers to the Infected layer at %s are the same. \n'
                                  'Input layers to the Infected layer should either all be Susceptible \n'
                                  'or Exposed. Consider changing the `layer_map`.' %
                                  self.layer_index)

        # warnings
        # undefined parameters
        if self.prev_layer_type == 'Susceptible' and not self.R_0:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                          'basic reproductive number is not defined. Please define as `R_0=Value`.' %
                          self.layer_index)
        if self.prev_layer_type == 'Susceptible' and not self.gamma:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                          'recovery rate is not defined. Please define as `gamma=Value.`' % self.layer_index)

        if self.prev_layer_type == 'Exposed' and not self.delta:  # pragma: no cover
            warnings.warn('The previous layer type to the Infected layer at %s is Exposed and the \n'
                          'incubation period is not defined. Please define as `delta=Value`.' % self.layer_index)

        # layer structures
        if self.prev_layer_type != 'Susceptible' and self.prev_layer_type != 'Exposed':  # pragma: no cover
            warnings.warn('Input layer types to the Infected layer at %s must be either \n'
                          'Susceptible or Exposed. Consider changing the Input layers in `layer_map` \n'
                          'or creating a custom Infected layer using `add_layer`.' % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Infected compartment
        all layers feeding into the infected layer must be of the same type and either Susceptible or
        Exposed

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_prev_layer = 0
        for prev_layer_index in self.prev_layer_indices:
            total_prev_layer += system[prev_layer_index]

        if self.prev_layer_type == 'Susceptible':
            derivative = self.gamma(time) * self.R_0(time) * total_prev_layer * \
                   system[self.layer_index] / self.N(time)
        if self.prev_layer_type == 'Exposed':
            derivative = self.delta(time) * total_prev_layer

        if self.p_recovery:
            derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
        if self.p_hospitalized:
            derivative -= self.p_hospitalized(time) * self.hospital_rate(time) * system[self.layer_index]
        if self.p_critical:
            derivative -= self.p_critical(time) * self.critical_rate(time) * system[self.layer_index]
        if self.p_death:
            derivative -= self.p_death(time) * self.death_rate(time) * system[self.layer_index]

        return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Infected compartment all layers feeding into the infected layer must be of the same type and either Susceptible or Exposed

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Infected compartment
    all layers feeding into the infected layer must be of the same type and either Susceptible or
    Exposed

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    total_prev_layer = 0
    for prev_layer_index in self.prev_layer_indices:
        total_prev_layer += system[prev_layer_index]

    if self.prev_layer_type == 'Susceptible':
        derivative = self.gamma(time) * self.R_0(time) * total_prev_layer * \
               system[self.layer_index] / self.N(time)
    if self.prev_layer_type == 'Exposed':
        derivative = self.delta(time) * total_prev_layer

    if self.p_recovery:
        derivative -= self.p_recovery(time) * self.recovery_rate(time) * system[self.layer_index]
    if self.p_hospitalized:
        derivative -= self.p_hospitalized(time) * self.hospital_rate(time) * system[self.layer_index]
    if self.p_critical:
        derivative -= self.p_critical(time) * self.critical_rate(time) * system[self.layer_index]
    if self.p_death:
        derivative -= self.p_death(time) * self.death_rate(time) * system[self.layer_index]

    return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_map_no in range(len(layer_map)):
        for next_layer in layer_map[layer_map_no]:
            if next_layer.get_layer_index() == self.layer_index:
                self.prev_layer_type = layer_names[layer_map_no]
                self.prev_layer_indices.append(layer_map_no)
            # warning if there are different input layer types
            if self.prev_layer_type is not None and next_layer.get_layer_index() == self.layer_index and \
               layer_names[layer_map_no] != self.prev_layer_type:  # pragma: no cover
                warnings.warn('Not all input layers to the Infected layer at %s are the same. \n'
                              'Input layers to the Infected layer should either all be Susceptible \n'
                              'or Exposed. Consider changing the `layer_map`.' %
                              self.layer_index)

    # warnings
    # undefined parameters
    if self.prev_layer_type == 'Susceptible' and not self.R_0:  # pragma: no cover
        warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                      'basic reproductive number is not defined. Please define as `R_0=Value`.' %
                      self.layer_index)
    if self.prev_layer_type == 'Susceptible' and not self.gamma:  # pragma: no cover
        warnings.warn('The previous layer type to the Infected layer at %s is Susceptible and the \n'
                      'recovery rate is not defined. Please define as `gamma=Value.`' % self.layer_index)

    if self.prev_layer_type == 'Exposed' and not self.delta:  # pragma: no cover
        warnings.warn('The previous layer type to the Infected layer at %s is Exposed and the \n'
                      'incubation period is not defined. Please define as `delta=Value`.' % self.layer_index)

    # layer structures
    if self.prev_layer_type != 'Susceptible' and self.prev_layer_type != 'Exposed':  # pragma: no cover
        warnings.warn('Input layer types to the Infected layer at %s must be either \n'
                      'Susceptible or Exposed. Consider changing the Input layers in `layer_map` \n'
                      'or creating a custom Infected layer using `add_layer`.' % self.layer_index)
class Recovered (layer_index, p_from_inf=None, from_inf_rate=None, p_from_cri=None, from_cri_rate=None, p_from_hos=None, from_hos_rate=None, p_resusceptibility=None, s_rate=None)

The Recovered class can act like the 'R' of the 'SIR' Model if the recovery and death rates are the same. This class actually consists of individuals who have had the disease and recovered (i.e. did not die). This class can be used as a terminal state.

Infected, Critical, Hospitalized –> Recovered –> Susceptible (?)

Structure:

- __init__
- get_layer_index
- test
- get_deriv

Initialize the Recovered class

  • layer_index: index of layer in layers
  • p_from_inf: =None, probability of recovery from Infected (only applicable if previous layer is Infected) implemented as a function p_from_inf(t)
    • t: time
    • return: probability of recovery
  • from_inf_rate: =None, 1 / time to recover from Infected (only applicable if previous layer is Infected) implemented as a function from_inf_rate(t)
    • t: time
    • return: recovery rate
  • p_from_cri: =None, probability of recovery from Critical (only applicable if previous layer is Critical) implemented as a function p_from_cri(t)
    • t: time
    • return: probability of recovery
  • from_cri_rate: =None, 1 / time to recover from Critical (only applicable if previous layer is Critical) implemented as a function from_cri_rate(t)
    • t: time
    • return: recovery rate
  • p_from_hos: =None, probability of recovery from Hospitalized– (only applicable if previous layer is Hospitalized) implemented as a function p_from_hos(t)
    • t: time
    • return: probability of recovery
  • from_hos_rate: =None, 1 / time to recover from Hospitalized– (only applicable if previous layer is Hospitalized) implemented as a function from_hos_rate(t)
    • t: time
    • return: recovery rate
  • p_resusceptibility: =None, probability of resusceptibility (only applicable if next layer is Susceptible) implemented as a function p_resusceptibility(t)
    • t: time
    • return: probability of resusceptibility
  • s_rate: =None, 1 / time to resusceptibility (only applicable if next layer is Susceptible) implemented as a function s_rate(t)
    • t: time
    • return: rate of resusceptibility
Expand source code
class Recovered(object):
    """
    The Recovered class can act like the 'R' of the 'SIR' Model if the recovery and death rates are the same.
    This class actually consists of individuals who have had the disease and recovered (i.e. did not die).
    This class can be used as a terminal state.

    Infected, Critical, Hospitalized --> Recovered --> Susceptible (?)

    ## Structure:

        - __init__
        - get_layer_index
        - test
        - get_deriv
    """

    def __init__(self, layer_index, p_from_inf=None, from_inf_rate=None, p_from_cri=None,
                 from_cri_rate=None, p_from_hos=None, from_hos_rate=None, p_resusceptibility=None,
                 s_rate=None):
        """
        Initialize the Recovered class

        - layer_index: index of layer in `layers`
        - p_from_inf: =None, probability of recovery from Infected (only applicable if previous layer is Infected)\
           implemented as a function p_from_inf(t)
            - t: time
            - return: probability of recovery
        - from_inf_rate: =None, 1 / time to recover from Infected (only applicable if previous layer is Infected)\
           implemented as a function from_inf_rate(t)
            - t: time
            - return: recovery rate
        - p_from_cri: =None, probability of recovery from Critical (only applicable if previous layer is Critical)\
           implemented as a function p_from_cri(t)
            - t: time
            - return: probability of recovery
        - from_cri_rate: =None, 1 / time to recover from Critical (only applicable if previous layer is Critical)\
           implemented as a function from_cri_rate(t)
            - t: time
            - return: recovery rate
        - p_from_hos: =None, probability of recovery from Hospitalized--
           (only applicable if previous layer is Hospitalized)\
           implemented as a function p_from_hos(t)
            - t: time
            - return: probability of recovery
        - from_hos_rate: =None, 1 / time to recover from Hospitalized--
           (only applicable if previous layer is Hospitalized)\
           implemented as a function from_hos_rate(t)
            - t: time
            - return: recovery rate
        - p_resusceptibility: =None, probability of resusceptibility (only applicable if next layer is Susceptible)\
           implemented as a function p_resusceptibility(t)
            - t: time
            - return: probability of resusceptibility
        - s_rate: =None, 1 / time to resusceptibility (only applicable if next layer is Susceptible)\
           implemented as a function s_rate(t)
            - t: time
            - return: rate of resusceptibility
        """

        self.layer_index = layer_index
        self.p_from_inf = p_from_inf
        self.from_inf_rate = from_inf_rate
        self.p_from_cri = p_from_cri
        self.from_cri_rate = from_cri_rate
        self.p_from_hos = p_from_hos
        self.from_hos_rate = from_hos_rate
        self.p_resusceptibility = p_resusceptibility
        self.s_rate = s_rate

        self.prev_layer_indices_by_type = [[], [], []]  # in order [infected, critical, hospitalized]

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    if layer_names[layer_no] == 'Infected':
                        self.prev_layer_indices_by_type[0].append(layer_no)
                    elif layer_names[layer_no] == 'Critical':
                        self.prev_layer_indices_by_type[1].append(layer_no)
                    elif layer_names[layer_no] == 'Hospitalized':
                        self.prev_layer_indices_by_type[2].append(layer_no)
                    else:  # pragma: no cover
                        warnings.warn('Previous layer at %s to Recovered layer at %s is not Infected, Critical, or \n'
                                      'Hospitalized. Consider either correcting the `layer_map` if this is not \n'
                                      'supposed to happen, or accomodating for this setup by using a custom \n'
                                      'Recovered layer.' % (layer_no, self.layer_index))

        # warnings
        for next_layer_index in range(len(layer_map[self.layer_index])):
            if layer_names[next_layer_index] != 'Susceptible':  # pragma: no cover
                warnings.warn('The next layer to the Recovered layer at %s must be a Susceptible layer. \n'
                              'Change the `layer_map` to avoid this complication or use a custom layer.'
                              % self.layer_index)

    def get_deriv(self, time, system):
        """
        Derivative of the Recovered compartment

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        derivative = 0

        # previous layers
        # infected
        for prev_layer_index in self.prev_layer_indices_by_type[0]:
            derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[prev_layer_index]

        # critical
        for prev_layer_index in self.prev_layer_indices_by_type[1]:
            derivative += self.p_from_cri(time) * self.from_cri_rate(time) * system[prev_layer_index]

        # hospitalized
        for prev_layer_index in self.prev_layer_indices_by_type[2]:
            derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[prev_layer_index]

        # next layers
        # susceptible
        if self.p_resusceptibility:
            derivative -= self.p_resusceptibility(time) * self.s_rate(time) * system[self.layer_index]

        return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Recovered compartment

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Recovered compartment

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    derivative = 0

    # previous layers
    # infected
    for prev_layer_index in self.prev_layer_indices_by_type[0]:
        derivative += self.p_from_inf(time) * self.from_inf_rate(time) * system[prev_layer_index]

    # critical
    for prev_layer_index in self.prev_layer_indices_by_type[1]:
        derivative += self.p_from_cri(time) * self.from_cri_rate(time) * system[prev_layer_index]

    # hospitalized
    for prev_layer_index in self.prev_layer_indices_by_type[2]:
        derivative += self.p_from_hos(time) * self.from_hos_rate(time) * system[prev_layer_index]

    # next layers
    # susceptible
    if self.p_resusceptibility:
        derivative -= self.p_resusceptibility(time) * self.s_rate(time) * system[self.layer_index]

    return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:
            if next_layer.get_layer_index() == self.layer_index:
                if layer_names[layer_no] == 'Infected':
                    self.prev_layer_indices_by_type[0].append(layer_no)
                elif layer_names[layer_no] == 'Critical':
                    self.prev_layer_indices_by_type[1].append(layer_no)
                elif layer_names[layer_no] == 'Hospitalized':
                    self.prev_layer_indices_by_type[2].append(layer_no)
                else:  # pragma: no cover
                    warnings.warn('Previous layer at %s to Recovered layer at %s is not Infected, Critical, or \n'
                                  'Hospitalized. Consider either correcting the `layer_map` if this is not \n'
                                  'supposed to happen, or accomodating for this setup by using a custom \n'
                                  'Recovered layer.' % (layer_no, self.layer_index))

    # warnings
    for next_layer_index in range(len(layer_map[self.layer_index])):
        if layer_names[next_layer_index] != 'Susceptible':  # pragma: no cover
            warnings.warn('The next layer to the Recovered layer at %s must be a Susceptible layer. \n'
                          'Change the `layer_map` to avoid this complication or use a custom layer.'
                          % self.layer_index)
class Susceptible (layer_index, R_0, gamma, N, p_resusceptibility=None, s_rate=None)

The Susceptible class is the 'S' of the 'SIR' Model. This is the portion of individuals who have not yet been exposed to the disease. This class can be used as a beginning state.

Recovered (?) –> Susceptible –> Infected

Structure:

  • init
  • get_layer_index
  • test
  • get_deriv

Initialize the Susceptible class

  • layer_index: index of layer in layers
  • R_0: the basic reproductive number– this is the average number of susceptibles infected by one infected implemented as a function R_0(t):
    • t: time
    • return: R_0 value
  • gamma: the infectious period– 1 / average duration of infectious period implemented as a function gamma(t):
    • t: time
    • return: infectious period
  • N: the total population implemented as a function N(t):
    • t: time
    • return: total population
  • p_resusceptibility: =None, probability of re-susceptibility (0 <= x <= 1)– only applicable if individuals can become susceptible again implemented as a function p_resusceptibility(t):
    • t: time
    • return: probability of re-susceptibility
  • s_rate: =None, 1 / average time to become susceptible again– only applicable if individuals can become susceptible again implemented as a function s_rate(t):
    • t: time
    • return: susceptiblity rate
Expand source code
class Susceptible(object):
    """
    The Susceptible class is the 'S' of the 'SIR' Model.
    This is the portion of individuals who have not yet been exposed to the disease.
    This class can be used as a beginning state.

    Recovered (?) --> Susceptible --> Infected

    ## Structure:

    - __init__
    - get_layer_index
    - test
    - get_deriv
    """

    def __init__(self, layer_index, R_0, gamma, N, p_resusceptibility=None, s_rate=None):
        """
        Initialize the Susceptible class
        
        - layer_index: index of layer in `layers`
        - R_0: the basic reproductive number--
            this is the average number of susceptibles infected by one infected\
            implemented as a function R_0(t):
            - t: time
            - return: R_0 value
        - gamma: the infectious period--
            1 / average duration of infectious period\
            implemented as a function gamma(t):
            - t: time
            - return: infectious period
        - N: the total population\
            implemented as a function N(t):
            - t: time
            - return: total population
        - p_resusceptibility: =None, probability of re-susceptibility (0 <= x <= 1)--
           only applicable if individuals can become susceptible again\
           implemented as a function p_resusceptibility(t):
            - t: time
            - return: probability of re-susceptibility
        - s_rate: =None, 1 / average time to become susceptible again--
           only applicable if individuals can become susceptible again\
           implemented as a function s_rate(t):
            - t: time
            - return: susceptiblity rate
        """

        self.layer_index = layer_index
        self.R_0 = R_0
        self.gamma = gamma
        self.N = N

        self.p_resusceptibility = p_resusceptibility
        self.s_rate = s_rate

        self.infected_category_indices = []
        self.prev_layer_indices = []
        self.first_layer = True

    def get_layer_index(self):
        return self.layer_index

    def test(self, layer_map, layer_names):
        """
        Test of the `get_deriv` method
        Used to setup commonly used variables and raise common errors

        - layer_map: next layers (as classes) for every layer in Model
        - layer_names: layer names in system
        - return: derivative
        """

        # setup
        for i in range(0, len(layer_names)):
            if layer_names[i] == 'Infected':
                self.infected_category_indices.append(i)

        for layer_no in range(len(layer_map)):
            for next_layer in layer_map[layer_no]:
                if next_layer.get_layer_index() == self.layer_index:
                    self.first_layer = False
                    self.prev_layer_indices.append(layer_no)

        # tests
        if not self.first_layer and not self.s_rate:  # pragma: no cover
            warnings.warn('The Susceptible layer at %s is not the first layer and there does not seem \n'
                          'to be any specified susceptibility rate. You can specify this \n'
                          'by passing `s_rate=Value` into this layer.' % self.layer_index)

        for prev_layer_index in self.prev_layer_indices:
            if layer_names[prev_layer_index] != 'Removed' and \
               layer_names[prev_layer_index] != 'Recovered':  # pragma: no cover
                warnings.warn('Previous layer at %s to the Susceptible layer at %s is neither Removed or \n'
                              'Recovered. If you want to create a layer which does this, add a custom \n'
                              'layer through `add_layer`. If not, fix the `layer_map`.' % (prev_layer_index,
                                                                                           self.layer_index))

    def get_deriv(self, time, system):
        """
        Derivative of the Susceptible compartment
        must be the *only* Susceptible compartment which people from other layers may enter

        - time: time to take derivative at
        - system: system of all states
        - return: derivative
        """

        total_infecteds = 0
        for infected_category_index in self.infected_category_indices:
            total_infecteds += system[infected_category_index]

        derivative = - self.gamma(time) * self.R_0(time) * system[self.layer_index] * \
                       total_infecteds / self.N(time)

        if self.first_layer:
            return derivative

        else:
            possible_new_susceptibles = 0
            for prev_layer_index in self.prev_layer_indices:
                possible_new_susceptibles += system[prev_layer_index]

            derivative += self.p_resusceptibility(time) * self.s_rate(time) * possible_new_susceptibles
            return derivative

Methods

def get_deriv(self, time, system)

Derivative of the Susceptible compartment must be the only Susceptible compartment which people from other layers may enter

  • time: time to take derivative at
  • system: system of all states
  • return: derivative
Expand source code
def get_deriv(self, time, system):
    """
    Derivative of the Susceptible compartment
    must be the *only* Susceptible compartment which people from other layers may enter

    - time: time to take derivative at
    - system: system of all states
    - return: derivative
    """

    total_infecteds = 0
    for infected_category_index in self.infected_category_indices:
        total_infecteds += system[infected_category_index]

    derivative = - self.gamma(time) * self.R_0(time) * system[self.layer_index] * \
                   total_infecteds / self.N(time)

    if self.first_layer:
        return derivative

    else:
        possible_new_susceptibles = 0
        for prev_layer_index in self.prev_layer_indices:
            possible_new_susceptibles += system[prev_layer_index]

        derivative += self.p_resusceptibility(time) * self.s_rate(time) * possible_new_susceptibles
        return derivative
def get_layer_index(self)
Expand source code
def get_layer_index(self):
    return self.layer_index
def test(self, layer_map, layer_names)

Test of the get_deriv method Used to setup commonly used variables and raise common errors

  • layer_map: next layers (as classes) for every layer in Model
  • layer_names: layer names in system
  • return: derivative
Expand source code
def test(self, layer_map, layer_names):
    """
    Test of the `get_deriv` method
    Used to setup commonly used variables and raise common errors

    - layer_map: next layers (as classes) for every layer in Model
    - layer_names: layer names in system
    - return: derivative
    """

    # setup
    for i in range(0, len(layer_names)):
        if layer_names[i] == 'Infected':
            self.infected_category_indices.append(i)

    for layer_no in range(len(layer_map)):
        for next_layer in layer_map[layer_no]:
            if next_layer.get_layer_index() == self.layer_index:
                self.first_layer = False
                self.prev_layer_indices.append(layer_no)

    # tests
    if not self.first_layer and not self.s_rate:  # pragma: no cover
        warnings.warn('The Susceptible layer at %s is not the first layer and there does not seem \n'
                      'to be any specified susceptibility rate. You can specify this \n'
                      'by passing `s_rate=Value` into this layer.' % self.layer_index)

    for prev_layer_index in self.prev_layer_indices:
        if layer_names[prev_layer_index] != 'Removed' and \
           layer_names[prev_layer_index] != 'Recovered':  # pragma: no cover
            warnings.warn('Previous layer at %s to the Susceptible layer at %s is neither Removed or \n'
                          'Recovered. If you want to create a layer which does this, add a custom \n'
                          'layer through `add_layer`. If not, fix the `layer_map`.' % (prev_layer_index,
                                                                                       self.layer_index))