Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1718:hauptcode

Hauptcode

Als Hauptcode ist die Datei „creatureV2.py“ gemeint. Sie beinhaltet (neben ein paar Funktionen, einem Koordinatenumrechner und einem Rating-Register) die Klasse „Creature“, welche eine Kreatur nach Bauplan erstellt. Praktisch das wichtigste, was wir für unser Programm brauchen!
Mit einer älteren Version1) haben wir begonnen zu programmieren, sie jedoch komplett neu geschrieben als wir fast fertig waren, wobei die Funktionalität dieselbe blieb.
Über den Lauf des Projektes haben sich unsere Programmierfähigkeiten enorm verbessert und wir bekamen genauere Vorstellungen von unserem Projekt und wie es aussehen soll. Der alte Code zeigte zu deutlich die Anfänge und hatte teilweise Inhaltsfehler, die auch durch erste (manchmal verworfene) Ideen verschuldet sind.

Der neue Hauptcode ist also nicht nur mit viel besserer Programmierung gelöst, sondern viel strukturierter und enorm benutzerfreundlich geworden!! Wer den alten Code vergleichen möchte, dem habe ich hier alte Codes verlinkt.


Bevor die Kreaturen Klasse erklärt wird ein paar Worte zu den anderen ebenfalls wichtigen Codestücken in der Datei:

Koordinaten Umrechner, Rating-Register und Co

Der Code beginnt mit einigen typischen Importen und wird direkt danach von dem Erstellen eines „Logger“ gefolgt. Kurzgesagt hilft uns der Logger beim Verstehen und Debuggen vom Programm, wer jedoch genaueres dazu erfahren möchte empfehle ich das Tutorial dazu von Stefan Born.


Als nächstes im Code stehen verschiedene Variablen die unter dem Kommentar „settings“ stehen.

#settings
start = {"facing" : 0.0, "spread" : 90}	#in degree
goal = {"coord" : (800,200) , "size" : (20,20)}
length_zoom = 1	#float ( <0 will invert coords)
step_cap = 10	#in steps
LR_ratio = 0.5	#in [0;1]
outlier_rate = 0.005	#in [0;1]
rating_number = 1	#must be a valid number

Alles hier verändert gewisse Funktionalitäten im Programm und ist deshalb mit Bedeutung oder Definitionsbereichen beschrieben.
start beeinflusst die Guckrichtung (mit einer Streuung), mit der die ersten Kreaturen losgehen2)
goal beeinflusst die Lage und größe vom Ziel, dass die Kreaturen versuchen zu erreichen
length_zoom verändert nur den Zoom vom Koordinatensystem ohne die Bewegung der Kreaturen zu skalieren
step_cap gibt an wie lange sich die Kreaturen maximal bewegen dürfen
LR_ratio beeinflusst mit welcher Wahrscheinlichkeit nach Links abgebogen wird
outlier_rate bedeutet, mit welcher Wahrscheinlichkeit eine Tochterkreatur trotz Eltern-Input an dieser ihren eigenen Schritt geht
rating_number gibt die Stelle im Rating-Register an, welches Rating benutzt werden soll


Die Coord Klasse als erstes großes Codeschnipsel kann das Simulations-Koordinatensystem auf andere Systeme anpassen. Anstatt also jedesmal Koordinaten einen Wert hinzuzurechnen, damit sie in der Visualisierung an gewollten Stellen platziert sind, muss nur noch mittels der passenden Umrechnungsfunktion gearbeitet werden. Wenn es keine Passende gibt, muss sie noch geschrieben werden.


Bevor über die wichtigsten Teile vom Programm geredet wird, ist ein kurzer Blick auf das Ende des Codes gewidmet, wo sich ein paar Funktionen befinden. Sie sollen Rechnungen verkürzen oder sind wichtig für die Simulation. So z.B procreation, welches aus einer übergebenen Kreatur eine Tochterkreatur zurückgibt. Letzten Endes sorgen sie für eine Übersichtlichkeit im Code.


