aegis_sim.submodels.resources.starvation

Overshoot resolver

Decides which individuals to eliminate when there is overcrowding.

  1"""Overshoot resolver
  2
  3Decides which individuals to eliminate when there is overcrowding.
  4"""
  5
  6import logging
  7import numpy as np
  8from aegis_sim.submodels.frailty import frailty
  9from aegis_sim import variables
 10
 11
 12class Starvation:
 13    """
 14
 15    GUI
 16    Starvation is a source of mortality useful for modeling death from lack of resources.
 17    It is usually the largest contributor to mortality. Generally, each individual requires one unit of resources to survive.
 18    When the population size exceeds the amount of resources available, the population experiences starvation.
 19    Starvation is either experienced by the whole population or by no individual; i.e. the distribution of resources is equal across individuals.
 20
 21    Starvation mortality can operate under one of two modes. Under the first mode, population is sensitive to the
 22    amount of resource deficit it is experiencing and will respond promptly to it. For example, if the population size is 1000
 23    and there are only 800 resource units available, approximately only 800 individuals will survive, thus approximately 200 will die,
 24    so the mortality will be about 20% (200/1000).
 25
 26    Under the second mode, population is not sensitive to the amount of resource deficit but rather to the number of consecutive simulation steps
 27    that it has experienced starvation. When it first experiences resource deficit, the mortality will be [[STARVATION_MORTALITY_FACTOR]].
 28    If in the next step, the population size is still greater than the amount of available resources, the mortality will now be
 29    approximately twice as high; in the next step, about three times, etc.
 30
 31    The probability to die is independent of genetics (genetics do not confer protection nor susceptibility to starvation).
 32    However, age can modify the probability to die, depending on the [[FRAILTY_MODIFIER]]. When [[FRAILTY_MODIFIER]] is 0,
 33    there is no age-dependent effect. When it is greater than 0, then starvation mortality is magnified by [[FRAILTY_MODIFIER]]
 34    for the oldest age class, by 0 for the youngest age class, and proportionally for the intermediate age classes (e.g. by 20% of the
 35    [[FRAILTY_MODIFIER]] for the age class 10 if [[AGE_LIMIT]] is 50 because 10/50=0.2).
 36
 37    If [[STARVATION_MORTALITY_MAXIMUM]] is set, the final computed mortality will be at maximum of that value, not higher.
 38
 39    Note that if the species is oviparious ([[INCUBATION_PERIOD]]), the produced eggs do not consume resources and are
 40    immune to starvation mortality (until they hatch).
 41    """
 42
 43    def init(self, STARVATION_MORTALITY_MAXIMUM, STARVATION_MORTALITY_FACTOR):
 44        self.STARVATION_MORTALITY_MAXIMUM = STARVATION_MORTALITY_MAXIMUM
 45        self.STARVATION_MORTALITY_FACTOR = STARVATION_MORTALITY_FACTOR
 46        self.consecutive_overshoot_n = 0
 47
 48    def get_mask_kill(self, ages, resources_scavenged):
 49        population_size = len(ages)
 50
 51        # No starvation
 52        if population_size <= resources_scavenged:
 53            self.consecutive_overshoot_n = 0
 54            return np.zeros(population_size, dtype=np.bool_)
 55        else:
 56            self.consecutive_overshoot_n += 1
 57
 58        # Compute mortality
 59        if self.STARVATION_MORTALITY_FACTOR is None:
 60            # If mortality depends on the resource deficit
 61            # This mode will prevent over- and undercorrections
 62            mortality = 1 - resources_scavenged / population_size
 63            assert 0 < mortality <= 1
 64        else:
 65            # If mortality depends on the number of consecutive steps experiencing starvation
 66            survival = (1 - self.STARVATION_MORTALITY_FACTOR) ** self.consecutive_overshoot_n
 67            mortality = 1 - survival
 68
 69        # Consider age
 70        mortalities = frailty.modify(hazard=mortality, ages=ages)
 71
 72        # Restrict the upper bound of mortality
 73        mortalities[mortalities > self.STARVATION_MORTALITY_MAXIMUM] = self.STARVATION_MORTALITY_MAXIMUM
 74
 75        # Compute mortality mask
 76        random_probabilities = variables.rng.random(population_size)
 77        mask = random_probabilities < mortalities
 78
 79        return mask
 80
 81    # @staticmethod
 82    # def _logistic(n, resource_availability):
 83    #     """Kill random individuals with logistic-like probability."""
 84    #     ratio = n / resource_availability
 85
 86    #     # when ratio == 1, kill_probability is set to 0
 87    #     # when ratio == 2, kill_probability is set to >0
 88    #     kill_probability = 2 / (1 + np.exp(-ratio + 1)) - 1
 89
 90    #     random_probabilities = variables.rng.random(n)
 91    #     mask = random_probabilities < kill_probability
 92    #     return mask
 93
 94    # def _gradual(self, n, resource_availability):
 95    #     """Kill random individuals with time-increasing probability.
 96
 97    #     The choice of individuals is random.
 98    #     The probability of dying increases each consecutive step of overcrowding.
 99    #     The probability of dying resets to the base value once the population dips under the maximum allowed size.
