Compare commits

...

2 Commits

Author SHA1 Message Date
Mateusz Dokowicz
a404e70058 mutations geneticAlg 2023-06-16 03:22:30 +02:00
Mateusz Dokowicz
74fd6d9263 crossover working version 2023-06-16 00:48:59 +02:00
5 changed files with 277 additions and 44 deletions

View File

@ -1,6 +1,9 @@
import copy
import random import random
import configparser import configparser
import math import math
import pygame
from domain.entities.entity import Entity from domain.entities.entity import Entity
@ -9,48 +12,128 @@ config.read("config.ini")
from domain.world import World from domain.world import World
from AI_brain.rotate_and_go_aStar import RotateAndGoAStar, State from AI_brain.rotate_and_go_aStar import RotateAndGoAStar, State
from random import randint
steps_distance_cashed = {} 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: class Path:
def __init__(self): def __init__(self):
self.walk = [] self.walk = []
self.permutation = []
self.real_path = []
self.distance = 0 self.distance = 0
def random_walk(self, dusts: list[Entity]): def random_walk(self, dusts: list[Entity]):
random_permutation = generate_random_permutation(len(dusts)) permutation = generate_random_permutation(len(dusts))
self.walk = addStopsForStopStation( self.permutation = permutation
random_permutation, config.getint("CONSTANT", "BananaFilling")
) self.walk = addStartAndStation(permutation)
def calculate_distance(self, world: World): def calculate_distance(self, world: World):
distance = 0 distance = 0
for i in range(len(self.walk) - 1): for i in range(len(self.walk) - 1):
distance += self.step_distance(self.walk[i], self.walk[i + 1], world) 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 self.distance = distance
def step_distance(self, from_id: int, to_id: int, world: World) -> int: 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: if (from_id, to_id) in steps_distance_cashed:
return steps_distance_cashed[(from_id, to_id)] 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( path_searcher = RotateAndGoAStar(
world, world,
self.getPosition(from_id, world.dustList, world.doc_station), self.getPosition(from_id, world.dustList),
self.getPosition(to_id, world.dustList, world.doc_station), self.getPosition(to_id, world.dustList),
) )
path_searcher.search() path_searcher.search()
number_of_go = path_searcher.number_of_moves_forward()
steps_distance_cashed[(from_id, to_id)] = path_searcher.cost
steps_distance_cashed[(to_id, from_id)] = path_searcher.cost
return path_searcher.cost
def getPosition(self, number: int, dusts: list[Entity], station: Entity) -> State: 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: if number == -1:
return State(station.x, station.y) 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(dusts[number].x, dusts[number].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): def generate_random_permutation(n):
@ -63,12 +146,136 @@ def generate_random_permutation(n):
return numbers return numbers
def addStopsForStopStation(permutation: list[int], bananaFilling: int): # BUG solution: inverse direction at the last step
frequency = math.ceil(100 / bananaFilling) def addStartAndStation(permutation: list[int]):
frequency = math.ceil(100 / config.getint("CONSTANT", "BananaFilling"))
numer_of_stops = math.ceil(len(permutation) / frequency) numer_of_stops = math.ceil(len(permutation) / frequency)
walk = permutation.copy()
for i in range(1, numer_of_stops): for i in range(1, numer_of_stops):
permutation.insert((frequency + 1) * i - 1, -1) walk.insert((frequency + 1) * i - 1, -1)
permutation.insert(len(permutation), -1) walk.insert(len(walk), -1)
walk.insert(0, -2)
return permutation 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 print_top(self):
print(
"Best path: ",
self.best_path.walk,
"Distance: ",
self.best_path.distance,
)
for path in self.paths[1:]:
print(path.walk, path.distance)
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)
child.calculate_distance(self.world)
return child
def run(self):
self.generate_population()
for i in range(self.iteration_number):
self.crossover()
self.evaluate_population()
self.best_real_path = self.paths[0].get_real_path(self.world)
print(hits, (misses + hits))
print(hits / (misses + hits))
def mutate(self, mutant: Path) -> Path:
random_number = randint(0, len(mutant.permutation) - 1)
random_number2 = random_number
while random_number == random_number2:
random_number2 = randint(0, len(mutant.permutation) - 1)
mutant.permutation[random_number], mutant.permutation[random_number2] = (
mutant.permutation[random_number2],
mutant.permutation[random_number],
)
if tuple(mutant.permutation) in self.checked_permutations:
return self.mutate(mutant)
mutant.walk = addStartAndStation(mutant.permutation)
mutant.calculate_distance(self.world)
return mutant
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)
mutant = Path()
mutant.permutation = child.permutation.copy()
mutant = self.mutate(mutant)
self.checked_permutations[tuple(mutant.permutation)] = True
self.paths.append(mutant)
self.evaluate_population()

