Machine_learning_2023/AI_brain/genetic_algorytm.py
2023-06-16 00:48:59 +02:00

248 lines
7.6 KiB
Python

import random
import configparser
import math
from domain.entities.entity import Entity
config = configparser.ConfigParser()
config.read("config.ini")
from domain.world import World
from AI_brain.rotate_and_go_aStar import RotateAndGoAStar, State
hits = 0
misses = 0
class Cashed_sub_paths(dict):
def __init__(self):
super().__init__()
def __missing__(self, key):
self[key] = Cashed_sub_paths()
return self[key]
class Cashed_sub_path:
def __init__(self, sub_path: list[str] = [], distance: int = 0):
self.sub_path = sub_path
self.distance = distance
steps_distance_cashed: dict[tuple[int, int], Cashed_sub_path] = Cashed_sub_paths()
class Path:
def __init__(self):
self.walk = []
self.permutation = []
self.real_path = []
self.distance = 0
def random_walk(self, dusts: list[Entity]):
permutation = generate_random_permutation(len(dusts))
self.permutation = permutation
self.walk = addStartAndStation(
permutation, config.getint("CONSTANT", "BananaFilling")
)
def calculate_distance(self, world: World):
distance = 0
for i in range(len(self.walk) - 1):
next_distance, next_real_path = self.step_distance(
self.walk[i], self.walk[i + 1], world
)
distance += next_distance
# BUG this part is not working and is not used, B.1 must be resolved
self.real_path = self.real_path + ["DEFAULT_ROTATION"] + next_real_path
self.distance = distance
def step_distance(
self, from_id: int, to_id: int, world: World
) -> tuple[int, list[str]]:
global hits, misses
if (from_id, to_id) in steps_distance_cashed:
hits += 1
distance = steps_distance_cashed[(from_id, to_id)].distance
sub_path = steps_distance_cashed[(from_id, to_id)].sub_path
return distance, sub_path
misses += 1
path_searcher = RotateAndGoAStar(
world,
self.getPosition(from_id, world.dustList),
self.getPosition(to_id, world.dustList),
)
path_searcher.search()
steps_distance_cashed[(from_id, to_id)] = Cashed_sub_path(
path_searcher.actions, path_searcher.cost
)
# BUG B.1 inverse path
inverse_sub_path = path_searcher.actions.copy()
steps_distance_cashed[(to_id, from_id)] = Cashed_sub_path(
inverse_sub_path, path_searcher.cost
)
return path_searcher.cost, path_searcher.actions
def inverse_sub_path(sub_path: list[str]) -> list[str]:
sub_path.reverse()
for command in sub_path:
command.replace("RL", "RR")
command.replace("RR", "RR")
def getPosition(
self,
number: int,
dustList: list[Entity],
) -> State:
if number == -1:
dock_start_x, dock_start_y = config.get(
"CONSTANT", "DockStationStartPosition"
).split(",")
dock_start_x, dock_start_y = int(dock_start_x), int(dock_start_y)
return State(dock_start_x, dock_start_y)
if number == -2:
vacuum_start_x, vacuum_start_y = config.get(
"CONSTANT", "RobotStartPosition"
).split(",")
vacuum_start_x, vacuum_start_y = int(vacuum_start_x), int(vacuum_start_y)
return State(vacuum_start_x, vacuum_start_y)
return State(dustList[number].x, dustList[number].y)
def get_real_path(self, world: World):
full_path = []
for index_place in range(len(self.walk) - 1):
path_searcher = RotateAndGoAStar(
world,
self.getPosition(self.walk[index_place], world.dustList),
self.getPosition(self.walk[index_place + 1], world.dustList),
)
path_searcher.search()
full_path = full_path + ["DEFAULT_ROTATION"] + path_searcher.actions
self.real_path = full_path
def generate_random_permutation(n):
# Create a list of numbers from 1 to n
numbers = list(range(0, n))
# Shuffle the list using the random.shuffle function
random.shuffle(numbers)
return numbers
# BUG solution: inverse direction at the last step
def addStartAndStation(permutation: list[int], bananaFilling: int):
frequency = math.ceil(100 / bananaFilling)
numer_of_stops = math.ceil(len(permutation) / frequency)
walk = permutation.copy()
for i in range(1, numer_of_stops):
walk.insert((frequency + 1) * i - 1, -1)
walk.insert(len(walk), -1)
walk.insert(0, -2)
return walk
class GeneticAlgorytm:
def __init__(self, world: World):
self.world = world
self.population_size = config.getint("GENETIC_ALGORITHM", "PopulationSize")
self.mutation_probability = config.getfloat(
"GENETIC_ALGORITHM", "MutationProbability"
)
self.iteration_number = config.getint("GENETIC_ALGORITHM", "IterationNumber")
self.descendants_number = config.getint(
"GENETIC_ALGORITHM", "DescendantsNumber"
)
self.dusts = world.dustList
self.doc_station = world.doc_station
self.paths: list[Path] = []
self.checked_permutations = {}
self.best_path = None
self.best_distance = math.inf
self.best_real_path = []
def generate_population(self):
for i in range(self.population_size):
path = Path()
path.random_walk(self.dusts)
self.checked_permutations[tuple(path.permutation)] = True
path.calculate_distance(self.world)
self.paths.append(path)
def evaluate_population(self):
self.paths.sort(key=lambda x: x.distance, reverse=False)
self.best_distance = self.paths[0].distance
self.best_path = self.paths[0]
for path in self.paths[self.population_size :]:
del self.checked_permutations[tuple(path.permutation)]
self.paths = self.paths[: self.population_size]
def create_child(self, parent1: Path, parent2: Path) -> Path:
child = Path()
child.permutation = parent1.permutation[: len(parent1.permutation) // 2]
# Add missing items from parent2 in the order they appear
for item in parent2.permutation:
if item not in child.permutation:
child.permutation.append(item)
child.walk = addStartAndStation(
child.permutation, config.getint("CONSTANT", "BananaFilling")
)
child.calculate_distance(self.world)
return child
def run(self):
self.generate_population()
for i in range(self.iteration_number):
self.crossover()
# self.mutate()
self.evaluate_population()
self.best_real_path = self.paths[0].get_real_path(self.world)
print(hits, (misses + hits))
print(hits / (misses + hits))
def crossover(self):
for i in range(self.descendants_number):
parent1 = self.paths[random.randint(0, self.population_size - 1)]
parent2 = self.paths[random.randint(0, self.population_size - 1)]
child = self.create_child(parent1, parent2)
while tuple(child.permutation) in self.checked_permutations:
parent1 = self.paths[random.randint(0, self.population_size - 1)]
parent2 = self.paths[random.randint(0, self.population_size - 1)]
child = self.create_child(parent1, parent2)
self.checked_permutations[tuple(child.permutation)] = True
self.paths.append(child)
self.evaluate_population()