Source code for src.biosim.island

# -*- coding: utf-8 -*-

"""
"""

__author__ = "Jon-Mikkel Korsvik & Petter Bøe Hørtvedt"
__email__ = "jonkors@nmbu.no & petterho@nmbu.no"

from .landscape import *


[docs]def check_length(lines): """ Compares length of all lines in a list of lines to the first Parameters ---------- lines : list lines Returns ------- bool False if not equal length, True if length is equal """ if not all(len(lines[0]) == len(line) for line in lines[1:]): return False return True
[docs]class Island: """ Initiates the Island class: holds method for updating the map Class attribute map_params holds information of which letter relates to which class of landscape.py Parameters ---------- island_map_string : multilinestring Multiple lines of string with characters representing cell type ini_pop : dict Key: location - Value: list of dict of species, age, and weight Attributes ---------- map : dict calls method make_map, map creation from a multilinestring herbivore_tot_data : list Total number of herbivores indexed by year carnivore_tot_data : list Total number of carnivore indexed by year stats : dict multiple nested dicts, stores all data on dead/ born animals. """ map_params = {'O': Ocean, 'M': Mountain, 'D': Desert, 'S': Savanna, 'J': Jungle} def __init__(self, island_map_string, ini_pop, store_stats=False): """ Initializes instance of Island Parameters ---------- island_map_string : str Multilinestring of map ini_pop : dict key : location - Value : list of dict store_stats : bool """ self.len_map_x = None self.len_map_y = None self.map = self.make_map(island_map_string) self.add_population(ini_pop) self._year = 0 self.herbivore_tot_data = [] self.carnivore_tot_data = [] self.update_data_list() self._store_stats = store_stats if store_stats: self.stats = {} self.create_and_update_stats_structure() @property def num_animals(self): """ Total number of animals on island. Returns ------- num_animals : int """ num_animals = 0 for num_type in self.num_animals_per_species.values(): num_animals += num_type return num_animals @property def num_animals_per_species(self): """ Number of animals per species in island, as dictionary. Returns ------- num_animals_per_species : dictionary """ num_animals_per_species = {} num_herbivores = 0 num_carnivores = 0 for cell in self.map.values(): num_herbivores += cell.num_herbivores num_carnivores += cell.num_carnivores num_animals_per_species['Herbivore'] = num_herbivores num_animals_per_species['Carnivore'] = num_carnivores return num_animals_per_species
[docs] def create_and_update_stats_structure(self): """Creates data structure for stats""" all_herbs = self.num_animals_per_species['Herbivore'] all_carns = self.num_animals_per_species['Carnivore'] for pos in self.map.keys(): self.stats[self.year] = {'Herbivore': {'death': {pos: []}, 'birth': {pos: []}, 'alive': all_herbs}, 'Carnivore': {'death': {pos: []}, 'birth': {pos: []}, 'alive': all_carns} }
[docs] def update_data_list(self): """Updates list for use in visualization""" animals_per_species = self.num_animals_per_species herbivores = animals_per_species['Herbivore'] carnivores = animals_per_species['Carnivore'] self.herbivore_tot_data.append(herbivores) self.carnivore_tot_data.append(carnivores)
[docs] @staticmethod def clean_multi_line_string(island_map_string): """ Strips and splits a multilinestring and checks for equal length of lines and specific letters at the edges: in this case 'O'. Raises ValueError if criteria are not met. Parameters ---------- island_map_string : multilinestring Lines is row, letter is column Returns ------- lines : list Lines of strings (cleaned) """ island_map_string = island_map_string.strip() lines = island_map_string.split('\n') if not check_length(lines): raise ValueError('Each line of the multi line string, ' 'shall be equal in length') for index in range(len(lines[0])): if lines[0][index] is not 'O' or lines[-1][index] is not 'O': raise ValueError('This is not an island. Islands are ' 'surrounded by water') for index in range(len(lines)): if lines[index][0] is not 'O' or lines[index][-1] is not 'O': raise ValueError('This is not an island. Islands are ' 'surrounded by water') return lines
[docs] def make_map(self, island_map_string): """ Creates a dictionary data frame that stores instances of cells by key: a tuple of (y, x)-coordinates Also saves the length and with of the island Parameters ---------- island_map_string : str string of multiple lines Returns ------- map : dict key : tuple, value : instance of subclass of BaseCell """ island_map = {} lines = self.clean_multi_line_string(island_map_string) # Possible to fix using type hinting (alt+enter) self.len_map_x = len(lines[0]) self.len_map_y = len(lines) for y_cord, line in enumerate(lines): for x_cord, letter in enumerate(line): if letter in self.map_params.keys(): island_map[(y_cord, x_cord)] = self.map_params[letter]() else: raise ValueError(f'String must consist of uppercase' f'letters like these:\n' f'{self.map_params}') return island_map
[docs] def probability_calc(self, pos, animal): """ Finds the preposition for all neighbouring cells within dx +- 1 and dy +-1. This means we have the positions North, West, South and East of the current position. Then calculates the probability for the type of animal. If the animal has nowhere to move, it returns None Parameters ---------- pos : tuple Position of current cell animal : str Name of the animal Returns ------- prob_list : list of tuples (Coordinate(y, x), and probabilities) """ y_cord, x_cord = pos loc_1 = (y_cord - 1, x_cord) loc_2 = (y_cord + 1, x_cord) loc_3 = (y_cord, x_cord - 1) loc_4 = (y_cord, x_cord + 1) option_1 = self.map[loc_1] option_2 = self.map[loc_2] option_3 = self.map[loc_3] option_4 = self.map[loc_4] list_ = [(loc_1, option_1), (loc_2, option_2), (loc_3, option_3), (loc_4, option_4)] propensity_list = [] for loc, option in list_: propensity_list.append((loc, option.propensity[animal]) ) prop_sum = np.sum(sum(dict(propensity_list).values())) if prop_sum == 0: return None prob_list = [] for loc, prop in propensity_list: prob_list.append((loc, (prop / prop_sum))) return prob_list
[docs] def add_herb_to_new_cell(self, new_loc, herbivore): """ Add herbivore to cell in new location """ self.map[new_loc].add_migrated_herb(herbivore)
[docs] def add_carn_to_new_cell(self, new_loc, carnivore): """ Add herbivore to cell in new location """ self.map[new_loc].add_migrated_carn(carnivore)
[docs] def migrate(self): """ Goes through the map attribute of Island and calculates probabilities for herbivores and carnivores, with position and name of class. calls migrate method in instance of subclass of BaseCell Methods ------- BaseCell.migrate() probability_calc(pos, animal) Notes ------ Adds herbivores and carnivores that has migrated to new cells """ for pos, cell in self.map.items(): if cell.passable and cell.num_animals > 0: prob_herb = self.probability_calc(pos, 'Herbivore') prob_carn = self.probability_calc(pos, 'Carnivore') moved_herb, moved_carn = cell.migrate(prob_herb, prob_carn) for loc, herb in moved_herb: self.add_herb_to_new_cell(loc, herb) for loc, carn in moved_carn: self.add_carn_to_new_cell(loc, carn)
[docs] def ready_for_new_year(self): """ Resets each cell in Island.map Methods ------- BaseCell.grow BaseCell.reset_calculate_propensity BaseAnimal.reset_has_moved """ for cell in self.map.values(): cell.grow() cell.reset_calculate_propensity() for herbivore in cell.herbivores: herbivore.reset_has_moved() for carnivore in cell.carnivores: carnivore.reset_has_moved()
[docs] def add_population(self, population): """ Feeds a dictionary of population to cell by position. Parameters ---------- population: list loc: tuple pop: dict Methods ------- BaseCell.add_animals() """ # map_location is a dictionary with loc for map_location in population: loc = map_location['loc'] if loc not in self.map.keys(): raise ValueError('Provided location does not exist') if not self.map[loc].passable: raise ValueError('Provided location is not passable') pop = map_location['pop'] self.map[loc].add_animals(pop)
[docs] def feed(self): """Calls feed_all in all cells of Island.map""" for cell in self.map.values(): cell.feed_all()
[docs] def procreate(self): """Calls procreate in all cells of Island.map, adds born to stats""" for pos, cell in self.map.items(): herb_birth, carn_birth = cell.procreate() if self._store_stats: self.stats[self.year]['Herbivore']['birth'][pos] = herb_birth self.stats[self.year]['Carnivore']['birth'][pos] = carn_birth
[docs] def age_animals(self): """Calls age_pop in all cells of Island.map""" for cell in self.map.values(): cell.age_pop()
[docs] def lose_weight(self): """Calls lose_weight in all cells of Island.map""" for cell in self.map.values(): cell.lose_weight()
[docs] def die(self): """Calls die in all cells of Island.map, adds dead to stats""" for pos, cell in self.map.items(): herb_death, carn_death = cell.die() if self._store_stats: self.stats[self.year]['Herbivore']['death'][pos] = herb_death self.stats[self.year]['Carnivore']['death'][pos] = carn_death
@property def year(self): """Last year simulated.""" return self._year @year.setter def year(self, new_value): """Sets year to new value""" self._year = new_value
[docs] def simulate_one_year(self): """ Simulates a whole year by the following sequence Returns ------- """ self.ready_for_new_year() self.feed() self.procreate() self.migrate() self.age_animals() self.lose_weight() self.die() self.year += 1 self.update_data_list() if self._store_stats: self.create_and_update_stats_structure()
if __name__ == '__main__': pass