View File

@ -4,10 +4,13 @@ movement = robot
#accept: human, robot #accept: human, robot
[CONSTANT] [CONSTANT]
NumberOfBananas = 5 NumberOfBananas = 15
NumberOfEarrings = 3 NumberOfEarrings = 0
NumberOfPlants = 5 NumberOfPlants = 5
BananaFilling = 25 BananaFilling = 25
RobotStartPosition = 5, 5
DockStationStartPosition = 5, 6
#9,8
[NEURAL_NETWORK] [NEURAL_NETWORK]
is_neural_network_off = True is_neural_network_off = True
@ -15,3 +18,9 @@ is_neural_network_off = True
[AI_BRAIN] [AI_BRAIN]
mode = full_clean mode = full_clean
#accept: full_clean, to_station #accept: full_clean, to_station
[GENETIC_ALGORITHM]
PopulationSize = 20
DescendantsNumber = 6
MutationProbability = 0.3
IterationNumber = 1_000

View File

@ -27,6 +27,7 @@ class VacuumMoveCommand(Command):
if self.vacuum.get_container_filling() < 100: if self.vacuum.get_container_filling() < 100:
self.vacuum.increase_container_filling() self.vacuum.increase_container_filling()
self.world.delete_entities_at_Of_type(item.x, item.y, item.type) self.world.delete_entities_at_Of_type(item.x, item.y, item.type)
self.world.dust[end_x][end_y].remove(item)
if self.world.is_docking_station_at(end_x, end_y): if self.world.is_docking_station_at(end_x, end_y):
self.vacuum.dump_trash() self.vacuum.dump_trash()

View File

@ -8,7 +8,7 @@ class World:
self.width = width self.width = width
self.height = height self.height = height
self.dust = [[[] for j in range(height)] for i in range(width)] self.dust = [[[] for j in range(height)] for i in range(width)]
self.dustList = [] self.dustList: list[Entity] = []
self.obstacles = [[[] for j in range(height)] for i in range(width)] self.obstacles = [[[] for j in range(height)] for i in range(width)]
self.entity = [[[] for j in range(height)] for i in range(width)] self.entity = [[[] for j in range(height)] for i in range(width)]

52
main.py
View File

