In einem früheren Beitrag habe ich ein einfaches agentenbasiertes Simulationsmodell erstellt, das Gruppen von Agenten enthält, die sich auf einem Schlachtfeldgitter befinden können. Das Modell wurde in Python unter Verwendung von matplotlib zur Visualisierung codiert. Ich führte einen einfachen Simulationslauf durch, der ein Kampfszenario und dessen Ergebnis zeigte.
Im Folgenden werde ich das Problem umgestalten, indem ich versuche, den Code zu bereinigen und die Funktionalität in wiederverwendbare Funktionen zu modularisieren. Das Ergebnis wird ein modulares Framework und eine allgemeine Beschreibung des Ansatzes sein. Beiden können zum Erstellen sehr fortschrittlicher agentenbasierter Simulationsmodelle verwendet werden.
Die Agenten werden wie folgt als Klasse modelliert:
# Klasse, die Agenten als abstrakte Datentypen definiert class agent: # init-Methode, die Konstruktormethode für Agenten def __init__(self,x,y,group): self.life = 100 # agent's life score self.x = x self.y = y self.group = group
Das Schlachtfeld selbst wird wie unten gezeigt als zweidimensionales Gitterarray modelliert:
# Erstellen einer leeren 100 x 100-Liste mithilfe des Listenverständnisses in Python battlefield = [[None for i in range(0,100)] for i in range(0,100)]
Agentengruppen können mit der agentCreator-Funktion in das Schlachtfeldraster eingefügt werden:
# Definieren Sie eine Funktion zum Erstellen und Zuweisen von Agenten zum Raster def agentCreator(size,group,groupList,field,n,m): # Schleife durch die gesamte Gruppe, d. h. in diesem Fall 1000 Einheiten for j in range(0,size): # Wählen Sie einen zufällig verfügbaren Ort aus while True: # zufällige x-Koordinate x = random.choice(range(0,n)) # zufällige y-Koordinate y = random.choice(range(0,m)) # prüfen, ob Platz frei ist; Wenn nicht, wiederholen Sie den Vorgang if field[x][y] == None: field[x][y] = agent(x=x,y=y,group=group) # Agentenobjektreferenz an Gruppenliste anhängen groupList.append(field[x][y]) # beende while-Schleife; Punkt auf dem Feld wird genommen break
Für das Plotten von Agenten mit einer positiven Lebensbewertung als visuelle Punkte auf dem Schlachtfeldgitter erstelle ich eine Plotfunktion, die jederzeit während der Simulation aufgerufen werden kann, um eine Momentaufnahme des aktuellen Kampfstatus zu erhalten:
# Pyplot und Farben aus Matplotlib importieren from matplotlib import pyplot, colors # Funktion zum Plotten des Schlachtfelds definieren (alle Agenten, die noch leben) def plotBattlefield(populationArr, plotTitle): # Definiere mithilfe von Farben aus matplotlib eine Farbkarte colormap = colors.ListedColormap(["lightgrey","green","blue"]) # Figurgröße mit Pyplot definieren pyplot.figure(figsize = (12,12)) # Mit Pyplot einen Titel hinzufügen pyplot.title(plotTitle, fontsize = 24) # Füge mit Pyplot x- und y-Beschriftungen hinzu pyplot.xlabel("x coordinates", fontsize = 20) pyplot.ylabel("y coordinates", fontsize = 20) # Passe die Ticks der x- und y-Achse mithilfe des Pyplots an pyplot.xticks(fontsize = 16) pyplot.yticks(fontsize = 16) # Verwende .imshow()-Methode von pyplot, um die Agentenpositionen zu visualisieren pyplot.imshow(X = populationArr, cmap = colormap)
Eine weitere Funktion, die ich benötige, ist eine Funktion, die einen Schlachtfeldstatus einem numerischen zweidimensionalen Array mit Werten von zuordnen kann. 0,0 wenn kein Agent in der respektiven Zelle auf dem Schlachtfeld ist, 1,0 wenn sich ein Agent vom Typ A in einer bestimmten Zelle des zweidimensionalen Schlachtfeldgitters befindet und dort lebt, Wert 2,0 wenn es einen Agenten vom Typ B ist. Ich verwende diesen Zuordnungsprozess um meiner Plotfunktion ein numerisches Raster anstelle eines Rasters mit Objekten einer abstrakten, d.h. benutzerdefinierten, Python-Klasse zuzuweisen:
# Diese Funktion ordnet ein Schlachtfeldraster einem numerischen Raster zu, wobei 1 für Agenten vom Typ A, 2 für Typ B und 0 für keinen Agenten gilt def mapBattlefield(battlefieldArr): # .imshow () benötigt eine Matrix mit float-Elementen; populationArr = [[0.0 for i in range(0,100)] for i in range(0,100)] # Wenn der Agent vom Typ A ist, geben Sie eine 1,0 ein. Wenn der Typ B ist, geben Sie eine 2,0 ein for i in range(1,100): for j in range(1,100): if battlefieldArr[i][j] == None: # leer pass # 0,0 in der Populationszelle belassen elif battlefieldArr[i][j].group == "A": # Agenten der Gruppe A. populationArr[i][j] = 1.0 # 1.0 bdeutet "A" else: # group B agents populationArr[i][j] = 2.0 # 2.0 bedeutet "B" # gebe codierten Werte zur¨ck return(populationArr)
Mit diesen Modellkomponenten erstellte ich eine erste Schlachtfeldpopulation und zeige mithilfe von matplotlib die Agentenpositionen auf dem Schlachtfeld auf. Dies geschieht im folgenden Code und ähnelt meinen vorherigen Beiträgen, nur dass ich in diesem Fall modularisierte Funktionen verwende. Die Funktionalität zum Einrichten eines anfänglichen Schlachtfeldgitters wird auf eine separate Funktion unten übertragen. Diese Funktion wird dann ausgeführt.
# Funktion zum Erstellen eines anfänglichen Schlachtfeldgitters def initBattlefield(populationSizeA,populationSizeB,battlefieldArr): # Initialisierung eines neuen leeren Schlachtfeldgitters battlefieldArr = [[None for i in range(0,100)] for i in range(0,100)] # Erstelle leere Liste für zukünftige Agentenreferenzen; für A und B Agenten agents_A = [] agents_B = [] # Ordne Agenten zufällig Positionen zu 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) # gebe das befüllte Schlachtfeld zurück return(battlefieldArr) # obige Funktion für beide Agentengruppen mit Bestandsgrösse 1000 durchführen battlefield = initBattlefield(populationSizeA=1000,populationSizeB=1000,battlefieldArr = battlefield) # aktuellen Stand auf dem Schlachtfeld plotten plotBattlefield(populationArr = mapBattlefield(battlefield), plotTitle = "battlefield before simulation run (green = A, blue = B)")
In meinem vorherigen Beitrag habe ich dann einen Simulationslauf ausgeführt, der auf den folgenden Regeln basiert:
Gruppe A hat die Strategie, in jeder Runde immer den gleichen Agenten zu treffen
Gruppe B hat eine zufällige und unabhängige Strategie, um Feinde anzugreifen. Dies bedeutet, dass jeder Agent vom Typ B einen zufällig ausgewählten feindlichen Agenten innerhalb seiner Reichweite angreift
Die Simulation wurde unter folgenden Bedingungen durchgeführt:
1) Jede Runde entspricht einer Iteration
2) In jeder Runde greif ein Agent einen Feind innerhalb dessen Reichtweite an
3) Die Reichweite eines Agenten wird bei Simulationsbeginn definiert, mit Standardwert 10
4) Tote Agenten werden von dem Schlachtfeld entfernt
5) Ein Agent stirbt wenn dessen Lebenswert auf oder unter 0 fällt
6) Jeder Agent hat einen zufallsverteilten Angriffswert zwischen 10 und 60
7) In jeder Runde darf jeder Agent einen Angriff ausführen
Wie in einem meiner vorherigen Beiträge werde ich jetzt 50 Kampfrunden durchlaufen. Nach jeder Runde werden Agenten mit einer nicht positiven Lebensbewertung aus dem Schlachtfeldgitter entfernt. Abweichend von meinem vorherigen Beitrag werde ich jetzt jedoch die Funktionalität modularisieren.
Zuerst definiere ich eine Funktion zum Entfernen von Agenten aus dem Schlachtfeldgitter:
# Funktion zum Entfernen von Agenten aus dem Schlachtfeldgitter, wenn die Lebensbewertung nicht unbedingt positiv ist def removeDeadAgents(battlefieldArr): # Identifizieren von Agenten mit einer Lebensbewertung von oder weniger - und Entfernen dieser aus dem Raster for i in range(0,len(battlefieldArr)): for j in range(0,len(battlefieldArr)): if battlefieldArr[i][j]: if battlefieldArr[i][j].life <= 0: # Entfernen Sie diesen Agenten, da die Lebensbewertung nicht unbedingt positiv ist battlefieldArr[i][j] = None
Als nächstes definiere ich eine Funktion, die die Kampfstrategie für Agenten vom Typ A implementiert:
# Funktion zur Implementierung einer Kampfrunde für einen Agenten vom Typ A. def oneRoundAgentA(i,j,attackRange): found_i = None found_j = None # Suchen Sie in benachbarten Zellen für jede Iteration in derselben Reihenfolge for k in range(i-attackRange,i+attackRange+1): for l in range(j-attackRange,j+attackRange+1): # auf negative Indexwerte prüfen; wenn ja - Pause! if k < 0 or l < 0: break # auf Indexwerte über 99 prüfen, wenn ja brechen! if k > 99 or l > 99: break if battlefield[k][l]: if battlefield[k][l].group == "B": # dann ist das ein Feind if found_i == None: found_i = k found_j = l # identifizierten Gegnern Schaden zufügen if found_i: battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
Dann mache ich dasselbe für Agenten vom Typ B:
# Funktion zur Implementierung einer Kampfrunde für einen Agenten vom Typ B. def oneRoundAgentB(i,j,attackRange): # Überprüfen Sie zunächst, ob sich in einer der umliegenden Zellen überhaupt ein Feind befindet enemy_found = False for k in range(i-attackRange,i+attackRange+1): for l in range(j-attackRange,j+attackRange+1): # auf negativen Index prüfen, wenn ja, mit der nächsten Iteration abbrechen if k < 0 or l < 0: break # auf Indexwerte über 99 prüfen, falls dies nicht der Fall ist if k > 99 or l > 99: break if battlefield[k][l] != None: if battlefield[k][l].group == "A": enemy_found = True # Wählen Sie eine zufällige Zeile und eine zufällige Spalte 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) # auf negativen Index prüfen, wenn ja, mit der nächsten Iteration fortfahren if k < 0 or l < 0: continue # auf Indexwert> 99 prüfen, wenn ja, weiter 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 # Füge dem identifizierten Feind Schaden zu if found_i: battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
Ich simuliere den Kampf mit den bereits implementierten Funktionen:
for counter in range(0,50): # in this case I am conducting 50 iterations # Durchlaufen aller Zellen auf dem Schlachtfeld for x in range(0,len(battlefield)): for y in range(0,len(battlefield)): # print ("Iteration der obersten Ebene, i:" + str (i) + ", j:" + str (j)) # Überprüfen Sie, ob sich in der jeweiligen Zelle ein Agent befindet if battlefield[x][y] != None: # je nach Typ: jeweilige Angriffsstrategie ausführen if battlefield[x][y].group == "A": # eine Kampfrunde für diesen Agenten vom Typ A. oneRoundAgentA(i = x, j = y,attackRange=10) else: # eine Kampfrunde für diesen Agenten vom Typ B. oneRoundAgentB(i = x, j = y,attackRange=10) # Identifizieren von Agenten mit einer Lebensbewertung von oder weniger - und Entfernen dieser aus dem Raster removeDeadAgents(battlefieldArr = battlefield) # Plot Schlachtfeldstatus plotBattlefield(populationArr = mapBattlefield(battlefield), plotTitle = "battlefield after 50 iterations (green = A, blue = B)")
Ich möchte jetzt ein Diagramm hinzufügen, um das Kampfergebnis und den Verlauf des Kampfes selbst zu analysieren. Ich definiere daher eine zusätzliche Plotfunktion, die die Anzahl der Agenten, die noch am Leben sind, nach Typ darstellt.
Ich implementiere auch eine Funktion zum Aktualisieren der Zeitreihen von Agenten, die noch am Leben sind.
# Funktion zum Aktualisieren der Zeitreihe der aktiven Agenten def calcAgentsAliveA(resultsA,battlefieldArr): # Diese Variablen werden zum Zählen der Anzahl der aktiven Agenten verwendet countA = 0 countB = 0 # Durchlaufen Sie das Raster, suchen Sie nach Agenten und aktualisieren Sie die Anzahl, falls 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 # Aktualisieren Sie die Ergebnisliste und geben Sie sie zurück resultsA.append(countA) return(resultsA) # Funktion zum Aktualisieren der Zeitreihe der aktiven Agenten def calcAgentsAliveB(resultsB,battlefieldArr): # Diese Variablen werden zum Zählen der Anzahl der aktiven Agenten verwendet countA = 0 countB = 0 # Durchlaufen Sie das Raster, suchen Sie nach Agenten und aktualisieren Sie gegebenenfalls die Anzahl 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 # Aktualisieren Sie die Ergebnisliste und geben Sie sie zurück resultsB.append(countB) return(resultsB) # Funktion zum Zeichnen der Anzahl der noch lebenden Agenten 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)
Jetzt kann ich mit der Anzeige des Diagramms einen weiteren Simulationslauf durchführen. Da ich verschiedene Simulationsszenarien ausführen werde, schreibe ich den Simulationslauf auch in eine Funktion:
# Definierende Funktion zur Durchführung eines Simulationslaufs def simulationRun(iterationLimit,attackRange,showPlot): iterations = [] resultsA = [] resultsB = [] for counter in range(0,iterationLimit): # In diesem Fall führe ich 50 Iterationen durch # Iterationen aktualisieren # Ergebnisse aktualisieren iterations.append(counter+1) resultsA = calcAgentsAliveA(resultsA,battlefield) resultsB = calcAgentsAliveB(resultsB,battlefield) # Iteration durch alle Zellen auf dem Schlachtfeld for x in range(0,len(battlefield)): for y in range(0,len(battlefield)): # print ("Top-Tier-Iteration, i:" + str (i) + ", j:" + str (j)) # Überprüfen Sie, ob sich in der jeweiligen Zelle ein Agent befindet if battlefield[x][y]: # je nach Typ: jeweilige Angriffsstrategie ausführen if battlefield[x][y].group == "A": # eine Kampfrunde für diesen Agenten vom Typ A. oneRoundAgentA(i = x, j = y, attackRange = attackRange) else: # eine Kampfrunde um diesen Agenten vom Typ B. oneRoundAgentB(i = x, j = y, attackRange = attackRange) # Identifizieren von Agenten mit einer Lebensbewertung von oder weniger - und Entfernen dieser aus dem Raster removeDeadAgents(battlefieldArr = battlefield) # Handlungsverlauf, aber nur, wenn die Handlung angezeigt werden soll if showPlot: plotNumberOfAgentsAlive("battle progression",iterations,resultsA,resultsB) # Ergebnisse zurückgeben return([resultsA,resultsB])
Ich kann jetzt einen Simulationslauf mit nur einer Codezeile durchführen:
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)
Es scheint, dass Agenten vom Typ B eine ziemlich effektive Angriffsstrategie haben. Aber was ist, wenn ihre Anzahl zu Beginn des Kampfes etwas niedriger ist? Unten zeichne ich den Verlauf des Kampfes in einem Kampf mit 1000 anfänglichen A- und 950 anfänglichen B-Agenten.
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 10, showPlot = True)
Was passiert, wenn die Angriffsreichweite auf 5 reduziert wird?
battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 1000,battlefieldArr = battlefield) results = simulationRun(iterationLimit = 50, attackRange = 5, showPlot = True)
Die anfängliche Position der Agenten auf dem Schlachtfeld ist zufällig. Das endgültige Kampfergebnis kann daher auch zufällig verteilt werden. Ich möchte untersuchen, inwieweit die Ergebnisse randomisiert sind. Dazu implementiere ich eine Empfindlichkeitstestfunktion, die die Simulation immer wieder wiederholt. Diese Funktion gibt Ergebnisse zurück, die in einem Histogramm visualisiert werden können und die Gesamtmenge der am Ende der Simulation lebenden Agenten darstellen. In diesem Fall wiederhole ich die Simulation 50 Mal.
Im Folgenden implementiere ich die Funktion, die den Empfindlichkeitstest durchführt:
# Diese Funktion wird zur Durchführung eines Empfindlichkeitstests hinsichtlich des Kampfergebnisses verwendet def sensitivityTest(iterationLimit,attackRange,runs): # gibt an, dass das Schlachtfeld eine globale Variable ist global battlefield # leere Listen, die am Ende des Kampfes die endgültige Anzahl der Agenten des jeweiligen Typs enthalten outcomeA = [] outcomeB = [] # Wiederholen der Simulation mit definiertem Angriffsbereich und Iterationslimit für "Läufe" mehrmals for i in range(0,runs): # Vor jedem Simulationslauf muss das Schlachtfeld initialisiert werden battlefield = initBattlefield(populationSizeA = 1000,populationSizeB = 950,battlefieldArr = battlefield) # Simulationslauf durchführen results = simulationRun(iterationLimit=iterationLimit,attackRange = attackRange,showPlot = False) # Ergebnis an relevante Ergebnisliste anfügen outcomeA.append(results[0][iterationLimit-1]) outcomeB.append(results[1][iterationLimit-1]) # Rückgabe des Ergebnisses in einer Liste mit zwei Unterlisten return([outcomeA,outcomeB])
Unten implementiere ich die Histogramm-Plot-Funktion:
# Funktion zum Zeichnen eines Histogramms 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)
Schließlich führt der folgende Code den Empfindlichkeitstest aus und zeichnet das Ergebnis auf.
# Ausführen des Empfindlichkeitstests results = sensitivityTest(iterationLimit = 50,attackRange = 5,runs = 50) plotHistogram(plotTitle = "distribution of agents",resultsA = results[0], resultsB = results[1])
Dieses Ergebnis gilt für eine Angriffsreichweite von 5.
Mit diesem Ansatz kann ein sehr fortschrittliches agentenbasiertes Simulationsmodell entwickelt werden.
Wirtschaftsingenieur mit Interesse an Optimierung, Simulation und mathematischer Modellierung in R, SQL, VBA und Python
Leave a Reply