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