@ -16,7 +16,7 @@ from domain.entities.earring import Earring
from domain.entities.docking_station import Doc_Station from domain.entities.docking_station import Doc_Station
from domain.world import World from domain.world import World
from view.renderer import Renderer from view.renderer import Renderer
from AI_brain.genetic_algorytm import Path from AI_brain.genetic_algorytm import GeneticAlgorytm, Path
if not config.getboolean("NEURAL_NETWORK", "is_neural_network_off"): if not config.getboolean("NEURAL_NETWORK", "is_neural_network_off"):
from AI_brain.image_recognition import VacuumRecognizer from AI_brain.image_recognition import VacuumRecognizer
@ -53,9 +53,6 @@ class Main:
def run_robot(self): def run_robot(self):
self.renderer.render(self.world) self.renderer.render(self.world)
# path_searcher = GoAnyDirectionBFS(self.world, start_state, end_state)
# path_searcher = RotateAndGoBFS(self.world, start_state, end_state)
if config["AI_BRAIN"]["mode"] == "to_station": if config["AI_BRAIN"]["mode"] == "to_station":
start_state = State(self.world.vacuum.x, self.world.vacuum.y) start_state = State(self.world.vacuum.x, self.world.vacuum.y)
end_state = State(self.world.doc_station.x, self.world.doc_station.y) end_state = State(self.world.doc_station.x, self.world.doc_station.y)
@ -67,13 +64,16 @@ class Main:
exit(0) exit(0)
print(path_searcher.actions) print(path_searcher.actions)
print(path_searcher.cost) print(path_searcher.cost)
robot_actions = path_searcher.actions
elif config["AI_BRAIN"]["mode"] == "full_clean": elif config["AI_BRAIN"]["mode"] == "full_clean":
x = Path() genetic_searcher = GeneticAlgorytm(self.world)
x.random_walk(self.world.dustList) genetic_searcher.run()
x.calculate_distance(self.world)
print(x.walk) genetic_searcher.print_top()
print(x.distance)
exit(0) robot_actions = genetic_searcher.best_path.real_path
else: else:
print("Wrong mode") print("Wrong mode")
exit(0) exit(0)
@ -83,8 +83,8 @@ class Main:
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
self.running = False self.running = False
if len(path_searcher.actions) > 0: if len(robot_actions) > 0:
action_direction = path_searcher.actions.pop(0) action_direction = robot_actions.pop(0)
# self.handle_action1(action_direction) # self.handle_action1(action_direction)
self.handle_action2(action_direction) self.handle_action2(action_direction)
@ -129,6 +129,8 @@ class Main:
self.world.vacuum.direction[1], self.world.vacuum.direction[1],
-self.world.vacuum.direction[0], -self.world.vacuum.direction[0],
) )
elif action == "DEFAULT_ROTATION":
self.world.vacuum.direction = (1, 0)
def process_input(self): def process_input(self):
for event in pygame.event.get(): for event in pygame.event.get():
@ -163,8 +165,14 @@ class Main:
def generate_world(tiles_x: int, tiles_y: int) -> World: def generate_world(tiles_x: int, tiles_y: int) -> World:
if config.getboolean("NEURAL_NETWORK", "is_neural_network_off"): if config.getboolean("NEURAL_NETWORK", "is_neural_network_off"):
world = World(tiles_x, tiles_y) world = World(tiles_x, tiles_y)
world.vacuum = Vacuum(1, 1)
world.doc_station = Doc_Station(9, 8) x, y = config.get("CONSTANT", "RobotStartPosition").split(",")
x, y = int(x), int(y)
world.vacuum = Vacuum(x, y)
x, y = config.get("CONSTANT", "DockStationStartPosition").split(",")
x, y = int(x), int(y)
world.doc_station = Doc_Station(x, y)
if config.getboolean("APP", "cat"): if config.getboolean("APP", "cat"):
world.cat = Cat(7, 8) world.cat = Cat(7, 8)
world.add_entity(world.cat) world.add_entity(world.cat)
@ -176,9 +184,17 @@ def generate_world(tiles_x: int, tiles_y: int) -> World:
world.add_entity(Entity(3, 4, "PLANT2")) world.add_entity(Entity(3, 4, "PLANT2"))
world.add_entity(Entity(8, 8, "PLANT2")) world.add_entity(Entity(8, 8, "PLANT2"))
world.add_entity(Entity(9, 3, "PLANT3")) world.add_entity(Entity(9, 3, "PLANT3"))
world.add_entity(Earring(9, 7))
world.add_entity(Earring(5, 5)) numberOfEarrings = config.getint("CONSTANT", "NumberOfEarrings")
world.add_entity(Earring(4, 6)) for _ in range(numberOfEarrings):
temp_x = randint(0, tiles_x - 1)
temp_y = randint(0, tiles_y - 1)
while world.is_entity_at(temp_x, temp_y):
temp_x = randint(0, tiles_x - 1)
temp_y = randint(0, tiles_y - 1)
world.add_entity(Earring(temp_x, temp_y))
for _ in range(config.getint("CONSTANT", "NumberOfBananas")): for _ in range(config.getint("CONSTANT", "NumberOfBananas")):
temp_x = randint(0, tiles_x - 1) temp_x = randint(0, tiles_x - 1)
@ -234,7 +250,7 @@ def generate_world(tiles_x: int, tiles_y: int) -> World:
if world.garbage_at(x, y): if world.garbage_at(x, y):
world.costs[x][y] = 1 world.costs[x][y] = 1
else: else:
world.costs[x][y] = 10 world.costs[x][y] = 2
return world return world