En una publicación anterior, construí un modelo de simulación simple basado en agentes, que contiene grupos de agentes que se pueden ubicar en una cuadrícula del campo de batalla. El modelo fue codificado en Python, usando matplotlib para la visualización.
Luego llevé a cabo una ejecución de simulación simple, mostrando un escenario de batalla y su resultado. A continuación, remodelo el problema, tratando de limpiar el código y modular la funcionalidad en funciones reutilizables. El resultado será un marco modular y una descripción de evaluación general que se puede utilizar para construir modelos de simulación basados en agentes muy avanzados.
Los agentes se modelan como una clase, como se muestra a continuación:
# clase, definiendo agentes como tipos de datos abstractos class agent: # init-method, el método constructor para agentes def __init__(self,x,y,group): self.life = 100 # agent's life score self.x = x self.y = y self.group = group
El campo de batalla en sí está modelado como una matriz de cuadrícula bidimensional, como se muestra a continuación:
# crear una lista vacía de 100 x 100 usando la comprensión de la lista en Python battlefield = [[None for i in range(0,100)] for i in range(0,100)]
Los grupos de agentes se pueden poblar en la cuadrícula del campo de batalla usando la función agentCreator:
# definir una función para crear agentes y asignarlos a la cuadrícula def agentCreator(size,group,groupList,field,n,m): # recorrer todo el grupo, es decir, en este caso 1000 unidades for j in range(0,size): # seleccionar ubicación aleatoria disponible while True: # coordenada x aleatoria x = random.choice(range(0,n)) # coordenada y aleatoria y = random.choice(range(0,m)) # comprobar si hay espacio disponible; si no, repita if field[x][y] == None: field[x][y] = agent(x=x,y=y,group=group) # agregar la referencia del objeto del agente a la lista de grupos groupList.append(field[x][y]) # salir mientras bucle; se toma el lugar en el campo break
Para trazar agentes con una puntuación de vida positiva como puntos visuales en la cuadrícula del campo de batalla, creo una función de trazado, que se puede llamar en cualquier momento durante la simulación para obtener una instantánea del estado actual de la batalla:
# importar pyplot y colores desde matplotlib from matplotlib import pyplot, colors # definir la función para trazar el campo de batalla (todos los agentes que aún están vivos) def plotBattlefield(populationArr, plotTitle): # usando colores de matplotlib, defina un mapa de colores colormap = colors.ListedColormap(["lightgrey","green","blue"]) # definir el tamaño de la figura usando pyplot pyplot.figure(figsize = (12,12)) # usando pyplot agregue un título pyplot.title(plotTitle, fontsize = 24) # usando pyplot agregue etiquetas xey pyplot.xlabel("x coordinates", fontsize = 20) pyplot.ylabel("y coordinates", fontsize = 20) # ajustar las marcas de los ejes xey, usando pyplot pyplot.xticks(fontsize = 16) pyplot.yticks(fontsize = 16) # use el método .imshow () de pyplot para visualizar las ubicaciones de los agentes pyplot.imshow(X = populationArr, cmap = colormap)
Otra función que necesitaré es una función que pueda asignar un estado de campo de batalla a una matriz bidimensional numérica que contenga valores de 1.0 si un agente de tipo A todavía está ubicado y vivo dentro de una celda dada de la cuadrícula de campo de batalla bidimensional, o el valor 2.0 si hay un agente de tipo B.Uso este proceso de mapeo para alimentar mi función de trazado con una cuadrícula numérica en lugar de una cuadrícula con objetos de una clase abstracta, es decir, personalizada:
# esta función asigna una cuadrícula del campo de batalla a una cuadrícula numérica con 1 para agentes de tipo A, 2 para tipo B y 0 para ningún agente def mapBattlefield(battlefieldArr): # .imshow () necesita una matriz con elementos flotantes; populationArr = [[0.0 for i in range(0,100)] for i in range(0,100)] # si el agente es de tipo A, poner 1.0, si es de tipo B, pyt 2.0 for i in range(1,100): for j in range(1,100): if battlefieldArr[i][j] == None: # vacía pass # dejar 0.0 en la celda de población elif battlefieldArr[i][j].group == "A": # agentes del grupo A populationArr[i][j] = 1.0 # 1.0 significa "A" else: # group B agents populationArr[i][j] = 2.0 # 2.0 significa "B" # devolver valores mapeados return(populationArr)
Usando estos componentes del modelo, creé una población inicial del campo de batalla y tracé las ubicaciones de los agentes usando matplotlib. Esto se hace en el código a continuación y es similar a mis publicaciones anteriores, solo que en este caso uso la funcionalidad modular. La funcionalidad de configurar una cuadrícula de campo de batalla inicial se transfiere a una función separada a continuación. Luego se ejecuta esa función.
# función para crear una cuadrícula de campo de batalla inicial def initBattlefield(populationSizeA,populationSizeB,battlefieldArr): # inicializando una nueva cuadrícula vacía del campo de batalla, usando la comprensión de listas en Python battlefieldArr = [[None for i in range(0,100)] for i in range(0,100)] # crear una lista vacía para contener referencias de agentes en el futuro, escriba A y B agents_A = [] agents_B = [] # asignar lugares aleatorios a los agentes del grupo A y B; import random agentCreator(size = populationSizeA, group = "A", groupList = agents_A, field = battlefieldArr, n = 100, m = 100) agentCreator(size = populationSizeB, group = "B", groupList = agents_B, field = battlefieldArr, n = 100, m = 100) # Volver cuadrícula de campo de batalla poblada return(battlefieldArr) # ejecutar la función anterior para un tamaño de población de 1000 para ambos grupos battlefield = initBattlefield(populationSizeA=1000,populationSizeB=1000,battlefieldArr = battlefield) # trazar el estado del campo de batalla plotBattlefield(populationArr = mapBattlefield(battlefield), plotTitle = "battlefield before simulation run (green = A, blue = B)")
En mi publicación anterior ejecuté una simulación basada en las siguientes reglas: El grupo A tiene la estrategia de golpear siempre al mismo agente en cada ronda. El grupo B tiene una estrategia aleatoria e independiente para atacar enemigos.
Esto significa que cada agente de tipo B atacará a un agente seleccionado al azar dentro del alcance de los agentes. La simulación se realizó en las siguientes condiciones:
– Cada ronda es una iteración.
– En cada ronda, cada agente puede atacar a un agente dentro de su alcance.
– El alcance de un agente se define al comienzo de la simulación y el valor predeterminado es 10.
– Si un agente muere, ya no estará ubicado en el campo de batalla.
– Un agente muere cuando su puntuación de vida es igual o inferior a cero.
– Cada agente tiene un daño de ataque distribuido aleatoriamente, que va de 10 a 60.
– En cada ronda, todos los agentes pueden lanzar un ataque.
Como en una de mis publicaciones anteriores, ahora recorreré 50 rondas de batalla. Después de cada ronda, los agentes con una puntuación de vida no positiva serán eliminados de la cuadrícula del campo de batalla. Sin embargo, como una desviación de mi publicación anterior, ahora modularé la funcionalidad. Primero, defino una función para eliminar agentes de la cuadrícula del campo de batalla:
# función para eliminar agentes de la cuadrícula del campo de batalla cuando la puntuación de vida no es estrictamente positiva def removeDeadAgents(battlefieldArr): # identificar agentes con una puntuación de vida de puntuación o inferior, y eliminarlos de la cuadrícula for i in range(0,len(battlefieldArr)): for j in range(0,len(battlefieldArr)): if battlefieldArr[i][j]: if battlefieldArr[i][j].life <= 0: # eliminar este agente ya que la puntuación de vida no es estrictamente positiva battlefieldArr[i][j] = None
A continuación, defino una función implementando la estrategia de combate para agentes de tipo A:
# función implementando una ronda de combate, para un agente de tipo A def oneRoundAgentA(i,j,attackRange): found_i = None found_j = None # buscar en las celdas vecinas en el mismo orden para cada iteración for k in range(i-attackRange,i+attackRange+1): for l in range(j-attackRange,j+attackRange+1): # comprobar valores de índice negativos; si es así, ¡rompe! if k < 0 or l < 0: break # ¡Compruebe si hay valores de índice superiores a 99, si es así, rompa! if k > 99 or l > 99: break if battlefield[k][l]: if battlefield[k][l].group == "B": # then this is an enemy if found_i == None: found_i = k found_j = l # infligir daño a enemigos identificados if found_i: battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
Then I do the same for agents of type B:
# función implementando una ronda de combate, para un agente de tipo B def oneRoundAgentB(i,j,attackRange): # primero verifica si hay un enemigo en una de las celdas circundantes enemy_found = False for k in range(i-attackRange,i+attackRange+1): for l in range(j-attackRange,j+attackRange+1): # comprobar el índice negativo, si es así, pasar a la siguiente iteración if k < 0 or l < 0: break # Compruebe si hay valores de índice superiores a 99, si es así, rompa if k > 99 or l > 99: break if battlefield[k][l] != None: if battlefield[k][l].group == "A": enemy_found = True # seleccione una fila aleatoria y una columna aleatoria found_i = None found_j = None while enemy_found and found_i == None: k = random.randint(i-attackRange,i+attackRange) l = random.randint(j-attackRange,j+attackRange) # compruebe el índice negativo, si es así, continúe con la siguiente iteración if k < 0 or l < 0: continue # compruebe el valor del índice> 99, si es así, continúe if k > 99 or l > 99: continue if k != i: if battlefield[k][l]: if battlefield[k][l].group == "A": found_i = k found_j = l else: if l != j: if battlefield[k][l]: if battlefield[k][l].group == "A": found_i = k found_j = l # infligir daño al enemigo identificado if found_i: battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
Procedo a simular la batalla, utilizando las funciones ya implementadas:
for counter in range(0,50): # en este caso estoy realizando 50 iteraciones # iterando a través de todas las celdas en el campo de batalla for x in range(0,len(battlefield)): for y in range(0,len(battlefield)): # print ("iteración de nivel superior, i:" + str (i) + ", j:" + str (j)) # comprobar si hay un agente dentro de la celda respectiva if battlefield[x][y] != None: # dependiendo del tipo: ejecutar la estrategia de ataque respectiva if battlefield[x][y].group == "A": # Una ronda de batalla para esta agente de tipo A(femininum) oneRoundAgentA(i = x, j = y,attackRange=10) else: # Una ronda de batalla para esta agente de tipo B(femininum) oneRoundAgentB(i = x, j = y,attackRange=10) # identificar agentes con una puntuación de vida de puntuación o inferior, y eliminarlos de la cuadrícula removeDeadAgents(battlefieldArr = battlefield) # trazar el estado del campo de batalla plotBattlefield(populationArr = mapBattlefield(battlefield), plotTitle = "battlefield after 50 iterations (green = A, blue = B)")
Hasta ahora, esto ha sido idéntico en contenido al estudio de simulación simple presentado en una publicación anterior.
Ahora quiero agregar un gráfico para analizar el resultado de la batalla y la progresión de la batalla en sí. Por lo tanto, defino una función de trazado adicional que traza el número de agentes por tipo, aún vivos.
También implemento una función para actualizar la serie temporal de agentes que aún están vivos.
# función para actualizar la serie temporal de agentes que están vivos def calcAgentsAliveA(resultsA,battlefieldArr): # estas variables se utilizarán para contar el número de agentes vivos countA = 0 countB = 0 # iterar a través de la cuadrícula, encontrar agentes y actualizar el recuento si es relevante for i in range(0,len(battlefieldArr)): for j in range(0,len(battlefieldArr)): if battlefieldArr[i][j]: if battlefieldArr[i][j].group == "A": countA = countA + 1 else: countB = countB + 1 # actualizar la lista de resultados y devolverla resultsA.append(countA) return(resultsA) # función para actualizar la serie temporal de agentes que están vivos def calcAgentsAliveB(resultsB,battlefieldArr): # estas variables se utilizarán para contar el número de agentes vivos countA = 0 countB = 0 # iterar a través de la cuadrícula, encontrar agentes y actualizar el recuento si es relevante for i in range(0,len(battlefieldArr)): for j in range(0,len(battlefieldArr)): if battlefieldArr[i][j]: if battlefieldArr[i][j].group == "A": countA = countA + 1 else: countB = countB + 1 # actualizar la lista de resultados y devolverla resultsB.append(countB) return(resultsB) # función para graficar el número de agentes aún vivos def plotNumberOfAgentsAlive(plotTitle,iterations,resultsA,resultsB): from matplotlib import pyplot pyplot.figure(figsize = (12,12)) pyplot.title(plotTitle, fontsize = 24) pyplot.xlabel("iteration", fontsize = 20) pyplot.ylabel("agents still alive", fontsize = 20) pyplot.xticks(fontsize = 16) pyplot.yticks(fontsize = 16) ax = pyplot.subplot() ax.plot(iterations,resultsA, label = "type a agents") ax.plot(iterations,resultsB, label = "type b agents") ax.legend(fontsize=16)
Ahora, puedo realizar otra ejecución de simulación usando, mostrando el gráfico. Como ejecutaré varios escenarios de simulación, también escribiré la ejecución de la simulación en una función:
# función de definición para realizar una ejecución de simulación def simulationRun(iterationLimit,attackRange,showPlot): iterations = [] resultsA = [] resultsB = [] for counter in range(0,iterationLimit): # en este caso estoy realizando 50iterations # actualización de iteraciones # actualizar resultados iterations.append(counter+1) resultsA = calcAgentsAliveA(resultsA,battlefield) resultsB = calcAgentsAliveB(resultsB,battlefield) # iterando a través de todas las celdas en el campo de batalla for x in range(0,len(battlefield)): for y in range(0,len(battlefield)): #print ("iteración de nivel superior, i:" + str (i) + ", j:" + str (j)) # comprobar si hay un agente dentro de la celda respectiva if battlefield[x][y]: # dependiendo del tipo: ejecutar la estrategia de ataque respectiva if battlefield[x][y].group == "A": # una ronda de batalla para esta agente de tipo A oneRoundAgentA(i = x, j = y, attackRange = attackRange) else: # una ronda de batalla para esta agente de tipo B oneRoundAgentB(i = x, j = y, attackRange = attackRange) # identificar agentes con una puntuación de vida de puntuación o inferior, y eliminarlos de la cuadrícula removeDeadAgents(battlefieldArr = battlefield) # trazar la progresión de la batalla, pero solo si se debe mostrar la trama if showPlot: plotNumberOfAgentsAlive("battle progression",iterations,resultsA,resultsB) # devolver resultados return([resultsA,resultsB])
Ahora puedo realizar una ejecución de simulación usando solo una línea de código:
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)
Parece que los agentes de tipo B tienen una estrategia de ataque algo eficaz. Pero, ¿qué pasa si su número es ligeramente menor cuando comienza la batalla? A continuación, trazo la progresión de la batalla en una pelea con 1000 agentes iniciales A y 950 agentes B iniciales.
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)
¿Qué sucede si el rango de ataque se reduce a 5?
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 5, showPlot = True)
La ubicación inicial de los agentes en el campo de batalla es aleatoria. El resultado final de la batalla también podría distribuirse aleatoriamente. Quiero investigar hasta qué punto los resultados son aleatorios. Para esto implemento una función de prueba de sensibilidad que realiza la simulación una y otra vez. Esta función devolverá resultados que se pueden visualizar en un histograma, que representa la cantidad total de agentes vivos al final de la simulación. En este caso repito la simulación 50 veces.
A continuación implemento la función que realiza la prueba de sensibilidad:
# esta función se utiliza para realizar una prueba de sensibilidad con respecto al resultado de la batalla def sensitivityTest(iterationLimit,attackRange,runs): # indica que el campo de batalla es una variable global global battlefield # listas vacías que contendrán el número final de agentes del tipo respectivo, al final de la batalla outcomeA = [] outcomeB = [] # repetir la simulación con rango de ataque definido y límite de iteración, para "ejecuciones" número de veces for i in range(0,runs): # antes de cada ejecución de simulación, se debe inicializar el campo de batalla battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield) # realizar una ejecución de simulación results = simulationRun(iterationLimit=iterationLimit,attackRange = attackRange,showPlot = False) # agregar resultado a la lista de resultados relevantes outcomeA.append(results[0][iterationLimit-1]) outcomeB.append(results[1][iterationLimit-1]) # convertir el resultado en una lista con dos sublistas return([outcomeA,outcomeB])
Below I implement the histogram plotting function:
# función para trazar un histograma def plotHistogram(plotTitle,resultsA,resultsB): from matplotlib import pyplot pyplot.figure(figsize = (12,12)) pyplot.title(plotTitle, fontsize = 24) pyplot.xlabel("number of agents still alive", fontsize = 20) pyplot.ylabel("absolute frequency", fontsize = 20) pyplot.xticks(fontsize = 16) pyplot.yticks(fontsize = 16) ax = pyplot.subplot() ax.hist(resultsA, bins=20, histtype="bar",color="red",label="agent A",alpha=0.25) ax.hist(resultsB, bins=20, histtype="bar",color="blue",label="agent B",alpha=0.25) ax.legend(fontsize=16)
Finalmente, el código siguiente ejecuta la prueba de sensibilidad y traza el resultado.
# ejecutando la prueba de sensibilidad results = sensitivityTest(iterationLimit = 50,attackRange = 5,runs = 50) plotHistogram(plotTitle = "distribution of agents",resultsA = results[0], resultsB = results[1])
Este resultado es para un rango de ataque de 5.
Con este enfoque, se puede desarrollar un modelo de simulación basado en agentes muy avanzado.
Ingeniero industrial especializado en optimización y simulación (R, Python, SQL, VBA)
Leave a Reply