100    #     """
101    #     surv_probability = (1 - self.STARVATION_MAGNITUDE) ** self.consecutive_overshoot_n
102    #     random_probabilities = variables.rng.random(n)
103    #     mask = random_probabilities > surv_probability
104    #     return mask
105
106    # def _worsening_proportional(self, n, resource_availability):
107    #     """Kill random individuals with time-increasing probability.
108
109    #     The choice of individuals is random.
110    #     The probability of dying increases each consecutive step of overcrowding.
111    #     The probability of dying resets to the base value once the population dips under the maximum allowed size.
112    #     """
113    #     surv_probability = (resource_availability / n) ** self.consecutive_overshoot_n
114    #     f = frailty.modify(1 - surv_probability, 10)
115    #     random_probabilities = variables.rng.random(n)
116    #     mask = random_probabilities > surv_probability
117    #     return mask
118
119    # @staticmethod
120    # def _treadmill_random(n, resource_availability):
121    #     """Kill random individuals.
122
123    #     The population size is brought down to the maximum allowed size in one go.
124    #     """
125    #     indices = variables.rng.choice(n, n - int(resource_availability), replace=False)
126    #     mask = np.zeros(n, dtype=np.bool_)
127    #     mask[indices] = True
128    #     return mask
129
130    # # def _cliff(self, n, resource_availability):
131    # #     """Kill all individuals except a small random proportion.
132
133    # #     The proportion is defined as the parameter CLIFF_SURVIVORSHIP.
134    # #     This function will not necessarily bring the population below the maximum allowed size.
135    # #     """
136    # #     indices = variables.rng.choice(
137    # #         n,
138    # #         int(resource_availability * self.CLIFF_SURVIVORSHIP),
139    # #         replace=False,
140    # #     )
141    # #     mask = np.ones(n, dtype=np.bool_)
142    # #     mask[indices] = False
143    # #     return mask
144
145    # @staticmethod
146    # def _treadmill_boomer(n, resource_availability):
147    #     """Kill the oldest individuals.
148
149    #     The population size is brought down to the maximum allowed size in one go.
150
151    #     NOTE: Why `-resource_availability :`? Because old individuals are at the beginning of the population array.
152    #     """
153    #     mask = np.ones(n, dtype=np.bool_)
154    #     mask[-int(resource_availability) :] = False
155    #     return mask
156
157    # @staticmethod
158    # def _treadmill_zoomer(n, resource_availability):
159    #     """Kill the youngest individuals.
160
161    #     The population size is brought down to the maximum allowed size in one go.
162
163    #     NOTE: Why `: resource_availability`? Because young individuals are appended to the end of the population array.
164    #     """
165    #     mask = np.ones(n, dtype=np.bool_)
166    #     mask[: int(resource_availability)] = False
167    #     return mask
168
169    # def _treadmill_boomer_soft(self, n, resource_availability):
170    #     """Kill older individuals more.
171    #     Old individuals are positioned more to the front of the array.
172    #     True in the mask means death.
173    #     """
174    #     mask = self._treadmill_soft(1, 0, n, resource_availability)
175    #     return mask
176
177    # def _treadmill_zoomer_soft(self, n, resource_availability):
178    #     """Kill younger individuals more.
179    #     Young individuals are positioned later in the array.
180    #     True in the mask means death."""
181    #     mask = self._treadmill_soft(0, 1, n, resource_availability)
182    #     return mask
183
184    # @staticmethod
185    # def _treadmill_soft(linspace_from, linspace_to, n, resource_availability):
186    #     """
187    #     Young individuals are positioned later in the array; older earlier.
188    #     True in the mask means death.
189    #     """
190    #     p = np.linspace(linspace_from, linspace_to, n) ** 5  # **5 to make it superlinear
191    #     p /= p.sum()  # ensure sum(p) is 1
192
193    #     mask = np.zeros(n, dtype=np.bool_)
194
195    #     a = np.arange(n)
196    #     indices_dead = variables.rng.choice(a, size=n - int(resource_availability), p=p, replace=False)
197    #     mask[indices_dead] = True
198    #     return mask
199
200
201starvation = Starvation()
class Starvation:
 13class Starvation:
 14    """
 15
 16    GUI
 17    Starvation is a source of mortality useful for modeling death from lack of resources.
 18    It is usually the largest contributor to mortality. Generally, each individual requires one unit of resources to survive.
 19    When the population size exceeds the amount of resources available, the population experiences starvation.
 20    Starvation is either experienced by the whole population or by no individual; i.e. the distribution of resources is equal across individuals.
 21
 22    Starvation mortality can operate under one of two modes. Under the first mode, population is sensitive to the
 23    amount of resource deficit it is experiencing and will respond promptly to it. For example, if the population size is 1000
 24    and there are only 800 resource units available, approximately only 800 individuals will survive, thus approximately 200 will die,
 25    so the mortality will be about 20% (200/1000).
 26
 27    Under the second mode, population is not sensitive to the amount of resource deficit but rather to the number of consecutive simulation steps
 28    that it has experienced starvation. When it first experiences resource deficit, the mortality will be [[STARVATION_MORTALITY_FACTOR]].
 29    If in the next step, the population size is still greater than the amount of available resources, the mortality will now be
 30    approximately twice as high; in the next step, about three times, etc.
 31
 32    The probability to die is independent of genetics (genetics do not confer protection nor susceptibility to starvation).
 33    However, age can modify the probability to die, depending on the [[FRAILTY_MODIFIER]]. When [[FRAILTY_MODIFIER]] is 0,
 34    there is no age-dependent effect. When it is greater than 0, then starvation mortality is magnified by [[FRAILTY_MODIFIER]]
 35    for the oldest age class, by 0 for the youngest age class, and proportionally for the intermediate age classes (e.g. by 20% of the
 36    [[FRAILTY_MODIFIER]] for the age class 10 if [[AGE_LIMIT]] is 50 because 10/50=0.2).
 37
 38    If [[STARVATION_MORTALITY_MAXIMUM]] is set, the final computed mortality will be at maximum of that value, not higher.
 39
 40    Note that if the species is oviparious ([[INCUBATION_PERIOD]]), the produced eggs do not consume resources and are
 41    immune to starvation mortality (until they hatch).
 42    """
 43
 44    def init(self, STARVATION_MORTALITY_MAXIMUM, STARVATION_MORTALITY_FACTOR):
 45        self.STARVATION_MORTALITY_MAXIMUM = STARVATION_MORTALITY_MAXIMUM
 46        self.STARVATION_MORTALITY_FACTOR = STARVATION_MORTALITY_FACTOR
 47        self.consecutive_overshoot_n = 0
 48
 49    def get_mask_kill(self, ages, resources_scavenged):
 50        population_size = len(ages)
 51
 52        # No starvation
 53        if population_size <= resources_scavenged:
 54            self.consecutive_overshoot_n = 0
 55            return np.zeros(population_size, dtype=np.bool_)
 56        else:
 57            self.consecutive_overshoot_n += 1
 58
 59        # Compute mortality
 60        if self.STARVATION_MORTALITY_FACTOR is None:
 61            # If mortality depends on the resource deficit
 62            # This mode will prevent over- and undercorrections
 63            mortality = 1 - resources_scavenged / population_size
 64            assert 0 < mortality <= 1
 65        else:
 66            # If mortality depends on the number of consecutive steps experiencing starvation
 67            survival = (1 - self.STARVATION_MORTALITY_FACTOR) ** self.consecutive_overshoot_n
 68            mortality = 1 - survival
 69
 70        # Consider age
 71        mortalities = frailty.modify(hazard=mortality, ages=ages)
 72
 73        # Restrict the upper bound of mortality
 74        mortalities[mortalities > self.STARVATION_MORTALITY_MAXIMUM] = self.STARVATION_MORTALITY_MAXIMUM
 75
 76        # Compute mortality mask
 77        random_probabilities = variables.rng.random(population_size)
 78        mask = random_probabilities < mortalities
 79
 80        return mask
 81
 82    # @staticmethod
 83    # def _logistic(n, resource_availability):
 84    #     """Kill random individuals with logistic-like probability."""
 85    #     ratio = n / resource_availability
 86
 87    #     # when ratio == 1, kill_probability is set to 0
 88    #     # when ratio == 2, kill_probability is set to >0
 89    #     kill_probability = 2 / (1 + np.exp(-ratio + 1)) - 1
 90
 91    #     random_probabilities = variables.rng.random(n)
 92    #     mask = random_probabilities < kill_probability
 93    #     return mask
 94
 95    # def _gradual(self, n, resource_availability):
 96    #     """Kill random individuals with time-increasing probability.
 97
 98    #     The choice of individuals is random.
 99    #     The probability of dying increases each consecutive step of overcrowding.
