def test_set_new_params_raise_errors(self):
     """ Tests that it is not possible to create new parameters, or parameters that is out of range"""
     a = Herbivores()
     b = Carnivores()
     with pytest.raises(TypeError):
         a.set_new_params([1243])
     with pytest.raises(TypeError):
         b.set_new_params([1234])
     with pytest.raises(KeyError):
         a.set_new_params({'W_BIRTH': 1})
     with pytest.raises(KeyError):
         b.set_new_params({'W_BIRTH': 1})
     with pytest.raises(ValueError):
         a.set_new_params({'w_birth': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'w_birth': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'sigma_birth': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'sigma_birth': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'beta': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'beta': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'eta': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'eta': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'a_half': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'a_half': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'phi_age': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'phi_age': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'w_half': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'w_half': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'phi_weight': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'phi_weight': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'mu': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'mu': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'lambda': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'lambda': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'gamma': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'gamma': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'zeta': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'zeta': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'xi': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'xi': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'omega': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'omega': -1})
     with pytest.raises(ValueError):
         a.set_new_params({'F': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'F': -1})
     with pytest.raises(ValueError):
         b.set_new_params({'DeltaPhiMax': -1})
 def test_set_new_params(self):
     """ Tests setting new parameter for both animal-subclases"""
     a = Herbivores()
     b = Carnivores()
     a.set_new_params({
         'w_birth': 12.0,
         'sigma_birth': 1.3,
         'beta': 1,
         'eta': 0.06,
         'a_half': 41.0,
         'phi_age': 0.3,
         'w_half': 11.0,
         'phi_weight': 0.2,
         'mu': 0.3,
         'lambda': 1.1,
         'gamma': 0.3,
         'zeta': 3.6,
         'xi': 1.3,
         'omega': 0.5,
         'F': 11.0
     })
     b.set_new_params({
         'w_birth': 8.0,
         'sigma_birth': 1.2,
         'beta': 0.9,
         'eta': 0.05,
         'a_half': 40.0,
         'phi_age': 0.2,
         'w_half': 10.0,
         'phi_weight': 0.1,
         'mu': 0.25,
         'lambda': 1.0,
         'gamma': 0.2,
         'zeta': 3.5,
         'xi': 1.2,
         'omega': 0.4,
         'F': 10.0,
         'DeltaPhiMax': 11
     })
     assert a.w_birth == 12
     assert a.sigma_birth == 1.3
     assert a.beta == 1
     assert a.eta == 0.06
     assert a.a_half == 41
     assert a.phi_age == 0.3
     assert a.w_half == 11
     assert a.phi_weight == 0.2
     assert a.mu == 0.3
     assert a.lambda1 == 1.1
     assert a.gamma == 0.3
     assert a.zeta == 3.6
     assert a.xi == 1.3
     assert a.omega == 0.5
     assert a.f == 11
     assert b.w_birth == 8
     assert b.sigma_birth == 1.2
     assert b.beta == 0.9
     assert b.eta == 0.05
     assert b.a_half == 40
     assert b.phi_age == 0.2
     assert b.w_half == 10
     assert b.phi_weight == 0.1
     assert b.mu == 0.25
     assert b.lambda1 == 1.0
     assert b.gamma == 0.2
     assert b.zeta == 3.5
     assert b.xi == 1.2
     assert b.omega == 0.4
     assert b.f == 10
     assert b.deltaphimax == 11
