Udvikling af en agent-baseret modelramme for simuleringer i Python

I et tidligere indlæg konstruerede jeg en simpel agentbaseret simulationsmodel. Modellen indeholder grupper af agenter der kan placeres på en slagmark. Modellen blev kodet i Python og benytter matplotlib til visualisering. I mit tidligere indlæg fortsatte jeg med at gennemføre en simulation. Simulationen viste et kampscenarie mellem to grupper af agenter og dets resultat.

Nedenfor ændrer jeg problemet, forsøger at rydde op i koden og modulere funktionaliteten til genbrugelige funktioner. Resultatet vil være en modulær ramme og en generel beskrivelse som kan bruges til at opbygge meget komplicerede og fremskredne agentbaserede simuleringsmodeller.

Agenterne er modelleret som en klasse. Klassen er vist nedefor:

# klasse, der definerer agenter som abstrakte datatyper
class agent:
    # init-metode, konstruktormetoden til agenter
    def __init__(self,x,y,group):
        self.life = 100 # agentens livscore
        self.x = x
        self.y = y
        self.group = group

Selve slagmarken er modelleret som et todimensionalt array, som vist nedenfor:

# opretter tom 100 x 100 liste ved hjælp af listeforståelse i python
battlefield = [[None for i in range(0,100)] for i in range(0,100)]

Agentgrupper kan befolkes på slagmarkens gitter ved hjælp af agentCreator-funktionen:

# definer en funktion til oprettelse af agenter og tildeling af dem til gitter
def agentCreator(size,group,groupList,field,n,m):
    # loop gennem hele gruppen, dvs. i dette tilfælde 1000 enheder
    for j in range(0,size):
        # vælg tilfældig tilgængelig placering
        while True:
            # tilfældig x koordinat
            x = random.choice(range(0,n))
            # tilfældig y koordinat
            y = random.choice(range(0,m))
            # kontroller om stedet er tilgængeligt; hvis ikke så gentages det
            if field[x][y] == None:
                field[x][y] = agent(x=x,y=y,group=group)
                # tilføj reference til agentobjekt til gruppelisten
                groupList.append(field[x][y])
                # exit under loop; stedet på marken tages
                break

For at plotte agenter med en positiv livscore som visuelle prikker på slagmarkens gitter opretter jeg en plotningsfunktion, som når som helst kan kaldes igennem hele simuleringen for at få et øjebliksbillede af den aktuelle kampstatus:

# importer pyplot og farver fra matplotlib
from matplotlib import pyplot, colors
# definer funktion til at planlægge slagmarken (alle agenter, der stadig er i live)
def plotBattlefield(populationArr, plotTitle):
    # Brug farver fra matplotlib til at definere et farvekort
    colormap = colors.ListedColormap(["lightgrey","green","blue"])
    # definer figurstørrelse ved hjælp af pyplot
    pyplot.figure(figsize = (12,12))
    # ved hjælp af pyplot tilføj en titel
    pyplot.title(plotTitle, fontsize = 24)
    # ved hjælp af pyplot tilføj x- og y-etiketter
    pyplot.xlabel("x coordinates", fontsize = 20)
    pyplot.ylabel("y coordinates", fontsize = 20)
    # juster x- og y-aksemærker ved hjælp af pyplot
    pyplot.xticks(fontsize = 16)
    pyplot.yticks(fontsize = 16)
    # brug metoden .imshow () fra pyplot til at visualisere agentplaceringer
    pyplot.imshow(X = populationArr, cmap = colormap)

En anden funktion, som jeg har brug for, er en funktion, der kan kortlægge en slagmarkstatus til et numerisk todimensionelt array med værdier på 1,0 hvis en agent af type A og 2,0 hvis der er en agent af type B. Funktionen tilordner værdien 0,0 hvis den respektive celle på slagmarkens gitter ikke indeholder en agent. Jeg bruger denne kortlægningsproces til at forberede min plotfunktion of for at overgive et numerisk gitter i stedet for et gitter med objekter fra et abstrakte agentobjekter:

# denne funktion kortlægger et slagmarkgitter til et numerisk gitter med 1 for agenter af type A, 2 for type B og 0 for ingen agent
def mapBattlefield(battlefieldArr):
    #.imshow () har brug for en matrix med flydelementer;
    populationArr = [[0.0 for i in range(0,100)] for i in range(0,100)]
    #hvis agenten er af type A, skal du sætte en 1.0, hvis af type B, pyt a 2.0
    for i in range(1,100):
        for j in range(1,100):
            if battlefieldArr[i][j] == None: # tom
                pass # efterlad 0,0 i befolkningscelle
            elif battlefieldArr[i][j].group == "A": # gruppe A-agenter
                populationArr[i][j] = 1.0 # 1.0 betyder"A"
            else: # gruppe B-agenter
                populationArr[i][j] = 2.0 # 2.0 betyder"B"
    # returner kortlagte værdier
    return(populationArr)

Ved hjælp af disse modelkomponenter opretter jeg en startpopulation på slagmarken og plotter agenternes placeringer ved hjælp af Matplotlib. Dette gøres i nedenstående kode og ligner mine tidligere indlæg, med den forskel at jeg i dette tilfælde bruger modulær funktionalitet.

# funktion til oprettelse af et indledende slagmarkgitter
def initBattlefield(populationSizeA,populationSizeB,battlefieldArr):
    # initialisering af nyt tomt slagmarkgitter ved hjælp af listeforståelse i Python
    battlefieldArr = [[None for i in range(0,100)] for i in range(0,100)]
    # Opret en tom liste til at indeholde agentreferencer i fremtiden, skriv A & B
    agents_A = []
    agents_B = []
    # tildele tilfældige pletter til agenter i gruppe A og 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)
    # returnerer befolket slagmarknet
    return(battlefieldArr)

# udfører ovenstående funktion til en befolkningsstørrelse på 1000 for begge grupper
battlefield = initBattlefield(populationSizeA=1000,populationSizeB=1000,battlefieldArr = battlefield)

# plot battlefield status
plotBattlefield(populationArr = mapBattlefield(battlefield), 
                    plotTitle = "battlefield before simulation run (green = A, blue = B)")

I mit tidligere indlæg kørte jeg derefter en simuleringskørsel baseret på følgende regler:

Gruppe A har strategien om altid at ramme den samme agent i hver runde

Gruppe B har en tilfældig og uafhængig strategi for angreb på fjender. Dette betyder, at hver agent af type B vil angribe en tilfældigt valgt agent inden for agentens rækkevidde.

Simuleringen blev udført under følgende betingelser:

1) Hver runde er en iteration

2) I hver runde kan hver agent angribe én anden agent indenfor sin rækkevidde

3) En agents rækkevidde defineres i starten af ​​simuleringen og er som standard 10

4) Hvis en agent dør vil agenten ikke længere være placeret på slagmarken

5) En agent dør når dens livscore er lig med eller mindre end nul (0)

6) Hver agent har en tilfældigt fordelt angrebsskade, der spænder fra 10 til 60

7) I hver runde kan alle agenter starte et enkelt angreb

Som i et af mine tidligere indlæg vil jeg nu gentage 50 kamprunder i mit simulationsmodel. Efter hver runde fjernes agenter med en ikke-positiv livscore fra slagmarkens gitter. Som en afvigelse fra mit tidligere indlæg anvender jeg i dette tilfælde modulær funktionalitet, dvs. en mere overskuelig kode.

Først definerer jeg en funktion til fjernelse af agenter fra slagmarkens gitter:

# funktion til fjernelse af agenter fra slagmarkens gitter, når livscore ikke er strengt positiv
def removeDeadAgents(battlefieldArr):
    # identificere agenter med livscore på score eller derunder - og fjerne dem fra nettet
    for i in range(0,len(battlefieldArr)):
        for j in range(0,len(battlefieldArr)):
            if battlefieldArr[i][j]:
                if battlefieldArr[i][j].life <= 0:
                    # fjern denne agent, da livsscore ikke er strengt positivt
                    battlefieldArr[i][j] = None

Dernæst definerer jeg en funktion til implementering kampstrategien for agenter af type A:

# funktion, der gennemfører en kamprunde for en agent af type A
def oneRoundAgentA(i,j,attackRange):
    found_i = None
    found_j = None
    # se i naboceller i samme rækkefølge for hver iteration
    for k in range(i-attackRange,i+attackRange+1):
        for l in range(j-attackRange,j+attackRange+1):
            # tjek for negative indeksværdier; hvis ja - pause!
            if k < 0 or l < 0:
                break
                # tjek for indeksværdier over 99, hvis ja bryde!
            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
                    
    # behandle skader på identificerede fjender
    if found_i:
        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)

Så gør jeg det samme for agenter af type B:

# funktion, der implementerer en kamprunde for en agent af type B
def oneRoundAgentB(i,j,attackRange):
    # Kontroller først, om der overhovedet er en fjende i en af ​​de omkringliggende celler
    enemy_found = False
    for k in range(i-attackRange,i+attackRange+1):
        for l in range(j-attackRange,j+attackRange+1):
            # tjek for negativt indeks, hvis det er tilfældet, gå til næste iteration
            if k < 0 or l < 0:
                break
                # tjek for indeksværdier over 99, hvis ja bryde
            if k > 99 or l > 99:
                break
            if battlefield[k][l] != None:
                if battlefield[k][l].group == "A":
                    enemy_found = True
    # vælg en tilfældig række og en tilfældig kolonne
    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)
        # tjek for negativt indeks, hvis så fortsæt til næste iteration
        if k < 0 or l < 0:
            continue
        # tjek for indeksværdi> 99, hvis det fortsætter
        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
    # behandle skader på identificeret fjende
    if found_i:
        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)

Jeg fortsætter med at simulere kampen ved hjælp af de allerede implementerede funktioner:

for counter in range(0,50): # i dette tilfælde gennemfører jeg 50 gentagelser
    # iterer gennem alle celler på slagmarken
    for x in range(0,len(battlefield)):
        for y in range(0,len(battlefield)):
            # print("top tier iteration, i: "+str(i)+", j: "+str(j))
            # kontroller, om der er en agent inden for den respektive celle
            if battlefield[x][y] != None:
                # afhængigt af typen: udfør den respektive angrebsstrategi
                if battlefield[x][y].group == "A":
                    # en kamprunde om denne agent af type A
                    oneRoundAgentA(i = x, j = y,attackRange=10)
                else: 
                    # en kamprunde om denne agent af type B
                    oneRoundAgentB(i = x, j = y,attackRange=10)
    # identificere agenter med livscore på score eller derunder - og fjerne dem fra nettet
    removeDeadAgents(battlefieldArr = battlefield)
# plot slagmark status
plotBattlefield(populationArr = mapBattlefield(battlefield), 
                plotTitle = "battlefield after 50 iterations (green = A, blue = B)")

Indtil videre har denne fremgangsmåde indholdsmæssigt været identisk med den enkle simulationsundersøgelse fra mit tidligere indlæg.

Jeg vil nu tilføje en graf for at analysere kampresultatet og kampprogression. Jeg definerer således en yderligere plottefunktion. Funktionen plotter antallet af levende agenter efter type.

Jeg implementerer også en funktion til opdatering af tidsserien for antallet af levende agenter.

# funktion til opdatering af tidsserien for agenter, der er i live
def calcAgentsAliveA(resultsA,battlefieldArr):
    # disse variabler vil blive brugt til at tælle antallet af agenter i live
    countA = 0
    countB = 0
    # gentag gennem gitteret, find agenter og opdater antallet, hvis det er relevant
    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
    # opdater resultatlisten og returner den
    resultsA.append(countA)
    return(resultsA)

# funktion til opdatering af tidsserien for agenter, der er i live
def calcAgentsAliveB(resultsB,battlefieldArr):
    # disse variabler vil blive brugt til at tælle antallet af agenter i live
    countA = 0
    countB = 0
    # gentag gennem gitteret, find agenter og opdater antallet, hvis det er relevant
    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
    # opdater resultatlisten og returner den
    resultsB.append(countB)
    return(resultsB)

# funktion til at planlægge antallet af agenter, der stadig er i live
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)

Nu kan jeg udføre endnu en simuleringskørsel ved at vise grafen. Da jeg vil køre forskellige simuleringsscenarier, vil jeg også skrive simuleringskørslen til en funktion:

