Benutzer-Werkzeuge

Webseiten-Werkzeuge


ws1718:hauptcode

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Nächste Überarbeitung
Vorhergehende Überarbeitung
ws1718:hauptcode [2018/04/06 01:39]
lenarost angelegt
ws1718:hauptcode [2018/04/19 09:27] (aktuell)
lorenztu
Zeile 1: Zeile 1:
-hallo+====== 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 Version((Wer sie noch kennt: CreatureR.py)) 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 {{:​ws1718:​genairation:​old_buggy-genairation.zip|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. 
 + 
 +<file python>​ 
 +#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 
 +</​file>​ 
 + 
 +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 losgehen((sie starten immer im Koordinaten Ursprung)) 
 +\\ ''​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. 
 + 
 +<code python>​ 
 +def ratingRegister(R):​ 
 + def rating1():​ 
 + #​dostuff 
 + def rating2():​ 
 + #​dostuff2 
 +  
 + if R == 0: 
 + return rating1 
 + elif R == 1: 
 + return rating2 
 +</​code>​ 
 + 
 +Jede Kreatur erstellt also eine Funktion ''​rating()''​ und bekommt nach folgender Zeile: 
 +<code python>​ 
 +rating() = ratingRegister(R=0) 
 +</​code>​ 
 +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. 
 + 
 +<file python>​ 
 + 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} 
 +</​file>​ 
 + 
 +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 ist((NOTIZ: Es soll in Zukunft eine Hinderniss Klasse geschrieben werden, mit welcher der Tod einer Kreatur ebenfalls geprüft wird)) 
 +  * 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 ==== 
 +<code python>​ 
 +#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() 
 +</​code>​ 
 +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. 
 + 
 +<code python>​ 
 +#​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"​] 
 +</​code>​ 
 +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 wird((100 bedeutet, dass die Einheit direkt neben dem Ziel ist; 0 bedeutet sie ist im Radius vom Ziel zu Start)) 
 + 
 +\\ **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?**  
 +<file python>​ 
 +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 
 +</​file>​ 
 +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: 
 +<code python>​ 
 +#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))) 
 +</​code>​ 
 + 
 +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 Input((mit einer geringen Wahrscheinlichkeit,​ dass die Kreatur an dieser Stelle aus der Vorbestimmung ausbricht)). 
 +<code python>​ 
 +#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) 
 +</​code>​ 
 + 
 +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.
ws1718/hauptcode.1522971595.txt.gz · Zuletzt geändert: 2018/04/06 01:39 von lenarost