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.
Industriingeniør som gerne beskæftiger sig med optimering, simulation og matematisk modellering i R, SQL, VBA og Python
Leave a Reply