Desarrollar un marco basado en agend para modelos de simulación en Python

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.

You May Also Like

Leave a Reply

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.