Mudanças entre as edições de "Genéticos: Problema da Mochila em Python"

De Aulas
Linha 45: Linha 45:
  
  
# Lê o arquivo de configuração com a informação dos
+
# ITEM CLASS ---------------------------------------------
# itens disponíveis, preço e pêso
+
# representa os itens do arquivo config.json
def load():
+
class Item:
    config = open('config.json')
+
    def __init__(self, name, value, weight):
    data = json.load(config)
+
        self.name = name        # nome do produto
    config.close()
+
        self.value = value      # valor
    return data
+
        self.weight = weight    # peso
  
  
def create_population():
+
# DATA STATIC CLASS ------------------------------------
    global data, population
+
# Lê as configurações do arquivo config.json e carrega
    population = []
+
# nessa classe estática para ser usada em todo o programa
    for p in range(population_size):
+
class Data:
        element = []
+
    size = 0       # quantidade de itens
        for i in range(len(data["itens"])):
+
    capacity = 0   # capacidade da mochila
            # 1 se o elemento está na mochila e 0 se ele está ausente
+
    items = []     # vetor contendo os itens
            element = element + [randint(0, 1)]
 
        element = element + [compute_fitness(element)]  # aptidão
 
        element = element + [0, 0# aptidão acumulada e se foi selecionado
 
        population = population + [element]
 
    # return population
 
  
 +
    @staticmethod
 +
    def load():
 +
        # abre o arquivo e converte para um objeto json
 +
        file = open("config.json")
 +
        data = json.load(file)
 +
        file.close()
 +
        # coloca as informações do objeto json na classe
 +
        # Data pra facilitar o acesso aos dados
 +
        Data.capacity = int(data["capacity"])
 +
        for i in range(len(data["items"])):
 +
            name = data["items"][i]["name"]
 +
            value = int(data["items"][i]["value"])
 +
            weight = int(data["items"][i]["weight"])
 +
            Data.items.append(Item(name, value, weight))
 +
            Data.size += 1
  
def unselect_all():
 
    global data, population
 
    for i in range(len(population)):
 
        population[i][len(data["itens"]) + 2] = 0
 
  
def clone_element(element):
+
# INDVIDUAL CLASS -------------------------------------------
    clone = []
+
# Cada instância dessa classe representa cada indivíduo da
    for i in range(len(element)):
+
# população do ecossistema do algoritmo
        clone += [element[i]]
+
class Individual:
    return clone
+
    def __init__(self):
 +
        self.fitness = 0                # aptidão do indivíduo
 +
        self.accumulated_fitness = 0    # aptidão acumulada (usada na seleção)
 +
        self.selected = False          # se indivíduo foi selecionado
 +
        self.gene = []                 # gens, representando os ítens na mochiila
 +
        for i in range(Data.size):
 +
            # coloca ítens aleatórios na mochila
 +
            self.gene.append(randint(0, 1))
  
 +
    # Calcula a aptidão do indivíduo
 +
    def evaluate(self):
 +
        self.accumulated_fitness = 0
 +
        self.fitness = 0
 +
        weight = 0
 +
        # primeiro soma todos os valores dos ítens que estão inclusos
 +
        # nessa solução de mochila
 +
        for i in range(Data.size):
 +
            if self.gene[i] == 1:
 +
                self.fitness += Data.items[i].value
 +
                weight += Data.items[i].weight
 +
        # caso o peso total de ítens incluso seja maior que a capacidade
 +
        # da mochila, dá uma penalidade, diminuindo em 100 a aptidão
 +
        if weight > Data.capacity:
 +
            self.fitness -= 100
  
def compute_fitness(element):
+
     # apresenta informações do indivíduo de forma simplificada para debug
     global data
+
     def show(self):
     value = 0
+
         print(self.gene, self.fitness, self.selected, self.accumulated_fitness)
    weight = 0
 
    for i in range(len(data["itens"])):
 
         if element[i] == 1:
 
            value = value + int(data["itens"][i]["value"])
 
            weight = weight + int(data["itens"][i]["weight"])
 
    fitness = value
 
    if weight > int(data["capacity"]):
 
        fitness = fitness - 100
 
    return fitness
 
  
 +
    # apresenta informações completas da solução, representada por um indivíduo
 +
    def show_info(self):
 +
        print("SOLUÇÃO ------------------------------")
 +
        print("Capacidade da mochila:", Data.capacity)
 +
        weight = 0
 +
        value = 0
 +
        for i in range(Data.size):
 +
            if self.gene[i] == 1:
 +
                weight += Data.items[i].weight
 +
                value += Data.items[i].value
 +
                print("-", Data.items[i].name, "valor:",
 +
                      Data.items[i].value, "gold - peso:", Data.items[i].weight)
 +
        print("Valor total:", value, "gold")
 +
        print("Peso total :", weight)
 +
        print("--------------------------------------")
  
def evaluate():
+
    # clona o indivíduo e retorna uma instância dessa cópia
    global population
+
    def clone(self):
    for i in range(len(population)):
+
        other = Individual()
        compute_fitness(population[i])
+
        for i in range(Data.size):
 +
            other.gene[i] = self.gene[i]
 +
        other.fitness = self.fitness
 +
        return other
  
  
def selection(best):
+
# GENETIC CLASS --------------------------------------------------
     print("selection", best)
+
class Genetic:
     unselect_all()
+
     # Configuro um padrão aqui, mas faço ajustes no main
     global data, population, population_size, selection_size
+
     population_size = 50            # tamanho da população
     # procura o menor fitness negativo
+
     selection_size = 20            # quantos vou selecionar
     lower = 0
+
     crossover_gene_size = 2        # quantos gens vou usar no cruzamento
     fitness_pos = len(data["itens"])
+
     mutation_individual_size = 10  # quantos indivíduos vou mutar cada geração
     for i in range(len(population)):
+
     mutation_gene_size = 2          # quantos gens de cada indivíduo vou mutar
        if population[i][fitness_pos] < lower:
+
     generations = 100              # por quantas gerações o algoritmo vai executar
            lower = population[i][fitness_pos]
 
  
     # Se houve aptidão negativa, subtrai o menor valor de todos os
+
     # constructor
     # elementos para não termos aptidão negativa
+
     def __init__(self):
    if lower < 0:
+
         Data.load()             # carrega as configurações
         for i in range(len(population)):
+
        self.population = []   # inicia com população vazia
            population[i][fitness_pos] = population[i][fitness_pos] - lower
 
  
     # ordena a população com base no fitness
+
     # mostra todos os indivíduos da população
     population = sorted(population, key=lambda x: x[fitness_pos])
+
     def show(self):
 +
        for i in self.population:
 +
            i.show()
  
     # calcula a aptidão acumulada e aptidão total
+
     # cria a população inicial com indivíduos contendo ítens aleatórios
     fitness_total = 0
+
     def create_population(self):
    for i in range(len(population)):
+
        print("CREIANDO POPULAÇÃO INICIAL...")
        fitness_total = fitness_total + population[i][fitness_pos]
+
        for i in range(Genetic.population_size):
        population[i][fitness_pos + 1] = fitness_total
+
            self.population.append(Individual())
  
     # seleciona os n melhores. Se for pra escolher quem vai sobreviver
+
     # calcula a aptidão de todos os indivíduos da população
    # escolhe o tamanho da população
+
     def evaluate(self):
     aux = selection_size
+
        for i in self.population:
    if not best:
+
            i.evaluate()
         aux = population_size
+
        # ordena a população com base na aptidão
 +
         self.population.sort(key=lambda x: x.fitness)
  
     for j in range(aux):
+
     # seleciona uma quantidade de indivíduos. Se best for verdadeiro,
         last = 0
+
    # então seleciona os melhores, se for falso, seleciona os piores.
         num = randint(0, fitness_total)
+
    def selection(self, best):
        # encontra a posição da roleta do selecionado
+
        who = "MELHORES"
         found = False
+
        if not best:
 +
            who = "PIORES"
 +
        print("SELECIONANDO OS", who, "INDIVÍDUOS ...")
 +
        # como para fins de cálculo não pode haver aptidão negativa,
 +
        # procura a menor aptidão negativa para subtrair de todos,
 +
        # deixando todos positivos.
 +
        lower = 0
 +
        for i in self.population:
 +
            if i.fitness < lower:
 +
                lower = i.fitness
 +
 
 +
        # Se houve aptidão negativa, subtrai o menor valor de
 +
        # todos os elementos para não termos aptidão negativa
 +
        if lower < 0:
 +
            for i in self.population:
 +
                i.fitness = i.fitness - lower
 +
 
 +
        # ordena a população com base na aptidão
 +
        self.population.sort(key=lambda x: x.fitness)
 +
 
 +
        # calcula a aptidão acumulada e aptidão total
 +
        fitness_total = 0
 +
        for i in self.population:
 +
            fitness_total += i.fitness
 +
            i.accumulated_fitness = fitness_total
 +
 
 +
        # se é pra selecionar os piores, inverte o aptidão
 +
        # acumulada e ordena novamente pela aptidão acumulada
 +
        # assim, os piores tem mais chances de serem escolhidos
 +
        if not best:
 +
            # Inverte as probabilidades. Os piores vão ganhar uma
 +
            # porção maior na roleta e os melhores menor
 +
            size = len(self.population)
 +
            for i in range(int(size / 2)):
 +
                a = self.population[i]
 +
                b = self.population[size - 1 - i]
 +
                aux = a.accumulated_fitness
 +
                a.accumulated_fitness = b.accumulated_fitness
 +
                b.accumulated_fitness = aux
 +
            self.population.sort(key=lambda x: x.accumulated_fitness)
 +
 
 +
        # e roda a roleta para selecionar a quantidade de indivúdios
 +
         # definida em Genetic.selection_size
 +
         for j in range(Genetic.selection_size):
 +
            # pega um número aleatório com base na aptidão total
 +
            num = randint(0, fitness_total)
 +
            last = 0
 +
            i = 0
 +
            found = False
 +
            # enquanto não encontrou um indivíduo que tem a ficha
 +
            # desse número, procura
 +
            while not found:
 +
                current = self.population[i].accumulated_fitness
 +
                # se encontrou o felizardo (ou não)
 +
                if last <= num <= current:
 +
                    # se já está selecionado, pega o próximo não
 +
                    # selecionado da roleta
 +
                    while self.population[i].selected:
 +
                        # se chegou ao final, volta para o início
 +
                        # veja que está girando a roleta e é circular
 +
                        i += 1
 +
                        if i >= len(self.population):
 +
                            i = 0
 +
                    # achou um indivíduo seleciona ele
 +
                    self.population[i].selected = True
 +
                    found = True
 +
                last = current
 +
                i += 1
 +
 
 +
    # faz o cruzamento dos indivíduos selecioados
 +
    def crossover(self):
 +
        print("CRUZAMENTO...")
 +
        # cria uma lista para os selecionados. Fica mais fácil.
 +
        selected = []
 +
         for i in self.population:
 +
            if i.selected:
 +
                selected.append(i)
 +
                # já deseleciona os pais
 +
                i.selected = False
 +
        # randomiza para fazer pares aleatórios
 +
        shuffle(selected)
 
         i = 0
 
         i = 0
         while not found:
+
        # vai seguindo a lista dos selecionados pegando os dois
             current = population[i][fitness_pos + 1]
+
        # primeiros, depois os dois seguintes e assim por diante
             if last < num and num <= current:
+
         while i < len(selected):
                is_selected = population[i][fitness_pos + 2]
+
             child_a = selected[i].clone()
                 # se já está selecionado, pega o próximo não selecionado da roleta
+
            child_b = selected[i+1].clone()
                 while is_selected:
+
             for j in range(Genetic.crossover_gene_size):
                    i = i + 1
+
                 # escolhe um gen aleatório e troca nos filhos
                    # se chegou ao final, volta no primeiro
+
                 k = randint(0, Data.size - 1)
                    if i >= len(population):
+
                child_a.gene[k] = selected[i+1].gene[k]
                        i = 0
+
                 child_b.gene[k] = selected[i].gene[k]
                    is_selected = population[i][fitness_pos + 2]
+
             # coloca os filhos na lista da população
                 # seleciona
+
             self.population.append(child_a)
                population[i][fitness_pos + 2] = 1
+
             self.population.append(child_b)
                found = True
+
            i += 2
             last = current
 
             i += 1
 
             if i >= len(population):
 
                i = 0
 
 
 
  
def crossover():
+
    # Faz a mutação de alguns indivíduos escolhidos aleatoriamente.
    print("crossover")
+
    def mutation(self):
    global data, population, crossover_size, selection_size
+
        print("MUTAÇÃO...")
    selected_pos = len(data["itens"]) + 2
+
        # pega uma quantidade aleatória de indivíduos para mutar
    unselected = []
+
        # a cada geração, entre 0 e Genetic.mutation_individual_size
    selected = []
+
        size = randint(0, Genetic.mutation_individual_size)
    for i in range(len(population)):
+
        for i in range(size):
        if population[i][selected_pos] == 0:
+
             # pega um indivíduo aleatório da população
             unselected = unselected + [population[i]]
+
             k = randint(0, len(self.population) - 1)
        else:
+
            # pega alguns gens aleatórios e inverte o valor
             selected = selected + [population[i]]
+
            for j in range(Genetic.mutation_gene_size):
    # randomiza para fazer pares aleatórios
+
                l = randint(0, Data.size - 1)
    shuffle(selected)
+
                if self.population[k].gene[l] == 1:
    i = 0
+
                    self.population[k].gene[l] = 0
    while i < len(selected):
+
                else:
        # deseleciona os pais
+
                    self.population[k].gene[l] = 1
        selected[i][selected_pos] = 0
 
        selected[i+1][selected_pos] = 0
 
        # clona os pais criando os filhos
 
        child_a = clone_element(selected[i])
 
        child_b = clone_element(selected[i+1])
 
        # faz as trocas genéticas
 
        for j in range(crossover_size):
 
            cross_gen = randint(0, len(data["itens"]) - 1)
 
            child_a[cross_gen] = selected[i+1][cross_gen]
 
            child_b[cross_gen] = selected[i][cross_gen]
 
        compute_fitness(child_a)
 
        compute_fitness(child_b)
 
        # coloca os filhos criados na lista unselected, que depois
 
        # irá compor a população
 
        unselected = unselected + [child_a, child_b]
 
        i = i + 2
 
    population = unselected + selected
 
  
 +
    # Depois de selecionar os piores indivíduos, mas com
 +
    # possibilidades de alguns bons serem selecionados na
 +
    # roleta, mata esses selecionados, deixando apenas os
 +
    # "melhores" para a próxima geração
 +
    def survival(self):
 +
        print("SOBREVIVENTES...")
 +
        aux = []
 +
        for i in self.population:
 +
            if not i.selected:
 +
                aux.append(i)
 +
        self.population = aux
  
def mutation():
+
    # Algoritmo genético propriamente dito.
    print("mutation")
+
    def run(self):
    global mutation_elements_size, mutation_size, population, data
+
        self.create_population()
    size = randint(0, mutation_elements_size)
+
        for g in range(Genetic.generations):
    for i in range(size):
+
            print("GERAÇÃO", g)
        element_pos = randint(1, len(population)) - 1
+
            self.evaluate()
        for j in range(mutation_size):
+
            self.selection(True)
             mutation_gen = randint(0, len(data["itens"]) - 1)
+
            self.show()
             if population[element_pos][mutation_gen] == 1:
+
             self.crossover()
                population[element_pos][mutation_gen] = 0
+
            self.mutation()
             else:
+
            self.evaluate()
                population[element_pos][mutation_gen] = 1
+
             self.selection(False)
 +
            self.show()
 +
             self.survival()
 +
        self.evaluate()
 +
        self.show()
 +
        self.population[-1].show_info()
  
  
def survival():
+
# MAIN PROGRAM -----------------------------------------
     print("survival")
+
def main():
     global population, data
+
     # Faço ajustes para ser mais fácil (atributos globais)
     selected_pos = len(data["itens"]) + 2
+
     Genetic.population_size = 100
     aux = population[:]
+
     Genetic.selection_size = 40
     population = []
+
     Genetic.crossover_gene_size = 2
     for i in range(len(aux)):
+
     Genetic.mutation_individual_size = 20
        if aux[i][selected_pos] == 1:
+
     Genetic.mutation_gene_size = 2
            population += [aux[i]]
+
    Genetic.generations = 500
  
 +
    # inicio o algoritmo
 +
    genetic = Genetic()
 +
    genetic.run()
  
# PROGRAMA PRINCIPAL ------------------------------------------
 
data = load()
 
population_size = 50
 
selection_size = 20
 
crossover_size = 2
 
mutation_elements_size = 10
 
mutation_size = 2
 
epochs = 100
 
  
create_population()
+
# -------------------------------------------------------
for epoch in range(epochs):
+
if __name__ == "__main__":
    print("epoch", epoch)
+
     main()
    selection(True)
 
    crossover()
 
    mutation()
 
     # evaluate()
 
    selection(False)
 
    survival()
 
    # show population
 
    for i in range(len(population)):
 
        print(population[i])
 
 
</syntaxhighlight>
 
</syntaxhighlight>

Edição das 10h29min de 19 de março de 2022

Afluentes: Inteligência Artificial, Modelos Evolutivos e Tratamento de Incertezas

Definição do Problema

Para mostrar uma implementação de um Algoritmo Genético, iremos trabalhar com o Problema da Mochila.

  • Problema de otimização combinatória (NP-Completo)
  • Estudado por mais de um século (desde ~1897)
  • Resolvido por vários algoritmos
Ag mochila.png

Podemos representar a aptidão de uma solução como a soma dos valores dos objetos e com uma penalidade para soluções que passam do limite de volume.

  • Queremos o maior valor possível sem ultrapassar o limite da mochila.
  • Vários itens que gostaria de levar em uma mochila
  • Cada item tem um peso e um benefício, ou valor
  • Há uma capacidade limite de peso
  • Deve-se carregar itens com o máximo valor total sem superar o limite de peso

Arquivo de Configuração

 1{
 2  "capacity": "25",
 3  "items": [
 4    { "name": "notebook", "value": "5", "weight": "7" },
 5    { "name": "tv", "value": "8", "weight": "8" },
 6    { "name": "tablet", "value": "3", "weight": "4" },
 7    { "name": "anel", "value": "2", "weight": "10" },
 8    { "name": "colar", "value": "7", "weight": "4" },
 9    { "name": "videogame", "value": "9", "weight": "6" },
10    { "name": "smarthone", "value": "4", "weight": "4" }
11  ]
12}

Programa em Python

Tplnote Bulbgraph.png

O algoritmo está com um bug e as vezes trava. Se travar, pressione CTRL+C e tente novamente. Depois vou tentar descobrir o que é.

  1import json
  2from random import *
  3
  4
  5# ITEM CLASS ---------------------------------------------
  6# representa os itens do arquivo config.json
  7class Item:
  8    def __init__(self, name, value, weight):
  9        self.name = name        # nome do produto
 10        self.value = value      # valor
 11        self.weight = weight    # peso
 12
 13
 14# DATA STATIC CLASS ------------------------------------
 15# Lê as configurações do arquivo config.json e carrega
 16# nessa classe estática para ser usada em todo o programa
 17class Data:
 18    size = 0        # quantidade de itens
 19    capacity = 0    # capacidade da mochila
 20    items = []      # vetor contendo os itens
 21
 22    @staticmethod
 23    def load():
 24        # abre o arquivo e converte para um objeto json
 25        file = open("config.json")
 26        data = json.load(file)
 27        file.close()
 28        # coloca as informações do objeto json na classe
 29        # Data pra facilitar o acesso aos dados
 30        Data.capacity = int(data["capacity"])
 31        for i in range(len(data["items"])):
 32            name = data["items"][i]["name"]
 33            value = int(data["items"][i]["value"])
 34            weight = int(data["items"][i]["weight"])
 35            Data.items.append(Item(name, value, weight))
 36            Data.size += 1
 37
 38
 39# INDVIDUAL CLASS -------------------------------------------
 40# Cada instância dessa classe representa cada indivíduo da
 41# população do ecossistema do algoritmo
 42class Individual:
 43    def __init__(self):
 44        self.fitness = 0                # aptidão do indivíduo
 45        self.accumulated_fitness = 0    # aptidão acumulada (usada na seleção)
 46        self.selected = False           # se indivíduo foi selecionado
 47        self.gene = []                  # gens, representando os ítens na mochiila
 48        for i in range(Data.size):
 49            # coloca ítens aleatórios na mochila
 50            self.gene.append(randint(0, 1))
 51
 52    # Calcula a aptidão do indivíduo
 53    def evaluate(self):
 54        self.accumulated_fitness = 0
 55        self.fitness = 0
 56        weight = 0
 57        # primeiro soma todos os valores dos ítens que estão inclusos
 58        # nessa solução de mochila
 59        for i in range(Data.size):
 60            if self.gene[i] == 1:
 61                self.fitness += Data.items[i].value
 62                weight += Data.items[i].weight
 63        # caso o peso total de ítens incluso seja maior que a capacidade
 64        # da mochila, dá uma penalidade, diminuindo em 100 a aptidão
 65        if weight > Data.capacity:
 66            self.fitness -= 100
 67
 68    # apresenta informações do indivíduo de forma simplificada para debug
 69    def show(self):
 70        print(self.gene, self.fitness, self.selected, self.accumulated_fitness)
 71
 72    # apresenta informações completas da solução, representada por um indivíduo
 73    def show_info(self):
 74        print("SOLUÇÃO ------------------------------")
 75        print("Capacidade da mochila:", Data.capacity)
 76        weight = 0
 77        value = 0
 78        for i in range(Data.size):
 79            if self.gene[i] == 1:
 80                weight += Data.items[i].weight
 81                value += Data.items[i].value
 82                print("-", Data.items[i].name, "valor:",
 83                      Data.items[i].value, "gold - peso:", Data.items[i].weight)
 84        print("Valor total:", value, "gold")
 85        print("Peso total :", weight)
 86        print("--------------------------------------")
 87
 88    # clona o indivíduo e retorna uma instância dessa cópia
 89    def clone(self):
 90        other = Individual()
 91        for i in range(Data.size):
 92            other.gene[i] = self.gene[i]
 93        other.fitness = self.fitness
 94        return other
 95
 96
 97# GENETIC CLASS --------------------------------------------------
 98class Genetic:
 99    # Configuro um padrão aqui, mas faço ajustes no main
100    population_size = 50            # tamanho da população
101    selection_size = 20             # quantos vou selecionar
102    crossover_gene_size = 2         # quantos gens vou usar no cruzamento
103    mutation_individual_size = 10   # quantos indivíduos vou mutar cada geração
104    mutation_gene_size = 2          # quantos gens de cada indivíduo vou mutar
105    generations = 100               # por quantas gerações o algoritmo vai executar
106
107    # constructor
108    def __init__(self):
109        Data.load()             # carrega as configurações
110        self.population = []    # inicia com população vazia
111
112    # mostra todos os indivíduos da população
113    def show(self):
114        for i in self.population:
115            i.show()
116
117    # cria a população inicial com indivíduos contendo ítens aleatórios
118    def create_population(self):
119        print("CREIANDO POPULAÇÃO INICIAL...")
120        for i in range(Genetic.population_size):
121            self.population.append(Individual())
122
123    # calcula a aptidão de todos os indivíduos da população
124    def evaluate(self):
125        for i in self.population:
126            i.evaluate()
127        # ordena a população com base na aptidão
128        self.population.sort(key=lambda x: x.fitness)
129
130    # seleciona uma quantidade de indivíduos. Se best for verdadeiro,
131    # então seleciona os melhores, se for falso, seleciona os piores.
132    def selection(self, best):
133        who = "MELHORES"
134        if not best:
135            who = "PIORES"
136        print("SELECIONANDO OS", who, "INDIVÍDUOS ...")
137        # como para fins de cálculo não pode haver aptidão negativa,
138        # procura a menor aptidão negativa para subtrair de todos,
139        # deixando todos positivos.
140        lower = 0
141        for i in self.population:
142            if i.fitness < lower:
143                lower = i.fitness
144
145        # Se houve aptidão negativa, subtrai o menor valor de
146        # todos os elementos para não termos aptidão negativa
147        if lower < 0:
148            for i in self.population:
149                i.fitness = i.fitness - lower
150
151        # ordena a população com base na aptidão
152        self.population.sort(key=lambda x: x.fitness)
153
154        # calcula a aptidão acumulada e aptidão total
155        fitness_total = 0
156        for i in self.population:
157            fitness_total += i.fitness
158            i.accumulated_fitness = fitness_total
159
160        # se é pra selecionar os piores, inverte o aptidão
161        # acumulada e ordena novamente pela aptidão acumulada
162        # assim, os piores tem mais chances de serem escolhidos
163        if not best:
164            # Inverte as probabilidades. Os piores vão ganhar uma
165            # porção maior na roleta e os melhores menor
166            size = len(self.population)
167            for i in range(int(size / 2)):
168                a = self.population[i]
169                b = self.population[size - 1 - i]
170                aux = a.accumulated_fitness
171                a.accumulated_fitness = b.accumulated_fitness
172                b.accumulated_fitness = aux
173            self.population.sort(key=lambda x: x.accumulated_fitness)
174
175        # e roda a roleta para selecionar a quantidade de indivúdios
176        # definida em Genetic.selection_size
177        for j in range(Genetic.selection_size):
178            # pega um número aleatório com base na aptidão total
179            num = randint(0, fitness_total)
180            last = 0
181            i = 0
182            found = False
183            # enquanto não encontrou um indivíduo que tem a ficha
184            # desse número, procura
185            while not found:
186                current = self.population[i].accumulated_fitness
187                # se encontrou o felizardo (ou não)
188                if last <= num <= current:
189                    # se já está selecionado, pega o próximo não
190                    # selecionado da roleta
191                    while self.population[i].selected:
192                        # se chegou ao final, volta para o início
193                        # veja que está girando a roleta e é circular
194                        i += 1
195                        if i >= len(self.population):
196                            i = 0
197                    # achou um indivíduo seleciona ele
198                    self.population[i].selected = True
199                    found = True
200                last = current
201                i += 1
202
203    # faz o cruzamento dos indivíduos selecioados
204    def crossover(self):
205        print("CRUZAMENTO...")
206        # cria uma lista para os selecionados. Fica mais fácil.
207        selected = []
208        for i in self.population:
209            if i.selected:
210                selected.append(i)
211                # já deseleciona os pais
212                i.selected = False
213        # randomiza para fazer pares aleatórios
214        shuffle(selected)
215        i = 0
216        # vai seguindo a lista dos selecionados pegando os dois
217        # primeiros, depois os dois seguintes e assim por diante
218        while i < len(selected):
219            child_a = selected[i].clone()
220            child_b = selected[i+1].clone()
221            for j in range(Genetic.crossover_gene_size):
222                # escolhe um gen aleatório e troca nos filhos
223                k = randint(0, Data.size - 1)
224                child_a.gene[k] = selected[i+1].gene[k]
225                child_b.gene[k] = selected[i].gene[k]
226            # coloca os filhos na lista da população
227            self.population.append(child_a)
228            self.population.append(child_b)
229            i += 2
230
231    # Faz a mutação de alguns indivíduos escolhidos aleatoriamente.
232    def mutation(self):
233        print("MUTAÇÃO...")
234        # pega uma quantidade aleatória de indivíduos para mutar
235        # a cada geração, entre 0 e Genetic.mutation_individual_size
236        size = randint(0, Genetic.mutation_individual_size)
237        for i in range(size):
238            # pega um indivíduo aleatório da população
239            k = randint(0, len(self.population) - 1)
240            # pega alguns gens aleatórios e inverte o valor
241            for j in range(Genetic.mutation_gene_size):
242                l = randint(0, Data.size - 1)
243                if self.population[k].gene[l] == 1:
244                    self.population[k].gene[l] = 0
245                else:
246                    self.population[k].gene[l] = 1
247
248    # Depois de selecionar os piores indivíduos, mas com
249    # possibilidades de alguns bons serem selecionados na
250    # roleta, mata esses selecionados, deixando apenas os
251    # "melhores" para a próxima geração
252    def survival(self):
253        print("SOBREVIVENTES...")
254        aux = []
255        for i in self.population:
256            if not i.selected:
257                aux.append(i)
258        self.population = aux
259
260    # Algoritmo genético propriamente dito.
261    def run(self):
262        self.create_population()
263        for g in range(Genetic.generations):
264            print("GERAÇÃO", g)
265            self.evaluate()
266            self.selection(True)
267            self.show()
268            self.crossover()
269            self.mutation()
270            self.evaluate()
271            self.selection(False)
272            self.show()
273            self.survival()
274        self.evaluate()
275        self.show()
276        self.population[-1].show_info()
277
278
279# MAIN PROGRAM -----------------------------------------
280def main():
281    # Faço ajustes para ser mais fácil (atributos globais)
282    Genetic.population_size = 100
283    Genetic.selection_size = 40
284    Genetic.crossover_gene_size = 2
285    Genetic.mutation_individual_size = 20
286    Genetic.mutation_gene_size = 2
287    Genetic.generations = 500
288
289    # inicio o algoritmo
290    genetic = Genetic()
291    genetic.run()
292
293
294# -------------------------------------------------------
295if __name__ == "__main__":
296    main()