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