├── .gitignore ├── Explanation paper.pdf ├── Item.py ├── README.md ├── auxiliar.py ├── config.py ├── dashboard.py ├── genetic.py ├── main.py └── web ├── grafic.py ├── grafico.png ├── lista.html ├── tabela.html ├── tabelaConfiguracoes.html └── tabela_valortotal.html /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /Explanation paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayes99/genetic-cargo/e8c1dd2c763183a44c96adceff38336ae78208e9/Explanation paper.pdf -------------------------------------------------------------------------------- /Item.py: -------------------------------------------------------------------------------- 1 | class Item: 2 | def __init__(self) -> None: 3 | pass 4 | 5 | def __init__(self, nome, peso, valor): 6 | self.nome = nome 7 | self.peso = peso #em quilos 8 | self.valor = valor #em Reais 9 | 10 | def getNome(self): 11 | return self.nome 12 | 13 | def getPeso(self): 14 | return self.peso 15 | 16 | def getValor(self): 17 | return self.valor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Genetic Cargo: Genetic Algorithm for Optimized Truck Loading 🚚🧬 2 | 3 | Optimize your truck loading process with a genetic algorithm designed to select the optimal combination between items and values. This algorithm ensures maximum value for your cargo while adhering to weight constraints, streamlining your logistics with precision 🧬 4 | 5 | ## Features 6 | 7 | - **Genetic Algorithm**: Utilizes a genetic algorithm to find the best combination of items. 8 | - **Item Selection**: Selects items based on their value and weight. 9 | - **Weight Limit**: Ensures that the total weight of the selected items does not exceed the truck's weight limit. 10 | - **Optimization**: Seeks to maximize the total value of the loaded items. 11 | 12 | ## Prerequisites 13 | Before running the application, ensure you have the following installed: 14 | 15 | - Python 🐍 (For running the application) 16 | 17 | //todo: improve this readme -------------------------------------------------------------------------------- /auxiliar.py: -------------------------------------------------------------------------------- 1 | from Item import * 2 | from tkinter import * 3 | from json2html import * 4 | import locale 5 | 6 | def instanciaItens() -> list: 7 | itens = [] 8 | 9 | itens.append(Item(nome="Cadeira", peso=15, valor=1500)) 10 | itens.append(Item(nome="Celular top", peso=0.8, valor=8700)) 11 | itens.append(Item(nome="Notebook", peso=3, valor=4000)) 12 | itens.append(Item(nome="Piano Yamaha", peso=250, valor=250000)) 13 | itens.append(Item(nome="Piano Roch", peso=250, valor=250000)) 14 | itens.append(Item(nome="Televisão 50 pol.", peso=12, valor=3000)) 15 | itens.append(Item(nome="Televisão 22 pol.", peso=8, valor=999)) 16 | itens.append(Item(nome="Cama king", peso=100, valor=20000)) 17 | itens.append(Item(nome="Sofá", peso=120, valor=7000)) 18 | itens.append(Item(nome="Kit academia", peso=500, valor=15000)) 19 | itens.append(Item(nome="Escultura de gesso", peso=500, valor=10000)) 20 | itens.append(Item(nome="Escultura Tigresa", peso=300, valor=87000)) 21 | itens.append(Item(nome="Escultura de mármore", peso=700, valor=90000)) 22 | itens.append(Item(nome="Geladeira top", peso=125, valor=10000)) 23 | itens.append(Item(nome="Geladeira", peso=90, valor=2500)) 24 | itens.append(Item(nome="Freezer", peso=90, valor=3500)) 25 | itens.append(Item(nome="Fogão a lenha n3", peso=100, valor=3500)) 26 | itens.append(Item(nome="Esteira", peso=130, valor=2500)) 27 | itens.append(Item(nome="Mesa tampo de mármore 6 cadeiras", peso=100, valor=8000)) 28 | itens.append(Item(nome="Livro", peso=0.3, valor=35)) 29 | itens.append(Item(nome="Jogo 4 pneus", peso=45, valor=2500)) 30 | itens.append(Item(nome="Quadro", peso=45, valor=5000)) 31 | itens.append(Item(nome="Kit anilhas", peso=450, valor=2000)) 32 | itens.append(Item(nome="2000kg de tinta", peso=2000, valor=20000)) 33 | 34 | 35 | return itens 36 | 37 | def geraTabelaConfiguracoes(capacidadeDeCarga, numeroDeIndividuos, geracoes): 38 | configuracoesJson = {"Capacidade de Carga":str(capacidadeDeCarga)+" kg","Número de Indivíduos":numeroDeIndividuos,"Número de Gerações":geracoes} 39 | 40 | html_file = open("tabelaConfiguracoes.html","w") 41 | tabelaConfiguracoes = '' 42 | tabelaConfiguracoes += '' 43 | tabelaConfiguracoes += json2html.convert(json=configuracoesJson,table_attributes='style="width: 100%;"') 44 | 45 | html_file.write(tabelaConfiguracoes) 46 | html_file.close() 47 | 48 | def get_best_individual(populacao, itens): 49 | cincoUltimosIndividuos = [] 50 | indiceMelhorIndividuo = 0 51 | valorTotalMelhorIndividuo = 0 52 | for i in range(5): 53 | cincoUltimosIndividuos.append(populacao[i]) 54 | valorTotalIndividuo = calculaValorTotal(populacao[i],itens) 55 | if valorTotalIndividuo > valorTotalMelhorIndividuo: 56 | valorTotalMelhorIndividuo = valorTotalIndividuo 57 | indiceMelhorIndividuo = i 58 | 59 | return populacao[indiceMelhorIndividuo] 60 | 61 | def calculaValorTotal(individuo, itens): 62 | qtdItens = len(individuo) 63 | valorTotal = 0 64 | 65 | for i in range(qtdItens): 66 | if individuo[i] == 1: 67 | valorTotal += itens[i].getValor() 68 | 69 | return valorTotal 70 | 71 | def generate_html_individual_items(individuo, itens): 72 | i = 0 73 | locale.setlocale(locale.LC_MONETARY, 'pt_BR.UTF-8') 74 | objetos = [] 75 | quantidadeCromossomos = len(individuo) 76 | valorTotal = 0 77 | pesoTotal = 0 78 | 79 | #Transforma os itens do indivíduo para um objeto JSON 80 | while i < quantidadeCromossomos: 81 | if individuo[i] == 1: 82 | valorTotal += itens[i].getValor() 83 | pesoTotal += itens[i].getPeso() 84 | objetos.append( 85 | {"Nome":itens[i].getNome(), 86 | "Valor":locale.currency(itens[i].getValor(), grouping=True), 87 | "Peso":str(itens[i].getPeso())+" kg"} 88 | ) 89 | i += 1 90 | 91 | html_file = open("tabela.html","w") 92 | 93 | #Adiciona o bootstrap no frame HTML 94 | tabelaHtml = '' 95 | tabelaHtml += '' 96 | 97 | #Transforma o objeto JSON em uma tabela 98 | tabelaHtml += json2html.convert(json=objetos,table_attributes='style="width: 100%;"') 99 | 100 | html_file.write(tabelaHtml) 101 | html_file.close() 102 | 103 | objValorTotal = [{"Valor Total da Carga": locale.currency(valorTotal, grouping=True), 104 | "Peso Total da Carga:": round(pesoTotal, 2) 105 | }] 106 | 107 | html_file = open("tabela_valortotal.html","w") 108 | tabelaHtmlValorTotal = '' 109 | tabelaHtmlValorTotal += '' 110 | tabelaHtmlValorTotal += json2html.convert(json=objValorTotal,table_attributes='style="width: 100%;"') 111 | 112 | html_file.write(tabelaHtmlValorTotal) 113 | html_file.close() 114 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from auxiliar import instanciaItens, geraTabelaConfiguracoes 2 | 3 | load_capacity = 4000 #kg 4 | individuals_number = 150 5 | generations = 100 6 | items = instanciaItens() #Instancia os itens disponíveis para serem carregados 7 | items_number = len(items) 8 | 9 | #Gera tabela com as informações de configurações para exibição na tela final 10 | geraTabelaConfiguracoes(load_capacity, individuals_number, generations) -------------------------------------------------------------------------------- /dashboard.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | import shutil 3 | import pathlib 4 | import os 5 | 6 | 7 | path = str(pathlib.Path().resolve()) 8 | 9 | def verificaExistenciaArquivo(nomeArquivo): 10 | path = str(pathlib.Path().resolve())+"/web/"+nomeArquivo 11 | fileObj = pathlib.Path(path) 12 | 13 | return pathlib.Path.exists(fileObj) 14 | 15 | 16 | 17 | def moveTabelas(): 18 | shutil.move(path+"/tabela.html",path+"/web") 19 | shutil.move(path+"/tabela_valortotal.html",path+"/web") 20 | shutil.move(path+"/tabelaConfiguracoes.html",path+"/web") 21 | 22 | def deletaTabelasAntigas(): 23 | if verificaExistenciaArquivo("tabela.html"): 24 | os.remove(path+"/web/tabela.html") 25 | 26 | if verificaExistenciaArquivo("tabela_valortotal.html"): 27 | os.remove(path+"/web/tabela_valortotal.html") 28 | 29 | if verificaExistenciaArquivo("tabelaConfiguracoes.html"): 30 | os.remove(path+"/web/tabelaConfiguracoes.html") 31 | 32 | def open_dashboard(): 33 | deletaTabelasAntigas() 34 | moveTabelas() 35 | webbrowser.open(path+"/web/lista.html",new=1) 36 | 37 | 38 | -------------------------------------------------------------------------------- /genetic.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits, random, randint 2 | 3 | def create_individual(numeroDeItens): 4 | """ 5 | Gera um array chamado indivíduo (cromossomos), onde cada posição desse array 6 | representa um item que caso estiver com 1 no valor será levado no caminhão 7 | e 0 não será levado. 8 | """ 9 | individual = [getrandbits(1) for x in range(numeroDeItens)] 10 | return individual 11 | 12 | def create_population(numeroDeIndividuos, numeroDeItens): 13 | #Cria a populacao de individuos 14 | population = [create_individual(numeroDeItens) for x in range(numeroDeIndividuos)] 15 | return population 16 | 17 | def calculate_average_fitness(population, pesoMaximo, itens): 18 | #Calcula a avaliação media da população de indivíduos 19 | total_fitness = sum(calculate_fitness(individual, pesoMaximo, itens) for individual in population if calculate_fitness(individual, pesoMaximo, itens) >= 0) 20 | fitness_average = total_fitness / (len(population) * 1.0) 21 | 22 | return fitness_average 23 | 24 | def calculate_fitness(individual, load_capacity, items): 25 | #Faz a avaliação de um único indivíduo 26 | total_weight = 0 27 | total_value = 0 28 | item_index = 0 29 | 30 | while item_index < len(individual): 31 | total_weight = total_weight + (individual[item_index] * items[item_index].getPeso()) 32 | total_value = total_value + (individual[item_index] * items[item_index].getValor()) 33 | item_index += 1 34 | 35 | if (load_capacity - total_weight) < 0: 36 | return -1 #Peso total do caminhão excedido. 37 | else: 38 | return total_value 39 | 40 | def evolve_population(population, load_capacity, items, indivibuals_number, mutation=0.05): 41 | #Classifica o fitness e seu respectivo indivíduo em um array 42 | parents_individuals = [[calculate_fitness(individual, load_capacity, items), individual] for individual in population if calculate_fitness(individual,load_capacity, items) >= 0] 43 | parents_individuals.sort(reverse=True) 44 | 45 | #Reprodução dos indivíduos 46 | children_individuals = [] 47 | 48 | while len(children_individuals) < indivibuals_number: 49 | dad, mom = draw_parents(parents_individuals) 50 | half_chromossome_index = len(dad) // 2 51 | 52 | #Filho vai ser a primeira metade do pai + a primeira metade da mãe 53 | children = dad[:half_chromossome_index] + mom[half_chromossome_index:] 54 | children_individuals.append(children) 55 | 56 | '''Há 5% de chance (definido no parâmetro "mutacao" do método) 57 | de fazer uma mutação em um cromossomo do indivíduo''' 58 | for children in children_individuals: 59 | if mutation > random(): 60 | chromossome_to_change = randint(0, len(children)-1) 61 | 62 | #to-do: estudar uma maneira mais bonita de fazer isso 63 | if children[chromossome_to_change] == 1: 64 | children[chromossome_to_change] = 0 65 | else: 66 | children[chromossome_to_change] = 1 67 | 68 | return children_individuals 69 | 70 | def draw_parents(parents_individuals): 71 | #Sorteia um pai e uma mãe baseado na regra da roleta 72 | 73 | def draw_index(total_fitness, ignored_index =-1): 74 | #Indice ignorado = indice do pai, para não ser sorteado de novo. 75 | 76 | roulette = [] 77 | accumulated = 0 78 | 79 | drawn_value = random() 80 | 81 | #fitness total ignora o indivíduo pai, se já houver 82 | if ignored_index != -1: 83 | total_fitness -= values[0][ignored_index] 84 | 85 | for index, i in enumerate(values[0]): 86 | if ignored_index == index: 87 | continue 88 | accumulated += i 89 | roulette.append(accumulated/total_fitness) 90 | 91 | if roulette[-1] >= drawn_value: 92 | return index 93 | 94 | #Cria duas listas com os valores de fitness e indivíduos 95 | values = list(zip(*parents_individuals)) 96 | total_fitness = sum(values[0]) 97 | 98 | dad_index = draw_index(total_fitness) 99 | mom_index = draw_index(total_fitness, dad_index) 100 | 101 | dad_individual = values[1][dad_index] 102 | mom_individual = values[1][mom_index] 103 | 104 | return dad_individual, mom_individual -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from dashboard import open_dashboard 2 | from genetic import * 3 | from config import * 4 | from web.grafic import * 5 | from auxiliar import get_best_individual, generate_html_individual_items 6 | 7 | #Cria a população inicial de forma psuedo-aleatória 8 | population = create_population(individuals_number, items_number) 9 | 10 | #Calcula o primeiro fitness da população 11 | fitness_history = [calculate_average_fitness(population, load_capacity, items)] 12 | 13 | #Evolui, calcula e guarda o fitness da população até acabar as gerações 14 | for generation in range(generations): 15 | population = evolve_population(population, load_capacity, items, individuals_number) 16 | fitness_average = calculate_average_fitness(population, load_capacity, items) 17 | fitness_history.append(fitness_average) 18 | 19 | #Gera gráfico em PNG com histórico da média de fitness e valor total da carga do caminhão 20 | generate_fitness_history_chart(fitness_history) 21 | 22 | #Processa o melhor indivíduo, seu set de itens e processa e abre a página HTML 23 | best_individual = get_best_individual(population, items) 24 | generate_html_individual_items(best_individual, items) 25 | 26 | open_dashboard() 27 | -------------------------------------------------------------------------------- /web/grafic.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | 3 | def generate_fitness_history_chart(historicoFitness): 4 | plt.plot(range(len(historicoFitness)), historicoFitness) 5 | plt.grid(True, zorder=0) 6 | plt.title("Evolução do valor total conforme as gerações") 7 | plt.xlabel("Geração") 8 | plt.ylabel("Valor médio carregado pelo caminhão (R$)") 9 | 10 | plt.savefig('./web/grafico.png') 11 | 12 | -------------------------------------------------------------------------------- /web/grafico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayes99/genetic-cargo/e8c1dd2c763183a44c96adceff38336ae78208e9/web/grafico.png -------------------------------------------------------------------------------- /web/lista.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 14 | 19 | 20 |Valor Total da Carga | Peso Total da Carga: |
---|---|
R$ 757.200,00 | 2998.8 |