Mit dem Rating-Register beginnt der essenzielle Part vom Programm. Unsere Kreaturen brauchen eine Aufgabe, um sich zu Messen und die Selektion ein Kriterium, nach dem aussortiert werden kann. Die Aufgabe ist einfach: „Erreicht das Ziel“. Nur wie bewertet man? Es gibt verschiedene Ansätze zu bewerten und jeder hat seine Berechtigung, da eben jeder sein eigenes Umfeld, mit anderen Faktoren, für die Kreaturen zum Überleben erstellt. Aus diesem Grund gibt es das Rating-Register mit einer Sammlung an möglichen Ratings. Es kann jederzeit erweitert werden. In den settings kann man sich die Nummer des Ratings aussuchen und das Register gibt die entsprechende innere Funktion aus.

def ratingRegister(R):
	def rating1():
		#dostuff
	def rating2():
		#dostuff2
 
	if R == 0:
		return rating1
	elif R == 1:
		return rating2

Jede Kreatur erstellt also eine Funktion rating() und bekommt nach folgender Zeile:

rating() = ratingRegister(R=0)

die gewollte Ratingfunktion zugewiesen, welche sich mit rating() abrufen lässt.

Die Kreaturen Klasse

Alles drumherum ist erklärt und es kann sich dem Herzstück der Kreaturen gewidmet werden: Der Creature Klasse.
Eine Klasse bedeutet, dass jede Kreatur nach dieser Klasse funktioniert und wir somit beliebig viele Kreaturen nach dem selben Schema erstellen können.


Angefangen mit der __init__, sind alle Attribute definiert und Eigenschaften gesetzt.

	def __init__(self, species=0, ancestor_way=[]):
		''' Kreatur wird erstellt '''
 
		#Spezienzuweisung
		if species == 0:
			self.traits = {"specie" : 0 , "tendency" : 70 , "arc" : (22.5,17.5) , "path_length" : 10 , "mutation_rate" : 0}
		else:
			self.traits = {"specie" : 1 , "tendency" : 66 , "arc" : (10,0) , "path_length" : 18 , "mutation_rate" : 5}
 
		#erste Generation startet innerhalb eines parametisierten Winkels
		if ancestor_way == [] and start["spread"] != 0:
			ancestor_way = [rng.randint(start["spread"])-start["spread"]/2]
 
		#Attributzuweisung
		self.briefing = []
		self.ancestor_way = ancestor_way
		self.rating = ratingRegister(self,rating_number)
		self.stats = {"pos" : (0.0,0.0) , "viewpoint" : start["facing"] , "step_counter" : 0 , "dead" : False , "success" : False}
		self.career = {"best" : 0.0 , "time_stamp" : 0 , "score" : 0}

Dem Konstruktor der Kreatur kann man eine Spezies und eine Wegbeschreibung übergeben. Beides ist für die Vererbung wichtig und bestimmt Eigenschaften und erste Wegentscheidungen.
Eigenschaften, Werte und Bewertungsdaten sind als ein dictionary in traits, stats und career sortiert und eingeordnet. Dazu eine kurze Erklärung einzelner Variablen:

  • traits
    specie benennt einfach nur die Spezie
    tendency gibt die prozentuale Wahrscheinlichkeit an abzubiegen bzw. als Gegenwahrscheinlichkeit geradeaus zu bewegen
    arc ist der Winkel in dem abgebogen wird und der Spanne drumherum
    path_length ist die Schrittlänge
    mutation_rate gibt die Stärke der Mutation bei Vererbung an.
  • stats
    pos sind die Koordinaten der Kreatur
    viewpoint ist die Blickrichtung als Winkel zur x-Achse
    step_counter zählt die Anzahl der zurückgelegten Schritte
    dead sagt ob die Kreatur Tod ist oder eben nicht
    success ist auf True, wenn die Kreatur das Ziel erreicht hat
  • career
    best speichert irgendwelche Bestwerte fürs Rating
    time_stamp speichert einen Zeitpunkt bis zu dem das Erbgut weitervererbt werden soll
    score ist das Rating als Zahl zusammengefasst