# definerende funktion til udførelse af en simuleringskørsel
def simulationRun(iterationLimit,attackRange,showPlot):
    iterations = []
    resultsA = []
    resultsB = []
    for counter in range(0,iterationLimit): # in this case I am conducting 50 iterations 
        # opdater iterationer
        # opdater resultater
        iterations.append(counter+1)
        resultsA = calcAgentsAliveA(resultsA,battlefield)
        resultsB = calcAgentsAliveB(resultsB,battlefield)
        # iterer gennem alle celler på slagmarken
        for x in range(0,len(battlefield)):
            for y in range(0,len(battlefield)):
                # print("top tier iteration, i: "+str(i)+", j: "+str(j))
                # kontroller, om der er en agent inden for den respektive celle
                if battlefield[x][y]:
                    # afhængigt af typen: udfør den respektive angrebsstrategi
                    if battlefield[x][y].group == "A":
                        # en kamprunde om denne agent af type A
                        oneRoundAgentA(i = x, j = y, attackRange = attackRange)
                    else: 
                        # en kamprunde om denne agent af type B
                        oneRoundAgentB(i = x, j = y, attackRange = attackRange)
        # identificere agenter med livscore på score eller derunder - og fjerne dem fra nettet
        removeDeadAgents(battlefieldArr = battlefield)
    # progression af plotkamp, ​​men kun hvis plot skal vises
    if showPlot:
        plotNumberOfAgentsAlive("battle progression",iterations,resultsA,resultsB)
    # returnerer resultater
    return([resultsA,resultsB])

Jeg kan nu udføre en simuleringskørsel kun ved hjælp af en enkelt linje kode:

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)

Agenter af type B har en åbenbart en ret effektiv angrebsstrategi. Men hvad hvis deres antal er lidt lavere ved kampens begyndelse? Nedenfor tegner jeg kampprogression i en kamp med 1000 oprindelige A- og 950 oprindelige B-agenter.

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)

Hvad sker der, hvis angrebsområdet reduceres til 5?

battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield)
results = simulationRun(iterationLimit = 50, attackRange = 5, showPlot = True)

Agenternes indledende placering på slagmarken er tilfældig. Det endelige kampresultat kan således også fordeles tilfældigt. Jeg vil undersøge i hvilket omfang resultaterne er randomiseret. Til dette implementerer jeg en følsomhedstestfunktion, som udfører gentagelser af simuleringen igen og igen. Denne funktion returnerer resultater, som kan visualiseres i et histogram over den samlede mængde af levende agenter simulationens sluttidspunkt. I dette tilfælde gentager jeg simuleringen 50 gange.

Nedenfor implementerer jeg funktionen til gennemførsel af sensitivitetstesten (følsomhedstest):

# denne funktion bruges til at udføre en følsomhedstest med hensyn til kampresultatet
def sensitivityTest(iterationLimit,attackRange,runs):
    # angiver, at slagmarken er en global variabel
    global battlefield
    # tomme lister, der vil indeholde det endelige antal agenter af den respektive type i slutningen af ​​kampen
    outcomeA = []
    outcomeB = []
    # gentagelse af simuleringen med defineret angrebsområde og iterationsgrænse for "kører" antal gange
    for i in range(0,runs):
        # før hver simuleringskørsel skal slagmarken initialiseres
        battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield)
        # udfør simuleringskørsel
        results = simulationRun(iterationLimit=iterationLimit,attackRange = attackRange,showPlot = False)
        # tilføj resultat til relevant resultatliste
        outcomeA.append(results[0][iterationLimit-1])
        outcomeB.append(results[1][iterationLimit-1])
    # returnerer resultatet i en liste med to-underlister
    return([outcomeA,outcomeB])

Nedenfor implementerer jeg histogramplotningsfunktionen:

# funktion til at plotte et histogram
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)

Aflusttende udfører koden nedenfor seensitivitetstesten og plotter resultatet.

# udfører sensitivitetstest
results = sensitivityTest(iterationLimit = 50,attackRange = 5,runs = 50)
plotHistogram(plotTitle = "distribution of agents",resultsA = results[0], resultsB = results[1])

Dette resultat er for en rækkevidde på 5.

Ved hjælp af denne tilgang kan der udvikles og implementeres meget avancerede agentbaserede simuleringsmodeller.

You May Also Like

Leave a Reply

Leave a Reply

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *

This site uses Akismet to reduce spam. Learn how your comment data is processed.