## Raport z podprojektu genetic_algorithm #### Autorka - Magdalena Biadała Program wykorzystuje algorytm genetyczny w celu znalezienia najlepszego dopasowania paczek do regałów, przy czym: - każda paczka musi zostać umieszczona na jakimś regale - waga wszystkich paczek umieszczoanych na regale nie może przekroczyć pojemności regału - paczki umieszczane są na regałach których parametry, takie jak temperatura czy wilgotność powietrza pozwalają na bezpieczne jej przechowywanie, bez zniszczenia jej zawartości. Algorytm jako struktury danych przyjmuje 2 listy: Listę *packs* w której indeksy oznaczają numery paczek, a wartości oznaczają wagę poszczególnych paczek. Na przykład: lista [2,6,2,5] to struktura, gdzie: - paczka o nr 0 ma wagę 2 - paczka o nr 1 ma wagę 6 - paczka o nr 2 ma wagę 2 - paczka o nr 3 ma wagę 5 Listę *racks* w której indeksy oznaczają numery regałów, a wartości oznaczają pojemność poszczególnych regałów. Na przykład: lista [15,13,20,16] to struktura, gdzie: - regał o nr 0 ma pojemność 15 - regał o nr 1 ma pojemność 13 - regał o nr 2 ma pojemność 20 - regał o nr 3 ma pojemność 16 #### Program zawiera kilka ważnych funkcji: ```python def first_gen(): first_generation = [] for individual in range(generation_size): individual = [] for pack in range(number_of_packages): r = random.randint(0,number_of_racks-1) individual.append(r) first_generation.append(individual) return first_generation ``` >funkcja *first_gen()* tworzy pierwsze pokolenie osobników; ```python def evaluation(individual): rest_of_capacity = racks.copy() for i in range(number_of_packages): rest_of_capacity[individual[i]] -= packages[i] fitness = 0 for i in range(number_of_racks): if rest_of_capacity[i] < 0: fitness += rest_of_capacity[i] elif rest_of_capacity[i] == 0: fitness += amount_of_promotion return fitness ``` > funkcja *evaluation()* ocenia wybranego osobnika pod względem tego, czy: - regały nie są przepełnione - coś. >Dodatkowo delikatnie promuje ona osobniki, króre wykorzystują pojemność regałów w całości, a na koniec zwraca parametr *fitness*. ```python def roulette(generation): evaluations = [] for i in range(generation_size): individual_fitness = evaluation(generation[i]) evaluations.append(individual_fitness) maximum = min(evaluations) normalized = [x+(-1*maximum)+1 for x in evaluations] sum_of_normalized = sum(normalized) roulette_tab = [x/sum_of_normalized for x in normalized] for i in range(1,generation_size-1): roulette_tab[i] += roulette_tab[i-1] roulette_tab[generation_size-1] = 1 survivors = [] for individual in range(generation_size): random_number = random.random() interval_number = 0 while random_number > roulette_tab[interval_number]: interval_number += 1 survivors.append(generation[interval_number]) return survivors ``` > funkcja *roulette()*: - liczy parametr *fitness* (oznaczający dopasowanie) dla każdego osobnika w pokoleniu - tworzy tablicę z przedziałami, przy czym im większy *fitness* osobnika tym większy jego przedział, a tym samym szansa że zostanie wylosowany - losuje liczby z zakresu od 0 do 1 (liczb jest tyle ile osobników w pokoleniu) -zwraca tych osobników, dla krótych wylosoana liczba wpadła w odpowiadający im przedział. ```python def crossover(individual1, individual2): cut = random.randint(1,number_of_packages-1) new1 = individual1[:cut] new2 = individual2[:cut] new1 = new1 + individual2[cut:] new2 = new2 + individual1[cut:] return new1, new2 ``` >funkcja *crossover()* zajmuje się krzyżowaniem osobników. ```python def mutation(individual): locus = random.randint(0,number_of_packages-1) individual[locus] = random.randint(0,number_of_racks-1) return individual ``` >funkcja *mutation()* dokonuje mutacji osobnika. #### Inicjalizacja pierwszego pokolenia Pierwsze pokolenie jest tworzone za pomocą funkcji *first_gen()*. Osobnikiem jest lista której indeksy oznaczają numery paczek, a wartości oznaczają numery regałów. Na przykład: osobnik [3,0,5,5] to przyporządkowanie, gdzie: - paczka nr 0 leży na regale nr 3 - paczka nr 1 leży na regale nr 0 - paczka nr 2 leży na regale nr 5 - paczka nr 3 leży na regale nr 5. Dla każdego osobnika w pierwszym pokoleniu liczony jest parametr *fitness*. #### Główna pętla programu Dla każdego pokolenia wykonywane są kolejno funkcje: - roulette() >pozostawia ona tylko część osobników w pokoleniu, część z nich zostaje zmultiplikowana - crossover() >dla pokolenia które pozostało po ruletce - mutation() > ta funkcja wykonywana jest tylko z pewnym prawdopodobieństem określonym w zmiennej *mutation_prob*. - evaluation() >dla pokolenia które pozostawiły po sobie poprzednie funkcje znalezione zostaje maksimum (największa wartość *fitness*). #### Wynik działania algorytmu Algorytm zwraca osobnika (przyporządkowanie) dla którego wartość fitness była największa. #### Przeprowadzone testy W celu rozeznania i dobrania wstępnych wartości parametrów: - *mutation_prob* (prawdopodobieństwo mutacji), - *generation_size* (wielkość pojedynczego pokolenia), - *number_of_generations* (liczba pokoleń), został przeprowadzony test. Wykonywał on 200 razy algorytm genetyczny dla wybranych parametrów, a następnie liczył średnią i medianę maksymalnej wartości *fitness* znalezionej w każdej z prób. W poniższej tabeli oprócz średniej i mediany można także odczytać czas działania algorytmu. ![Tabela wyników](wyniki_testu.PNG) - *amount_of_promotion* (wartość jaka zostaje dodana do *fitness* osobnika, jeśli wykorzysta jakiś regał w pełni)