100    #     The probability of dying resets to the base value once the population dips under the maximum allowed size.
101    #     """
102    #     surv_probability = (1 - self.STARVATION_MAGNITUDE) ** self.consecutive_overshoot_n
103    #     random_probabilities = variables.rng.random(n)
104    #     mask = random_probabilities > surv_probability
105    #     return mask
106
107    # def _worsening_proportional(self, n, resource_availability):
108    #     """Kill random individuals with time-increasing probability.
109
110    #     The choice of individuals is random.
111    #     The probability of dying increases each consecutive step of overcrowding.
112    #     The probability of dying resets to the base value once the population dips under the maximum allowed size.
113    #     """
114    #     surv_probability = (resource_availability / n) ** self.consecutive_overshoot_n
115    #     f = frailty.modify(1 - surv_probability, 10)
116    #     random_probabilities = variables.rng.random(n)
117    #     mask = random_probabilities > surv_probability
118    #     return mask
119
120    # @staticmethod
121    # def _treadmill_random(n, resource_availability):
122    #     """Kill random individuals.
123
124    #     The population size is brought down to the maximum allowed size in one go.
125    #     """
126    #     indices = variables.rng.choice(n, n - int(resource_availability), replace=False)
127    #     mask = np.zeros(n, dtype=np.bool_)
128    #     mask[indices] = True
129    #     return mask
130
131    # # def _cliff(self, n, resource_availability):
132    # #     """Kill all individuals except a small random proportion.
133
134    # #     The proportion is defined as the parameter CLIFF_SURVIVORSHIP.
135    # #     This function will not necessarily bring the population below the maximum allowed size.
136    # #     """
137    # #     indices = variables.rng.choice(
138    # #         n,
139    # #         int(resource_availability * self.CLIFF_SURVIVORSHIP),
140    # #         replace=False,
141    # #     )
142    # #     mask = np.ones(n, dtype=np.bool_)
143    # #     mask[indices] = False
144    # #     return mask
145
146    # @staticmethod
147    # def _treadmill_boomer(n, resource_availability):
148    #     """Kill the oldest individuals.
149
150    #     The population size is brought down to the maximum allowed size in one go.
151
152    #     NOTE: Why `-resource_availability :`? Because old individuals are at the beginning of the population array.
153    #     """
154    #     mask = np.ones(n, dtype=np.bool_)
155    #     mask[-int(resource_availability) :] = False
156    #     return mask
157
158    # @staticmethod
159    # def _treadmill_zoomer(n, resource_availability):
160    #     """Kill the youngest individuals.
161
162    #     The population size is brought down to the maximum allowed size in one go.
163
164    #     NOTE: Why `: resource_availability`? Because young individuals are appended to the end of the population array.
165    #     """
166    #     mask = np.ones(n, dtype=np.bool_)
167    #     mask[: int(resource_availability)] = False
168    #     return mask
169
170    # def _treadmill_boomer_soft(self, n, resource_availability):
171    #     """Kill older individuals more.
172    #     Old individuals are positioned more to the front of the array.
173    #     True in the mask means death.
174    #     """
175    #     mask = self._treadmill_soft(1, 0, n, resource_availability)
176    #     return mask
177
178    # def _treadmill_zoomer_soft(self, n, resource_availability):
179    #     """Kill younger individuals more.
180    #     Young individuals are positioned later in the array.
181    #     True in the mask means death."""
182    #     mask = self._treadmill_soft(0, 1, n, resource_availability)
183    #     return mask
184
185    # @staticmethod
186    # def _treadmill_soft(linspace_from, linspace_to, n, resource_availability):
187    #     """
188    #     Young individuals are positioned later in the array; older earlier.
189    #     True in the mask means death.
190    #     """
191    #     p = np.linspace(linspace_from, linspace_to, n) ** 5  # **5 to make it superlinear
192    #     p /= p.sum()  # ensure sum(p) is 1
193
194    #     mask = np.zeros(n, dtype=np.bool_)
195
196    #     a = np.arange(n)
197    #     indices_dead = variables.rng.choice(a, size=n - int(resource_availability), p=p, replace=False)
198    #     mask[indices_dead] = True
199    #     return mask

