# -*- coding: utf-8 -*-
"""
"""
__author__ = "Jon-Mikkel Korsvik & Petter Bøe Hørtvedt"
__email__ = "jonkors@nmbu.no & petterho@nmbu.no"
from .animals import Herbivore, Carnivore
import numpy as np
import itertools
import math
import random
from numba import jit
[docs]def choose_new_location(prob_list):
"""
Draws one out of a list with weights.
Parameters
----------
prob_list : list
tuple(loc, probabilities)
Returns
-------
locations[index] : tuple
new_location - (y, x)
"""
probabilities = [x[1] for x in prob_list]
cumulative_sum = np.cumsum(probabilities)
locations = [x[0] for x in prob_list]
index = 0
random_number = random.random()
while random_number > cumulative_sum[index]:
index += 1
return locations[index]
[docs]class BaseCell:
"""
Attributes
------
herbivores : list
carnivores : list
fodder : float
death_list : list
list for stats
birth_list : list
list for stats
Methods
--------
grow
add_animals
add_migrated_herb
add_migrated_carn
remove_migrated_herb
remove_migrated_carn
chain_lists
migrate
procreate
lose_weight
sort_by_fitness
feed_all
feed_herbivores
feed_carnivores
age_pop
die
reset_calculate_propensity
"""
passable = True
f_max = 0
alpha = 0
[docs] @classmethod
def set_parameters(cls, passable=None, f_max=None, alpha=None):
"""
Method for changing one or all parameters with a dictionary for class
of BaseCell class
By checking all parameters first, set parameters does not change
any parameters before it is sure that all parameters are valid
Parameters
----------
passable : bool
If Animals can enter cell
f_max : float
Maximum amount of fodder in cell
alpha : float
Growth rate for fodder
Returns
-------
"""
bool_passable = False
bool_f_max = False
bool_alpha = False
if passable:
if type(passable) is bool:
bool_passable = True
else:
raise ValueError('passable takes bool arguments only')
if f_max:
if f_max >= 0:
bool_f_max = True
else:
raise ValueError('f_max takes int or float arguments only')
if alpha:
if alpha >= 0:
bool_alpha = True
else:
raise ValueError('alpha takes int or float arguments only')
if bool_passable is True:
cls.passable = passable
if bool_f_max is True:
cls.f_max = f_max
if bool_alpha is True:
cls.alpha = alpha
def __init__(self):
"""
Initializes the cell and creates empty lists for herbivores and
carnivores.
Creates attribute for fodder
"""
self.herbivores = []
self.carnivores = []
self._calculate_propensity = True
self._propensity = None
self.fodder = 0
[docs] def grow(self):
"""Grows fodder in cell"""
pass
[docs] def add_animals(self, animal_list):
"""
Adds a new population from a list of dictionaries to the Cell
In the dictionaries the species, age and weight of each animal
resides.
Parameters
----------
animal_list : list
List of Dictionaries
Returns
-------
"""
# Takes list of dicts with are the animal
for animal in animal_list:
species = animal['species']
age = animal['age']
weight = animal['weight']
if age < 0 or (age is not int and age != int(age)):
raise ValueError('age can only be a positive integer')
if weight < 0:
raise ValueError('weight can only be positive')
if species == 'Herbivore':
self.herbivores.append(Herbivore(age, weight))
if species == 'Carnivore':
self.carnivores.append(Carnivore(age, weight))
[docs] def add_migrated_herb(self, herbivore):
"""Add herbivore to list of herbivores"""
self.herbivores.append(herbivore)
[docs] def add_migrated_carn(self, carnivore):
"""Add carnivore to list of carnivores"""
self.carnivores.append(carnivore)
[docs] def remove_migrated_herb(self, herbivore):
"""Remove herbivore from list of herbivores"""
self.herbivores.remove(herbivore)
[docs] def remove_migrated_carn(self, carnivore):
"""Remove carnivore from list of carnivores"""
self.carnivores.remove(carnivore)
# Remove if not used
[docs] def chain_lists(self):
"""Chains list of cell.herbivores with cell.carnivores lists"""
return itertools.chain(self.herbivores, self.carnivores)
[docs] def migrate(self, prob_herb, prob_carn):
"""
Goes through herbivores, migrates, appends to moved_herb.
Goes through carnivores, migrates, appends to moved_carn.
Deletes the animal that has moved from the list of class.
Parameters
----------
prob_herb : list of tuples
Location (y, x) and Probability
prob_carn : list of tuples
Location (y, x) and Probability
Returns
-------
moved_herb : list of tuples
New position (y, x) and animal instance
moved_carn : list of tuples
New position (y, x) and animal instance
"""
moved_herb = []
moved_carn = []
if prob_herb is None and prob_carn is None:
return moved_herb, moved_carn
for herb in self.herbivores:
if herb.will_migrate():
loc = choose_new_location(prob_herb)
moved_herb.append((loc, herb))
for carn in self.carnivores:
if carn.will_migrate():
loc = choose_new_location(prob_carn)
moved_carn.append((loc, carn))
for loc, herb in moved_herb:
self.remove_migrated_herb(herb)
for loc, carn in moved_carn:
self.remove_migrated_carn(carn)
return moved_herb, moved_carn
[docs] def procreate(self):
"""
Goes through herbivores and carnivores if there is more than one
animal the same list.
Calls birth method, and if offspring returned appends the offspring
to the cells appropriate list.
Methods
-------
birth(num_of_same_species)
Returns
-------
birth_list_herb : list
list of offspring
birth_list_carn : list
list of offspring
"""
birth_list_herb = []
number_of_adult_herbivores = self.num_herbivores
if number_of_adult_herbivores > 1:
for herbivore in self.herbivores:
offspring = herbivore.birth(number_of_adult_herbivores)
if not offspring:
continue
self.herbivores.append(offspring)
birth_list_herb.append(offspring)
birth_list_carn = []
number_of_adult_carnivores = self.num_carnivores
if number_of_adult_carnivores > 1:
for carnivore in self.carnivores:
offspring = carnivore.birth(number_of_adult_carnivores)
if not offspring:
continue
self.carnivores.append(offspring)
birth_list_carn.append(offspring)
return birth_list_herb, birth_list_carn
[docs] def lose_weight(self):
"""Makes animals in cell lose_weight"""
for herbivore in self.herbivores:
herbivore.lose_weight()
for carnivore in self.carnivores:
carnivore.lose_weight()
[docs] @staticmethod
def sort_by_fitness(animal_list):
"""Sort list of animals by fitness"""
sorted_list = sorted(animal_list, key=lambda var: var.fitness)
return sorted_list
[docs] def feed_all(self):
"""Makes all animals eat"""
self.feed_herbivores()
self.feed_carnivores()
[docs] def feed_herbivores(self):
"""
Herbivores is sorted by fitness thereafter goes through the updated
list in reverse. This makes the most fit animals first to feed.
Updated the cells amount of food(fodder) after each animal has fed.
Methods
-------
feed()
"""
self.herbivores = self.sort_by_fitness(self.herbivores)
for herbivore in reversed(self.herbivores):
self.fodder = herbivore.feed(self.fodder)
[docs] def feed_carnivores(self):
"""
Sorts herbivores by fitness
Sorts carnivores by fitness
The fittest carnivore feeds first with sorted list of herbivores
as input.
Sets the returned list as the new list for herbivores.
Methods
------
feed(least_fit_herbivores)
Returns list of herbivores that are still living
"""
self.herbivores = self.sort_by_fitness(self.herbivores)
self.carnivores = self.sort_by_fitness(self.carnivores)
for carnivore in reversed(self.carnivores):
self.herbivores = carnivore.feed(self.herbivores)
[docs] def age_pop(self):
"""Adds a increment of 1 to the animals age attribute"""
for herbivore in self.herbivores:
herbivore.age_one_year()
for carnivore in self.carnivores:
carnivore.age_one_year()
[docs] def die(self):
"""
Iterates through animals, adds them to a death list.
Iterates through death list and removes them from the cell's
appropriate list by object instance.
Methods
-------
BaseAnimal.death()
Returns bool
Returns
-------
death_list_herb : list
Herbivore instances that died
death_list_carn : list
Carnivore instances that died
"""
death_list_herb = []
for herbivore in self.herbivores:
if herbivore.death():
death_list_herb.append(herbivore)
for dead in death_list_herb:
self.herbivores.remove(dead)
death_list_carn = []
for carnivore in self.carnivores:
if carnivore.death():
death_list_carn.append(carnivore)
for dead in death_list_carn:
self.carnivores.remove(dead)
return death_list_herb, death_list_carn
@property
def propensity(self):
r"""
Property of each cell, calculates each year once because of
self._calculate_propensity
if cell is not passable propensities will be zero
Propensity is calculated by:
.. math::
\epsilon_k = \frac{f_k}{(n_k + 1)F'}
.. math::
\pi_k = e^{\gamma\epsilon_j}
.. math::
\pi_j = \frac{\pi_j}{\sum\epsilon_C(i)}
sets self.calculate_propensity to False when propensity is calculated
Returns
-------
self._propensity: dict
key - str containing class.__name__ and value - propensity
"""
if not self._calculate_propensity:
return self._propensity
if not self.passable:
self._propensity = {'Carnivore': 0,
'Herbivore': 0}
else:
lambda_ = Herbivore.lambda_
appetite = Herbivore.F
dividend = ((self.num_herbivores + 1) * appetite)
exponent_herb = (lambda_ * (self.fodder
/ dividend))
propensity_herb = math.exp(exponent_herb)
lambda_ = Carnivore.lambda_
appetite_ = Carnivore.F
dividend = ((self.num_carnivores + 1) * appetite_)
exponent_carn = (lambda_ * (self.meat_for_carnivores
/ dividend))
propensity_carn = math.exp(exponent_carn)
self._propensity = {'Carnivore': propensity_carn,
'Herbivore': propensity_herb}
self._calculate_propensity = False
return self._propensity
[docs] def reset_calculate_propensity(self):
"""Sets _calculate_propensity to True"""
self._calculate_propensity = True
@property
def num_carnivores(self):
"""Property: number of carnivores derived from len of list"""
return len(self.carnivores)
@property
def num_herbivores(self):
"""Property: number of herbivores derived from len of list"""
return len(self.herbivores)
@property
def num_animals(self):
"""Property: sum of all num_carnivores and num_herbivores"""
return self.num_carnivores + self.num_herbivores
@property
def meat_for_carnivores(self):
"""Property: Sum the weight of all herbivores in cell"""
meat = 0
for herbivore in self.herbivores:
meat += herbivore.weight
return meat
[docs]class Ocean(BaseCell):
"""Subclass of cell"""
passable = False
def __init__(self):
super().__init__()
[docs]class Mountain(BaseCell):
"""Subclass of cell"""
passable = False
def __init__(self):
super().__init__()
[docs]class Desert(BaseCell):
"""
Subclass of cell, is passable but contains no food
"""
passable = True
f_max = 0
def __init__(self):
super().__init__()
[docs]class Savanna(BaseCell):
"""
Subclass of cell
Attributes
------
f_max : float
max amount of fodder(food) in the cell
alpha : float
value used for grow method
passable : bool
Methods
--------
grow : updates amount of fodder in cell
"""
passable = True
f_max = 300.0
alpha = 0.3
def __init__(self):
super().__init__()
self.fodder = self.f_max
[docs] def grow(self):
self.fodder += self.alpha * (self.f_max - self.fodder)
[docs]class Jungle(BaseCell):
"""
Subclass of cell
Attributes
---------
f_max : float
max amount of fodder(food) in the cell
alpha : float
value used for grow method
passable : bool
Methods
-------
grow : updates amount of fodder in cell
"""
passable = True
f_max = 800.0
def __init__(self):
super().__init__()
self.fodder = self.f_max
[docs] def grow(self):
self.fodder = self.f_max
if __name__ == '__main__':
pass