Merge remote-tracking branch 'origin/master'

This commit is contained in:
Marcin Kwapisz 2020-05-13 11:56:38 +02:00
commit 93b16795b9
4 changed files with 615 additions and 1 deletions

View File

@ -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
View 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
View 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

View 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