Reto de la exploración optimizada
Un sistema multiagente (SMA) es un sistema compuesto por múltiples agentes que interactúan entre ellos. Los agentes no tienen por qué ser necesariamente inteligentes, a nivel individual.
Un sistema multiagente es un sistema distribuido en el cual los elementos son sistemas de inteligencia artificial, o bien un sistema distribuido donde la conducta combinada de dichos elementos produce un resultado en conjunto inteligente.
Es decir, hay dos enfoques para construir sistemas multiagentes:
El enfoque formal o clásico, que consiste en dotar de los agentes de la mayor inteligencia posible utilizando descripciones formales del problema que resolver.
El enfoque constructivista, que persigue la idea de brindarle inteligencia al conjunto de todos los agentes, para que, a través de mecanismos de interacción, el sistema mismo genere comportamiento inteligente que no necesariamente estaba definido dentro de los agentes mismos (que pueden ser realmente simples). Este tipo de conducta es habitualmente llamado comportamiento emergente.
Ventajas: Solucionan problemas complejos en un periodo de tiempo aceptable, siendo un sistema muy robusto a mínimos locales. Al ser los agentes, elementos independientes, es muy sencillo paralelizar su comportamiento.
Desventajas: No garantizan una solución óptima.
Existen multitud de sistemas multi-agente
- Optimización de la colonia de hormigas: algoritmos de optimización inspirada en las acciones de una colonia de hormigas. Método muy útil en problemas que necesitan encontrar caminos hacia metas. El objetivo es localizar soluciones óptimas moviéndose a través de un espacio de parámetros que representa todas las posibles soluciones. Las hormigas naturales utilizan las feromonas para orientarse entre ellas, señalizando los recursos y explorando su entorno.
- Algoritmo de manada
- Algoritmo de construcción de las termitas
En los últimos años han surgido multitud de algoritmos enjambre
- Optimización enjambre de partículas
- Optimización de múltiples enjambres
- Colonia de abejas
- Sistemas inmunológicos artificiales
- Búsqueda de sistema cargado
- Búsqueda gravitacional
- Caída inteligente de gotas de agua
- Optimización magnética
Si bien, este reto, se va a centrar en la optimización de la colonia de hormigas
Elementos de un sistema multiagente
- Entorno
- Es el problema que queremos resolver.
- Este es uno de los puntos más importantes del proceso, y por norma general no se le da la importancia necesaria. Cuando se estudian este tipo de algoritmos, se suele dar el entorno hecho para que el alumno se centre en el algoritmo. Pero sin diseñar el entorno, es imposible que se comprenda bien el algoritmo. Volveremos a esto en breve.
- Puede evolucionar con el paso del tiempo, ser modificable (o no), tener objetos (o no).
- Objetos
- Fuentes de alimento, bloques de construcción, ciudades que visitar, obstáculos que salvar… puede ser transportables (o no), temporales o permanentes. Las “BadZones” deben tener las características de alcance (radio) y tiempo restante de vida.
- Agentes
- Hay que definir el comportamiento de los agentes, las relaciones que tendrán entre sí (jerárquica o igual) y la comunicación entre ellos. Cada agente poseerá un conjunto de operaciones sobre los objetos (transportarlos, usarlos, señalarlos) y sobre los demás agentes (intercambiar objetos, información).
- Percepción del mundo
- Pueden tener una percepción global del entorno (ver el tablero completo, mapa completo, laberinto desde arriba) o una percepción localizada a su alrededor (como cuando estás dentro de un laberinto, solo puedes ver lo que tienes inmediatamente a tu alrededor). Distintos agentes podrían tener percepciones distintas del entorno.
- Toma de decisiones
- Debemos determinar cómo reaccionará un individuo en función de sus percepciones y unas reglas a aplicar (sistema experto, lógica difusa, redes neuronales). Con unas pocas reglas simples es posible resolver problemas muy complejos (las redes neuronales se basan en regresiones lineales). Se pueden agregar aspectos estocásticos (aleatoriedad) para permitir que el sistema sea más fluido y flexible.
- Cooperación y comunicación
- Debemos determinar cómo van a comunicarse los individuos. Podría ser con comunicación directa (dos individuos que se encuentran y utilizan un lenguaje) o comunicación asíncrona (dejando trazas en el entorno como las hormigas).
- Capacidad del agente
- Pueden tener todos las mismas capacidades o estar organizados en castas (donde cada uno tiene una tarea / capacidad distinta). Es posible que los agentes aprendan con el paso del tiempo o que tengan conocimientos fijos. Si se agrega aprendizaje hay que determinar al algoritmo de aprendizaje subyacente (redes neuronales, algoritmo genético…).
¿Qué usos podría tener un sistema multi agente?
Simulación de multitudes, análisis de reacciones en caso de evacuación, descubrir zonas de posibles aglomeraciones, planificación de tráfico, simulación de tropas, optimización de flotas de vehículos, organización de fábricas (búsqueda de rutas adaptándose a la circulación).
Pero también comprensión de fenómenos complejos, como el crecimiento de poblaciones de bacterias, control de vehículos no tripulados, la Nasa lo está estudiando para el autoensamblaje orbital, cartografía planetaria, detección y eliminación de tumores dentro del cuerpo, a través de nanorobots.
O para la minería. Ya sea de datos, o minería real.
Como veis, tiene multitud de aplicaciones.
¿Cuándo deberíamos usarlos?
Funcionamiento del algoritmo ACO
Las hormigas se comunican mediante feromonas depositadas en el suelo. Las exploradoras se desplazan aleatoriamente por el entorno, cuando encuentran comida, vuelven al hormiguero por el mismo camino dejando un rastro de feromonas (las hormigas recuerdan perfectamente cada paso que han realizado).
Las demás exploradoras que encuentran la feromona tienen tendencia a seguirla (la probabilidad depende de la cantidad de feromona depositada).
Las feromonas se evaporan de manera natural.
Siguiendo este sistema, las hormigas son capaces de determinar el camino más corto entre la colonia y el alimento.
Generación del entorno
Necesitamos un generador de entornos a resolver
En este caso, un generador de laberintos, que sea capaz de generar entornos con 0, 1 o N soluciones. Para que podamos poner a prueba el algoritmo, y ver qué tal funciona.
La generación de entornos es uno de los puntos más importantes a la hora de extrapolar conocimientos y adquirir la capacidad de resolver problemas con inteligencia artificial.
Prestar atención a la construcción de tu propio entorno es esencial (en vez de que te den uno hecho). Por ejemplo:
¿Son estos dos laberintos semejantes?
No
Aunque ambos tienen N soluciones, el de la izquierda tiene un problema de “rotondas” y el de la derecha de “rotondas +”plazas”. Por lo que es más complejo de resolver para las hormigas.
Reto de la exploración optimizada
En las páginas siguientes os facilito el código que genera, y resuelve un laberinto con rotondas y plazas (como el de la derecha). Sin embargo, la exploración es “ineficiente” debido a la existencia de ambos elementos.
¿Eres capaz de encontrar y programar una solución a ambos problemas, sin tocar la generación del laberinto?
Manda tu solución a gmelendez@grupobme.es
Guillermo Meléndez Alonso
Responsable del laboratorio de Inteligencia Artificial en Bolsas y Mercados Españoles Inntech
Director del máster de Inteligencia Artificial Aplicada a los Mercados Financieros en Instituto BME
Código para resolver laberinto con rotondas
import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
from IPython.display import clear_output
import time
import PySimpleGUI as sg
import easygui
class ant:
def __init__(self ,laberinto, hormiguero, muro, pasillo, comida, hormiga):
self.muro = muro
self.pasillo = pasillo
self.hormiguero = hormiguero
self.comida = comida
self.hormiga = hormiga
self.lista_movimientos = list()
self.lista_movimientos.append(np.array(np.where(laberinto == hormiguero))[:,0])
self.comida_encontrada = False
def moverse(self, laberinto, matriz_feromonas, matriz_fiel):
self.comida_encontrada = False
# Debemos anular la opción de que retroceda por donde ha venido.
mov_posibles = np.copy(matriz_feromonas)
if len(self.lista_movimientos) > 1:
mov_posibles[self.lista_movimientos[-2][0],self.lista_movimientos[-2][1]] = 0
# La hormiga mira alrededor para determinar cuantas opciones de camino tiene
subir = mov_posibles[self.lista_movimientos[-1][0]-1, self.lista_movimientos[-1][1]]
bajar = mov_posibles[self.lista_movimientos[-1][0]+1, self.lista_movimientos[-1][1]]
izq = mov_posibles[self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]-1]
der = mov_posibles[self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]+1]
# Debemos comprobar cuantas opciones de movimiento tiene. Si no tiene ninguna, debe retroceder y "anular" la casilla.
if subir + bajar + izq + der == 0:
if laberinto[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] != self.hormiguero:
# Retrocedemos una posición y anulamos la casilla, para que ninguna otra hormiga la pise
matriz_feromonas[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] = 0
laberinto[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] = self.muro
del self.lista_movimientos[-1]
else:
# Si no la reiniciamos, se queda bloqueada en el hormiguero.
self.matar_hormiga(laberinto)
else:
# Decidimos movimiento "aleatoriamente" en función de las feromonas
# Comparamos el número aleatorio con la distribución actual de feromonas por cada casilla.
# El intervalo en el que esté el número aleatorio será la dirección que elegirá.
# Ej con tres posibles caminos a=20% , b=50%, c=30%, si el número aleatorio sale 75% --> 75>20 (no lo elije), 75>20+50 (no lo elije), 75<(20+50+30) (lo elije)
feromona_total = subir + bajar + izq + der
subir = subir / feromona_total
bajar = bajar / feromona_total
izq = izq / feromona_total
der = der / feromona_total
posibilidades = np.array([subir, bajar, izq, der])
prox_mov = np.random.rand()
for pos in range(len(posibilidades)):
if prox_mov < sum(posibilidades[0:pos+1]):
break
if pos == 0: # Subimos
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0]-1, self.lista_movimientos[-1][1]]))
elif pos == 1: # Bajamos
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0]+1, self.lista_movimientos[-1][1]]))
elif pos == 2: # Izquierda
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]-1]))
else: # Derecha
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]+1]))
# Comprobamos la distancia que ha recorrido la hormiga. Si está perdida, la "matamos de hambre"
if len(self.lista_movimientos) > (laberinto.shape[0] * laberinto.shape[1]):
self.matar_hormiga(laberinto)
else:
# Mostramos el movimiento
self.mostrar_movimiento(laberinto)
# Si hemos encontrado la comida, depositamos las feromonas
if laberinto[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] == self.comida:
self.depositar_feromonas(laberinto, matriz_feromonas, matriz_fiel)
def mostrar_movimiento(self, laberinto):
# Mostramos el movimiento visualmente (debemos comprobar que no es el hormiguero ni la comida)
if laberinto[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] == self.pasillo:
laberinto[self.lista_movimientos[-1][0],self.lista_movimientos[-1][1]] = self.hormiga
# Borramos el movimiento anterior (volviendo a ser un pasillo)
if len(self.lista_movimientos) > 1:
if laberinto[self.lista_movimientos[-2][0],self.lista_movimientos[-2][1]] == self.hormiga:
laberinto[self.lista_movimientos[-2][0],self.lista_movimientos[-2][1]] = self.pasillo
def matar_hormiga(self, laberinto):
# Borramos visualmente la hormiga muerta
if laberinto[self.lista_movimientos[-2][0],self.lista_movimientos[-2][1]] == self.hormiga:
laberinto[self.lista_movimientos[-2][0],self.lista_movimientos[-2][1]] = self.pasillo
self.lista_movimientos = list()
self.lista_movimientos.append(np.array(np.where(laberinto == self.hormiguero))[:,0])
def depositar_feromonas(self, laberinto, matriz_feromonas, matriz_fiel):
# Podamos la solución encontrada y depositamos las feromonas
self.poda_solucion(laberinto, matriz_feromonas, matriz_fiel)
# Matamos la hormiga
self.comida_encontrada = True
self.matar_hormiga(laberinto)
def poda_solucion(self, laberinto, matriz_feromonas, matriz_fiel):
lista_movimientos = [list(x) for x in self.lista_movimientos]
# Posiciones sin repetición a chequear (para iterar sobre ellas)
pos_unicas = map(tuple, lista_movimientos)
pos_unicas = set(pos_unicas)
lista_pos_uni = [list(x) for x in pos_unicas]
# Localizamos y borramos todos los bucles que haya realizado la hormiga
distancia_maxima = 1
while distancia_maxima > 0:
# Calcular la distancia entre repeticiones, buscando el mayor bucle a eliminar
distancia_maxima = 0
for val in lista_pos_uni:
# Localización de repeticiones (cuando ha vuelto a estar en la misma celda)
repeticiones = [i for i, j in enumerate(lista_movimientos) if j == val]
if len(repeticiones) > 0:
distancia = repeticiones[-1] - repeticiones[0]
if distancia > distancia_maxima:
distancia_maxima = distancia
# Eliminamos el bucle identificado
inicio = repeticiones[0]
fin = repeticiones[-1]
lista_movimientos[inicio:fin] = []
# Mostramos el camino y depositamos las feromonas, excepto la 1ª y última posición (hormiguero, comida)
for paso in range(1, len(lista_movimientos)-1):
laberinto[lista_movimientos[paso][0],lista_movimientos[paso][1]] = 6
matriz_feromonas[lista_movimientos[paso][0],lista_movimientos[paso][1]] += 1/len(self.lista_movimientos)
# Evaporamos un porcentaje de las feromonas de las casillas
matriz_feromonas[laberinto==self.pasillo] = matriz_feromonas[laberinto==self.pasillo] * 0.9
# Reconstruimos el camino que ha seguido, para la hormiga fiel
for paso in range(1, len(lista_movimientos)-1):
if matriz_fiel[lista_movimientos[paso][0], lista_movimientos[paso][1]] > (len(lista_movimientos) - paso -1):
matriz_fiel[lista_movimientos[paso][0], lista_movimientos[paso][1]] = (len(lista_movimientos) - paso -1)
# Pintamos el laberinto con la ruta seguida y la asignación de feromonas.
self.pinta_laberinto(laberinto, matriz= matriz_feromonas, mostrar = False)
time.sleep(1)
# Borramos el camino, para que el programa pueda continuar
laberinto[laberinto==6] = self.pasillo
def movimiento_fiel(self, laberinto, matriz_fiel):
# Miramos alrededor
subir = matriz_fiel[self.lista_movimientos[-1][0]-1, self.lista_movimientos[-1][1]]
bajar = matriz_fiel[self.lista_movimientos[-1][0]+1, self.lista_movimientos[-1][1]]
izq = matriz_fiel[self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]-1]
der = matriz_fiel[self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]+1]
if subir == min(subir, bajar, izq, der):
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0]-1, self.lista_movimientos[-1][1]]))
elif bajar == min(subir, bajar, izq, der):
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0]+1, self.lista_movimientos[-1][1]]))
elif izq == min(subir, bajar, izq, der):
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]-1]))
else:
self.lista_movimientos.append(np.array([self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]+1]))
# Marcamos el movimiento en el laberinto
laberinto[self.lista_movimientos[-1][0], self.lista_movimientos[-1][1]] = 8
def pinta_laberinto(self, laberinto, matriz, mostrar = False):
if mostrar == False:
clear_output(wait=True)
plt.figure(figsize = (laberinto.shape[0]/2.5, laberinto.shape[1]/2.5))
plt.imshow(laberinto, cmap='hot')
plt.show()
else:
# Mostramos la cantidad de feromonas que tiene cada celda
clear_output(wait=True)
values = matriz
# Limites para cuadrar el texto
x_start = 0
x_end = laberinto.shape[1]
y_start = 0
y_end = laberinto.shape[0]
extent = [x_start, x_end, y_start, y_end]
# Creamos el gráfico normal
fig = plt.figure(figsize = (laberinto.shape[0]/2.5, laberinto.shape[1]/2.5))
ax = fig.add_subplot(111)
im = ax.imshow(laberinto, extent=extent, cmap='hot')
# Añadimos el texto
jump_x = (x_end - x_start) / (2.0 * laberinto.shape[1])
jump_y = (y_end - y_start) / (2.0 * laberinto.shape[0])
x_positions = np.linspace(start=x_start, stop=x_end, num=laberinto.shape[1], endpoint=False)
y_positions = np.linspace(start=y_start, stop=y_end, num=laberinto.shape[0], endpoint=False)
if laberinto.shape[0] < 70 and laberinto.shape[1] < 70 and laberinto.shape[0] == laberinto.shape[1]:
tamaño_letra = 8
else:
tamaño_letra = 5
for y_index, y in enumerate(reversed(y_positions)):
for x_index, x in enumerate(x_positions):
label = values[y_index, x_index]
text_x = x + jump_x
text_y = y + jump_y
ax.text(text_x, text_y, label, color='black', ha='center', va='center', size=tamaño_letra)
plt.show()
class laberinto:
def __init__(self):
self.muro = 0
self.pasillo = 10
self.hormiguero = 5
self.comida = 7
self.hormiga = 2
def aco(self):
try:
# Construimos el laberinto y depositamos las feromonas iniciales
self.laberinto, self.matriz_feromonas, self.matriz_fiel = self.construye_laberinto(muro = self.muro, pasillo = self.pasillo, hormiguero = self.hormiguero, comida = self.comida)
# El número de hormigas dependerá del tamaño del laberinto
n_hormigas = int(round((sum(self.laberinto.shape)/2)))
lista_hormigas = [ant(self.laberinto, muro = self.muro, pasillo = self.pasillo, hormiguero = self.hormiguero,
comida = self.comida, hormiga = self.hormiga) for ele in range(n_hormigas)]
# Comprobamos movimientos
veces_comida_encontrada = 0
iteraciones = 0
while veces_comida_encontrada < (self.laberinto.shape[0]*self.laberinto.shape[1]/100):
iteraciones += 1
if iteraciones > self.laberinto.shape[0]*self.laberinto.shape[1] and veces_comida_encontrada == 0:
easygui.msgbox("Este laberinto no tiene solución.\n \nCuando el laberinto es muy pequeño esto puede pasar.\n \nVuelve a intentarlo con uno más grande.\n \n",
title="Conclusión")
break
for ele in lista_hormigas:
ele.moverse(laberinto= self.laberinto, matriz_feromonas= self.matriz_feromonas, matriz_fiel= self.matriz_fiel)
if ele.comida_encontrada == True:
veces_comida_encontrada += 1
self.pinta_laberinto(self.laberinto, matriz= self.matriz_feromonas, mostrar = False)
# Hormiga fiel
if veces_comida_encontrada > 0:
fiel = ant(self.laberinto, muro = self.muro, pasillo = self.pasillo, hormiguero = self.hormiguero,
comida = self.comida, hormiga = self.hormiga)
while self.matriz_fiel[fiel.lista_movimientos[-1][0], fiel.lista_movimientos[-1][1]] != 1:
fiel.movimiento_fiel(self.laberinto, self.matriz_fiel)
self.pinta_laberinto(self.laberinto, matriz= self.matriz_feromonas, mostrar = False)
self.laberinto[self.laberinto==8] = self.pasillo
easygui.msgbox("Solución encontrada", title="Camino óptimo")
except:
print("""Los laberintos pueden tener 0, 1, o N soluciones. Aunque pasa muy pocas veces, es posible que el hormiguero esté atrapado
y las hormigas no tengan adonde ir. No pasa nada, símplemente, inténtalo de nuevo""")
def pinta_laberinto(self, laberinto, matriz, mostrar = False):
if mostrar == False:
clear_output(wait=True)
plt.figure(figsize = (laberinto.shape[0]/2.5, laberinto.shape[1]/2.5))
plt.imshow(laberinto, cmap='hot')
plt.show()
else:
# Mostramos la cantidad de feromonas que tiene cada celda
clear_output(wait=True)
values = matriz
# Limites para cuadrar el texto
x_start = 0
x_end = laberinto.shape[1]
y_start = 0
y_end = laberinto.shape[0]
extent = [x_start, x_end, y_start, y_end]
# Creamos el gráfico normal
fig = plt.figure(figsize = (laberinto.shape[0]/2.5, laberinto.shape[1]/2.5))
ax = fig.add_subplot(111)
im = ax.imshow(laberinto, extent=extent, cmap='hot')
# Añadimos el texto
jump_x = (x_end - x_start) / (2.0 * laberinto.shape[1])
jump_y = (y_end - y_start) / (2.0 * laberinto.shape[0])
x_positions = np.linspace(start=x_start, stop=x_end, num=laberinto.shape[1], endpoint=False)
y_positions = np.linspace(start=y_start, stop=y_end, num=laberinto.shape[0], endpoint=False)
if laberinto.shape[0] < 70 and laberinto.shape[1] < 70 and laberinto.shape[0] == laberinto.shape[1]:
tamaño_letra = 8
else:
tamaño_letra = 5
for y_index, y in enumerate(reversed(y_positions)):
for x_index, x in enumerate(x_positions):
label = values[y_index, x_index]
text_x = x + jump_x
text_y = y + jump_y
ax.text(text_x, text_y, label, color='black', ha='center', va='center', size=tamaño_letra)
plt.show()
def tamaño_laberinto(self):
layout = [[sg.Text("¿Cómo quieres de alto el laberinto? \n \nPrueba con 50, por ejemplo")],
[sg.Input()],
[sg.Button('Ok')] ]
sg.theme('Reddit')
window = sg.Window('Selección de alto ', layout, finalize = True)
window.BringToFront()
event, alto = window.read()
window.close()
layout = [[sg.Text("¿Cómo quieres de ancho el laberinto? \n \nPrueba con 100, por ejemplo")],
[sg.Input()],
[sg.Button('Ok')] ]
sg.theme('Reddit')
window = sg.Window('Selección de ancho ', layout, finalize = True)
window.BringToFront()
event, ancho = window.read()
window.close()
try:
alto = int(alto[0])
ancho = int(ancho[0])
except:
print("Debes indicar un número para el tamaño")
alto, ancho = tamaño_laberinto()
return alto, ancho
def construye_laberinto(self, muro = 0, pasillo = 10, hormiguero = 5, comida = 7):
'''
Lo primero que debemos hacer es construir un entorno en el que podamos poder a prueba la colonia de hormigas
Este punto es muy importante. La manera en la que construyamos el laberinto puede hacer que sea muy fácil de resolver (solo haya una solución)
Queremos generar laberintos aleatorios que puedan tener entre 0 y N soluciones
Por otro lado, el cómo hagamos los caminos puede derivar en "rotondas" (como en la solución de excel), o "plazas" (como está programado aquí)
En estas rotondas o plazas será donde la hormiga tendrá más dificultad a la hora de orientarse
Por otro lado, La relación entre la feromona inicial y la feromono depositada (en función a la distancia) debe estar en perfecto equilibrio o el enjambre no funcionará
Prestad atención a que la cantidad de feromona inicial es muy distinta si el laberinto es cuadrado u horizontal. Hay que prestar mucha atención a este punto
'''
alto, ancho = self.tamaño_laberinto()
laberinto = np.zeros((alto, ancho))
# Construimos los caminos del laberinto
for camino in range(int(round((alto * ancho)/ 5))):
inicio_ok = False
while inicio_ok == False:
fila = np.random.randint(low = 1, high = alto-1, size = 1)
columna = np.random.randint(low = 1, high = ancho -1, size = 1)
if laberinto[fila, columna] == muro:
inicio_ok = True
if camino == 0:
laberinto[fila, columna] = hormiguero
else:
laberinto[fila, columna] = pasillo
# A partir de la posición inicial, comenzamos a "tirar muros"
# Condiciones de parada: que llegue a un pasillo o al muro exterior
direccion = np.random.randint(low = 1, high = 4, size = 1)
stop = False
pos_fila = fila
pos_col = columna
longitud = 0
while stop == False:
if direccion == 1: # subimos
# Comprobamos que la nueva posición es un muro y que no está en los límites
if (laberinto[pos_fila - 1, pos_col] == muro and (pos_fila - 1) != 0 and laberinto[pos_fila, pos_col +1] == muro and laberinto[pos_fila, pos_col -1] == muro and longitud < round(laberinto.shape[0]/4)):
laberinto[pos_fila - 1, pos_col] = pasillo
pos_fila = pos_fila - 1
longitud += 1
else:
stop = True
elif direccion == 2: # bajamos
# Comprobamos que la nueva posición es un muro y que no está en los límites
if (laberinto[pos_fila + 1, pos_col] == muro and (pos_fila + 1) != laberinto.shape[0]-1 and laberinto[pos_fila, pos_col +1] == muro and laberinto[pos_fila, pos_col -1] == muro and longitud < round(laberinto.shape[0]/4)):
laberinto[pos_fila + 1, pos_col] = pasillo
pos_fila = pos_fila + 1
longitud += 1
else:
stop = True
elif direccion == 3: # vamos a la derecha
# Comprobamos que la nueva posición es un muro y que no está en los límites
if (laberinto[pos_fila, pos_col + 1] == muro and (pos_col + 1) != laberinto.shape[1]-1 and laberinto[pos_fila +1, pos_col] == muro and laberinto[pos_fila-1, pos_col] == muro and longitud < round(laberinto.shape[0]/4)):
laberinto[pos_fila, pos_col + 1] = pasillo
pos_col = pos_col + 1
longitud += 1
else:
stop = True
else: # vamos a la izquierda
# Comprobamos que la nueva posición es un muro y que no está en los límites
if (laberinto[pos_fila, pos_col - 1] == muro and (pos_col - 1) != 0 and laberinto[pos_fila +1, pos_col] == muro and laberinto[pos_fila-1, pos_col] == muro and longitud < round(laberinto.shape[0]/4)):
laberinto[pos_fila, pos_col - 1] = pasillo
pos_col = pos_col - 1
longitud += 1
else:
stop = True
# La última posición será la comida
laberinto[fila, columna] = self.comida
# La relación entre la feromona inicial y la feromono depositada (en función a la distancia) debe estar en perfecto equilibrio o el enjambre no funcionará
feromona_inicial = 1 / (sum(laberinto.shape)/2)
# Depositamos la feromona inicial en todas las casillas que no sean muros
matriz_feromonas = np.zeros((laberinto.shape[0], laberinto.shape[1]))
matriz_feromonas[laberinto != muro] = feromona_inicial
# Depositamos una feromona muy alta en la comida, para que si pasan a su lado, la vean.
matriz_feromonas[laberinto == comida] = feromona_inicial*1000
# Por último, pintamos el laberinto terminado
self.pinta_laberinto(laberinto, matriz= matriz_feromonas, mostrar = False)
# Creamos una matriz_fiel para la hormiga fiel
matriz_fiel = np.copy(laberinto)
matriz_fiel[laberinto == self.muro] = 1000
matriz_fiel[laberinto == self.pasillo] = 1000
matriz_fiel[laberinto == self.hormiguero] = 1000
matriz_fiel[laberinto == self.comida] = 0
return laberinto, matriz_feromonas, matriz_fiel
def mostrar_matriz_feromonas(self):
# Mostramos la matriz de feromonas
self.matriz_feromonas = np.round(self.matriz_feromonas, 2)
self.pinta_laberinto(self.laberinto, matriz = self.matriz_feromonas, mostrar = True)
def mostrar_matriz_hormiga_fiel(self):
# Mostramos las rutas para la hormiga fiel
self.matriz_fiel[self.matriz_fiel==1000]=0
self.matriz_fiel = np.round(self.matriz_fiel, 0)
self.pinta_laberinto(self.laberinto, matriz = self.matriz_fiel, mostrar = True)
Instanciamos el laberinto a resolver por el método Ant Colony Optimization
laberinto_a_resolver = laberinto()
laberinto_a_resolver.aco()
También podemos mostrar la matriz de feromonas que guía a las hormigas
El laberinto puede ser tan complejo como nosotros queramos
matriz de feromonas que guía a las hormigas
laberinto_a_resolver.mostrar_matriz_feromonas()
Así como mostrar la matriz de caminos de la hormiga fiel
Matriz de caminos de la hormiga fiel
laberinto_a_resolver.mostrar_matriz_feromonas()