GUI Starvation is a source of mortality useful for modeling death from lack of resources. It is usually the largest contributor to mortality. Generally, each individual requires one unit of resources to survive. When the population size exceeds the amount of resources available, the population experiences starvation. Starvation is either experienced by the whole population or by no individual; i.e. the distribution of resources is equal across individuals.

Starvation mortality can operate under one of two modes. Under the first mode, population is sensitive to the amount of resource deficit it is experiencing and will respond promptly to it. For example, if the population size is 1000 and there are only 800 resource units available, approximately only 800 individuals will survive, thus approximately 200 will die, so the mortality will be about 20% (200/1000).

Under the second mode, population is not sensitive to the amount of resource deficit but rather to the number of consecutive simulation steps that it has experienced starvation. When it first experiences resource deficit, the mortality will be [[STARVATION_MORTALITY_FACTOR]]. If in the next step, the population size is still greater than the amount of available resources, the mortality will now be approximately twice as high; in the next step, about three times, etc.

The probability to die is independent of genetics (genetics do not confer protection nor susceptibility to starvation). However, age can modify the probability to die, depending on the [[FRAILTY_MODIFIER]]. When [[FRAILTY_MODIFIER]] is 0, there is no age-dependent effect. When it is greater than 0, then starvation mortality is magnified by [[FRAILTY_MODIFIER]] for the oldest age class, by 0 for the youngest age class, and proportionally for the intermediate age classes (e.g. by 20% of the [[FRAILTY_MODIFIER]] for the age class 10 if [[AGE_LIMIT]] is 50 because 10/50=0.2).

