Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
93b16795b9
320
Piotrek.py
320
Piotrek.py
@ -1,9 +1,327 @@
|
||||
import field, pathfinding_tractorless, pathfinding_tractor
|
||||
import random
|
||||
|
||||
class main():
|
||||
|
||||
def __init__(self,traktor,field,ui,path):
|
||||
self.traktor = traktor
|
||||
self.field = field
|
||||
self.ui = ui
|
||||
self.path = path
|
||||
self.pathfinding_tractorless = pathfinding_tractorless.pathfinding_tractorless()
|
||||
self.pathfinding_tractor = pathfinding_tractor.pathfinding_tractor()
|
||||
|
||||
def wspolrzedne(self): #wyznacza wspolrzedne pol danego rodzaju na planszy
|
||||
znalezione_pola = []
|
||||
k = 0
|
||||
ktore_pole = self.traktor.get_modes_values() #rodzaj pól zależy od ustawionego trybu pracy agenta
|
||||
for i in self.field.field_matrix:
|
||||
l = 0
|
||||
for j in i:
|
||||
if j in ktore_pole:
|
||||
znalezione_pola.append(k*10+l)
|
||||
l = l + 1
|
||||
k = k + 1
|
||||
pierwsze_szukane_pole = znalezione_pola[0] #początkowa współrzędna, w każdym przypadku pole startowe [0,0]
|
||||
znalezione_pola.append(pierwsze_szukane_pole)
|
||||
print("Współrzędne szukanych pól: " + str(znalezione_pola))
|
||||
return znalezione_pola
|
||||
|
||||
def koszt_przejazdu(self,znalezione_pola): #wyznacza koszt trasy przez pola danego rodzaju w zadanej kolejnosci
|
||||
self.liczba_pol = len(znalezione_pola)
|
||||
total_cost = 0
|
||||
i = 0
|
||||
while i < (self.liczba_pol - 1):
|
||||
# print(str(self.pathfinding_tractorless.pathfinding_tractorless(self.field,znalezione_pola,i)))
|
||||
total_cost = total_cost + self.pathfinding_tractorless.pathfinding_tractorless(self.field,znalezione_pola,i)
|
||||
# print(str(total_cost))
|
||||
i = i + 1
|
||||
# print("Koszt przejścia przez pola w zadanej kolejności: " + str(total_cost))
|
||||
# print("###################")
|
||||
return total_cost
|
||||
|
||||
def tworzenie_pokolenia(self,znalezione_pola,i):
|
||||
x = len(znalezione_pola) - 2
|
||||
wspolrzedne_shuffle = []
|
||||
while x > 0:
|
||||
wspolrzedne_shuffle.append(znalezione_pola[x])
|
||||
x = x - 1
|
||||
x = len(znalezione_pola) - 1
|
||||
lista_osobnikow = []
|
||||
while i > 0: #liczebność pierwszego pokolenia (domyślnie 10)
|
||||
nowy_osobnik = random.sample(wspolrzedne_shuffle, len(wspolrzedne_shuffle))
|
||||
nowy_osobnik.insert(0,0) #dodanie na początek listy 0, jako współrzenej startowej
|
||||
nowy_osobnik.insert(x,0) #dodanie na koniec listy 0, jako współrzenej końcowej
|
||||
lista_osobnikow.append(nowy_osobnik)
|
||||
i = i - 1
|
||||
# print("Lista osobników: " + str(lista_osobnikow))
|
||||
return lista_osobnikow
|
||||
|
||||
def ocena_przystosowania(self,pokolenia):
|
||||
suma_kosztow_tras = 0
|
||||
ile_osobnikow = 0
|
||||
koszty_tras_osobnikow = []
|
||||
y = 0
|
||||
pierwszy_koszt = self.koszt_przejazdu(pokolenia[y])
|
||||
najtanszy_osobnik = pokolenia[y]
|
||||
najnizszy_koszt = pierwszy_koszt
|
||||
najwyzszy_koszt = pierwszy_koszt
|
||||
for i in pokolenia:
|
||||
koszty_tras_osobnikow.append(self.koszt_przejazdu(i))
|
||||
suma_kosztow_tras = suma_kosztow_tras + self.koszt_przejazdu(i)
|
||||
ile_osobnikow = ile_osobnikow + 1
|
||||
if self.koszt_przejazdu(i) < najnizszy_koszt:
|
||||
najnizszy_koszt = self.koszt_przejazdu(i)
|
||||
najtanszy_osobnik = i
|
||||
if self.koszt_przejazdu(i) > najwyzszy_koszt:
|
||||
najwyzszy_koszt = self.koszt_przejazdu(i)
|
||||
# print("Najtansza trasa w danym pokoleniu: " + str(najnizszy_koszt))
|
||||
# print("Najdrozsza trasa w danym pokoleniu: " + str(najwyzszy_koszt))
|
||||
srednie_przystosowanie = suma_kosztow_tras/ile_osobnikow #parametr potrzebny do oceny przystosowania osobnikow
|
||||
przystosowanie_osobnikow = []
|
||||
sumaryczne_przystosowanie_osobnikow = 0
|
||||
l = 0
|
||||
for i in koszty_tras_osobnikow:
|
||||
przystosowanie_osobnikow.append(round(((srednie_przystosowanie/koszty_tras_osobnikow[l])*10),2))
|
||||
sumaryczne_przystosowanie_osobnikow += round(((srednie_przystosowanie/koszty_tras_osobnikow[l])*10),2)
|
||||
l = l + 1
|
||||
# print(str(round(sumaryczne_przystosowanie_osobnikow,2)))
|
||||
# print("Ocena przystosowania każdego z osobników: " + str(przystosowanie_osobnikow))
|
||||
# print("Koszty tras każdego z osobników: " + str(koszty_tras_osobnikow))
|
||||
# print("Średnie przystosowanie wszystkich osobników: " + str(srednie_przystosowanie))
|
||||
return(przystosowanie_osobnikow, najnizszy_koszt, najwyzszy_koszt, srednie_przystosowanie, najtanszy_osobnik)
|
||||
|
||||
def wybor_populacji_posredniej(self,pierwsze_pokolenie,przystosowanie_osobnikow):
|
||||
x = len(przystosowanie_osobnikow)
|
||||
tabela_ruletki = []
|
||||
populacja_posrednia = []
|
||||
i = 0
|
||||
przedzial = 0
|
||||
while x > 0: #stworzenie "koła ruletki" do selecji osobników populacji pośredniej
|
||||
przedzial = przedzial + przystosowanie_osobnikow[i]
|
||||
tabela_ruletki.append(round(przedzial,2))
|
||||
x = x - 1
|
||||
i = i + 1
|
||||
#print("Tabela ruletki do losowania z przedziałami dla każdego osobnika: " + str(tabela_ruletki))
|
||||
x = len(przystosowanie_osobnikow)/2 #losowanie połowy liczby osobników populacji początkowej do populacji pośredniej
|
||||
maks = tabela_ruletki[i-1]
|
||||
while x > 0:
|
||||
i = 0
|
||||
n = random.uniform(0, maks) #losowanie przedziału
|
||||
while n > tabela_ruletki[i]:
|
||||
i = i + 1
|
||||
populacja_posrednia.append(pierwsze_pokolenie[i])
|
||||
x = x - 1
|
||||
# print("Populacja pośrednia (rodziców): " + str(populacja_posrednia)) #populacja posrednia, z której zostanie utworzona populacja potomków
|
||||
return populacja_posrednia
|
||||
|
||||
def krzyzowanie(self,populacja_posrednia):
|
||||
populacja_po_krzyzowaniu = []
|
||||
x = len(populacja_posrednia) - 1
|
||||
while x > 0:
|
||||
rodzic_1 = populacja_posrednia[x]
|
||||
#print("Rodzic nr 1: " + str(rodzic_1))
|
||||
rodzic_2 = populacja_posrednia[x-1]
|
||||
#print("Rodzic nr 2: " + str(rodzic_2))
|
||||
dziecko_1 = []
|
||||
dziecko_2 = []
|
||||
czy_krzyzowac = random.randint(1,100) #losowanie czy krzyzowac rodzicow, czy nie (szanse 10%)
|
||||
if (czy_krzyzowac < 11) and (rodzic_1 != rodzic_2): #jesli krzyzowanie nastepuje
|
||||
miejsce_krzyzowania = random.randint(1,(len(populacja_posrednia[x])-3)) #wybor miejsca krzyzowania
|
||||
l = 0
|
||||
k = miejsce_krzyzowania
|
||||
while k >= 0: #dodawanie do dziecka pierwszej połowy z pierwszego rodzica
|
||||
dziecko_1.append(rodzic_1[l])
|
||||
l = l + 1
|
||||
k = k - 1
|
||||
k = miejsce_krzyzowania
|
||||
while k < (len(rodzic_1)-2): #dodawanie do dziecka drugiej połowy z drugiego rodzica
|
||||
for i in rodzic_2:
|
||||
if i not in dziecko_1:
|
||||
dziecko_1.append(i)
|
||||
k = k + 1
|
||||
l = 0
|
||||
k = miejsce_krzyzowania
|
||||
while k >= 0: #dodawanie do dziecka pierwszej połowy z pierwszego rodzica
|
||||
dziecko_2.append(rodzic_2[l])
|
||||
l = l + 1
|
||||
k = k - 1
|
||||
k = miejsce_krzyzowania
|
||||
while k < (len(rodzic_1)-2): #dodawanie do dziecka drugiej połowy z drugiego rodzica
|
||||
for i in rodzic_1:
|
||||
if i not in dziecko_2:
|
||||
dziecko_2.append(i)
|
||||
k = k + 1
|
||||
dziecko_1.append(0)
|
||||
dziecko_2.append(0)
|
||||
else: #jesli krzyzowanie nie nastepuje, wowczas potencjalni rodzice staja sie dziecmi
|
||||
dziecko_1 = rodzic_1
|
||||
dziecko_2 = rodzic_2
|
||||
populacja_po_krzyzowaniu.append(dziecko_1)
|
||||
populacja_po_krzyzowaniu.append(dziecko_2)
|
||||
# print("Dziecko nr 1: " + str(dziecko_1))
|
||||
# print("Dziecko nr 2: " + str(dziecko_2))
|
||||
x = x - 1
|
||||
|
||||
#ostatnie krzyżowanie, pomiędzy pierwszym a ostatnim rodzicem z listy osobnikow nalezacych do populacji posredniej
|
||||
|
||||
rodzic_1 = populacja_posrednia[0]
|
||||
#print("Rodzic nr 1: " + str(rodzic_1))
|
||||
rodzic_2 = populacja_posrednia[(len(populacja_posrednia)-1)]
|
||||
#print("Rodzic nr 2: " + str(rodzic_2))
|
||||
dziecko_1 = []
|
||||
dziecko_2 = []
|
||||
czy_krzyzowac = random.randint(1,100) #losowanie czy krzyzowac rodzicow, czy nie (szanse 10%)
|
||||
if (czy_krzyzowac < 11) and (rodzic_1 != rodzic_2): #jesli krzyzowanie nastepuje
|
||||
miejsce_krzyzowania = random.randint(1,(len(populacja_posrednia[x])-3)) #wybor miejsca krzyzowania
|
||||
l = 0
|
||||
k = miejsce_krzyzowania
|
||||
while k >= 0: #dodawanie do dziecka pierwszej połowy z pierwszego rodzica
|
||||
dziecko_1.append(rodzic_1[l])
|
||||
l = l + 1
|
||||
k = k - 1
|
||||
k = miejsce_krzyzowania
|
||||
while k < (len(rodzic_1)-2): #dodawanie do dziecka drugiej połowy z drugiego rodzica
|
||||
for i in rodzic_2:
|
||||
if i not in dziecko_1:
|
||||
dziecko_1.append(i)
|
||||
k = k + 1
|
||||
l = 0
|
||||
k = miejsce_krzyzowania
|
||||
while k >= 0: #dodawanie do dziecka pierwszej połowy z pierwszego rodzica
|
||||
dziecko_2.append(rodzic_2[l])
|
||||
l = l + 1
|
||||
k = k - 1
|
||||
k = miejsce_krzyzowania
|
||||
while k < (len(rodzic_1)-2): #dodawanie do dziecka drugiej połowy z drugiego rodzica
|
||||
for i in rodzic_1:
|
||||
if i not in dziecko_2:
|
||||
dziecko_2.append(i)
|
||||
k = k + 1
|
||||
dziecko_1.append(0)
|
||||
dziecko_2.append(0)
|
||||
else: #jesli krzyzowanie nie nastepuje, wowczas potencjalni rodzice staja sie dziecmi
|
||||
dziecko_1 = rodzic_1
|
||||
dziecko_2 = rodzic_2
|
||||
populacja_po_krzyzowaniu.append(dziecko_1)
|
||||
populacja_po_krzyzowaniu.append(dziecko_2)
|
||||
# print("Dziecko nr 1: " + str(dziecko_1))
|
||||
# print("Dziecko nr 2: " + str(dziecko_2))
|
||||
return populacja_po_krzyzowaniu
|
||||
|
||||
def mutacja(self,populacja_po_krzyzowaniu):
|
||||
k = len(populacja_po_krzyzowaniu) - 1
|
||||
while k >= 0:
|
||||
czy_mutacja = random.randint(0,100)
|
||||
if czy_mutacja < 3: # Szanse 2%
|
||||
kogo_mutujemy = populacja_po_krzyzowaniu[k]
|
||||
populacja_po_krzyzowaniu.remove(kogo_mutujemy)
|
||||
l = len(kogo_mutujemy) - 1
|
||||
# print("Osobnik przed mutacją: " + str(kogo_mutujemy))
|
||||
x = random.randint(1,l)
|
||||
y = random.randint(1,l)
|
||||
while x == y:
|
||||
y = random.randint(1,l)
|
||||
zamiennik = kogo_mutujemy[x]
|
||||
kogo_mutujemy[x] = kogo_mutujemy[y]
|
||||
kogo_mutujemy[y] = zamiennik
|
||||
# print("Osobnik po mutacji: " + str(kogo_mutujemy))
|
||||
populacja_po_krzyzowaniu.insert(k,kogo_mutujemy)
|
||||
else:
|
||||
pass
|
||||
k = k - 1
|
||||
populacja_po_mutacji = populacja_po_krzyzowaniu
|
||||
# print("Populacja po mutacji: " + str(populacja_po_mutacji))
|
||||
return populacja_po_mutacji
|
||||
|
||||
def optymalizacja(self,populacja_po_mutacji,znalezione_pola): #polega na eliminacji powtarzających się tras
|
||||
populacja_po_optymalizacji = populacja_po_mutacji
|
||||
i = len(populacja_po_mutacji)
|
||||
l = 1
|
||||
while l < i:
|
||||
k = l
|
||||
while k >= 0:
|
||||
if populacja_po_mutacji[l] == populacja_po_mutacji[k-1]:
|
||||
populacja_po_optymalizacji.remove(populacja_po_mutacji[k-1])
|
||||
x = len(znalezione_pola) - 2
|
||||
wspolrzedne_shuffle = []
|
||||
while x > 0:
|
||||
wspolrzedne_shuffle.append(znalezione_pola[x])
|
||||
x = x - 1
|
||||
x = len(znalezione_pola) - 1
|
||||
nowy_osobnik = random.sample(wspolrzedne_shuffle, len(wspolrzedne_shuffle))
|
||||
nowy_osobnik.insert(0,0) #dodanie na początek listy 0, jako współrzenej startowej
|
||||
nowy_osobnik.insert(x,0)
|
||||
populacja_po_optymalizacji.append(nowy_osobnik)
|
||||
# print("Nastąpiła optymalizacja")
|
||||
else:
|
||||
pass
|
||||
k = k - 1
|
||||
l = l + 1
|
||||
# print("Populacja po optymalizacji: " + str(populacja_po_optymalizacji))
|
||||
return populacja_po_optymalizacji
|
||||
|
||||
def algorytm_genetyczny(self):
|
||||
self.znalezione_pola = self.wspolrzedne()
|
||||
self.koszt_trasy = self.koszt_przejazdu(self.znalezione_pola)
|
||||
# Utworzenie pokolenia
|
||||
self.pierwsze_pokolenie = self.tworzenie_pokolenia(self.znalezione_pola,10)
|
||||
# Funkcja przystosowania
|
||||
self.przystosowanie, self.najnizszy_koszt, self.najwyzszy_koszt, self.srednie_przystosowanie_pierwszego_pokolenia, self.najtanszy_osobnik = self.ocena_przystosowania(self.pierwsze_pokolenie)
|
||||
# Populacja pośrednia wybrana metodą ruletki
|
||||
self.populacja_posrednia = self.wybor_populacji_posredniej(self.pierwsze_pokolenie, self.przystosowanie)
|
||||
# Krzyżowanie populacji pośredniej
|
||||
self.populacja_po_krzyzowaniu = self.krzyzowanie(self.populacja_posrednia)
|
||||
# Mutacja populacji pośredniej
|
||||
self.populacja_po_mutacji = self.mutacja(self.populacja_po_krzyzowaniu)
|
||||
# Optymalizacja populacji pośredniej
|
||||
self.populacja_po_optymalizacji = self.optymalizacja(self.populacja_po_mutacji,self.znalezione_pola)
|
||||
self.maks_koszt = self.najwyzszy_koszt
|
||||
self.min_koszt = self.najnizszy_koszt
|
||||
self.najtansza_trasa = self.najtanszy_osobnik
|
||||
i = 2
|
||||
self.ktore_pokolenie = 1
|
||||
while i < 41:
|
||||
print(" ")
|
||||
print("*********************")
|
||||
print("Pokolenie " + str(i))
|
||||
print("*********************")
|
||||
print(" ")
|
||||
# Funkcja przystosowania
|
||||
self.przystosowanie, self.najnizszy_koszt, self.najwyzszy_koszt, self.srednie_przystosowanie, self.najtanszy_osobnik = self.ocena_przystosowania(self.populacja_po_optymalizacji)
|
||||
if self.najwyzszy_koszt > self.maks_koszt:
|
||||
self.maks_koszt = self.najwyzszy_koszt
|
||||
if self.najnizszy_koszt < self.min_koszt:
|
||||
self.min_koszt = self.najnizszy_koszt
|
||||
self.najtansza_trasa = self.najtanszy_osobnik
|
||||
self.ktore_pokolenie = i
|
||||
print("Nowy najnizszy koszt: " + str(self.min_koszt))
|
||||
print("Nowa najtansza trasa: " + str(self.najtansza_trasa))
|
||||
# Populacja pośrednia wybrana metodą ruletki
|
||||
self.populacja_posrednia = self.wybor_populacji_posredniej(self.populacja_po_mutacji, self.przystosowanie)
|
||||
# Krzyżowanie populacji pośredniej
|
||||
self.populacja_po_krzyzowaniu = self.krzyzowanie(self.populacja_posrednia)
|
||||
# Mutacja populacji pośredniej
|
||||
self.populacja_po_mutacji = self.mutacja(self.populacja_po_krzyzowaniu)
|
||||
# Optymalizacja populacji pośredniej
|
||||
self.populacja_po_optymalizacji = self.optymalizacja(self.populacja_po_mutacji,self.znalezione_pola)
|
||||
i = i + 1
|
||||
if (self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia) < (0.69):
|
||||
print("Zakończono wykonywanie algorytmu po " + str(i) + " pokoleniach")
|
||||
break
|
||||
print("Średnie przygotowanie pierwszego pokolenia: " + str(self.srednie_przystosowanie_pierwszego_pokolenia))
|
||||
print("Stosunek poprawienia kosztu trasy względem początku: " + str((self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia)))
|
||||
print("Najnizszy znaleziony koszt to " + str(self.min_koszt) + " znaleziony w pokoleniu nr " + str(self.ktore_pokolenie))
|
||||
print("Najtansza znaleziona trasa to " + str(self.najtansza_trasa))
|
||||
# print("Najwyzszy znaleziony koszt: " + str(self.maks_koszt))
|
||||
|
||||
def wykonanie_trasy(self):
|
||||
i = len(self.najtansza_trasa) - 1
|
||||
l = 0
|
||||
while l < i:
|
||||
self.pathfinding_tractor.pathfinding_tractor(self.field, self.traktor, self.ui, self.najtansza_trasa, l)
|
||||
l = l + 1
|
||||
|
||||
def main(self):
|
||||
pass
|
||||
self.algorytm_genetyczny()
|
||||
self.wykonanie_trasy()
|
146
geneticalgorithm.md
Normal file
146
geneticalgorithm.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Sztuczna Inteligencja - Raport 3
|
||||
|
||||
**Członkowie zespołu:** Marcin Kwapisz, Kamila Matysiak, Piotr Rychlicki, Justyna Zarzycka
|
||||
|
||||
**Temat podprojektu:** Szukanie najkrótszej trasy za pomocą algorytmu genetycznego
|
||||
|
||||
**Autor podprojektu:** Piotr Rychlicki
|
||||
|
||||
|
||||
### Algorytm genetyczny
|
||||
|
||||
**Algorytm genetyczny** jest jednym z algorytmów służących do optymalizacji rozwiązania pewnego problemu. Sprawdza się on najlepiej wówczas, gdy nie jest znany lub ściśle określony prosty sposób rozwiązania problemu, a także gdy stosunkowo trudno otrzymać najlepsze rozwiązanie, lecz gdy łatwo można ocenić jakość otrzymanego rozwiązania i je zaakceptować.
|
||||
Ogólna zasada działania algorytmu (który w kolejnej części zostanie dokładniej omówiony) polega na wygenerowaniu zbioru potencjalnych rozwiązań, które następnie podlegają ocenie. Spośród nich wybierane są te najlepsze, by potem za pomocą operacji genetycznych stworzyć nową grupę, które ponownie przejdzie przez ten cykl. Wynikiem działania takiego algorytmu powinno być otrzymanie optymalnego rozwiązania, spełniającego warunek zakończenia działania algorytmu.
|
||||
|
||||
### Problem
|
||||
|
||||
W tym podprojekcie algorytm genetyczny został użyty do szukania optymalnej trasy przejazdu agenta (dalej zwanego traktorem) przez wszystkie pola danego typu począwszy i skończywszy na polu startowym (o współrzędnych **(0,0)**).
|
||||
Po uruchomieniu programu, w swoim środowisku traktor zastaje określoną konfigurację pól, z których każde (pole nienawodnione, zachwaszczone, niezasadzone, gotowe do zbiorów) występuje określoną liczbę razy. Naturalnym jest, by traktor wykonując określone zadanie (np. zebranie plonu z pola), przemieszczając się między różnymi polami, zrobił to jak najmniejszym kosztem. Stąd też idea i potrzeba właśnie takiego zastosowania algorytmu genetycznego.
|
||||
Ten problem, nazywany problemem komiwojażera, należy do problemów NP-zupełnych, czyli takich, które pomimo prostego ich zdefiniowania, są bardzo czasochłonne do rozwiązania. Dla liczby pól z warzywami, na których testowałem swój algorytm i których jest w środowisku testowym 13, liczba potencjalnych rozwiązań wynosi 12!, czyli 479001600 możliwych tras.
|
||||
Algorytm genetyczny, z powodów wymienionych na początku raportu, jako rodzaj algorytmu heurystycznego (oferującego rozwiązanie przybliżone do idealnego), sprawdza się przy problemie komiwojażera bardzo dobrze.
|
||||
|
||||
### Pojęcia
|
||||
|
||||
* Osobnik - pojedyncze rozwiązanie problemu
|
||||
* Populacja - zbiór osobników, którymi operuje algorytm
|
||||
* Pokolenie - populacja przetwarzana w jednej iteracji algorytmu
|
||||
* Genotyp - to informacja dziedziczna, którą zawiera pojedynczy osobnik
|
||||
* Gen - to najmniejszy element informacji zawarty w genotypie
|
||||
|
||||
### Algorytm
|
||||
|
||||
```
|
||||
def main(self):
|
||||
self.algorytm_genetyczny()
|
||||
self.wykonanie_trasy()
|
||||
```
|
||||
|
||||
Cały algorytm jest wykonywany w funckji self.algorytm_genetyczny, natomiast funkcja self.wykonanie_trasy odpowiada integrację podprojektu w projekcie, realizując wyznaczoną przez algorytm trasę wraz z odpowiednią akcją agenta.
|
||||
|
||||
```
|
||||
self.znalezione_pola = self.wspolrzedne()
|
||||
```
|
||||
|
||||
Pierwszy krokiem całego programu jest wyznaczenie współrzędnych pól, przez które będziemy chcieli przejść w najkrótszej trasie. Współrzedne te są reprezentowane macierzą zawierającą liczby dwucyfrową [0,99]: np. pole 32 oznacza współrzędne [2,3].
|
||||
|
||||
```
|
||||
self.koszt_trasy = self.koszt_przejazdu(self.znalezione_pola)
|
||||
```
|
||||
|
||||
Następnie wyznaczany jest pierwotny koszt trasy wiodącej przez wszystkie pola danego typu w porządku rosnącym (z góry na dół i z prawa na lewo).
|
||||
|
||||
Poniżej przestawiony został już właściwy algorytm:
|
||||
|
||||
```
|
||||
# Utworzenie pokolenia
|
||||
self.pierwsze_pokolenie = self.tworzenie_pokolenia(self.znalezione_pola,10)
|
||||
|
||||
# Funkcja przystosowania
|
||||
self.przystosowanie, self.najnizszy_koszt, self.najwyzszy_koszt, self.srednie_przystosowanie_pierwszego_pokolenia, self.najtanszy_osobnik = self.ocena_przystosowania(self.pierwsze_pokolenie)
|
||||
|
||||
# Populacja pośrednia wybrana metodą ruletki
|
||||
self.populacja_posrednia = self.wybor_populacji_posredniej(self.pierwsze_pokolenie, self.przystosowanie)
|
||||
|
||||
# Krzyżowanie populacji pośredniej
|
||||
self.populacja_po_krzyzowaniu = self.krzyzowanie(self.populacja_posrednia)
|
||||
|
||||
# Mutacja populacji pośredniej
|
||||
self.populacja_po_mutacji = self.mutacja(self.populacja_po_krzyzowaniu)
|
||||
|
||||
# Optymalizacja populacji pośredniej
|
||||
self.populacja_po_optymalizacji = self.optymalizacja(self.populacja_po_mutacji,self.znalezione_pola)
|
||||
|
||||
if (self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia) < (0.69):
|
||||
print("Zakończono wykonywanie algorytmu po " + str(i) + " pokoleniach")
|
||||
break
|
||||
```
|
||||
|
||||
Pierwszym krokiem każdego algorytmu genetycznego jest utworzenie pierwszego pokolenia osobników. Każde pokolenie w podprojekcie liczy sobie 10 osobników, z których każdy jest permutacją bez powtórzeń współrzędnych zawartych w tablicy self.znalezione_pola wraz z dodaną na koniec współrzędną 0, jako polem, do którego agent ma powrócić na końcu.
|
||||
Następnie wykonywana jest tzw. funkcja przystosowania, której zadaniem jest ocenić jakość każdego osobnika (czyli potencjalnego rozwiazania):
|
||||
|
||||
```
|
||||
przystosowanie_osobnikow.append(round(((srednie_przystosowanie/koszty_tras_osobnikow[l])*10),2))
|
||||
```
|
||||
|
||||
Przystosowanie każdego z osobników przechowywane jest w tablicy przystosowanie_osobnikow i wyliczane za pomocą ilorazu średniego przystosowania wszystkich osobników w danym pokoleniu i kosztu konkretnego osobnika. Im lepsza jakość osobnika (czyli im niższy koszt proponowanej przez niego trasy), tym wyższy wynik funkcji przystosowania.
|
||||
Po ocenie przystosowania dokonuje się wyboru osobników do populacji pośredniej, z której otrzymamy potomków osobników obecnego pokolenia. Tutaj zdecydowano się na wybór tzw. metodą ruletki, w której każdemu osobnikowi przydziela się zakres należący do przedziału [1,100], proporcjonalny do jego przystosowania. Im większe przystosowanie, tym większy przedział zostanie danemu osobnikowy przydzielony. Następnie losowane jest pięć liczb, właśnie z przedziału [1,100] i do populacji pośredniej trafiają osobnicy, w których zakresach mieszczą się wylosowane liczby. Grupa takich pięciu osobników (do której osobnik może trafić więcej niż raz) staje się populacją pośrednią, która przystąpi do operacji genetycznych w celu poprawienia genotypu.
|
||||
Tymi operacjami genetycznymi są krzyżowanie, mutacja i dodatkowa optymalizacja.
|
||||
|
||||
```
|
||||
czy_krzyzowac = random.randint(1,100)
|
||||
if (czy_krzyzowac < 11) and (rodzic_1 != rodzic_2):
|
||||
```
|
||||
|
||||
Prawdopodobieństwo krzyżowania dwóch kolejnych osobników populacji pośredniej wyznaczono na 10%. Jeśli krzyżowanie nie nastąpi lub osobnicy są identyczni, wówczas przechodzą oni do kolejnego etapu w niezmienionej postaci.
|
||||
Jeśli krzyżowanie dojdzie do skutki, wówczas dla obu osobników wykonuje się następującą operację: wybiera się losowe miejsce krzyżowania w tablicy zawierającej genotyp, sprzed którego wszystkie geny przechodzą w niezmienionej kolejności do potomka. Następnie brakujące geny potomka uzupełnia się genami drugiego osobnika, których jeszcze nie zawiera potomek (tak by geny, czyli współrzędne, się nie powtórzyły w genotypie)
|
||||
|
||||
```
|
||||
k = len(populacja_po_krzyzowaniu) - 1
|
||||
while k >= 0:
|
||||
czy_mutacja = random.randint(0,100)
|
||||
if czy_mutacja < 3:
|
||||
kogo_mutujemy = populacja_po_krzyzowaniu[k]
|
||||
```
|
||||
|
||||
Jeśli chodzi o mutację, tu prawdopodobieństwo jej zajścia dla każdego osobnika ustalono na 2%. Mutacja polega na wylosowaniu dwóch różnych genów osobnika i zamienieniu ich miejscami.
|
||||
Na koniec procesu tworzenia potomków dochodzi do opcjonalnej operacji optymalizacji. Jest to nieobowiązkowa funkcja zaimplemetowana w celu dywersyfikacji potomków. Polega ona na usunięciu z populacji pośredniej potwórzeń wśród osobników i uzupełnienie braku nowym, losowym osobnikiem. Po optymalizacji, pokolenie nadal będzie liczyło 10 osobników, lecz każdy z nich z pewnością będzie różny od pozostałych.
|
||||
Te 10 osobników storzy nową populację potomków. Tę populację poddaje się ocenie, czyli sprawdzeniu warunku końca działania algorytmu.
|
||||
|
||||
```
|
||||
if (self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia) < (0.69):
|
||||
print("Zakończono wykonywanie algorytmu po " + str(i) + " pokoleniach")
|
||||
break
|
||||
```
|
||||
|
||||
Jeśli zostanie spełniony warunek znacznego poprawienia kosztu najtańszej trasy najnowszego pokolenia względem średniej długości tras pierwszego pokolenia, wówczas można uznać, że znalezione rozwiazanie jest zadowalające i algorytm zostaje przerwany.
|
||||
Jeśli jednak warunek nie jest spełniony, wtedy algorytm wykonuje się na nowo, (zaczynając od etapu oceny przystosowania nowego pokolenia), aż do momentu przejścia przez x zadanych pokoleń (w mojej iplementacji jest to 40 pokoleń)
|
||||
|
||||
```
|
||||
def wykonanie_trasy(self):
|
||||
i = len(self.najtansza_trasa) - 1
|
||||
l = 0
|
||||
while l < i:
|
||||
self.pathfinding_tractor.pathfinding_tractor(self.field, self.traktor, self.ui, self.najtansza_trasa, l)
|
||||
l = l + 1
|
||||
```
|
||||
|
||||
Ostatnim elementem mojego programu jest ,,wcielenie w życie" znalezionej trasy za pomocą powyższej funkcji wykonanie_trasy. Taką trasę podaje się funkcji pathfinding_tractor, która następnie egzekwuje agentem wykonanie konkretnej akcji na polach danego typu w zadanej kolejności.
|
||||
|
||||
|
||||
### Dobrane parametry
|
||||
|
||||
Na sprawność działania algorytmu i jakość zwracanych rozwiązań wpływ mają dobrane parametry. Do najważniejszych należą:
|
||||
|
||||
* Liczba pokoleń
|
||||
* Liczebność pokolenia
|
||||
* Prawdopodobieństwo zajścia krzyzowania
|
||||
* Prawdopodobieństwo zajścia mutacji
|
||||
* Warunek zatrzymania algorytmu
|
||||
|
||||
Na początek ustalono prawdopodobieństwa zajścia operacji genetycznych:
|
||||
Prawdopodobieństwo krzyżowania to 10%. Zazwyczaj jest ono większe, lecz dla klasycznego wariantu ułożenia genów, gdzie geny nie zmieniają pozycji w genotypie, lecz wartość. Tutaj użyto permutacyjnego wariantu ułożenia genów, a zatem krzyżowanie nie prowadzi do satysfakcjonujących wyników z dużą częstotliwością.
|
||||
Prawdopodobieństwo mutacji dla każdego osobnika to 2%, ponieważ przy permutacjnym ułożeniu genów nie mamy pewności co do korzystności takiego zabiegu. Ma on na celu jedynie dywersyfikację genotypu w celu hipotetycznego znalezienia lepszych rozwiazań.
|
||||
|
||||
Liczba pokoleń - tu jej wartość ustawiono na 40. Wartość tę otrzymano wywołując algorytm 10 razy dla każdej z trzech różnych wartości liczby pokoleń: 20, 50 oraz 100 i sprawdzaniu, w którym pokoleniu otrzymano najkrótszą trasę. Okazało się, że dla 50 i 100 pokoleń wartość ta była zbliżona i wynosiła odpowiednio 34 i 27. Zdecydowano zatem o doborze 40 pokoleń.
|
||||
Liczebność pokolenia - ta została dobrana losowo na wartość 10, choć oczywiście nie ostatecznie. Jednak rezultaty przy tej konkretnej wartości okazały się na tyle zadowalające, że postanowiono jej nie zmieniać.
|
||||
Warunek zatrzymania algorytmu - ten ustalono na 69% wartości średniej długości wszystkich tras z pierwszego pokolenia. Wybrano taką wartość ponownie, na podstawie wywoływanych wcześniej algorytmów dla 20, 50, 100 i ostatecznych 40 pokoleń. Za każdym razem iloraz najlepszego rozwiazania ze średnim kosztem tras pierwszego pokolenia oscylował między 0,67 a 0,7.
|
82
pathfinding_tractor.py
Normal file
82
pathfinding_tractor.py
Normal file
@ -0,0 +1,82 @@
|
||||
from queue import PriorityQueue
|
||||
import time
|
||||
|
||||
class pathfinding_tractor():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def heuristic(self,a, b):
|
||||
(x1, y1) = a
|
||||
(x2, y2) = b
|
||||
return abs(x1 - x2) + abs(y1 - y2)
|
||||
|
||||
def points(self, point):
|
||||
self.point = []
|
||||
for i in [[point[0],point[1]-1],[point[0]-1,point[1]],[point[0],point[1]+1],[point[0]+1,point[1]]]:
|
||||
if i[0] in [-1,10] or i[1] in [-1,10]:
|
||||
pass
|
||||
else:
|
||||
self.point.append(i)
|
||||
return self.point
|
||||
|
||||
def pathfinding_tractor(self,field,traktor,ui,pola_buraczane,i):
|
||||
self.pola = pola_buraczane
|
||||
self.ui = ui
|
||||
self.traktor = traktor
|
||||
self.start_position = [int(self.pola[i])%10,int(self.pola[i])//10]
|
||||
# print(str(self.start_position))
|
||||
self.field = field
|
||||
self.end_point = [int(self.pola[i+1])%10,int(self.pola[i+1])//10]
|
||||
# print(str(self.end_point))
|
||||
|
||||
if self.start_position == self.end_point:
|
||||
self.traktor.work()
|
||||
else:
|
||||
self.route = self.a_star(self.start_position,self.end_point)
|
||||
for i in self.route[::-1]:
|
||||
self.poz = self.traktor.get_poz()
|
||||
if i[1]> self.poz[1]:
|
||||
self.traktor.move_down()
|
||||
elif i[1]< self.poz[1]:
|
||||
self.traktor.move_up()
|
||||
elif i[0]> self.poz[0]:
|
||||
self.traktor.move_right()
|
||||
elif i[0]< self.poz[0]:
|
||||
self.traktor.move_left()
|
||||
self.ui.update()
|
||||
time.sleep(0.1)
|
||||
self.traktor.work()
|
||||
|
||||
|
||||
def a_star(self,start,end):
|
||||
self.a_queue = PriorityQueue()
|
||||
self.a_queue.put(start,0)
|
||||
self.cost = {tuple(start): 0}
|
||||
self.path_from = {tuple(start): None}
|
||||
self.finall_path = [tuple(end)]
|
||||
self.found = 0
|
||||
while not self.a_queue.empty():
|
||||
self.current = tuple(self.a_queue.get())
|
||||
|
||||
if self.current == tuple(end):
|
||||
break
|
||||
|
||||
for self.next in self.points(self.current):
|
||||
self.new_cost = self.cost[tuple(self.current)] + self.field.get_value(self.next)
|
||||
if tuple(self.next) not in self.cost or self.new_cost < self.cost[tuple(self.next)]:
|
||||
self.cost[tuple(self.next)] = self.new_cost
|
||||
self.priority = self.new_cost + self.heuristic(end, self.next)
|
||||
self.a_queue.put(self.next,self.priority)
|
||||
self.path_from[tuple(self.next)] = self.current
|
||||
if self.next == end:
|
||||
self.found = 1
|
||||
break
|
||||
if self.found:
|
||||
break
|
||||
|
||||
self.pth = self.path_from[tuple(end)]
|
||||
while not self.pth == tuple(start):
|
||||
self.finall_path.append(self.pth)
|
||||
self.pth = self.path_from[self.pth]
|
||||
|
||||
return self.finall_path
|
68
pathfinding_tractorless.py
Normal file
68
pathfinding_tractorless.py
Normal file
@ -0,0 +1,68 @@
|
||||
from queue import PriorityQueue
|
||||
import time
|
||||
|
||||
class pathfinding_tractorless():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def heuristic(self,a, b):
|
||||
(x1, y1) = a
|
||||
(x2, y2) = b
|
||||
return abs(x1 - x2) + abs(y1 - y2)
|
||||
|
||||
def points(self, point):
|
||||
self.point = []
|
||||
for i in [[point[0],point[1]-1],[point[0]-1,point[1]],[point[0],point[1]+1],[point[0]+1,point[1]]]:
|
||||
if i[0] in [-1,10] or i[1] in [-1,10]:
|
||||
pass
|
||||
else:
|
||||
self.point.append(i)
|
||||
return self.point
|
||||
|
||||
def pathfinding_tractorless(self,field,pola_buraczane,i):
|
||||
self.pola = pola_buraczane
|
||||
self.start_position = [int(self.pola[i])%10,int(self.pola[i])//10]
|
||||
# print(str(self.start_position))
|
||||
self.field = field
|
||||
self.end_point = [int(self.pola[i+1])%10,int(self.pola[i+1])//10]
|
||||
# print(str(self.end_point))
|
||||
|
||||
if self.start_position == self.end_point:
|
||||
return 0
|
||||
else:
|
||||
self.route = self.a_star(self.start_position,self.end_point)
|
||||
return self.route
|
||||
|
||||
|
||||
def a_star(self,start,end):
|
||||
self.a_queue = PriorityQueue()
|
||||
self.a_queue.put(start,0)
|
||||
self.cost = {tuple(start): 0}
|
||||
self.path_from = {tuple(start): None}
|
||||
self.finall_path = [tuple(end)]
|
||||
self.found = 0
|
||||
while not self.a_queue.empty():
|
||||
self.current = tuple(self.a_queue.get())
|
||||
|
||||
if self.current == tuple(end):
|
||||
break
|
||||
|
||||
for self.next in self.points(self.current):
|
||||
self.new_cost = self.cost[tuple(self.current)] + self.field.get_value(self.next)
|
||||
if tuple(self.next) not in self.cost or self.new_cost < self.cost[tuple(self.next)]:
|
||||
self.cost[tuple(self.next)] = self.new_cost
|
||||
self.priority = self.new_cost + self.heuristic(end, self.next)
|
||||
self.a_queue.put(self.next,self.priority)
|
||||
self.path_from[tuple(self.next)] = self.current
|
||||
if self.next == end:
|
||||
self.found = 1
|
||||
break
|
||||
if self.found:
|
||||
break
|
||||
|
||||
self.pth = self.path_from[tuple(end)]
|
||||
while not self.pth == tuple(start):
|
||||
self.finall_path.append(self.pth)
|
||||
self.pth = self.path_from[self.pth]
|
||||
|
||||
return self.new_cost
|
Loading…
Reference in New Issue
Block a user