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()
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).
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