If [[STARVATION_MORTALITY_MAXIMUM]] is set, the final computed mortality will be at maximum of that value, not higher.

Note that if the species is oviparious ([[INCUBATION_PERIOD]]), the produced eggs do not consume resources and are immune to starvation mortality (until they hatch).

def init(self, STARVATION_MORTALITY_MAXIMUM, STARVATION_MORTALITY_FACTOR):
44    def init(self, STARVATION_MORTALITY_MAXIMUM, STARVATION_MORTALITY_FACTOR):
45        self.STARVATION_MORTALITY_MAXIMUM = STARVATION_MORTALITY_MAXIMUM
46        self.STARVATION_MORTALITY_FACTOR = STARVATION_MORTALITY_FACTOR
47        self.consecutive_overshoot_n = 0
def get_mask_kill(self, ages, resources_scavenged):
49    def get_mask_kill(self, ages, resources_scavenged):
50        population_size = len(ages)
51
52        # No starvation
53        if population_size <= resources_scavenged:
54            self.consecutive_overshoot_n = 0
55            return np.zeros(population_size, dtype=np.bool_)
56        else:
57            self.consecutive_overshoot_n += 1
58
59        # Compute mortality
60        if self.STARVATION_MORTALITY_FACTOR is None:
61            # If mortality depends on the resource deficit
62            # This mode will prevent over- and undercorrections
63            mortality = 1 - resources_scavenged / population_size
64            assert 0 < mortality <= 1
65        else:
66            # If mortality depends on the number of consecutive steps experiencing starvation
67            survival = (1 - self.STARVATION_MORTALITY_FACTOR) ** self.consecutive_overshoot_n
68            mortality = 1 - survival
69
70        # Consider age
71        mortalities = frailty.modify(hazard=mortality, ages=ages)
72
73        # Restrict the upper bound of mortality
74        mortalities[mortalities > self.STARVATION_MORTALITY_MAXIMUM] = self.STARVATION_MORTALITY_MAXIMUM
75
76        # Compute mortality mask
77        random_probabilities = variables.rng.random(population_size)
78        mask = random_probabilities < mortalities
79
80        return mask
starvation = <Starvation object>