class BioSim:
    def __init__(self,
                 island_map,
                 ini_pop,
                 seed,
                 ymax_animals=None,
                 cmax_animals=None,
                 img_base=None,
                 img_fmt='png'):
        """
        :param island_map: Multi-line string specifying island geography
        :param ini_pop: List of dictionaries specifying initial population
        :param seed: Integer used as random number seed
        :param ymax_animals: Number specifying y-axis limit for graph showing animal numbers
        :param cmax_animals: Dict specifying color-code limits for animal densities
        :param img_base: String with beginning of file name for figures, including path
        :param img_fmt: String with file type for figures, e.g. 'png'

        If ymax_animals is None, the y-axis limit should be adjusted automatically.

        If cmax_animals is None, sensible, fixed default values should be used.
        cmax_animals is a dict mapping species names to numbers, e.g.,
           {'Herbivore': 50, 'Carnivore': 20}

        If img_base is None, no figures are written to file.
        Filenames are formed as

            '{}_{:05d}.{}'.format(img_base, img_no, img_fmt)

        where img_no are consecutive image numbers starting from 0.
        img_base should contain a path and beginning of a file name.
        """
        # variables for code/setup/classes
        self.ini_pop = ini_pop
        self.island_map = island_map
        self.tot_num_years = 0

        # variables for movie/save
        if img_base is not None:
            self._img_base = img_base
        else:
            self._img_base = None

        self._img_fmt = img_fmt
        self._img_ctr = 0

        # Variables for visualization/plot
        self.ax1 = None
        self.ax2 = None
        self.ax3 = None
        self.ax4 = None
        self.line = None
        self.line2 = None

        if ymax_animals is None:
            self.ymax_animals = 20000
        else:
            self.ymax_animals = ymax_animals
        if cmax_animals is None:
            self.cmax_animals = {'Herbivore': 100, 'Carnivore': 100}
        else:
            self.cmax_animals = cmax_animals

        # Initialization, setting classes and seed
        self.island = Island(island_map)
        self.island.limit_map_vals()
        self.herbivores = Herbivores()
        self.carnivores = Carnivores()
        self.animal = Animal()
        self.setup_simulation()
        np.random.seed(seed)
        random.seed(seed)

    def set_animal_parameters(self, species, params):
        """
        Set parameters for animal species.

        :param species: String, name of animal species
        :param params: Dict with valid parameter specification for species
        """
        if species == 'Herbivore':
            self.herbivores.set_new_params(params)
        else:
            self.carnivores.set_new_params(params)

    def set_landscape_parameters(self, terrain, params):
        """
        Set parameters for landscape types.

        :param terrain: Tells which terrain shall get new parameters
        :param params: Dict with valid parameter specification for landscape
        """
        self.island.set_new_params(terrain, params)

    def setup_simulation(self):
        """
        Function to set up the simulation, sets up the food and inserts the initial population.
        """
        for i in range(self.island.rader):
            for j in range(self.island.col):
                self.island.set_food((i, j))
        self.island.add_animals(self.ini_pop)

    def simulate_one_year(self):
        """
        Function used to simulate one year
        """
        for i in range(self.island.rader):
            for j in range(self.island.col):
                pos = (i, j)
                self.island.grow_food(pos)
                self.herbivores.calculate_fitness(pos, self.island.herbs)
                self.herbivores.sort_by_fitness(pos, self.island.herbs)
                self.herbivores.animals_eat(pos, self.island,
                                            self.island.herbs)
                self.herbivores.sort_before_getting_hunted(
                    pos, self.island.herbs)
                self.carnivores.calculate_fitness(pos, self.island.carns)
                self.carnivores.sort_by_fitness(pos, self.island.carns)
                self.carnivores.carnivores_eat(pos, self.island,
                                               self.island.carns)
                self.herbivores.breeding(pos, self.island, self.island.herbs)
                self.carnivores.calculate_fitness(pos, self.island.carns)
                self.carnivores.breeding(pos, self.island, self.island.carns)
                self.herbivores.calculate_fitness(pos, self.island.herbs)
                self.carnivores.calculate_fitness(pos, self.island.carns)
        self.herbivores.migration_calculations(self.island, self.island.herbs)
        self.carnivores.migration_calculations(self.island, self.herbivores,
                                               self.island.carns)
        self.herbivores.migration_execution(self.island, self.island.herbs)
        self.carnivores.migration_execution(self.island, self.island.carns)
        for i in range(self.island.rader):
            for j in range(self.island.col):
                pos = (i, j)
                self.herbivores.aging(pos, self.island.herbs)
                self.carnivores.aging(pos, self.island.carns)
                self.herbivores.loss_of_weight(pos, self.island.herbs)
                self.carnivores.loss_of_weight(pos, self.island.carns)
                self.herbivores.calculate_fitness(pos, self.island.herbs)
                self.carnivores.calculate_fitness(pos, self.island.carns)
                self.herbivores.death(pos, self.island.herbs)
                self.carnivores.death(pos, self.island.carns)

    def simulate(self, num_years, vis_years=1, img_years=None):
        """
        Function used to simulate num_years, visualizing every vis_years and taking a picture every img_years.

        :param num_years: Number of years to simulate
        :param vis_years: Number that indicates how many years between each visualization
        :param img_years: Number that indicates how many years between taking images, if None a picture will be taken
                          for each visualization
        """
        self._setup_graphics(num_years)
        for year in range(num_years):
            if year % vis_years == 0:
                self._update_graphics()
            if year % img_years == 0:
                self._save_graphics()
            self.simulate_one_year()
            self.tot_num_years += 1

    def add_population(self, population):
        """
        Add a population to the island

        :param population: List of dictionaries specifying population
        """
        self.island.add_animals(population)

    def _setup_graphics(self, num_years):
        """
        This function is used to setup the figure of the grapichs, it creates a figure with 4 subplots and initializes
        the animal lines which are plotted in the upper right subplot.
        :param num_years:
        :return:
        """
        fig = plt.figure()
        axt = fig.add_axes([0.4, 0.8, 0.2, 0.2])
        axt.axis('off')
        self.ax1 = plt.subplot2grid((2, 2), (0, 0))
        self.ax2 = plt.subplot2grid((2, 2), (0, 1))
        self.ax3 = plt.subplot2grid((2, 2), (1, 0))
        self.ax4 = plt.subplot2grid((2, 2), (1, 1))

        self.ax2.set_xlim(self.year + 1, self.year + num_years)
        self.ax2.set_ylim(0, self.ymax_animals)

        self.line = self.ax2.plot(np.arange(num_years + self.year + 1),
                                  np.full(num_years + self.year + 1, np.nan),
                                  'b-')[0]
        self.line2 = self.ax2.plot(np.arange(num_years + self.year + 1),
                                   np.full(num_years + self.year + 1, np.nan),
                                   'r-')[0]

    def _update_graphics(self):
        """
        Updates graphics with current data.
        """
        plt.suptitle('Years since the simulation started: ' + str(self.year) +
                     ' years')
        rgb_value = {
            "O": mcolors.to_rgba("navy"),
            "J": mcolors.to_rgba("forestgreen"),
            "S": mcolors.to_rgba("#e1ab62"),
            "D": mcolors.to_rgba("yellow"),
            "M": mcolors.to_rgba("lightslategrey")
        }

        kart_rgb = [[rgb_value[column] for column in row]
                    for row in self.island_map.split()]

        self.ax1.imshow(kart_rgb)
        self.ax1.set_xticks(range(len(kart_rgb[0])))
        self.ax1.set_xticklabels(range(1, 1 + len(kart_rgb[0])))
        self.ax1.set_yticks(range(len(kart_rgb)))
        self.ax1.set_yticklabels(range(1, 1 + len(kart_rgb)))
        self.ax1.axis('scaled')
        self.ax1.set_title('Map')

        # Upper right subplot
        ydata = self.line.get_ydata()
        ydata2 = self.line2.get_ydata()
        ydata[self.year] = self.num_animals_per_species['Herbivore']
        ydata2[self.year] = self.num_animals_per_species['Carnivore']
        self.line.set_ydata(ydata)
        self.line2.set_ydata(ydata2)
        self.ax2.set_title('Total number of animals')

        self.ax3.imshow(self.animal_distribution_for_plot[:, :, 0],
                        'Greens',
                        vmax=self.cmax_animals['Herbivore'])
        self.ax3.set_xticks(range(len(kart_rgb[0])))
        self.ax3.set_xticklabels(range(1, 1 + len(kart_rgb[0])))
        self.ax3.set_yticks(range(len(kart_rgb)))
        self.ax3.set_yticklabels(range(1, 1 + len(kart_rgb)))
        self.ax3.axis('scaled')
        self.ax3.set_title('Herbivore distribution: ' +
                           str(self.num_animals_per_species['Herbivore']))

        self.ax4.imshow(self.animal_distribution_for_plot[:, :, 1],
                        'Reds',
                        vmax=self.cmax_animals['Carnivore'])
        self.ax4.set_xticks(range(len(kart_rgb[0])))
        self.ax4.set_xticklabels(range(1, 1 + len(kart_rgb[0])))
        self.ax4.set_yticks(range(len(kart_rgb)))
        self.ax4.set_yticklabels(range(1, 1 + len(kart_rgb)))
        self.ax4.axis('scaled')
        self.ax4.set_title('Carnivore distribution: ' +
                           str(self.num_animals_per_species['Carnivore']))
        plt.pause(1e-9)

    def _save_graphics(self):
        """Saves graphics to file if file name given."""

        if self._img_base is None:
            return

        plt.savefig('{base}_{num:05d}.{type}'.format(base=self._img_base,
                                                     num=self._img_ctr,
                                                     type=self._img_fmt))
        self._img_ctr += 1

    def make_movie(self):
        import glob

        img_array = []
        for filename in glob.glob(self._img_base + '*' + self._img_fmt):
            img = cv2.imread(filename)
            height, width, layers = img.shape
            size = (width, height)
            img_array.append(img)

        out = cv2.VideoWriter('project.avi', cv2.VideoWriter_fourcc(*'DIVX'),
                              6, size)

        for i in range(len(img_array)):
            out.write(img_array[i])
        out.release()

    @property
    def year(self):
        """Last year simulated."""
        return self.tot_num_years

    @property
    def num_animals(self):
        """Total number of animals on island."""
        return sum(self.island.num_animals())

    @property
    def num_animals_per_species(self):
        """Number of animals per species in island, as dictionary."""
        herb, carn = self.island.num_animals()
        return {'Herbivore': herb, 'Carnivore': carn}

    @property
    def animal_distribution_for_plot(self):
        """Pandas DataFrame with animal count per species for each cell on island."""
        animal_list = np.zeros((self.island.rader, self.island.col, 2))
        for i in range(self.island.rader):
            for j in range(self.island.col):
                if (i, j) in self.island.herbs.keys():
                    animal_list[i, j, 0] = len(self.island.herbs[(i, j)])
                if (i, j) in self.island.carns.keys():
                    animal_list[i, j, 1] = len(self.island.carns[(i, j)])
        return animal_list

    @property
    def animal_distribution(self):
        a = np.zeros((self.island.rader * self.island.col, 4))
        for i in range(self.island.rader):
            a[i * self.island.col:(i + 1) * self.island.col, 0] = i

        c = np.arange(self.island.col)
        for i in range(self.island.rader):
            a[i * self.island.col:(i + 1) * self.island.col, 1] = c

        for pos in self.island.herbs.keys():
            a[pos[0] * self.island.col + pos[1],
              2] += len(self.island.herbs[pos])

        for pos in self.island.carns.keys():
            a[pos[0] * self.island.col + pos[1],
              3] += len(self.island.carns[pos])

        return pd.DataFrame(a,
                            columns=['Row', 'Col', 'Herbivore', 'Carnivore'])