From 2edbb89c99222a0bf451f12861bf636f0be264f1 Mon Sep 17 00:00:00 2001 From: Dawid Pylak Date: Sun, 5 Jun 2022 18:52:21 +0200 Subject: [PATCH 1/3] implemented mockup of the algorithm --- src/constants.py | 4 ++ src/utils/GeneticAlgorithm.py | 52 +++++++++++++++++++++++ src/utils/Plants.py | 80 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/utils/GeneticAlgorithm.py create mode 100644 src/utils/Plants.py diff --git a/src/constants.py b/src/constants.py index 4346e82..31c993c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -26,3 +26,7 @@ class Constants: CACTUS = 'kaktus' POTATO = 'ziemniak' WEATH = 'pszenica' + + # Genetic algorithm points average + + POINTS_AVERAGE = 20 diff --git a/src/utils/GeneticAlgorithm.py b/src/utils/GeneticAlgorithm.py new file mode 100644 index 0000000..318a1d9 --- /dev/null +++ b/src/utils/GeneticAlgorithm.py @@ -0,0 +1,52 @@ +from Plants import * + +from random import choice, random + + +class GeneticAlgorithm: + + def __init__(self): + self.mutation_probability = 0.1 + self.stop_condition = stop_condition + + def selection_strategy(self, generation: BaseField): + maximum_selected_items = int(len(generation) / 10) + sorted_elements = sorted(generation, key=lambda x: x.evaluation) + return sorted[:maximum_selected_items] + + def _generate_random_plants(self): + plant_names = [choice(BaseField.possibilities) for _ in range(9)] + return [plant_selector(plant_name) for plant_name in plant_names] + + def _generate_first_population(self): + return [BaseField(self._generate_random_plants()) for _ in range(100)] + + def run(self): + for population in self._generate_first_population(): + print(population) + first_population = self._generate_first_population() + first_population.sort(key=lambda x: x.evaluation) + population_length = len(first_population) + i = 0 + while True: + selected = self.selection_strategy(first_population) + new_population = selected.copy() + while len(new_population) != population_length: + child = choice(first_population).crossover(choice(first_population)) + if random() <= self.mutation_probability: + child.mutate() + new_population.append(child) + + first_population = new_population + best_match = min(first_population, key=lambda x: x.evaluation) + i += 1 + if self.stop_condition(best_match): + break + + +def main(): + GeneticAlgorithm().run() + + +if __name__ == '__main__': + main() diff --git a/src/utils/Plants.py b/src/utils/Plants.py new file mode 100644 index 0000000..6564486 --- /dev/null +++ b/src/utils/Plants.py @@ -0,0 +1,80 @@ +from constants import Constants +from random import randint, choice + + +class BasePlant: + + def __init__(self): + self.appearance_points = 0 + self.difficulty_points = 0 + self.profit_points = 0 + + def first_population_generator(self): + pass + + def __int__(self): + return sum([self.appearance_points, self.difficulty_points, self.profit_points]) + + def __float__(self): + return self.__int__() + + +def stop_condition(average): + return average == Constants.POINTS_AVERAGE + + +def plant_selector(plant_name: str) -> BasePlant: + if plant_name == Constants.POTATO: + return Potato() + elif plant_name == Constants.WEATH: + return Weath() + elif plant_name == Constants.CACTUS: + return Cactus() + + +class BaseField: + """Class that represents what plants grow on a certain field divided into 9 tiles""" + + possibilities = [Constants.WEATH, Constants.POTATO, Constants.CACTUS] + + def __init__(self, plants): + self.plants: list(BasePlant) = plants + self.evaluation = self.evaluate_function() + + def mutate(self): + self._perform_mutation() + self.evaluation = self.evaluate_function() + + def _perform_mutation(self): + random_index = randint(0, 8) + self.plants[random_index] = plant_selector(choice(self.possibilities)) + + def crossover(self, other_field): + length = int(randint(0, 8)) + new_plants = self.plants[:length] + other_field.plants[length:] + return BaseField(new_plants) + + def evaluate_function(self): + current_fields_average = sum([int(plant) for plant in self.plants]) / 9 + return Constants.POINTS_AVERAGE - current_fields_average + + def __str__(self): + return ''.join([str(plant) + ' ' for plant in self.plants]) + + +class Potato(BasePlant): + + def __str__(self): + return Constants.POTATO + + +class Cactus(BasePlant): + + def __str__(self): + return Constants.CACTUS + + +class Weath(BasePlant): + + def __str__(self): + return Constants.WEATH -- 2.20.1 From 3387d41839c1b958e48572dd1bcd4a6dc390a412 Mon Sep 17 00:00:00 2001 From: Dawid Pylak Date: Mon, 6 Jun 2022 17:35:24 +0200 Subject: [PATCH 2/3] first vesrion of working algorithm --- src/constants.py | 4 +-- src/utils/GeneticAlgorithm.py | 7 +++-- src/utils/Plants.py | 49 ++++++++++++++++++++++++++++------- src/world.py | 4 +-- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/constants.py b/src/constants.py index 31c993c..0a8b653 100644 --- a/src/constants.py +++ b/src/constants.py @@ -25,8 +25,8 @@ class Constants: NONE = 'brak' CACTUS = 'kaktus' POTATO = 'ziemniak' - WEATH = 'pszenica' + WHEAT = 'pszenica' # Genetic algorithm points average - POINTS_AVERAGE = 20 + POINTS_AVERAGE = range(5, 8) diff --git a/src/utils/GeneticAlgorithm.py b/src/utils/GeneticAlgorithm.py index 318a1d9..6c5cc17 100644 --- a/src/utils/GeneticAlgorithm.py +++ b/src/utils/GeneticAlgorithm.py @@ -12,7 +12,7 @@ class GeneticAlgorithm: def selection_strategy(self, generation: BaseField): maximum_selected_items = int(len(generation) / 10) sorted_elements = sorted(generation, key=lambda x: x.evaluation) - return sorted[:maximum_selected_items] + return sorted_elements[:maximum_selected_items] def _generate_random_plants(self): plant_names = [choice(BaseField.possibilities) for _ in range(9)] @@ -22,8 +22,6 @@ class GeneticAlgorithm: return [BaseField(self._generate_random_plants()) for _ in range(100)] def run(self): - for population in self._generate_first_population(): - print(population) first_population = self._generate_first_population() first_population.sort(key=lambda x: x.evaluation) population_length = len(first_population) @@ -40,8 +38,9 @@ class GeneticAlgorithm: first_population = new_population best_match = min(first_population, key=lambda x: x.evaluation) i += 1 - if self.stop_condition(best_match): + if self.stop_condition(float(best_match)): break + print(best_match) def main(): diff --git a/src/utils/Plants.py b/src/utils/Plants.py index 6564486..0ce7cc1 100644 --- a/src/utils/Plants.py +++ b/src/utils/Plants.py @@ -13,21 +13,21 @@ class BasePlant: pass def __int__(self): - return sum([self.appearance_points, self.difficulty_points, self.profit_points]) + return sum([self.appearance_points, self.profit_points]) - self.difficulty_points def __float__(self): return self.__int__() def stop_condition(average): - return average == Constants.POINTS_AVERAGE + return average in Constants.POINTS_AVERAGE def plant_selector(plant_name: str) -> BasePlant: if plant_name == Constants.POTATO: return Potato() - elif plant_name == Constants.WEATH: - return Weath() + elif plant_name == Constants.WHEAT: + return Wheat() elif plant_name == Constants.CACTUS: return Cactus() @@ -35,7 +35,7 @@ def plant_selector(plant_name: str) -> BasePlant: class BaseField: """Class that represents what plants grow on a certain field divided into 9 tiles""" - possibilities = [Constants.WEATH, Constants.POTATO, Constants.CACTUS] + possibilities = [Constants.WHEAT, Constants.POTATO, Constants.CACTUS] def __init__(self, plants): self.plants: list(BasePlant) = plants @@ -54,27 +54,56 @@ class BaseField: new_plants = self.plants[:length] + other_field.plants[length:] return BaseField(new_plants) - def evaluate_function(self): - current_fields_average = sum([int(plant) for plant in self.plants]) / 9 - return Constants.POINTS_AVERAGE - current_fields_average + def evaluate_function(self) -> float: + current_fields_average = self.__float__() + if current_fields_average not in Constants.POINTS_AVERAGE: + if current_fields_average < Constants.POINTS_AVERAGE[0]: + return abs(current_fields_average - Constants.POINTS_AVERAGE[0]) + elif current_fields_average > Constants.POINTS_AVERAGE[-1]: + return abs(current_fields_average - Constants.POINTS_AVERAGE[1]) + return 0 def __str__(self): return ''.join([str(plant) + ' ' for plant in self.plants]) + def __float__(self): + return sum([int(plant) for plant in self.plants]) / 9 + class Potato(BasePlant): + def __init__(self): + + super().__init__() + self.appearance_points = 3 + self.difficulty_points = 4 + self.profit_points = 7 + def __str__(self): return Constants.POTATO class Cactus(BasePlant): + def __init__(self): + + super(Cactus, self).__init__() + self.appearance_points = 6 + self.difficulty_points = 3 + self.profit_points = 2 + def __str__(self): return Constants.CACTUS -class Weath(BasePlant): +class Wheat(BasePlant): + + def __init__(self): + + super(Wheat, self).__init__() + self.appearance_points = 5 + self.difficulty_points = 7 + self.profit_points = 9 def __str__(self): - return Constants.WEATH + return Constants.WHEAT diff --git a/src/world.py b/src/world.py index 081283a..5aa1fda 100644 --- a/src/world.py +++ b/src/world.py @@ -53,7 +53,7 @@ class World: img = pygame.transform.scale(self.farmland_empty, (self.settings.tile_size, self.settings.tile_size)) elif to_water == 0 and rodzaj_rosliny == Constants.CACTUS: img = pygame.transform.scale(self.farmland_cactus, (self.settings.tile_size, self.settings.tile_size)) - elif to_water == 0 and rodzaj_rosliny == Constants.WEATH: + elif to_water == 0 and rodzaj_rosliny == Constants.WHEAT: img = pygame.transform.scale(self.farmland_wheat, (self.settings.tile_size, self.settings.tile_size)) elif to_water == 0 and rodzaj_rosliny == Constants.POTATO: img = pygame.transform.scale(self.farmland_potato, (self.settings.tile_size, self.settings.tile_size)) @@ -61,7 +61,7 @@ class World: img = pygame.transform.scale(self.dirt_empty, (self.settings.tile_size, self.settings.tile_size)) elif to_water == 1 and rodzaj_rosliny == Constants.CACTUS: img = pygame.transform.scale(self.dirt_cactus, (self.settings.tile_size, self.settings.tile_size)) - elif to_water == 1 and rodzaj_rosliny == Constants.WEATH: + elif to_water == 1 and rodzaj_rosliny == Constants.WHEAT: img = pygame.transform.scale(self.dirt_wheat, (self.settings.tile_size, self.settings.tile_size)) elif to_water == 1 and rodzaj_rosliny == Constants.POTATO: img = pygame.transform.scale(self.dirt_potato, (self.settings.tile_size, self.settings.tile_size)) -- 2.20.1 From c42409b21020fb6b2d46017a04125beb0ab3242b Mon Sep 17 00:00:00 2001 From: Dawid Pylak Date: Mon, 6 Jun 2022 18:25:14 +0200 Subject: [PATCH 3/3] improved version of working algorithm; generated plants are now displayed on the map --- src/constants.py | 2 +- src/utils/GeneticAlgorithm.py | 25 ++++++++++++++++++------- src/utils/Plants.py | 11 +++-------- src/world.py | 4 +++- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/constants.py b/src/constants.py index 0a8b653..b2ae9e7 100644 --- a/src/constants.py +++ b/src/constants.py @@ -29,4 +29,4 @@ class Constants: # Genetic algorithm points average - POINTS_AVERAGE = range(5, 8) + POINTS_AVERAGE = 6.33 diff --git a/src/utils/GeneticAlgorithm.py b/src/utils/GeneticAlgorithm.py index 6c5cc17..7827f47 100644 --- a/src/utils/GeneticAlgorithm.py +++ b/src/utils/GeneticAlgorithm.py @@ -1,5 +1,4 @@ -from Plants import * - +from src.utils.Plants import * from random import choice, random @@ -19,9 +18,9 @@ class GeneticAlgorithm: return [plant_selector(plant_name) for plant_name in plant_names] def _generate_first_population(self): - return [BaseField(self._generate_random_plants()) for _ in range(100)] + return [BaseField(self._generate_random_plants()) for _ in range(15)] - def run(self): + def run(self) -> BaseField: first_population = self._generate_first_population() first_population.sort(key=lambda x: x.evaluation) population_length = len(first_population) @@ -31,7 +30,8 @@ class GeneticAlgorithm: new_population = selected.copy() while len(new_population) != population_length: child = choice(first_population).crossover(choice(first_population)) - if random() <= self.mutation_probability: + propability = random() + if propability <= self.mutation_probability: child.mutate() new_population.append(child) @@ -40,11 +40,22 @@ class GeneticAlgorithm: i += 1 if self.stop_condition(float(best_match)): break - print(best_match) + print(f'Best match is {best_match} with {i} iterations') + return best_match + + def get_plants(self) -> list: + result_array = [] + for i in range(4): + result_array = result_array + self.run().plants + return result_array def main(): - GeneticAlgorithm().run() + result_array = [] + genetic_algorithm = GeneticAlgorithm() + for i in range(4): + result_array = result_array + genetic_algorithm.run().plants + print(result_array) if __name__ == '__main__': diff --git a/src/utils/Plants.py b/src/utils/Plants.py index 0ce7cc1..bb37d0f 100644 --- a/src/utils/Plants.py +++ b/src/utils/Plants.py @@ -20,7 +20,7 @@ class BasePlant: def stop_condition(average): - return average in Constants.POINTS_AVERAGE + return round(average, 2) == Constants.POINTS_AVERAGE def plant_selector(plant_name: str) -> BasePlant: @@ -56,12 +56,7 @@ class BaseField: def evaluate_function(self) -> float: current_fields_average = self.__float__() - if current_fields_average not in Constants.POINTS_AVERAGE: - if current_fields_average < Constants.POINTS_AVERAGE[0]: - return abs(current_fields_average - Constants.POINTS_AVERAGE[0]) - elif current_fields_average > Constants.POINTS_AVERAGE[-1]: - return abs(current_fields_average - Constants.POINTS_AVERAGE[1]) - return 0 + return abs(current_fields_average - Constants.POINTS_AVERAGE) def __str__(self): return ''.join([str(plant) + ' ' for plant in self.plants]) @@ -88,7 +83,7 @@ class Cactus(BasePlant): def __init__(self): super(Cactus, self).__init__() - self.appearance_points = 6 + self.appearance_points = 4 self.difficulty_points = 3 self.profit_points = 2 diff --git a/src/world.py b/src/world.py index 5aa1fda..0a58f3c 100644 --- a/src/world.py +++ b/src/world.py @@ -2,6 +2,7 @@ import pygame from constants import Constants from src.tile import Tile +from utils.GeneticAlgorithm import GeneticAlgorithm class World: @@ -30,6 +31,7 @@ class World: self.farmland_wheat = pygame.image.load('assets/images/farmland_wheat.jpg') self.farmland_potato = pygame.image.load('assets/images/farmland_potato.jpg') self.tiles = pygame.sprite.Group() # mamy tiles jako Sprite Group, to sie przyda potem do kolizji itp. + self.plants = GeneticAlgorithm().get_plants() self.create_tiles() def create_tiles(self): @@ -45,7 +47,7 @@ class World: rodzaj_gleby = self.model.df.iloc[df_idx][Constants.SOIL_TYPE] stan_nawiezienia = self.model.df.iloc[df_idx][Constants.FERTILIZATION_STATUS] stopien_rozwoju = self.model.df.iloc[df_idx][Constants.GROWTH_LEVEL] - rodzaj_rosliny = self.model.df.iloc[df_idx][Constants.PLANT_TYPE] + rodzaj_rosliny = self.plants[df_idx].__str__() rodzaj_nawozu = self.model.df.iloc[df_idx][Constants.FERTILISER_TYPE] to_water = self.model.df.iloc[df_idx][Constants.TO_WATER] -- 2.20.1