Dazu kommen noch ein paar weitere Variablen wie:
briefing ist die Wegbeschreibung
ancestor_way ist ein Teil der Wegbeschreibung der Eltern der unterschiedlich grob kopiert wird
rating beinhaltet die Ratingfunktion nach der Bewertet wird


Mit allen diesen Variablen lässt sich die Kreatur komplett beschreiben, nur muss sie damit auch etwas anfangen können. Jede Kreatur muss folgendes können:

  • Bewegen → dazu zählt:
    • Wegbeschreibung ständig verlängern (unter Berücksichtigung von Spezies und Input)
    • Einen Schritt gehen, also einen Teil der Wegbeschreibung bearbeiten können
    • Beides kombiniert zu einer fliesenden Bewegung mit Ende führen
  • Sterben → entweder nach dem Erreichen des Ziels oder wenn die Kreatur bereits zulange unterwegs ist3)
  • Vererben → Es müssen Vorbereitungen getroffen werden, damit procreation arbeiten kann


Die Klasse besitzt mehrere Funktionen, damit jede Kreatur die Aufgaben bewältigen kann. Im Code steht zu jeder Funktion was sie tut, sodass es hier nicht extra erläutert ist. Stattdessen wird die Funktionsweise näher erklärt:

Wie funktioniert eine Kreatur

#Bewegung
self.appendWay() #erste Wegerweiterung
 
#eigentliche Bewegung
for e in self.briefing:
	self.nextStep(e)
	if self.stats["step_counter"] < step_cap and not self.stats["dead"]:
		self.appendWay()

Jede Kreatur, nachdem sie konstruiert wurde, besitzt eine leere Wegbeschreibung. Bevor die Bewegung initiiert wird, muss die Wegbeschreibung, also briefing, einmal erweitert werden. Dann kann der erste Schritt getan werden und briefing wieder erweitert werden. Wieder ein Schritt und wieder erweitert und das solange, bis die Kreatur stirbt oder die step_cap erreicht. So kommt eine flüssige, kontinuierliche Bewegung zustande. Zwischen jeden Schritten wird geprüft ob die Kreatur noch lebt und das Rating aufgerufen und aktualisiert.

#Intensitäts-Rating
if not self.stats["dead"]:
	''' wird nach jeder Bewegung ausgeführt '''
	self.career["score"] += 100 - (distance(self.stats["pos"], goal["coord"])/distance(goal["coord"])*100)
 
elif self.stats["dead"]:
	''' wird nach dem Tod erstellt '''
	self.career["time_stamp"] = self.stats["step_counter"]
 
	return self.career["score"]

In jeder Rating-Funktion wird unterschieden ob die Kreatur noch lebt oder nicht. Falls sie Tod ist werden die letzten Dinge für die Vererbung vorbereitet und gibt den score zurück mit dem eine spätere Sortierfunktion arbeiten wird. Falls die Kreatur noch lebt wird die letzte Bewegung analysiert und entsprechend der score aktualisiert. Im Intensitäts-Rating bedeutet das, dass nach jedem Schritt die Kreatur „riecht“ und dabei erkennt wie intensiv es „riecht“ bzw. wie nah sie dem Ziel ist. Je näher die Einheit dem Ziel kommt, desto höher ist der Wert, der dem score hinzugefügt wird4)


Vererbung bedeutet, dass eine neue Kreatur zurückgegeben wird, welche die selben Eigenschaften und die Wegbeschreibung vom Vorgänger (als ancestor_way) übermittelt bekommt.


Was bedeutet einen Schritt gehen?

def nextStep(self, r):
		''' bewegt die Kreatur einen Schritt '''
 
		if self.stats["dead"]:
			''' Kreatur bereits gestorben -> Bewegung gestoppt '''
			return
 
		self.stats["viewpoint"] += r
 
		#auf dem kreis bleiben:
		if self.stats["viewpoint"] > 359:
			self.stats["viewpoint"] -= 360
		elif self.stats["viewpoint"] < 0:
			self.stats["viewpoint"] += 360
 
		#eigentliche Bewegung im Koordinatensystem
		self.stats["pos"] = (self.stats["pos"][0] + length_zoom*(self.traits["path_length"]) * np.cos(self.stats["viewpoint"]/180*np.pi),
					self.stats["pos"][1] + length_zoom*(self.traits["path_length"]) * np.sin(self.stats["viewpoint"]/180*np.pi))
 
		self.stats["step_counter"] += 1

