ML-INTA commited on
Commit
dcc42b8
·
1 Parent(s): f0f70b8

Upload 6 files

Browse files
pages/Ejecucion.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ from pages.vrp.Origen import Grafo, Vehiculos
4
+ from pages.vrp.Algoritmo_genético import Algoritmo_Genetico
5
+
6
+ #######################
7
+ ## FUNCIONES AUXILIARES
8
+ #######################
9
+
10
+ def grafar(reparto : list[list[int]]):
11
+
12
+ result = "digraph { \n"
13
+
14
+ for ruta in reparto:
15
+ result += "Almacén ->"
16
+ for nodo in ruta:
17
+ result += str(nodo) + "\n"
18
+ result += str(nodo) + "->"
19
+ result += "Almacén \n"
20
+
21
+ result += "}"
22
+
23
+ return result
24
+
25
+ def presentar(reparto : list[list[int]]):
26
+
27
+ result = ""
28
+
29
+ for i in range(len(reparto)):
30
+ result += "Camion " + str(i) + ": " + "Almacén -> "
31
+ for parada in reparto[i]:
32
+ result += str(parada) + " -> "
33
+ result += "Almacén \n"
34
+
35
+ return result
36
+
37
+ st.set_page_config(page_title="LupercAI", page_icon= "🚛")
38
+
39
+ st.title("LupercAI 🚛")
40
+
41
+ valores_vitales = ["distancias", "demandas_clientes", "capacidad_vehiculos"]
42
+ importado = True
43
+ for valor in valores_vitales: importado = importado and valor in st.session_state
44
+
45
+ if not importado:
46
+ st.warning("Faltan datos", icon="⚠️")
47
+ else:
48
+ #if "num_nodos" not in st.session_state: st.session_state["num_nodos"] = len(st.session_state["demandas_clientes"])
49
+ #if "num_vehiculos" not in st.session_state: st.session_state["num_vehiculos"] = len(st.session_state["capacidad_vehiculos"])
50
+
51
+ grafo = Grafo(st.session_state["distancias"], st.session_state["demandas_clientes"])
52
+ vehiculos = Vehiculos(st.session_state["capacidad_vehiculos"])
53
+
54
+ if "activar_generaciones" in st.session_state:
55
+ if st.session_state["activar_generaciones"]:
56
+ if "alg_genetico" not in st.session_state: st.session_state["alg_genetico"] = Algoritmo_Genetico(tamano_poblacion = st.session_state["tamano_poblacion"],
57
+ generaciones = st.session_state["generaciones"], tamano_torneo = st.session_state["tamano_torneo"],
58
+ crossover_prob = st.session_state["crossover_prob"], mutation_prob = st.session_state["mutation_prob"])
59
+ else:
60
+ if "alg_genetico" not in st.session_state: st.session_state["alg_genetico"] = Algoritmo_Genetico()
61
+ else:
62
+ if "alg_genetico" not in st.session_state: st.session_state["alg_genetico"] = Algoritmo_Genetico()
63
+
64
+ alg_genetico = st.session_state["alg_genetico"]
65
+
66
+ from threading import Thread
67
+ Thread(target=alg_genetico.generar, args = (grafo, vehiculos)).start()
68
+
69
+
70
+ #Computación:
71
+
72
+ empty_latest_iteration = st.empty()
73
+ empty_mejor_coste = st.empty()
74
+
75
+ st.subheader("Barra de progreso")
76
+ barra_progreso = st.progress(0)
77
+
78
+ st.subheader("Grafo del mejor recorrido actual")
79
+
80
+ empty_txt = st.empty()
81
+
82
+ empty_grafo = st.empty()
83
+
84
+ i = 0
85
+ while alg_genetico.generacion_actual < alg_genetico.generaciones:
86
+ #Generación por escrito
87
+ empty_latest_iteration.write("Generación: " + str(alg_genetico.generacion_actual))
88
+
89
+ #Barra de progreso
90
+ barra_progreso.progress( alg_genetico.generacion_actual/alg_genetico.generaciones)
91
+
92
+ #Mejor coste actual (texto)
93
+ empty_mejor_coste.write("**Mejor coste**: " + str(alg_genetico.mejor_candidato[1]))
94
+
95
+ #Descarga de grafo
96
+ empty_txt.download_button("Mejor recorrido 🔽", presentar(alg_genetico.mejor_candidato[0]), file_name="Mejor reparto - Generacion " + str(alg_genetico.generacion_actual) + ".txt", key=str(i))
97
+ i += 1
98
+
99
+ #Mostrar el grafo
100
+ empty_grafo.graphviz_chart(grafar(alg_genetico.mejor_candidato[0]))
101
+
102
+
103
+ #Ultima iteracion
104
+ empty_latest_iteration.write("Generación: " + str(alg_genetico.generacion_actual))
105
+ barra_progreso.progress( alg_genetico.generacion_actual/alg_genetico.generaciones)
106
+ empty_mejor_coste.write("**Mejor coste**: " + str(alg_genetico.mejor_candidato[1]))
107
+ empty_txt.download_button("Mejor recorrido 🔽", presentar(alg_genetico.mejor_candidato[0]), file_name="Mejor reparto - Generacion " + str(alg_genetico.generacion_actual) + ".txt", key=str(i))
108
+ empty_grafo.graphviz_chart(grafar(alg_genetico.mejor_candidato[0]))
pages/vrp/Algoritmo_genético.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #VERSIÓN 4: VERSIÓN SEMI-ORIENTADA A OBJETOS
2
+ #(F): A añadir en (F)uturas versiones
3
+
4
+ ### CLASES DEL ALGORITMO ###
5
+
6
+ from .Reparto import Reparto, division_rutas
7
+
8
+ class Algoritmo_Genetico:
9
+ def __init__(self, tamano_poblacion : int = 50, generaciones : int = 100, tamano_torneo : int = 5,
10
+ crossover_prob : float = 0.8, mutation_prob : float = 0.2):
11
+
12
+ #Variables del algoritmo (int)
13
+ self.tamano_poblacion = tamano_poblacion
14
+ self.generaciones = generaciones
15
+ self.tamano_torneo = tamano_torneo
16
+
17
+ #Variables del algoritmo (%)
18
+ self.crossover_prob = crossover_prob
19
+ self.mutation_prob = mutation_prob
20
+
21
+ #Variables en ejecución
22
+ self.generacion_actual = 0
23
+ self.mejor_candidato = ([[1,2,3],[4]], float("inf"))
24
+
25
+
26
+ def _poblar(self, grafo, vehiculos):
27
+ Reparto.grafo = grafo
28
+ Reparto.vehiculos = vehiculos
29
+ return crear_poblacion(self)
30
+
31
+ def generar(self, grafo, vehiculos):
32
+
33
+ #print("##############\n##EMPEZAMOS \n##############")
34
+
35
+ Poblacion = self._poblar(grafo, vehiculos)
36
+ poblacion = Poblacion()
37
+
38
+ from time import sleep
39
+
40
+ for _ in range(self.generaciones):
41
+ sleep(0.1)
42
+ poblacion = poblacion.evolucionar()
43
+ self.generacion_actual = self.generacion_actual + 1
44
+ self.mejor_candidato = (division_rutas(poblacion.poblacion[0]), poblacion.poblacion[0].coste)
45
+
46
+ from .Evolucion import mejor_individuo
47
+
48
+ return mejor_individuo(poblacion.poblacion)
49
+
50
+ def crear_poblacion(alg_genetico : Algoritmo_Genetico): #quizas haga falta , clase_Reparto : type = Reparto
51
+ import random
52
+
53
+ from .Evolucion import Evolucion
54
+
55
+ class Poblacion:
56
+ def __init__(self, poblacion : list[Reparto] = [Reparto()]*alg_genetico.tamano_poblacion):
57
+ self.poblacion = poblacion
58
+
59
+ def evolucionar(self):
60
+ new_poblacion = [Evolucion.elitismo(self.poblacion)]
61
+
62
+ for _ in range(alg_genetico.tamano_poblacion - 1):
63
+ new_poblacion += [self._evolucionar_individuo()]
64
+
65
+ return Poblacion(new_poblacion)
66
+
67
+ def _evolucionar_individuo(self):
68
+ parent1 = Evolucion.torneo(self.poblacion, alg_genetico.tamano_torneo)
69
+
70
+ child = parent1
71
+
72
+ if random.random() < alg_genetico.crossover_prob: #Cruce %
73
+ parent2 = Evolucion.torneo(self.poblacion, alg_genetico.tamano_torneo)
74
+ child = Evolucion.crossover(parent1, parent2)
75
+ if random.random() < alg_genetico.mutation_prob: #Mutación %
76
+ child = Evolucion.mutate(child)
77
+
78
+ return child
79
+
80
+ return Poblacion
81
+
82
+
83
+
84
+
pages/vrp/Evolucion.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #VERSIÓN 4: VERSIÓN SEMI-ORIENTADA A OBJETOS
2
+ #(F): A añadir en (F)uturas versiones
3
+
4
+ ### FUNCIONES AUXILIARES ###
5
+
6
+ import random
7
+
8
+ from .Reparto import Reparto
9
+
10
+
11
+ def mejor_individuo(list_individuos : list[Reparto]) -> Reparto: #paralelizable
12
+ mejor_individuo = list_individuos[0]
13
+ for individuo in list_individuos[1:]:
14
+ mejor_individuo = individuo if individuo.coste < mejor_individuo.coste else mejor_individuo
15
+ #print("mejor_individuo: ", mejor_individuo.ruta, mejor_individuo.coste)
16
+ return mejor_individuo
17
+
18
+ class Evolucion:
19
+
20
+ @staticmethod
21
+ def elitismo(poblacion : list[Reparto]) -> Reparto: #mejor ruta de la poblacion
22
+ return mejor_individuo(poblacion)
23
+
24
+ @staticmethod
25
+ def torneo(poblacion : list[Reparto], tamano_torneo) -> list[Reparto]: #mejor ruta de una parte de la poblacion
26
+ selected_individuos = random.sample(poblacion, tamano_torneo)
27
+ return mejor_individuo(selected_individuos)
28
+
29
+ @staticmethod
30
+ def mutate(ruta : Reparto) -> Reparto: #intercambia dos posiciones en la ruta
31
+ idx1, idx2 = random.sample(range(Reparto.grafo.num_nodos + Reparto.vehiculos.num_vehiculos - 2), 2)
32
+ lista = ruta.ruta.copy()
33
+ lista[idx1], lista[idx2] = lista[idx2], lista[idx1]
34
+ return Reparto(lista)
35
+
36
+ @staticmethod
37
+ def crossover(parent1 : Reparto, parent2 : Reparto) -> Reparto: #cruza dos rutas, pegando un cacho de la primera en la segunda
38
+ num_total = Reparto.grafo.num_nodos + Reparto.vehiculos.num_vehiculos - 2
39
+ #-1 por el Almacén
40
+ #-1 porque si hay n vehiculos, solo añadimos n-1 simbolos
41
+
42
+ start = random.randint(0, num_total - 1) #random.randint(a, b) toma un número entero al azar entre [a,b], no entre [a,b)
43
+ end = random.randint(start, num_total - 1)
44
+
45
+ child = [None] * num_total
46
+
47
+ for i in range(start, end+1):
48
+ child[i] = parent1.ruta[i]
49
+ remaining = [item for item in parent2.ruta if item not in child]
50
+ remaining_index = 0
51
+ for i in range(num_total):
52
+ if child[i] == None:
53
+ child[i] = remaining[remaining_index]
54
+ remaining_index += 1
55
+
56
+ return Reparto(child)
57
+
pages/vrp/Origen.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #VERSIÓN 5: VRP
2
+ #(F): A añadir en (F)uturas versiones
3
+
4
+ ### CLASES DEL GRAFO ###
5
+ class Grafo:
6
+ def __init__(self, distancias: list[list[float]], demandas_clientes: list[float], num_nodos: int = None):
7
+ if num_nodos == None: num_nodos = len(distancias)
8
+
9
+ if len(demandas_clientes) == num_nodos:
10
+ self.num_nodos = num_nodos
11
+ self.distancias = distancias
12
+ self.demandas_clientes = demandas_clientes ##El almacén ya está incluido
13
+ else:
14
+ raise IndexError("Las demandas no son igual al número de clientes")
15
+
16
+ class Vehiculos:
17
+ def __init__(self, capacidad_vehiculos: list[float], num_vehiculos: int = None):
18
+ if num_vehiculos == None: num_vehiculos = len(capacidad_vehiculos)
19
+
20
+ if len(capacidad_vehiculos) == num_vehiculos or num_vehiculos == None:
21
+ self.num_vehiculos = num_vehiculos
22
+ self.capacidad_vehiculos = sorted(capacidad_vehiculos)
23
+ else:
24
+ raise IndexError("Las capacidades no son igual al número de vehiculos")
25
+
26
+
27
+
28
+
pages/vrp/Reparto.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from .Origen import Grafo, Vehiculos
3
+
4
+ class Reparto:
5
+ grafo = None
6
+ vehiculos = None
7
+
8
+ def __init__(self, ruta = None):
9
+ if type(self.grafo) == Grafo and type(self.vehiculos) == Vehiculos:
10
+ if ruta == None:
11
+ ruta = list(range(-self.vehiculos.num_vehiculos + 1, self.grafo.num_nodos))
12
+ ruta.remove(0)
13
+
14
+ self.ruta = ruta
15
+ self.coste = self._coste()
16
+
17
+ def __str__(self):
18
+ return str((self.ruta,self.coste))
19
+
20
+ def _coste(self):
21
+ # Función de aptitud para evaluar la calidad de las soluciones
22
+ (distancia_total , demanda_total) = (0,0)
23
+ capacidades_actual = self.vehiculos.capacidad_vehiculos.copy()
24
+
25
+ rutas = division_rutas(self)
26
+
27
+ i = 1
28
+ for ruta in rutas:
29
+ distancia_total += coste_1ruta(ruta)
30
+ (demanda_actual, capacidades_actual) = capacidad_1ruta(ruta, capacidades_actual)
31
+ demanda_total += demanda_actual
32
+ i += 1
33
+
34
+ if demanda_total < float('inf'):
35
+ return distancia_total
36
+ else:
37
+ return float('inf')
38
+
39
+
40
+ def coste_1ruta(ruta : list[int]) -> int: #ruta = [1,2,3] sin #
41
+ recorrido = [0] + ruta + [0] #recorrido = [0,1,2,3,0]
42
+
43
+ distacia_total = 0
44
+
45
+ prev_nodo = 0
46
+ for nodo in recorrido[1:]:
47
+
48
+ distacia_total += Reparto.grafo.distancias[prev_nodo][nodo]
49
+ prev_nodo = nodo
50
+
51
+ return distacia_total
52
+
53
+ def capacidad_1ruta(ruta : list[int], capacidades_actual : list[int]) -> int: #ruta = [1,2,3] sin #
54
+ demanda_total = 0
55
+
56
+ for nodo in ruta:
57
+ demanda_total += Reparto.grafo.demandas_clientes[nodo]
58
+
59
+ if demanda_total <= capacidades_actual[-1]:
60
+ indice_capacidad = 0
61
+ while demanda_total > capacidades_actual[indice_capacidad]:
62
+ indice_capacidad += 1
63
+ capacidades_actual.pop(indice_capacidad)
64
+ return (demanda_total, capacidades_actual)
65
+ else:
66
+ return (float('inf'), capacidades_actual)
67
+
68
+ def division_rutas(reparto : Reparto) -> list[list[int]]:
69
+ result = []
70
+ ruta = []
71
+ for nodo in reparto.ruta:
72
+ if nodo >= 0:
73
+ ruta += [nodo]
74
+ else:
75
+ result += [ruta.copy()]
76
+ ruta = []
77
+ result += [ruta.copy()]
78
+ return result
79
+
80
+
pages/vrp/__init__.py ADDED
File without changes