From 00a5ce4ba71f5a20614914374932bb1a76676a67 Mon Sep 17 00:00:00 2001 From: Malte Woidt <m.woidt@tu-braunschweig.de> Date: Tue, 20 Dec 2022 22:02:20 +0100 Subject: [PATCH] =?UTF-8?q?=C3=9Cbung=2007?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Uebung07/Uebung07.ipynb | 938 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 Uebung07/Uebung07.ipynb diff --git a/Uebung07/Uebung07.ipynb b/Uebung07/Uebung07.ipynb new file mode 100644 index 0000000..46246fc --- /dev/null +++ b/Uebung07/Uebung07.ipynb @@ -0,0 +1,938 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e3870386-6318-4bfd-861b-0a26542466a1", + "metadata": {}, + "source": [ + "# <font color='blue'>**Übung 5 - Berechnung eines statisch bestimmten Fachwerks - Teil 3**</font>\n", + "\n", + "In diesem Teil der Übung beschäftigen wir uns mit der Ausgabe der Ergebnisse für unser Fachwerkberechnungsprogramm. Hierbei benutzen wir wieder *maptplotlib* für die grafische Ausgabe. Wir wollen eine Textausgabe der konkreten Stabkräfte und Auflagerreaktionen ermöglichen und eine grafische Darstellung der Kräfte zur qualitativen Abschätzung der Gesamtreaktion.\n", + "\n", + "## Das bisherige Programm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5ef75da-521c-417a-8779-8de11b044a58", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches\n", + "import matplotlib\n", + "%matplotlib inline\n", + "\n", + "class Knoten:\n", + " def __init__(self, coo):\n", + " self._coo=np.array(coo)# wir sollten sicherstellen, das es sich bei der intern gespeicherten Koordinate um ein np.array handelt. Potentiell sollte noch der Datentyp auf Fließkomma gesetzt werden und die Größe überprüft werden\n", + " self._kraft=np.zeros(2) #die Kraft ist standardmäßig 0\n", + " self._lager=[False,False] #Beide Lagerbedingungen sind nicht gesetzt\n", + " \n", + " self._staebe=[] #Es gibt bei Erstellung noch keine benachbarten Stäbe\n", + " def _addStab(self,stab):\n", + " self._staebe.append(stab)#Einen neuen Nachbarstab hinzufügen\n", + " def _removeStab(self,stab):\n", + " self._staebe.remove(stab)#Einen Nachbarstab entfernen\n", + " \n", + " def setKraft(self,kraft):\n", + " self._kraft[:]=kraft#Die Einträge von Kraft werden neu gesetzt. So ist sichergestellt, das die Attribute immer ein 2D-Vektor ist\n", + " def setLager_x(self):\n", + " self._lager[0]=True#Setze die Lagerbedingung in x-Richtung\n", + " def setLager_y(self):\n", + " self._lager[1]=True#Setze die Lagerbedingung in y-Richtung\n", + " def removeLager_x(self):\n", + " self._lager[0]=False#Entferne die Lagerbedingung in x-Richtung\n", + " def removeLager_y(self):\n", + " self._lager[1]=False#Entferne die Lagerbedingung in y-Richtung\n", + "\n", + " def getKoordinate(self):\n", + " return self._coo#gibt die Koordinaten des Punktes zurück\n", + "\n", + "class Stab:\n", + " def __init__(self,k1,k2):\n", + " self.k1=k1#Der erste Endpunkt\n", + " self.k2=k2#Der zweite Endpunkt\n", + " def getRichtung(self,k):\n", + " if k == self.k1:#Falls der gerade betrachtete Punkt k1 ist\n", + " vektor= self.k2.getKoordinate()-k.getKoordinate()#bilde den Richtungsvektor von K1(bzw. K) zu K2\n", + " return vektor/np.linalg.norm(vektor)#Durch den Betrag teilen und zurückgeben\n", + " if k == self.k2:#Falls der gerade betrachtete Punkt k2 ist\n", + " vektor= self.k1.getKoordinate()-k.getKoordinate()#Bilde den Richtungsvektor von K2(bzw. K) zu K1\n", + " return vektor/np.linalg.norm(vektor)#Durch den Betrag teilen um Einheitsvektor zu erhalten\n", + " return np.zeros(2)#nur als Fail-Safe, wenn der betrachtete Punkt nicht zum Stab gehört\n", + "class Fachwerk:\n", + " def getKnoten(self):\n", + " return self._knoten[:]#Eine flache Kopie der Knotenliste\n", + " def getStaebe(self):\n", + " return self._staebe[:]#Eine flache Kopie der Stabliste\n", + " def getNumKnoten(self):\n", + " return len(self._knoten)#Die Länge der Knotenliste\n", + " def getNumStaebe(self):\n", + " return len(self._staebe)#Die Länge der Stabliste\n", + " def getNumLager(self):\n", + " numLager=0#Eine Variable die zählt, wie viele Lager gefunden wurden\n", + " for knoten in self._knoten:#Eine Schleife über alle Knoten\n", + " if knoten._lager[0]:#Falls der Knoten eine Lagerbedingung in x-Richtung besitzt\n", + " numLager+=1#Die Zählvariable hochzählen\n", + " if knoten._lager[1]:#Falls der Knoten eine Lagerbedingung in y-Richtung besitzt\n", + " numLager+=1#Die Zählvariable hochzählen\n", + " return numLager#Die Gesamtzahl der Lager zurückgeben\n", + " def isStatischBestimmt(self):\n", + " unbekannte=self.getNumStaebe()+self.getNumLager()#Die Anzahl der Unbekannten ist die Zahl der Stäbe plus die Zahl der Lager\n", + " gleichungen=self.getNumKnoten()*2#Die Zahl der Gleichungen ist die Zahl der Knoten mal zwei\n", + " return unbekannte==gleichungen#Zurückgeben ob die Zahl der Unbekannten gleich der Zahl der Gleichungen ist\n", + " def removeStab(self,stab):\n", + " if stab in self._staebe:#Überprüfen ob der Stab zum Fachwerk gehört\n", + " stab.k1._removeStab(stab)#Der Stab ist kein Nachbar seiner Endpunkte mehr\n", + " stab.k2._removeStab(stab)\n", + " self._staebe.remove(stab)#Den Stab aus der Liste der Stäbe entfernen\n", + " \n", + " def removeKnoten(self,knoten):\n", + " if knoten in self._knoten:#Überprüfen, ob der Knoten zum Fachwerk gehört\n", + " for stab in knoten._staebe[:]:#Über alle Stäbe in der Nachbarschaft iterieren (bzw. einer flachen Kopie, da die Liste verändert wird)\n", + " self.removeStab(stab)#Den Stab entfernen (das verändert die Nachbarschaftsliste des Knotens, dh. wird über eine flache Kopie iteriert\n", + " self._knoten.remove(knoten)#Den Knoten aus der Liste der Knoten entfernen\n", + " def __init__(self):\n", + " self._knoten=[]\n", + " self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten\n", + " def addKnoten(self, coo):\n", + " neuer_knoten=Knoten(coo)#Neues Knotenobjekt anlegen\n", + " self._knoten.append(neuer_knoten)#Zur Liste hinzufügen\n", + " return neuer_knoten#Und das neue Objekt zurückgeben\n", + " def addStab(self,k1,k2):\n", + " if not k1 in self._knoten:#Überprüfen, ob die Endpunkte überhaupt zum Fachwerk gehören\n", + " return None\n", + " if not k2 in self._knoten:\n", + " return None\n", + " neuer_stab=Stab(k1,k2)#Neuen Stab anlegen\n", + " k1._addStab(neuer_stab)#Der neue Stab ist nun ein Nachbar der Endknoten\n", + " k2._addStab(neuer_stab)\n", + " self._staebe.append(neuer_stab)#Stab in die Liste der Stäbe einfügen\n", + " return neuer_stab#Und neuen Stab zurückgeben\n", + " def plot(self):\n", + " fig= plt.figure(figsize=(5, 5))\n", + " ax = fig.subplots()#Figure und Diagramm ersellen\n", + " #zunaechst alle Knoten darstellen. Matplotlib erwartet dafür eine Darstellung in der Form [x_0,x_1,...],[y_0,y_1,..]\n", + " x_werte=[]#Eine Liste für die x- und y-Werte der Knoten\n", + " y_werte=[]\n", + " for knoten in self._knoten:#Über die Knoten iterieren\n", + " koordinate=knoten.getKoordinate()#die Koordinate holen\n", + " x_werte.append(koordinate[0])#den x-Wert zu den x-Werten hinzufügen\n", + " y_werte.append(koordinate[1])#den y-Wert zu den y-Werten hinzufügen\n", + " if np.linalg.norm(knoten._kraft)>0.0001:#Schauen, ob eine Gesamtkraft angreift. Direkte Vergleiche zwischen Fließkommazahlen vermeiden. Diese Methode der Überprüfung ist nicht ideal\n", + " kraftvektor=knoten._kraft/np.linalg.norm(knoten._kraft)#Normalenvektor der Kraft berechnen. Die Kraftvektorendarstellung ist sonst sehr lang, da Kräfte oft verhältnismäßig große Zahlen\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-kraftvektor[0],koordinate[1]-kraftvektor[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"r\",fc=\"r\")#Pfeil in Rot zeichnen\n", + " ax.add_patch(pfeil)#Pfeil dem Diagrmm hinzufügen\n", + " if knoten._lager[0]:#Falls ein Lager in x-Richtung existiert\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-1,koordinate[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"b\",fc=\"b\")#Einen Pfeil in Blau Zeichnen, in X-Richtung, auf den Knoten zu\n", + " ax.add_patch(pfeil)#Pfeil dem Diagramm hinzufügen\n", + " if knoten._lager[1]:# Das Gleiche für Y-Richtung\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0],koordinate[1]-1),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"b\",fc=\"b\")\n", + " ax.add_patch(pfeil)\n", + " ax.scatter(x_werte,y_werte)#Zeichnet alle Knoten als Kreise\n", + " #als nächstes alle Stäbe\n", + " for stab in self._staebe:#Über alle Stäbe iterieren\n", + " koordinate1=stab.k1.getKoordinate()#Koordinate des ersten Punktes holen\n", + " koordinate2=stab.k2.getKoordinate()#Und des zweiten Punktes\n", + " ax.plot([koordinate1[0],koordinate2[0]],[koordinate1[1],koordinate2[1]],\"r\")#Zeichent einen Stab in rot. Durch die andere vorgesehene Nutzung von Matploltib müssen wir uns die x und y Koordinaten der Punkte etwas umständlich umsortieren\n", + "class LoeseFachwerk:\n", + " def __init__(self,fachwerk):\n", + " self.fachwerk=fachwerk#Das Fachwerkobjekt speichern, da die anderen Methoden es brauchen\n", + " if not fachwerk.isStatischBestimmt():#Falls das Fachwerk nicht statisch bestimmt ist hier abbrechen\n", + " return\n", + " self.matrix=np.zeros((fachwerk.getNumKnoten()*2,fachwerk.getNumKnoten()*2))#Eine Matrix der benötigten Größe mit Nullen anlegen. Wenn wir hier angekommen ist, ist die Matrix quadratisch\n", + " self.kraftvektor=np.zeros(fachwerk.getNumKnoten()*2)#Den Gesamtkraftvektor mit Nullen erstellen\n", + " self._nummeriereKnoten()#Dictionary mit Knotennummern erstellen\n", + " self._baueSystem()#Matrix und Vektor aufbauen\n", + " result=np.linalg.solve(self.matrix,-self.kraftvektor)#Gleichungssystem lösen (Grund für das Minus oben)\n", + " self.result=result#Ergebnis speichern\n", + " def _baueSystem(self):\n", + " i=0#Variable um mitzuzählen, welche Zeilen wir belegen\n", + " for knoten in self.fachwerk.getKnoten():#Über alle Knoten iterieren\n", + " zeilen=self.matrix[i:i+2,:]#Die beiden Zeilen der Matrix. Wir erschaffen einen View, die Gesamtmatrix wird also mitverändert\n", + " for stab in knoten._staebe:#Über alle verbundenen Stäbe iterieren\n", + " spalte=self.nummernUnbekannte[stab]#Die Spalte, die zum Stab gehört herausfinden\n", + " zeilen[:,spalte]=stab.getRichtung(knoten)#In diese Spalte der zwei Zeilen den Richtungsvektor des Stabes weg von diesem Knoten schreiben\n", + " self.kraftvektor[i:i+2]=knoten._kraft#Die Kraft in den Kraftvektor einfügen\n", + " if knoten._lager[0]:#Falls der Knoten ein x-Lager besitzt\n", + " zeilen[0,self.nummernUnbekannte[knoten][0]]=1.#in die erste Zeile in der betreffenden Spalte eine 1 Einfügen\n", + " if knoten._lager[1]:#Das Gleiche für das y-Lager, nur in der zweiten Zeile\n", + " zeilen[1,self.nummernUnbekannte[knoten][1]]=1\n", + " i+=2#In der nächsten Iteration zwei Zeilen weiter schreiben\n", + " def _nummeriereKnoten(self):\n", + " nummernUnbekannte={}#Ein leeres Dictionary\n", + " nr=0#ein Zähler, welche Spalte die nächste unbelegte ist\n", + " for stab in self.fachwerk.getStaebe():#über alle Stäbe iterieren\n", + " nummernUnbekannte[stab]=nr#In das Dictionary für dieses Stabobjekt die nächste freie Spalte hinterlegen\n", + " nr+=1#Die aktuelle Spalte ist belegt\n", + " for knoten in self.fachwerk.getKnoten():#Über alle Knoten iterieren\n", + " if knoten._lager[0] or knoten._lager[1]:#Falls der Knoten ein Lager in x- oder y-Richtung besitzt\n", + " nummern=[-1,-1]#Erst einmal eine Liste mit zwei ungültigen Indizes erstellen\n", + " if knoten._lager[0]:#Falls der Knoten eine Lagerbedingung in x-Richtung besitzt\n", + " nummern[0]=nr#Der x-Richtung die nächste Spalte zuordnen\n", + " nr+=1#Die aktuelle Spalte ist belegt, die nächste leere auswählen\n", + " if knoten._lager[1]:#das Gleiche für die y-Richtung\n", + " nummern[1]=nr\n", + " nr+=1\n", + " nummernUnbekannte[knoten]=nummern#Die erstellte Liste für dieses Knoten-Objekt hinterlegen\n", + " self.nummernUnbekannte=nummernUnbekannte#Die Liste als Attribut der Klasse speichern" + ] + }, + { + "cell_type": "markdown", + "id": "c1dd136c-ec01-4311-9876-f2b3b4ade9a5", + "metadata": {}, + "source": [ + "## **Schritt 1 - Speichern der Lösung und Textausgabe**\n", + "\n", + "Im ersten Schritt soll eine Ausgabe der Ergebnisse in Textform erfolgen. Die Zahlenwerte liegen in der *LoeseFachwerk*-Klasse bereits vor. Die Zuordnung der Zahlen zu einem konkreten Stab oder einer Lagerkraft ist im Moment noch schwierig. Es wäre gut, wenn jedem Stab und jeder Auflagerreaktion ein Name oder ein Label zugeordnet werden könnte, das sich in der grafischen Ausgabe anzeigen lässt. Damit wäre es möglich eine Tabelle auszugeben, die aus *label : wert* Einträgen besteht und somit eine Zuordnung erlaubt.\n", + "\n", + "Zusätzlich müssen die Ergebnisse auch sinvoll gespeichert werden. Der Ergebnisvektor ist ohne die Fachwerk-Definition wertlos. Als gemeinsamen Speicherort können wir die *LoeseFachwerk*-Klasse verwenden. Sie speichert im Moment eigentlich nach Erstellung nur den Ergebnisvektor und die Zuordnung von Stäben und Lagerkräften zu den Zeilen im Ergebnisvektor. Beides Dinge, die für eine Darstellung der Ergebnisse benötigt werden.\n", + "\n", + "### Einige Vorüberlegungen\n", + "In der Ergebnis-Klasse sollte die Fachwerksdefinition, also die *Fachwerk*-Klasse, gespeichert werden. Die Ergebnisse sind nur zusammen mit diesen Informationen von Nutzen. Eine Referenz auf ein *Fachwerk* als Attribut zu speichern führt zu neuen Problemen. Das Fachwerk kann später verändert werden. Das würde zu neuen Ergebnissen führen. Es wäre also sinvoll, eine echte Kopie des Fachwerks abzuspeichern. Das ist in *python* ein Problem, da wir bereits gesehen haben, dass sich hinter Variablennamen immer Referenzen verbergen. Einen Ausweg bietet das *copy*-Modul (siehe https://docs.python.org/3/library/copy.html). Das Modul hat eine Funktion *deepcopy*, die eine echte Kopie eines Objektes und aller enthaltenen Objekte erzeugen kann. Das ist für diesen Zweck genau das richtige.\n", + "\n", + "### Zur eigentlichen Ausgabe\n", + "\n", + "Die Textausgabe besteht aus einigen Zeilen in der Form *label : wert*. Als Label können wir für einen ersten Versuch einfach die Zeilennummern der Stäbe und Lagerkräfte nutzen (bzw. die Spaltennummern in der aufgebauten Matrix). Sie sind ein-eindeutig und wir haben die Zuordnungstabelle in Form eines *dictionary* Objektes bereits im Attribut *nummernUnbekannte* erstellt. Eine Ausgabe für alle Stäbe und Auflager in dieser Form zu realisieren sollte also kein grundsätzliches Problem darstellen.\n", + "\n", + "Der zweite Teil besteht aus dem Darstellen der Labels in der *matplotlib* Ausgabe. Dazu müssen wir eine modifizierte Variante der *plot*-Methode der *Fachwerk*-Klasse implementieren. Dazu gibt es erst einmal zwei Optionen. Wir könnten der *LoeseFachwerk*-Klasse eine entsprechende Methode spendieren. Wir könnten alternativ die Methode der *Fachwerk*-Klasse so modifizieren, dass sie optional ein *dictionary* mit Labels erhält und dann die Ausgabe entsprechend anpasst. Keine der beiden Optionen ist falsch oder richtig und im Endeffekt \"Geschmackssache\". An dieser Stelle möchte ich lieber die Methode in der *Fachwerk*-Klasse modifizieren. Vielleicht ist die Option, Stäbe durch Labels kenntlich zu machen, auch während der Definition sinvoll einsetzbar. Außerdem wird dadurch das Kopieren von Quelltext vermieden. Die andere Option würde zu fast identischen Methoden in zwei Klassen führen. Ist eine Methode fehlerhaft, müssten beide repariert werden, was Quelltext in echten Projekten sehr schwer wartbar macht.\n", + "\n", + "Das Ziel ist, die Methode so zu verändern, dass sie optional ein Dictionary mit Labels bzw. Namen erhält. Hat ein Stab bzw. eine Auflagerkraft einen Namen in diesem Dictionary, wird er in die grafische Ausgabe integriert. Wenn kein Name hinterlegt ist, dann wird keiner Ausgegeben. Als Standardwert für den Parameter kann dann einfach ein leeres *dictionary* gewählt werden. Das hat dann zur Folge, dass die Ausgabe so bleibt, wie sie bisher war. \n", + "\n", + "Ein neues Problem bietet die Textausgabe der Kräfte. Das *dictionary*-Objekt mit den Labels soll so aufgebaut sein, wie das der Spaltennummern. Abhängig davon, ob der Schlüssel, also das Objekt, ein Stab oder ein Knoten ist, enthält das *dictionary* eine Zahl bzw. ein Label, oder eine Liste mit zwei Zahlen bzw. Labels. Wir müssen also eine Entscheidung treffen, die vom Typ einer Variablen abhängt. Das kommt in Programmiersprachen wie *python* häufig vor. Dafür gibt es die Funktion *isinstance(obj, class)*, die *True* zurückgibt, falls der Parameter *obj* eine Instanz von *class* ist. Zusätzlich müssen wir überprüfen, ob der Eintrag in der Liste ggf. -1 ist. Das war der Eintrag, den wir für Lager an einem Knoten genutzt haben, die nicht existieren. Wir überprüfen im folgenden Quelltext zusätzlich auch den Wert *None*, der in *python* ein spezieller Wert für ungenutzte Einträge ist. Probieren wir es aus:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b8f99c0-2846-4585-98a0-14c53511fd2f", + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches\n", + "import matplotlib\n", + "%matplotlib inline\n", + "\n", + "class Fachwerk:\n", + " def getKnoten(self):\n", + " return self._knoten[:]\n", + " def getStaebe(self):\n", + " return self._staebe[:]\n", + " def getNumKnoten(self):\n", + " return len(self._knoten)\n", + " def getNumStaebe(self):\n", + " return len(self._staebe)\n", + " def getNumLager(self):\n", + " numLager=0\n", + " for knoten in self._knoten:\n", + " if knoten._lager[0]:\n", + " numLager+=1\n", + " if knoten._lager[1]:\n", + " numLager+=1\n", + " return numLager\n", + " def isStatischBestimmt(self):\n", + " unbekannte=self.getNumStaebe()+self.getNumLager()\n", + " gleichungen=self.getNumKnoten()*2\n", + " return unbekannte==gleichungen\n", + " def removeStab(self,stab):\n", + " if stab in self._staebe:\n", + " stab.k1._removeStab(stab)\n", + " stab.k2._removeStab(stab)\n", + " self._staebe.remove(stab)\n", + " \n", + " def removeKnoten(self,knoten):\n", + " if knoten in self._knoten:\n", + " for stab in knoten._staebe[:]:\n", + " self.removeStab(stab)\n", + " self._knoten.remove(knoten)\n", + " def __init__(self):\n", + " self._knoten=[]\n", + " self._staebe=[]\n", + " def addKnoten(self, coo):\n", + " neuer_knoten=Knoten(coo)\n", + " self._knoten.append(neuer_knoten)\n", + " return neuer_knoten\n", + " def addStab(self,k1,k2):\n", + " if not k1 in self._knoten:\n", + " return None\n", + " if not k2 in self._knoten:\n", + " return None\n", + " neuer_stab=Stab(k1,k2)\n", + " k1._addStab(neuer_stab)\n", + " k2._addStab(neuer_stab)\n", + " self._staebe.append(neuer_stab)\n", + " return neuer_stab\n", + " ##############################################in diesem Schritt neu####################################################################################\n", + " def plot(self,labels={}):# Ein optionaler Parameter labels. Falls nicht angegeben ein leeres dictionary\n", + " #######################################################################################################################################################\n", + " fig= plt.figure(figsize=(5, 5))\n", + " ax = fig.subplots()\n", + " x_werte=[]\n", + " y_werte=[]\n", + " for knoten in self._knoten:\n", + " koordinate=knoten.getKoordinate()\n", + " x_werte.append(koordinate[0])\n", + " y_werte.append(koordinate[1])\n", + " if np.linalg.norm(knoten._kraft)>0.0001:\n", + " kraftvektor=knoten._kraft/np.linalg.norm(knoten._kraft)\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-kraftvektor[0],koordinate[1]-kraftvektor[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"r\",fc=\"r\")\n", + " ax.add_patch(pfeil)\n", + " ###########################################In diesem Schritt verändert###########################################################################################\n", + " if knoten._lager[0]:#Falls ein Lager in x-Richtung existiert\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-1,koordinate[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"b\",fc=\"b\")\n", + " ax.add_patch(pfeil)\n", + " \n", + " if knoten in labels:#überprüfen, ob für diesen Knoten ein Label im Dictionary existiert\n", + " ax.text(koordinate[0]-0.5,koordinate[1],str(labels[knoten][0])) #Einen Text im Zentrum des Pfeils ausgeben. Im label-Dictionary ist für den Knoten eine Liste von zwei Einträgen hinterlegt. Wir wollen hier den x-Eintrag, also den ersten Eintrag. Wir konvertieren den Eintrag vorsichtshalber in eine Zeichenkette\n", + " \n", + " if knoten._lager[1]:# Das Gleiche für Y-Richtung\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0],koordinate[1]-1),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"b\",fc=\"b\")\n", + " ax.add_patch(pfeil)\n", + " \n", + " if knoten in labels:#überprüfen, ob für diesen Knoten ein Label im Dictionary existiert\n", + " ax.text(koordinate[0],koordinate[1]-0.5,str(labels[knoten][1])) #Einen Text im Zentrum des Pfeils ausgeben. Im label-Dictionary ist für den Knoten eine Liste von zwei Einträgen hinterlegt. Wir wollen hier den y-Eintrag, also den zweiten Eintrag\n", + " #################################################################################################################################################################\n", + " ax.scatter(x_werte,y_werte)\n", + " for stab in self._staebe: \n", + " koordinate1=stab.k1.getKoordinate() \n", + " koordinate2=stab.k2.getKoordinate() \n", + " ax.plot([koordinate1[0],koordinate2[0]],[koordinate1[1],koordinate2[1]],\"r\") \n", + " ###########################################In diesem Schritt verändert###########################################################################################\n", + " if stab in labels:#Überprüfen, ob ein Label für diesen Stab existiert\n", + " zentrum=(koordinate1+koordinate2)/2 #Das Zentrum des Stabes berechnen\n", + " ax.text(zentrum[0],zentrum[1],str(labels[stab])) #Einen Text im Zentrum des Stabes ausgeben. Der Text kommt aus dem label-Dictionary. Für Stäbe ist keine Liste hinterlegt sondern nur der Text für den Stab. Wir konvertieren den Eintrag in eine Zeichenkette, um sicherzustellen, das die Methode auch mit der durchnummerierung funktioniert\n", + " #################################################################################################################################################################\n", + " \n", + "class LoeseFachwerk:\n", + " def __init__(self,fachwerk):\n", + " ##########################In diesem Schrit verändert############################################\n", + " self.fachwerk=copy.deepcopy(fachwerk)#Wir erzeugen eine tiefe Kopie, damit wir jederzeit die Fachwerkdefinition zum Anzeigen verwenden können\n", + " #########################################################################################\n", + " if not fachwerk.isStatischBestimmt():\n", + " return\n", + " self.matrix=np.zeros((fachwerk.getNumKnoten()*2,fachwerk.getNumKnoten()*2))\n", + " self.kraftvektor=np.zeros(fachwerk.getNumKnoten()*2)\n", + " self._nummeriereKnoten()\n", + " self._baueSystem()\n", + " result=np.linalg.solve(self.matrix,-self.kraftvektor)\n", + " self.result=result\n", + " def _baueSystem(self):\n", + " i=0\n", + " for knoten in self.fachwerk.getKnoten():\n", + " zeilen=self.matrix[i:i+2,:]\n", + " for stab in knoten._staebe:\n", + " spalte=self.nummernUnbekannte[stab]\n", + " zeilen[:,spalte]=stab.getRichtung(knoten)\n", + " self.kraftvektor[i:i+2]=knoten._kraft\n", + " if knoten._lager[0]:\n", + " zeilen[0,self.nummernUnbekannte[knoten][0]]=1.\n", + " if knoten._lager[1]:\n", + " zeilen[1,self.nummernUnbekannte[knoten][1]]=1\n", + " i+=2\n", + " def _nummeriereKnoten(self):\n", + " nummernUnbekannte={}\n", + " nr=0\n", + " for stab in self.fachwerk.getStaebe():\n", + " nummernUnbekannte[stab]=nr\n", + " nr+=1\n", + " for knoten in self.fachwerk.getKnoten():\n", + " if knoten._lager[0] or knoten._lager[1]:\n", + " nummern=[-1,-1]\n", + " if knoten._lager[0]:\n", + " nummern[0]=nr\n", + " nr+=1\n", + " if knoten._lager[1]:\n", + " nummern[1]=nr\n", + " nr+=1\n", + " nummernUnbekannte[knoten]=nummern\n", + " self.nummernUnbekannte=nummernUnbekannte\n", + " ########################In diesem Schritt neu#####################################################\n", + " def _printTabelle(self,namen):#gibt die Kraftwerte in Textform aus. Der Parameter namen ist das dictionary mit den labels, die auch in der grafischen Ausgabe verwendet werden\n", + " for obj in namen:#iteriert über alle Einträge im dictionary. Dabei wird über die key-Werte iteriert\n", + " label=namen[obj]#Den zum key gehörenden Eintrag mit den Namen ermitteln\n", + " if isinstance(label,list):#Falls der Eintrag eine Liste ist (alternativ könnte man obj auf Instanz von Knoten überprüfen)\n", + " if label[0]!=-1 and label[0]!=None:#Überprüfen, ob der erste Eintrag gültig ist (er darf nicht -1 sein dazu siehe letzte Übung und nicht None, dazu später)\n", + " nr=self.nummernUnbekannte[obj][0]#Die Zeilennummer im Ergebnisvektor ermitteln\n", + " print (str(label[0])+\" : \"+str(self.result[nr]))#Name und Wert ausgeben\n", + " if label[1]!=-1 and label[1]!=None:#Das selbe für den zweiten Eintrag (also das y-Lager)\n", + " nr=self.nummernUnbekannte[obj][1]\n", + " print (str(label[1])+\" : \"+str(self.result[nr]))\n", + " else:#Falls nicht ist der Eintrag ein Stab, hat also nur einen Namen und nur eine Kraft\n", + " nr= self.nummernUnbekannte[obj]#Die Zeile im Ergebnisvektor ermitteln\n", + " print(str(label)+\" : \"+str(self.result[nr]))#Den Namen und den Wert ausgeben.\n", + " \n", + " def plot(self):\n", + " self.fachwerk.plot(self.nummernUnbekannte)#das Fachwerk mit den Spaltennummern als Namen ausgeben\n", + " self._printTabelle(self.nummernUnbekannte)#Die zugehörige Tabelle ausgeben" + ] + }, + { + "cell_type": "markdown", + "id": "e677e8ec-f38d-4e3b-8ac7-689586f565c7", + "metadata": {}, + "source": [ + "Ein kleiner Test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51d7eeee-aee1-4c0a-aed4-5f967e30937c", + "metadata": {}, + "outputs": [], + "source": [ + "#Ein kleines Beispielfachwerk aus drei Stäben, einer Kraft und 3 Lagern\n", + "f=Fachwerk()\n", + "K1=f.addKnoten([0,0])\n", + "K2=f.addKnoten([5,5])\n", + "K3=f.addKnoten([15,0])\n", + "S1=f.addStab(K1,K2)\n", + "S2=f.addStab(K2,K3)\n", + "S3=f.addStab(K1,K3)\n", + "K1.setLager_x()\n", + "K1.setLager_y()\n", + "K3.setLager_y()\n", + "K2.setKraft([10,-40])\n", + "result=LoeseFachwerk(f)\n", + "result.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b0bfa337-2881-4ee5-aa48-5a9e451c879e", + "metadata": {}, + "source": [ + "## **Schritt 2 - Grafische Ausgabe und verbesserte Textausgabe**\n", + "So weit funktioniert alles wie geplant. Es wäre evtl. schön, wenn die Nummerierung der Stäbe nicht bei 0 beginnen würde und die Lager Extranamen wie L_1, L_2 etc. hätten. Dazu müssen wir ein *dictionary* mit entsprechenden Namen anlegen. Das sollte nicht zu schwierig sein.\n", + "\n", + "Außerdem wäre eine grafische Ausgabe schön. Die Stäbe und Auflagerpfeile sollen unterschiedliche Farben in Abhängigkeit ihrer Kräfte haben. Wir müssen also je nach Kraft den dargestellten Objekten eine Farbe zuordnen. Solche Farbverläufe nennen sich *colormaps* ( https://matplotlib.org/stable/gallery/color/colormap_reference.html#sphx-glr-gallery-color-colormap-reference-py) und sind relativ gebräuchlich bei der Datenvisualisierung.\n", + "\n", + "Die Klasse, die wir benötigen, heißt *matplotlib.cm.ScalarMappable*. Für einen Farbverlauf (ich verwende hier \"rainbow\") und einen Minimal- und Maximalwert, liefert diese Klasse für jeden Wert eine zugehörige Farbe. Wir benötigen also eine Colormap, die zwischen der minimalen Kraft und der maximalen Kraft einen Farbverlauf berechnen kann. Zusätzlich benötigen wir die Kraftwerte für alle Stäbe und Auflager. Für die Anzeige der Farben gilt die selbe Überlegung, wie für die Anzeige der Labels in der Darstellung. Wir erweitern die Methode in der *Fachwerk*-Klasse. Prinzipiell sind die Kraftwerte bereits bekannt. Die Zuordnung zwischen Vektorzeile und Objekt ist allerdings Sache der *LoeseFachwerk*-Klasse. Um eine zu starke Abhängigkeit zu vermeiden, bereiten wir die Werte in einem *dictionary* identisch dem der Labels vor. In diesem *dictionary* stehen die Kraftwerte. Die eigentliche Colormap wird als extra-Parameter übergeben. Man könnte die Farbwerte auch gleich speichern, allerdings kann man mit der Colormap eine Legende ausgeben lassen, was insgesammt schöner aussieht.\n", + "\n", + "Das Ergebnis könnte so aussehen:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6932a700-122d-4fb0-a061-0806ddf05a7a", + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches\n", + "import matplotlib\n", + "%matplotlib inline\n", + "\n", + "class Fachwerk:\n", + " def getKnoten(self):\n", + " return self._knoten[:]\n", + " def getStaebe(self):\n", + " return self._staebe[:]\n", + " def getNumKnoten(self):\n", + " return len(self._knoten)\n", + " def getNumStaebe(self):\n", + " return len(self._staebe)\n", + " def getNumLager(self):\n", + " numLager=0\n", + " for knoten in self._knoten:\n", + " if knoten._lager[0]:\n", + " numLager+=1\n", + " if knoten._lager[1]:\n", + " numLager+=1\n", + " return numLager\n", + " def isStatischBestimmt(self):\n", + " unbekannte=self.getNumStaebe()+self.getNumLager()\n", + " gleichungen=self.getNumKnoten()*2\n", + " return unbekannte==gleichungen\n", + " def removeStab(self,stab):\n", + " if stab in self._staebe:\n", + " stab.k1._removeStab(stab)\n", + " stab.k2._removeStab(stab)\n", + " self._staebe.remove(stab)\n", + " \n", + " def removeKnoten(self,knoten):\n", + " if knoten in self._knoten:\n", + " for stab in knoten._staebe[:]:\n", + " self.removeStab(stab)\n", + " self._knoten.remove(knoten)\n", + " def __init__(self):\n", + " self._knoten=[]\n", + " self._staebe=[]\n", + " def addKnoten(self, coo):\n", + " neuer_knoten=Knoten(coo)\n", + " self._knoten.append(neuer_knoten)\n", + " return neuer_knoten\n", + " def addStab(self,k1,k2):\n", + " if not k1 in self._knoten:\n", + " return None\n", + " if not k2 in self._knoten:\n", + " return None\n", + " neuer_stab=Stab(k1,k2)\n", + " k1._addStab(neuer_stab)\n", + " k2._addStab(neuer_stab)\n", + " self._staebe.append(neuer_stab)\n", + " return neuer_stab\n", + " ######################################################In diesem Schritt neu#######################################\n", + " def plot(self,labels={},werte={},cmap=None):#Das Wert-dictionary und eine colormap sind neue optionale Parameter\n", + " if cmap!=None:#Falls eine colormap angegeben ist, ändern wir das Seitenverhältnis, um Platz für die Legende zu machen\n", + " fig= plt.figure(figsize=(7, 5))#Eine etwas breitere figure\n", + " ax,ax2 = fig.subplots(1,2,width_ratios=(10,1))#Zwei Diagramme nebeneinander erstellen. Ein breites links für das Fachwerk und ein Schmales für die Farblegende\n", + " else:\n", + " fig= plt.figure(figsize=(5, 5))#Falls keine Colormap angegeben ist wie vorher ein Diagramm für das Stabtragwerk erstellen\n", + " ax = fig.subplots()\n", + " ############################################################################################################## \n", + " \n", + " x_werte=[]\n", + " y_werte=[]\n", + " for knoten in self._knoten:\n", + " koordinate=knoten.getKoordinate()\n", + " x_werte.append(koordinate[0])\n", + " y_werte.append(koordinate[1])\n", + " if np.linalg.norm(knoten._kraft)>0.0001:\n", + " kraftvektor=knoten._kraft/np.linalg.norm(knoten._kraft)\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-kraftvektor[0],koordinate[1]-kraftvektor[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"r\",fc=\"r\")\n", + " ax.add_patch(pfeil)\n", + " ######################################################In diesem Schritt neu#######################################\n", + " if knoten._lager[0]:#Falls ein Lager in x-Richtung existiert\n", + " color=\"b\"#Standardfarbe auf blau setzen\n", + " if cmap!=None and knoten in werte:#falls eine Colormap und ein Wert für die Lagerkraft existiert\n", + " color=cmap.to_rgba(werte[knoten][0])#Die Kraft in eine Farbe umwandeln\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-1,koordinate[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=color,fc=color)#Einen Pfeil in angegebener Farbe Zeichnen, in X-Richtung, auf den Knoten zu\n", + " ax.add_patch(pfeil)\n", + " \n", + " if knoten in labels:\n", + " ax.text(koordinate[0]-0.5,koordinate[1],str(labels[knoten][0]))\n", + " \n", + " if knoten._lager[1]:# Das Gleiche für Y-Richtung\n", + " if cmap!=None and knoten in werte:\n", + " color=cmap.to_rgba(werte[knoten][1])\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0],koordinate[1]-1),(koordinate[0],koordinate[1]),mutation_scale=30,ec=color,fc=color)\n", + " ax.add_patch(pfeil)\n", + " \n", + " if knoten in labels:\n", + " ax.text(koordinate[0],koordinate[1]-0.5,str(labels[knoten][1])) \n", + " ######################################################################################################################\n", + " ax.scatter(x_werte,y_werte)\n", + " for stab in self._staebe: \n", + " koordinate1=stab.k1.getKoordinate() \n", + " koordinate2=stab.k2.getKoordinate() \n", + " ######################################################In diesem Schritt neu#######################################\n", + " color=\"r\"#Standardfarbe auf Rot setzen\n", + " if cmap!=None and stab in werte:#Falls colormap und Wert für diesen Stab verfügbar\n", + " color=cmap.to_rgba(werte[stab])#Farbe berechnen\n", + " ax.plot([koordinate1[0],koordinate2[0]],[koordinate1[1],koordinate2[1]],color=color) #Stab in angegebener Farbe zeichnen\n", + " ####################################################################################################################\n", + " if stab in labels:\n", + " zentrum=(koordinate1+koordinate2)/2 \n", + " ax.text(zentrum[0],zentrum[1],str(labels[stab])) \n", + "\n", + " ######################################################In diesem Schritt neu#######################################\n", + " if cmap!=None:#Falls eine colormap existiert\n", + " fig.colorbar(cmap,cax=ax2)#die colormap im rechten Diagramm ausgeben\n", + " #####################################################################################################################\n", + "class LoeseFachwerk:\n", + " def __init__(self,fachwerk):\n", + " self.fachwerk=copy.deepcopy(fachwerk)\n", + " if not fachwerk.isStatischBestimmt():\n", + " return\n", + " self.matrix=np.zeros((fachwerk.getNumKnoten()*2,fachwerk.getNumKnoten()*2))\n", + " self.kraftvektor=np.zeros(fachwerk.getNumKnoten()*2)\n", + " self._nummeriereKnoten()\n", + " self._baueSystem()\n", + " result=np.linalg.solve(self.matrix,-self.kraftvektor)\n", + " self.result=result\n", + " def _baueSystem(self):\n", + " i=0\n", + " for knoten in self.fachwerk.getKnoten():\n", + " zeilen=self.matrix[i:i+2,:]\n", + " for stab in knoten._staebe:\n", + " spalte=self.nummernUnbekannte[stab]\n", + " zeilen[:,spalte]=stab.getRichtung(knoten)\n", + " self.kraftvektor[i:i+2]=knoten._kraft\n", + " if knoten._lager[0]:\n", + " zeilen[0,self.nummernUnbekannte[knoten][0]]=1.\n", + " if knoten._lager[1]:\n", + " zeilen[1,self.nummernUnbekannte[knoten][1]]=1\n", + " i+=2\n", + " def _nummeriereKnoten(self):\n", + " nummernUnbekannte={}\n", + " nr=0\n", + " for stab in self.fachwerk.getStaebe():\n", + " nummernUnbekannte[stab]=nr\n", + " nr+=1\n", + " for knoten in self.fachwerk.getKnoten():\n", + " if knoten._lager[0] or knoten._lager[1]:\n", + " nummern=[-1,-1]\n", + " if knoten._lager[0]:\n", + " nummern[0]=nr\n", + " nr+=1\n", + " if knoten._lager[1]:\n", + " nummern[1]=nr\n", + " nr+=1\n", + " nummernUnbekannte[knoten]=nummern\n", + " self.nummernUnbekannte=nummernUnbekannte\n", + " def _printTabelle(self,namen):\n", + " for obj in namen:\n", + " label=namen[obj]\n", + " if isinstance(label,list):\n", + " if label[0]!=-1 and label[0]!=None:\n", + " nr=self.nummernUnbekannte[obj][0]\n", + " print (str(label[0])+\" : \"+str(self.result[nr]))\n", + " if label[1]!=-1 and label[1]!=None:\n", + " nr=self.nummernUnbekannte[obj][1]\n", + " print (str(label[1])+\" : \"+str(self.result[nr]))\n", + " else:\n", + " nr= self.nummernUnbekannte[obj]\n", + " print(str(label)+\" : \"+str(self.result[nr]))\n", + " \n", + " def plot(self):\n", + " ######################################################In diesem Schritt neu#######################################\n", + " ergebnis={}#Dictionary für die Ergebiswerte zum plotten\n", + " label={}#Ein dictionary für die Namen\n", + " n_stab=1#Variable zum Zählen der Stäbe\n", + " n_randbedingung=1#Variable zum Zählen der Lager\n", + " for obj in self.nummernUnbekannte:#Iterieren über alle Objekte\n", + " nr=self.nummernUnbekannte[obj]#Die Zeilenzahlen zum Objekt\n", + " if isinstance(obj,Stab):#Falls das Objekt ein Stab ist\n", + " ergebnis[obj]=self.result[nr]#Den Wert aus dem Ergebnisvektor heraussuchen und anschließend im dictionary speichern\n", + " label[obj]=str(n_stab)#Dem Stab als Namen die nächste Freie nummer zuweisen\n", + " n_stab+=1#Die Stabnummer ist jetzt verwendet, also als nächstes die nächste Nummer verwenden\n", + " elif isinstance(obj,Knoten):#Falls das Objekt ein Knoten ist\n", + " t=[None,None]#Eine Liste mit zwei nicht vorhandenen Werten (wir vewenden hier None, da eine Kraft jeden beliebigen Wert annehmen kann)\n", + " nummern=self.nummernUnbekannte[obj]#Die Zeilennummern der Auflager\n", + " l=[None,None]#Zwei nicht definierte Namen\n", + " if nummern[0]!=-1:#Falls die Zeilennummer -1 ist, wäre das Lager nicht definiert, dann überspringen wir den Block\n", + " l[0]=\"L_\"+str(n_randbedingung)#Als Namen für dieses Lager die nächste verfügbare Zahl verwenden\n", + " n_randbedingung+=1#Das nächste Lager\n", + " t[0]=result.result[nummern[0]]#Den Kraftwert aus dem Ergebnisvektor nehmen\n", + " if nummern[1]!=-1:#Das selbe für das Lager in y-Richtung\n", + " l[1]=\"L_\"+str(n_randbedingung)\n", + " n_randbedingung+=1\n", + " t[1]=self.result[nummern[1]]\n", + " ergebnis[obj]=t#Im dictionary die Liste mit Kraftwerten anlegen\n", + " label[obj]=l#Im dictionary die Namen als Liste anlegen\n", + " mapper=matplotlib.cm.ScalarMappable(cmap=\"rainbow\")#Eine colormap mit dem Farbverlauf \"rainbow\" erstellen\n", + " mapper.set_clim(self.result.min(),self.result.max())#Minimal- und Maximalwert aus dem Ergebnisvektor nehmen \n", + " ##################################################################################################################\n", + " self.fachwerk.plot(label,ergebnis,cmap=mapper)\n", + " self._printTabelle(label)" + ] + }, + { + "cell_type": "markdown", + "id": "e2add6be-7d23-4f40-b1a0-7eff393c64da", + "metadata": {}, + "source": [ + "Ein Test:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8cd64f6-e38a-4d97-9add-03c14ca7da14", + "metadata": {}, + "outputs": [], + "source": [ + "#Ein kleines Beispielfachwerk aus drei Stäben, einer Kraft und 3 Lagern\n", + "f=Fachwerk()\n", + "K1=f.addKnoten([0,0])\n", + "K2=f.addKnoten([5,5])\n", + "K3=f.addKnoten([15,0])\n", + "S1=f.addStab(K1,K2)\n", + "S2=f.addStab(K2,K3)\n", + "S3=f.addStab(K1,K3)\n", + "K1.setLager_x()\n", + "K1.setLager_y()\n", + "K3.setLager_y()\n", + "K2.setKraft([10,-40])\n", + "result=LoeseFachwerk(f)\n", + "result.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "05bb8453-5fe8-4271-b3fe-c4521ba19261", + "metadata": {}, + "source": [ + "### **Aufgaben zum zu Hause ausprobieren**\n", + "- Probiere komplizierte Stabtragwerke aus\n", + "- Ermittle die Nullstäbe mithilfe der bekannten Regeln. Stelle sie in den Ergebnissen als gestrichelte Linien dar\n", + "- Durch den Designfehler keine eigene Klasse für Lager erstellt zu haben, ist das Label-Dictionary uneinheitlich. Für Knoten enthält es zwei Einträge als Liste, für Stäbe einen Eintrag. Dieser Fehler macht die Typ-Abfrage bei der Darstellung nötig. Behebe ihn, indem du eine eigene Klasse für Lagerbedingungen implementierst. Dabei kann jeder Knoten ein Lager-Objekt in x und ein Lager-Objekt in y Richtung besitzen\n", + "- Erweitere das komplette Programm auf 3D-Stabtragwerke" + ] + }, + { + "cell_type": "markdown", + "id": "5683ae15-f5f1-48ac-b06e-4ecab67eceaf", + "metadata": {}, + "source": [ + "Das komplette Programm:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46d441fa-7d04-4762-a055-a45c152b584a", + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches\n", + "import matplotlib\n", + "%matplotlib inline\n", + "class Knoten:\n", + " def __init__(self, coo):\n", + " self._coo=np.array(coo)# wir sollten sicherstellen, das es sich bei der intern gespeicherten Koordinate um ein np.array handelt. Potentiell sollte noch der Datentyp auf Fließkomma gesetzt werden und die Größe überprüft werden\n", + " self._kraft=np.zeros(2) #die Kraft ist standardmäßig 0\n", + " self._lager=[False,False] #Beide Lagerbedingungen sind nicht gesetzt\n", + " \n", + " self._staebe=[] #Es gibt bei Erstellung noch keine benachbarten Stäbe\n", + " def _addStab(self,stab):\n", + " self._staebe.append(stab)#Einen neuen Nachbarstab hinzufügen\n", + " def _removeStab(self,stab):\n", + " self._staebe.remove(stab)#Einen Nachbarstab entfernen\n", + " \n", + " def setKraft(self,kraft):\n", + " self._kraft[:]=kraft#Die Einträge von Kraft werden neu gesetzt. So ist sichergestellt, das die Attribute immer ein 2D-Vektor ist\n", + " def setLager_x(self):\n", + " self._lager[0]=True#Setze die Lagerbedingung in x-Richtung\n", + " def setLager_y(self):\n", + " self._lager[1]=True#Setze die Lagerbedingung in y-Richtung\n", + " def removeLager_x(self):\n", + " self._lager[0]=False#Entferne die Lagerbedingung in x-Richtung\n", + " def removeLager_y(self):\n", + " self._lager[1]=False#Entferne die Lagerbedingung in y-Richtung\n", + "\n", + " def getKoordinate(self):\n", + " return self._coo#gibt die Koordinaten des Punktes zurück\n", + "\n", + "class Stab:\n", + " def __init__(self,k1,k2):\n", + " self.k1=k1#Der erste Endpunkt\n", + " self.k2=k2#Der zweite Endpunkt\n", + " def getRichtung(self,k):\n", + " if k == self.k1:#Falls der gerade betrachtete Punkt k1 ist\n", + " vektor= self.k2.getKoordinate()-k.getKoordinate()#bilde den Richtungsvektor von K1(bzw. K) zu K2\n", + " return vektor/np.linalg.norm(vektor)#Durch den Betrag teilen und zurückgeben\n", + " if k == self.k2:#Falls der gerade betrachtete Punkt k2 ist\n", + " vektor= self.k1.getKoordinate()-k.getKoordinate()#Bilde den Richtungsvektor von K2(bzw. K) zu K1\n", + " return vektor/np.linalg.norm(vektor)#Durch den Betrag teilen um Einheitsvektor zu erhalten\n", + " return np.zeros(2)#nur als Fail-Safe, wenn der betrachtete Punkt nicht zum Stab gehört\n", + "class Fachwerk:\n", + " def getKnoten(self):\n", + " return self._knoten[:]#Eine flache Kopie der Knotenliste\n", + " def getStaebe(self):\n", + " return self._staebe[:]#Eine flache Kopie der Stabliste\n", + " def getNumKnoten(self):\n", + " return len(self._knoten)#Die Länge der Knotenliste\n", + " def getNumStaebe(self):\n", + " return len(self._staebe)#Die Länge der Stabliste\n", + " def getNumLager(self):\n", + " numLager=0#Eine Variable die zählt, wie viele Lager gefunden wurden\n", + " for knoten in self._knoten:#Eine Schleife über alle Knoten\n", + " if knoten._lager[0]:#Falls der Knoten eine Lagerbedingung in x-Richtung besitzt\n", + " numLager+=1#Die Zählvariable hochzählen\n", + " if knoten._lager[1]:#Falls der Knoten eine Lagerbedingung in y-Richtung besitzt\n", + " numLager+=1#Die Zählvariable hochzählen\n", + " return numLager#Die Gesamtzahl der Lager zurückgeben\n", + " def isStatischBestimmt(self):\n", + " unbekannte=self.getNumStaebe()+self.getNumLager()#Die Anzahl der Unbekannten ist die Zahl der Stäbe plus die Zahl der Lager\n", + " gleichungen=self.getNumKnoten()*2#Die Zahl der Gleichungen ist die Zahl der Knoten mal zwei\n", + " return unbekannte==gleichungen#Zurückgeben ob die Zahl der Unbekannten gleich der Zahl der Gleichungen ist\n", + " def removeStab(self,stab):\n", + " if stab in self._staebe:#Überprüfen ob der Stab zum Fachwerk gehört\n", + " stab.k1._removeStab(stab)#Der Stab ist kein Nachbar seiner Endpunkte mehr\n", + " stab.k2._removeStab(stab)\n", + " self._staebe.remove(stab)#Den Stab aus der Liste der Stäbe entfernen\n", + " \n", + " def removeKnoten(self,knoten):\n", + " if knoten in self._knoten:#Überprüfen, ob der Knoten zum Fachwerk gehört\n", + " for stab in knoten._staebe[:]:#Über alle Stäbe in der Nachbarschaft iterieren (bzw. einer flachen Kopie, da die Liste verändert wird)\n", + " self.removeStab(stab)#Den Stab entfernen (das verändert die Nachbarschaftsliste des Knotens, dh. wird über eine flache Kopie iteriert\n", + " self._knoten.remove(knoten)#Den Knoten aus der Liste der Knoten entfernen\n", + " def __init__(self):\n", + " self._knoten=[]\n", + " self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten\n", + " def addKnoten(self, coo):\n", + " neuer_knoten=Knoten(coo)#Neues Knotenobjekt anlegen\n", + " self._knoten.append(neuer_knoten)#Zur Liste hinzufügen\n", + " return neuer_knoten#Und das neue Objekt zurückgeben\n", + " def addStab(self,k1,k2):\n", + " if not k1 in self._knoten:#Überprüfen, ob die Endpunkte überhaupt zum Fachwerk gehören\n", + " return None\n", + " if not k2 in self._knoten:\n", + " return None\n", + " neuer_stab=Stab(k1,k2)#Neuen Stab anlegen\n", + " k1._addStab(neuer_stab)#Der neue Stab ist nun ein Nachbar der Endknoten\n", + " k2._addStab(neuer_stab)\n", + " self._staebe.append(neuer_stab)#Stab in die Liste der Stäbe einfügen\n", + " return neuer_stab#Und neuen Stab zurückgeben\n", + " def plot(self,labels={},werte={},cmap=None):#Das Wert-dictionary und eine colormap sind neue optionale Parameter\n", + " if cmap!=None:#Falls eine colormap angegeben ist, ändern wir das Seitenverhältnis, um Platz für die Legende zu machen\n", + " fig= plt.figure(figsize=(7, 5))#Eine etwas breitere figure\n", + " ax,ax2 = fig.subplots(1,2,width_ratios=(10,1))#Zwei Diagramme nebeneinander erstellen. Ein breites links für das Fachwerk und ein Schmales für die Farblegende\n", + " else:\n", + " fig= plt.figure(figsize=(5, 5))#Falls keine Colormap angegeben ist wie vorher ein Diagramm für das Stabtragwerk erstellen\n", + " ax = fig.subplots() \n", + " x_werte=[]#Eine Liste für die x- und y-Werte der Knoten\n", + " y_werte=[]\n", + " for knoten in self._knoten:#Über die Knoten iterieren\n", + " koordinate=knoten.getKoordinate()#die Koordinate holen\n", + " x_werte.append(koordinate[0])#den x-Wert zu den x-Werten hinzufügen\n", + " y_werte.append(koordinate[1])#den y-Wert zu den y-Werten hinzufügen\n", + " if np.linalg.norm(knoten._kraft)>0.0001:#Schauen, ob eine Gesamtkraft angreift. Direkte Vergleiche zwischen Fließkommazahlen vermeiden. Diese Methode der Überprüfung ist nicht ideal\n", + " kraftvektor=knoten._kraft/np.linalg.norm(knoten._kraft)#Normalenvektor der Kraft berechnen. Die Kraftvektorendarstellung ist sonst sehr lang, da Kräfte oft verhältnismäßig große Zahlen\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-kraftvektor[0],koordinate[1]-kraftvektor[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=\"r\",fc=\"r\")#Pfeil in Rot zeichnen\n", + " ax.add_patch(pfeil)#Pfeil dem Diagrmm hinzufügen\n", + " if knoten._lager[0]:#Falls ein Lager in x-Richtung existiert\n", + " color=\"b\"#Standardfarbe auf blau setzen\n", + " if cmap!=None and knoten in werte:#falls eine Colormap und ein Wert für die Lagerkraft existiert\n", + " color=cmap.to_rgba(werte[knoten][0])#Die Kraft in eine Farbe umwandeln\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0]-1,koordinate[1]),(koordinate[0],koordinate[1]),mutation_scale=30,ec=color,fc=color)#Einen Pfeil in angegebener Farbe Zeichnen, in X-Richtung, auf den Knoten zu\n", + " ax.add_patch(pfeil)#Pfeil dem Diagramm hinzufügen\n", + " \n", + " if knoten in labels:\n", + " ax.text(koordinate[0]-0.5,koordinate[1],str(labels[knoten][0]))\n", + " \n", + " if knoten._lager[1]:# Das Gleiche für Y-Richtung\n", + " if cmap!=None and knoten in werte:\n", + " color=cmap.to_rgba(werte[knoten][1])\n", + " pfeil=mpatches.FancyArrowPatch((koordinate[0],koordinate[1]-1),(koordinate[0],koordinate[1]),mutation_scale=30,ec=color,fc=color)\n", + " ax.add_patch(pfeil)\n", + " \n", + " if knoten in labels:\n", + " ax.text(koordinate[0],koordinate[1]-0.5,str(labels[knoten][1])) \n", + " ax.scatter(x_werte,y_werte)#Zeichnet alle Knoten als Kreise\n", + " for stab in self._staebe: #Über alle Stäbe iterieren\n", + " koordinate1=stab.k1.getKoordinate()#Koordinate des ersten Punktes holen\n", + " koordinate2=stab.k2.getKoordinate()#Und des zweiten Punktes\n", + " color=\"r\"#Standardfarbe auf Rot setzen\n", + " if cmap!=None and stab in werte:#Falls colormap und Wert für diesen Stab verfügbar\n", + " color=cmap.to_rgba(werte[stab])#Farbe berechnen\n", + " ax.plot([koordinate1[0],koordinate2[0]],[koordinate1[1],koordinate2[1]],color=color) #Stab in angegebener Farbe zeichnen\n", + " if stab in labels:#Überprüfen, ob ein Label für den Stab existiert\n", + " zentrum=(koordinate1+koordinate2)/2 #Das Zentrum des Stabes berechnen\n", + " ax.text(zentrum[0],zentrum[1],str(labels[stab])) #Das Label ausgeben\n", + " if cmap!=None:#Falls eine colormap existiert\n", + " fig.colorbar(cmap,cax=ax2)#die colormap im rechten Diagramm ausgeben\n", + "class LoeseFachwerk:\n", + " def __init__(self,fachwerk):\n", + " self.fachwerk=copy.deepcopy(fachwerk)#Das Fachwerk als tiefe Kopie speichern\n", + " if not fachwerk.isStatischBestimmt():#Falls das Fachwerk nicht statisch bestimmt ist hier abbrechen\n", + " return\n", + " self.matrix=np.zeros((fachwerk.getNumKnoten()*2,fachwerk.getNumKnoten()*2))#Eine Matrix der benötigten Größe mit Nullen anlegen. Wenn wir hier angekommen ist, ist die Matrix quadratisch\n", + " self.kraftvektor=np.zeros(fachwerk.getNumKnoten()*2)#Den Gesamtkraftvektor mit Nullen erstellen\n", + " self._nummeriereKnoten()#Dictionary mit Knotennummern erstellen\n", + " self._baueSystem()#Matrix und Vektor aufbauen\n", + " result=np.linalg.solve(self.matrix,-self.kraftvektor)#Gleichungssystem lösen (Grund für das Minus oben)\n", + " self.result=result#Ergebnis speichern\n", + " def _baueSystem(self):\n", + " i=0#Variable um mitzuzählen, welche Zeilen wir belegen\n", + " for knoten in self.fachwerk.getKnoten():#Über alle Knoten iterieren\n", + " zeilen=self.matrix[i:i+2,:]#Die beiden Zeilen der Matrix. Wir erschaffen einen View, die Gesamtmatrix wird also mitverändert\n", + " for stab in knoten._staebe:#Über alle verbundenen Stäbe iterieren\n", + " spalte=self.nummernUnbekannte[stab]#Die Spalte, die zum Stab gehört herausfinden\n", + " zeilen[:,spalte]=stab.getRichtung(knoten)#In diese Spalte der zwei Zeilen den Richtungsvektor des Stabes weg von diesem Knoten schreiben\n", + " self.kraftvektor[i:i+2]=knoten._kraft#Die Kraft in den Kraftvektor einfügen\n", + " if knoten._lager[0]:#Falls der Knoten ein x-Lager besitzt\n", + " zeilen[0,self.nummernUnbekannte[knoten][0]]=1.#in die erste Zeile in der betreffenden Spalte eine 1 Einfügen\n", + " if knoten._lager[1]:#Das Gleiche für das y-Lager, nur in der zweiten Zeile\n", + " zeilen[1,self.nummernUnbekannte[knoten][1]]=1\n", + " i+=2#In der nächsten Iteration zwei Zeilen weiter schreiben\n", + " def _nummeriereKnoten(self):\n", + " nummernUnbekannte={}#Ein leeres Dictionary\n", + " nr=0#ein Zähler, welche Spalte die nächste unbelegte ist\n", + " for stab in self.fachwerk.getStaebe():#über alle Stäbe iterieren\n", + " nummernUnbekannte[stab]=nr#In das Dictionary für dieses Stabobjekt die nächste freie Spalte hinterlegen\n", + " nr+=1#Die aktuelle Spalte ist belegt\n", + " for knoten in self.fachwerk.getKnoten():#Über alle Knoten iterieren\n", + " if knoten._lager[0] or knoten._lager[1]:#Falls der Knoten ein Lager in x- oder y-Richtung besitzt\n", + " nummern=[-1,-1]#Erst einmal eine Liste mit zwei ungültigen Indizes erstellen\n", + " if knoten._lager[0]:#Falls der Knoten eine Lagerbedingung in x-Richtung besitzt\n", + " nummern[0]=nr#Der x-Richtung die nächste Spalte zuordnen\n", + " nr+=1#Die aktuelle Spalte ist belegt, die nächste leere auswählen\n", + " if knoten._lager[1]:#das Gleiche für die y-Richtung\n", + " nummern[1]=nr\n", + " nr+=1\n", + " nummernUnbekannte[knoten]=nummern#Die erstellte Liste für dieses Knoten-Objekt hinterlegen\n", + " self.nummernUnbekannte=nummernUnbekannte#Die Liste als Attribut der Klasse speichern\n", + " def _printTabelle(self,namen):#gibt die Kraftwerte in Textform aus. Der Parameter namen ist das dictionary mit den labels, die auch in der grafischen Ausgabe verwendet werden\n", + " for obj in namen:#iteriert über alle Einträge im dictionary. Dabei wird über die key-Werte iteriert\n", + " label=namen[obj]#Den zum key gehörenden Eintrag mit den Namen ermitteln\n", + " if isinstance(label,list):#Falls der Eintrag eine Liste ist (alternativ könnte man obj auf Instanz von Knoten überprüfen)\n", + " if label[0]!=-1 and label[0]!=None:#Überprüfen, ob der erste Eintrag gültig ist (er darf nicht -1 sein dazu siehe letzte Übung und nicht None, dazu später)\n", + " nr=self.nummernUnbekannte[obj][0]#Die Zeilennummer im Ergebnisvektor ermitteln\n", + " print (str(label[0])+\" : \"+str(self.result[nr]))#Name und Wert ausgeben\n", + " if label[1]!=-1 and label[1]!=None:#Das selbe für den zweiten Eintrag (also das y-Lager)\n", + " nr=self.nummernUnbekannte[obj][1]\n", + " print (str(label[1])+\" : \"+str(self.result[nr]))\n", + " else:#Falls nicht ist der Eintrag ein Stab, hat also nur einen Namen und nur eine Kraft\n", + " nr= self.nummernUnbekannte[obj]#Die Zeile im Ergebnisvektor ermitteln\n", + " print(str(label)+\" : \"+str(self.result[nr]))#Den Namen und den Wert ausgeben. \n", + " def plot(self):\n", + " ergebnis={}#Dictionary für die Ergebiswerte zum plotten\n", + " label={}#Ein dictionary für die Namen\n", + " n_stab=1#Variable zum Zählen der Stäbe\n", + " n_randbedingung=1#Variable zum Zählen der Lager\n", + " for obj in self.nummernUnbekannte:#Iterieren über alle Objekte\n", + " nr=self.nummernUnbekannte[obj]#Die Zeilenzahlen zum Objekt\n", + " if isinstance(obj,Stab):#Falls das Objekt ein Stab ist\n", + " ergebnis[obj]=self.result[nr]#Den Wert aus dem Ergebnisvektor heraussuchen und anschließend im dictionary speichern\n", + " label[obj]=str(n_stab)#Dem Stab als Namen die nächste Freie nummer zuweisen\n", + " n_stab+=1#Die Stabnummer ist jetzt verwendet, also als nächstes die nächste Nummer verwenden\n", + " elif isinstance(obj,Knoten):#Falls das Objekt ein Knoten ist\n", + " t=[None,None]#Eine Liste mit zwei nicht vorhandenen Werten (wir vewenden hier None, da eine Kraft jeden beliebigen Wert annehmen kann)\n", + " nummern=self.nummernUnbekannte[obj]#Die Zeilennummern der Auflager\n", + " l=[None,None]#Zwei nicht definierte Namen\n", + " if nummern[0]!=-1:#Falls die Zeilennummer -1 ist, wäre das Lager nicht definiert, dann überspringen wir den Block\n", + " l[0]=\"L_\"+str(n_randbedingung)#Als Namen für dieses Lager die nächste verfügbare Zahl verwenden\n", + " n_randbedingung+=1#Das nächste Lager\n", + " t[0]=result.result[nummern[0]]#Den Kraftwert aus dem Ergebnisvektor nehmen\n", + " if nummern[1]!=-1:#Das selbe für das Lager in y-Richtung\n", + " l[1]=\"L_\"+str(n_randbedingung)\n", + " n_randbedingung+=1\n", + " t[1]=self.result[nummern[1]]\n", + " ergebnis[obj]=t#Im dictionary die Liste mit Kraftwerten anlegen\n", + " label[obj]=l#Im dictionary die Namen als Liste anlegen\n", + " mapper=matplotlib.cm.ScalarMappable(cmap=\"rainbow\")#Eine colormap mit dem Farbverlauf \"rainbow\" erstellen\n", + " mapper.set_clim(self.result.min(),self.result.max())#Minimal- und Maximalwert aus dem Ergebnisvektor nehmen \n", + " self.fachwerk.plot(label,ergebnis,cmap=mapper)#das Fachwerk ausgeben\n", + " self._printTabelle(label)#Die zugehörige Tabelle ausgeben" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} -- GitLab