Ein Schritt heißt, dass die Kreatur, mit der nextStep Funktion und einem übergeben Input aus der Wegbeschreibung, die aktuellen Positionskoordinaten ändert. Dazu wird die Blickrichtung (viewpoint) entsprechend des Inputs geändert und dann, zusammen mit der Schrittlänge (path_length) und der Trigonometrie, die neuen Koordinaten errechnet.


Zu guter Letzt ist nur noch ungeklärt, wie die Wegbeschreibung erweitert wird. Ganz wichtig ist dabei zu Unterscheiden, ob es für die zu erweiternde Stelle eine von „Eltern“ vorgegebene Richtung gibt oder eben nicht.
Bei der ersten Generation gibt es diese nicht und die Funktion bezieht sich auf die Eigenschaften der Spezies:

#ohne Input nach Eigenschaften gehen
else:
	if rng.random()*100 > self.traits["tendency"]:
		self.briefing.append(0)
	elif rng.random() < LR_ratio:
		self.briefing.append(rng.randint(self.traits["arc"][0]-self.traits["arc"][1],
					self.traits["arc"][0]+self.traits["arc"][1]+1))
	else:
		self.briefing.append(-(rng.randint(self.traits["arc"][0]-self.traits["arc"][1],
					self.traits["arc"][0]+self.traits["arc"][1]+1)))

Ein zufälliger Wert bestimmt, ob sich die Kreatur geradeaus bewegt oder abbiegt. Ähnlich wird geguckt ob die Bewegung nach links oder recht gehen soll. Dementsprechend wird briefing ein Wert hinzugehangen, der entweder 0 für geradeaus oder positiv/negativ um den durch arc definierten Winkel für links/rechts Drehungen steht.

Falls ancestor_way von den Eltern eine Richtung vorbestimmt, widmit sich die Funktion diesem Input5).

#Eltern Input existiert
r = self.ancestor_way[self.stats["step_counter"]]
 
# MUTATION VON R
#r begrenzen
if r < (self.traits["arc"][0]+self.traits["arc"][1])*-2:
	r += rng.random()*self.traits["mutation_rate"]
elif r > (self.traits["arc"][0]+self.traits["arc"][1])*2:
	r -= rng.random()*self.traits["mutation_rate"]
 
#ansonsten Mutation freien Lauf lassen
elif rng.random() < LR_ratio:
	r += rng.random()*self.traits["mutation_rate"]
else:
	r -= rng.random()*self.traits["mutation_rate"]
 
self.briefing.append(r)

Es wird zunächst der Wert eins zu eins übernommen, allerdings im Anschluss nochmal leicht verändert. Diese Mutation ist enorm wichtig für die gesamte Evolution! Damit die Mutation nicht unkontrolliert in alle Richtungen ausschert ist r begrenzt und lässt an den Grenzen nur Mutation zu, die davon wegführt. Ansonsten kann sich r frei mutieren; je höher die Rate in den Eigenschaften, desto stärker.


Mit all den Funktionen zusammen bekommen wir voll funktionsfähige Kreaturen für unser Programm und es liegt nun an einer geschickten Visualisierung, aus dem Programm eine gelungene Simulation zu erstellen.

1) Wer sie noch kennt: CreatureR.py
2) sie starten immer im Koordinaten Ursprung
3) NOTIZ: Es soll in Zukunft eine Hinderniss Klasse geschrieben werden, mit welcher der Tod einer Kreatur ebenfalls geprüft wird
4) 100 bedeutet, dass die Einheit direkt neben dem Ziel ist; 0 bedeutet sie ist im Radius vom Ziel zu Start
5) mit einer geringen Wahrscheinlichkeit, dass die Kreatur an dieser Stelle aus der Vorbestimmung ausbricht
ws1718/hauptcode.txt · Zuletzt geändert: 2018/04/19 09:27 von lorenztu