Skip to content
Snippets Groups Projects
Commit b3827582 authored by Malte Woidt's avatar Malte Woidt
Browse files

Übung05

parent 2ea84781
No related branches found
No related tags found
No related merge requests found
source diff could not be displayed: it is too large. Options to address this: view the blob.
%% Cell type:markdown id:c716411b-f1c6-4aee-840f-7bae163f4251 tags:
# <font color='blue'>**Übung 5 - Berechnung eines statisch bestimmten Fachwerks**</font>
In dieser Übung werden wir uns mit der Berechnung eines 2D Stabtragwerks befassen und damit erstmals eine praktische Aufgabe umsetzen. Im ersten Schritt soll in dieser Übung eine Klasse *Fachwerk* erstellt werden, die das später zu lösende Fachwerk darstellt. In diesem Zuge soll die Übung einige Grundlagen zur Objektorientierten Programmierung vorführen. Außerdem wird *matplotlib* zur visualisierung des Stabtragwerks eingesetzt. Das Packet ist ursprünglich nicht dazu gedacht, es funktioniert aber trotzdem sehr gut.
## Grundlegende Überlegungen
Die objektorientierte Programmierung ist ein Konzept, das darauf aufbaut, unsere Vorstellungen über die Funktionsweise eines Programms möglichst einfach in ein Programm umzusetzen. Um ein Projekt, wie die Berechnung eines Fackwerks in ein Programm umzusetzen gibt es verschiedene Techniken. Als ersten Anhaltspunkt hilft es oft, sich den Ablauf des Programms grob zu skizzieren. Dabei soll der Funtkionsumfang des Programms umrissen werden. Am einfachsten gelingt das, indem man sich klar macht, was man eigentlich mit dem Programm tun möchte. Ein Beispiel:
1. Ich möchte das Aussehen des Stabtragwerks definieren. Damit ich sicher sein kann, dass ich alles richtig gemacht habe sollte es möglich sein, mir den aktuellen Stand visuell anzuschauen und ggf. anzupassen
2. Ich möchte Auflager und Kräfte definieren. Auch hier möchte ich, dass ich sehen kann, ob alles so aussieht, wie ich es mir vorstelle
3. Ich möchte herausfinden, ob mein Stabtragwerk (potentiell) ein statisch bestimmtes Tragwerk ist, ansonsten brauche ich auch nicht versuchen, es zu berechnen bzw. muss es verändern
4. Ich möchte die Auflagerreaktionen und die Stabkräfte berechnen
5. Ich möchte mir das Ergebnis ausgeben
Diese Punkte kann man grob in Kategorien eines Ablaufs einordnen. Punkte 1-3 kann man als Definitionsphase einstufen. In diesen Punkten soll interaktiv ein Tragwerk definiert und überprüft werden. Danach folgt die Berechnung des Tragwerkes. Dieser Schritt unterscheidet sich in so weit von den Punkten 1-3, dass die Berechnung keine Interaktion erfordert. Das definierte Tragwerk wird berechnet. Entweder funktioniert das, oder es funktioniert nicht. Falls nicht, muss etwas am Tragwerk geändert werden und anschließend die Berechnung komplett neu gestartet werden. Die abschließende Visualsierung stellt nur Ergebnisse dar, die Darstellung mag anpassbar sein, die Ergebnisdaten bleiben aber immer die Selben.
In dieser Übung wollen wir zunächst die Definitionsphase genauer betrachten. Aus den Stichpunkten ist in etwa definiert, was das das Programm können soll. Ein Fachwerk aufbauen. Jetzt sollte man sich klar machen, was ein Fachwerk aus sicht der Aufgabenstellung eigentlich ist. Das funktioniert z.B. durch einen Freitext:
```
Ein Fachwerk besteht aus beliebig vielen Stäben und Knotenpunkten. Ein Knotenpunkt hat eine Position. Ein Stab verbindet zwei Knotenpunkte eines Fachwerks. An einem Knotenpunkt können Lasten angreifen. Falls keine Lasten angreifen, kann man auch sagen, die Last ist null. Außerdem können Knotenpunkte durch Lager blockiert werden. Dabei können die Lager entweder eine verschiebung in x-Richtung, in y-Richtung oder in beide Richtungen verhindern. Die meisten Punkte haben keine Lagerbedingungen.
```
Das ist natürlich nur eine Sichtweise. Man könnte auch damit anfangen, dass ein Fachwerkproblem aus der Struktur des Fachwerks und Randbedingungen besteht etc. Bei größeren Projekten ist es oft hilfreich, sich mehrere alternative Sichtweisen zu verdeutlichen. Für diese Übung nehmen wir den Text erst einmal als Grundlage
## Schritt 1 Definition von Klassen:
Als grober Anhaltspunkt sind alle Nomen aus dem Freitext potentielle Klassen sind. Gehen wir den erstellten Satz durch, haben wir die potentiellen Klassen Fachwerk, Stab, Knoten, Position, Kraft und Lagerbedingung.
Der Text enthält auch Zusammenhänge zwischen den Klassen. Man könnte sagen, sowohl Knoten als auch Stäbe gehören exklusiv zu einem Fachwerk. Das Fachwerk sollte die in ihm enthaltenen Stäbe und Knoten kennen. Ob auch die Knoten oder Stäbe wissen müssen, zu welchem Fachwerk sie gehören, ist erst einmal unbekannt. Die Lasten und Randbedingungen gehören zu einem Knoten und jeder Knoten hat eine Last und eine Lagerbedingung (die entweder frei, x-Achse, y-Achse oder beide Achsen sein kann). Außerdem hat jeder Knoten eine Position.
Wenn ein Zusammenhang zwischen Klassen besteht, z.B. ein Stab gehört zu einem Tragwerk, deutet das darauf hin, dass die Stäbe ein Attribut, also eine Variable der Klasse Tragwerg sind. Das gleiche gilt auch für die Verbindung zwishcen Knoten und Last etc.
Als nächstes versuchen wir potentielle Klassen zu finden, die wir durch bestehende Klassen oder Variablentypen in Python abbilden können.
Die Position eines Knotens ist ein Vektor im 2D-Raum. Die Last eines Knotens ist auch ein Vektor. Dafür können wir numpy-Arrays verwenden, die bereits Vektorrechnung unterstützt. Die Lagerbedingungen bestehen aus fest oder losgelagert in x- und y-Richtung. Man könnte sie als eigene Klasse definieren, wir wollen das hier vorerst nicht tun.
Die anderen 3 Klassen müssen selbst implementieren werden. Dazu müssen wir erst einmal die Verbindungen der Klassen geeignet abbilden. Ein Fachwerk hat beliebig viele Knoten und beliebig viele Stäbe. Um diese zu speichern können wir Listen verwenden. Eine für die Knoten und eine für die Stäbe. Außerdem müssen Stäbe Knoten verbinden, die auch zum Fachwerk gehören. Offensichtlich sollte man also auf den Inhalt der Listen ein wenig aufpassen, bzw. die Fachwerkklasse sollte die Listen verwalten. Wenn das der Fall ist, sollte der Name der Listen mit einem _ beginnen, um Benutzern der Klassen mitzuteilen, dass diese Listen nicht direkt verändert werden sollten. Außerdem benötigt die Klasse Knoten eine Koordinate und eine Last. Ohne Koordinate ist ein Knoten nicht besonders sinvoll und ein Standardwert bringt nicht viel, da Knoten nicht übereinander liegen sollten. Daher sollte die Koordinate bei der Erstellung des Knotens ein Parameter der \_\_init\_\_ Methode sein. Für die Last können wir als Standardwert ein Vektor mit zwei Nullen annehmen. Der Stab ist ersteinmal noch etwas unklar, da wir überlegen müssen, wie wir sicherstellen, das der Stab zwei Knoten des selben Tragwerks verbindet. Wir lassen ihn erst einmal leer. Die Lagerbedingungen sind auch noch etwas unklar, auch sie lassen wir für den ersten Schritt weg.
Jetzt wäre ein Zeitpunkt diese Klassen erst einmal nach den bereits bekannten Spezifikationen zu definieren
- Drei Klassen: Knoten, Stab, Fachwerk
- Die Klasse Knoten benötigt eine Koordinate zur Erstellung, die als numpy Array gespeichert werden soll
- Die Klasse Knoten hat ein Attribut für die Kraft, ein 2D-Numpy Vektor, der bei der Erstellung auf null gestzt wird
- Die Klasse Fachwerk besitzt zei Attribute _knoten und _staebe, die nach erstellung leere Listen sind
#### Hinweis:
Variablen die zu einem Objekt gehören werden in Python in der \_\_init\_\_ Methode einer Klasse angelegt
%% Cell type:code id:5518cd95-6cdd-4e6d-a0c0-5792e4c88c4f tags:
``` python
import numpy as np
class Knoten:
def __init__(self, coo):
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
self._kraft=np.zeros(2)
class Stab:
pass #An den Stab haben wir noch keine Anforderungen definiert. wir lassen ihn zunächst einfach einmal stehen
class Fachwerk:
def __init__(self):
self._knoten=[]
self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten
```
%% Cell type:markdown id:9e1d018a-332d-41a3-ad14-4a45875b72d5 tags:
## Schritt 2a Definition von weiteren Attributen und Methoden:
Die Klassen sind grob definiert, können aber noch nicht viel. Im nächsten Schritt sollte daher überlegt werden, wie das Programm funktioniert. Es gibt verschiedene Möglichkeiten dazu. Eine Möglichkeit ist, aus Sicht des Benutzers verschiedene sogenannte User-Storys zu schreiben. Eine beschreibung was der Benutzer später mit den Klassen machen möchte. Bei komplexeren Programmen ist es auch eine gute Idee zu versuchen verschiedene Benutzerrollen zu identifizieren (z.B. bei einem Onlinestore gäbe es einen Kunden, jemand der Artikel einstellt, einen Einkäufer, etc.), was hier aber eher nicht der Fall ist. Man sollte die Einzeltexte so kurz wie möglich halten. Für unser Beispiel nehmen wir folgendes an:
- Ein Fachwerk ist nach Erstellung erst einmal leer. Es soll nach und nach aufgebaut werden
- Ich möchte dem Fachwerk Knotenpunkte hinzufügen. Dazu sage ich dem Tragwerk, wo der neue Knoten hin soll
- Ich möchte dem Fachwerk Stäbe hinzufügen. Dazu sage ich dem Tragwerk, zwischen welchen Knoten der Stab liegen soll
- Ich möchte zu einem Knoten eine Last zuweisen. Das ist ein Kraftvektor im 2D-Raum
- Ich möchte Lagerbedingungen an einem Knoten definieren. Dazu muss ich dem Knoten mitteilen, ob er in x oder in y Richtung gelagert ist.
Bei komplizierteren Anforderungen, sollten zusätzlich Akzeptanzkriterien definiert werden.
Aus diesen kurzen Geschichten kann schrittweise festgelegt werden, welche Eigenschaften die Klassen besitzen sollen. Verben oder Tätigkeiten deuten auf Methoden von Klassen hin. Eigenschaften auf Attribute, also Variablen der Klassen. Wenn für die Tätigkeit etwas benötigt wird, muss dem Objekt das benötigte schon bekannt sein oder es ist ein Parameter. Das Ergebnis einer Tätigkeit kann ein Rückgabewert sein. Bei jeder Story kann erst einmal gefragt werden, welche Klasse zuständig ist, welche Informationen zur Durchführung benötigt werden und was das Ergebnis eigentlich ist.
Nehmen wir z.B. Knotenpunkt hinzufügen. Dem Fachwerk soll ein Knotenpunkt hinzugegügt werden. Es ist also ein Methode des Fachwerks. Nächste Frage ist, wer erstellt das Knoten Objekt. Soll der Benutzer ein Knotenobjekt erstellen und als Parameter übergeben, oder eine Koordinate und das Fachwerk erstellt sich das Objekt selbst. Wenn wir kurz über den Knoten nachdenken kann man sagen, dass ein Knoten ohne Fachwerk nicht sinvoll ist. Außerdem sollte ein Knoten nur zu einem Fachwerk gehören. Das Fachwerk besitzt sozusagen den Knoten. Deswegen wählen wir die Variante, den Knoten innerhalb der Methode aus einer Koordinate zu erstellen (wie man diese Entscheidung trifft ist in der Tat viel Erfahrung. Prinzipiell sind beide Wege akzeptabel, wir werden aber später sehen, dass diese Sichtweise später Vorteile bietet). Das Ergebnis ist ein neuer Knoten. Die Frage ist, sollte der neu erstellte Knoten ein Rückgabewert der Methode sein. Der Knoten gehört dem Fachwerk, er wird also in der Liste *\_knoten* gespeichert. Es wäre nicht zwingend nötig, ihn als Rückgabewert zu liefern. Lassen wir die Entscheidung vorerst offen.
Einem Knoten sollen Lasten oder Lagerbindungen zugewiesen werden. Beides sind Eigenschaften des Knotens. Um ihm diese Eigenschaften zuzuweisen, benötigt der Knoten entsprechende Attribute oder Methoden. Viel wichtiger an diesem Punkt ist noch, dass das Knoten-Objekt benötigt wird. Die interessante Frage ist also, wo kommt das Knoten-Objekt her. Es ist in der Liste des Fachwerks, die wir nicht direkt anfassen wollen. Es ist also vermutlich eine gute Idee, das Knotenobjekt bei Erstellung als Rückgabewert zu erhalten. Damit wäre die Entscheidung, ob ein Knoten bei Erstellung zurückgegeben wird getroffen.
Für die Erstellung der Stäbe gilt im Endeffekt selbiges, wie für die Knoten. Sie werden vom Fachwerk erstellt, wir benötigen zwei Knoten (die haben wir, da sie bei Erstellung zurückgegeben werden) usw.
Den Knoten können Lasten bzw. Kräfte zugewiesen werden. Entweder betrachten wir die Kraft als Attribut, das wir direkt zuweisen oder wir schreiben eine Methode dafür. Es ist für die spätere Berechnung hilfreich, wenn die Kräfte als numpy-Arrays gespeichert sind. Da wir momentan nicht wissen, wie wir das sonst sicherstellen sollen, entscheiden wir uns für eine Methode.
Die Lagerbedingungen sind momentan noch etwas unklar. Wir können den Knoten in X- und Y- Richtung festhalten. Der einfachheit halber möchte ich die Lagerbedingungen als zwei Boolsche-Werte speichern. Festgehalten in x-Richtung und festgehalten in y-Richtung. Zum Setzen und Entfernen der Lagerbedingung sind dann Methoden definiert, die das übernehmen.
Mit diesen Anforderungen lassen sich die definierten Klassen nun erweitern.
- Die Klasse Fachwerk hat eine Methode addKnoten. Parameter ist die Koordinate des neuen Knoten. Rückgabewert ist ein neuer Knoten. Der Knoten wird außerdem der Liste des Fachwerks hinzugefügt.
- Die Klasse Fachwerk hat eine Methode addStab. Parameter sind zwei Knoten. Es wird überprüft ob die Knoten zum Fachwerk gehören. Rückgabewert ist der neue Stab. Der Stab wird der Liste der Säbe hinzugefügt
- Die Klasse Knoten hat ein Attribut \_kraft. Bei erstellung ist das ein 2D Vektor mit Nullen
- Die Klasse Knoten hat eine Methode setKraft. Die Methode setzt die Einträge des Kraftvektors
- Die Klasse Knoten hat ein Attribut \_lager das ist ein Vektor der bei Erzeugung zwei mal false enthält
- Die Klasse Knoten hat Methoden setLager_x/y und removeLager_x/y.
- Die Klasse Stab hat die Attribute k1 und k2. Zwei Knoten, die bei Erstellung gesetzt werden.
Diese Anforderungen können in der Form implementiert werden
%% Cell type:code id:c948b483-90e2-4994-996d-0f205770a8dd tags:
``` python
import numpy as np
class Knoten:
def __init__(self, coo):
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
self._kraft=np.zeros(2)
self._lager=[False,False]
def setKraft(self,kraft):
self._kraft[:]=kraft
def setLager_x(self):
self._lager[0]=True
def setLager_y(self):
self._lager[1]=True
def removeLager_x(self):
self._lager[0]=False
def removeLager_y(self):
self._lager[1]=False
class Stab:
def __init__(self,k1,k2):
self._k1=k1
self._k2=k2
class Fachwerk:
def __init__(self):
self._knoten=[]
self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten
def addKnoten(self, coo):
neuer_knoten=Knoten(coo)
self._knoten.append(neuer_knoten)
return neuer_knoten
def addStab(self,k1,k2):
if not k1 in self._knoten:
return None
if not k2 in self._knoten:
return None
neuer_stab=Stab(k1,k2)
self._staebe.append(neuer_stab)
return neuer_stab
```
%% Cell type:markdown id:35bd4b01-16ea-40fa-b2eb-2fe7f2270e4a tags:
## Schritt 2b Definition von weiteren Attributen und Methoden:
Definieren wir eine weitere Nutzerstory, die etwas komplizierter umzusetzen ist
- Ich möchte das Fachwerk visualisieren können. Dabei erwarte ich alle definierten Knoten und alle Stäbe zu sehen
Der Zweite Satz ist ein Akzeptanzkriterium; ich möchte alle Stäbe und Knoten sehen.
Das Visualisieren ist eine Methode des Fachwerks. Dazu muss es alle Stäbe und Knoten kennnen, was bereits der Fall ist. Das Ergebnis ist eine Ausgabe, die wir mit *matplotlib* erstellen.
Wir haben in der letzten Übung bereits mit *matplotlib* gearbeitet. Dabei haben wir gesehen, dass mit *matplotlib* einzelne Punkte im Raum als Scatter-Plot darstellbar sind. Das wäre z.B. gut für die Knoten des Fachwerks. Um einen Knoten darzustellen, benötigen wir die Koordinate des Knotens. Die ist im Knoten als Attribut bekannt. Allerdings sollten wir auf Attribute mit Unterstrich nicht direkt zugreifen. Also definiren wir für die Klasse Punkt eine Methode getKoordinate, die uns den gewünschten Wert liefert.
Die Stäbe sind eine Verbindungslinie zwischen den Knoten. Wir können uns mit *matplotlib* mit der Methode plot behelfen, die eine Linie zwischen beliebig vielen Punkten zeichnet.
So sollte es möglich sein die Methode zu implementieren
%% Cell type:code id:3da3f717-b749-40d6-8ec5-0a3246808210 tags:
``` python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
class Knoten:
def __init__(self, coo):
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
self._kraft=np.zeros(2)
self._lager=[False,False]
def setKraft(self,kraft):
self._kraft[:]=kraft
def setLager_x(self):
self._lager[0]=True
def setLager_y(self):
self._lager[1]=True
def removeLager_x(self):
self._lager[0]=False
def removeLager_y(self):
self._lager[1]=False
def getKoordinate(self):
return self._coo
class Stab:
def __init__(self,k1,k2):
self.k1=k1
self.k2=k2
class Fachwerk:
def __init__(self):
self._knoten=[]
self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten
def addKnoten(self, coo):
neuer_knoten=Knoten(coo)
self._knoten.append(neuer_knoten)
return neuer_knoten
def addStab(self,k1,k2):
if not k1 in self._knoten:
return None
if not k2 in self._knoten:
return None
neuer_stab=Stab(k1,k2)
self._staebe.append(neuer_stab)
return neuer_stab
##############################################Neu in diesem Schritt#######################################################################################################################
def plot(self):
fig= plt.figure(figsize=(5, 5))
ax = fig.subplots()
#zunaechst alle Knoten darstellen. Matplotlib erwartet dafür eine Darstellung in der Form [x_0,x_1,...],[y_0,y_1,..]
x_werte=[]
y_werte=[]
for knoten in self._knoten:
koordinate=knoten.getKoordinate()#die Koordinate holen
x_werte.append(koordinate[0])
y_werte.append(koordinate[1])
ax.scatter(x_werte,y_werte)#Zeichnet alle Knoten als blaue Kreise
#als nächstes alle Stäbe
for stab in self._staebe:
koordinate1=stab.k1.getKoordinate()
koordinate2=stab.k2.getKoordinate()
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
#####################################################################################################################################################################################
```
%% Cell type:markdown id:4ae4e971-de59-482b-bc96-5f590e9e5f84 tags:
Zeit für einen kleinen Akzeptanztest. Probieren wir alles aus, was wir bisher in den Userstorys gesehen haben
%% Cell type:code id:c83bb13a-1670-44b3-9c35-0e2767f530af tags:
``` python
t=Fachwerk()# Ein leeres Tragwerk erstellen
a=t.addKnoten([0.,0.])#Punkte hinzufügen
b=t.addKnoten([10.,0.])
c=t.addKnoten([5.,5.])
d=t.addKnoten([15.,5.])
t.addStab(a,b)#Stäbe hinzufügen
t.addStab(a,c)
t.addStab(b,c)
t.addStab(c,d)
t.addStab(b,d)
a.setKraft([-100.,0.])
b.setLager_x()
b.setLager_y()
c.setLager_x()
t.plot()
```
%% Cell type:markdown id:a41947e1-00af-49a8-84a3-08181af74d77 tags:
Die Randbedingungen und Kräfte werden noch nicht angezeigt, darum kümmern wir uns später.
%% Cell type:markdown id:e7b571b4-9284-4da4-93b8-ead316c6e68e tags:
## Schritt 3 Das erste Architektur-Problem
So weit scheint alles gut zu laufen. Diese und jene Funktionalität kann noch verbessert werden, aber das Ergebnis lässt sich eigentlich sehen. Versuchen wir uns an zwei weiteren Features
- Ich möchte einen Stab löschen.
- Ich möchte einen Knoten löschen. Alle Stäbe, die mit diesem Knoten verbunden sind, sollen dabei mit gelöscht werden.
Das erste Feature ist einfach zu implementieren. Der Stab muss nur aus der Liste der Stäbe entfernt werden.
Das zweite Feature kann auf verschiedene Arten umgestzt werden. Kritischer Punkt ist die Identifikation aller Stäbe, die mit einem Punkt verbunden sind. Wir könnten hier eine Methode des Tragwerks erstellen, die die Liste der Stäbe nach Stäben durchsucht, die mit einem bestimmten Knoten verbunden sind. Das wäre vermutlich das einfachste und unproblematischste. Ich möchte allerdings einen anderen Weg zeigen. Die Frage, welche Stäbe an einem Knoten hängen werden wir bei der Berechnung noch öfter beantworten müssen. Wir könnten eine Liste in den Punkten führen, die angeschlossene Stäbe enthält. Jeder Stab wird bei Erstellung in die Liste der Stäbe seiner beiden Endpunkte eingetragen und wird ausgetragen, wenn er aus dem Fachwerk entfernt wird.
Dazu gibt es die Methode \_\_del\_\_, die aufgerugen wird, wenn ein Objekt gelöscht wird. Man könnte meinen, dass das ein guter Ort ist, den Stab aus der Nachbarschaftsliste auszutragen. Er könnte sich selbstständig ein- und wieder austragen. Das stimmt allerdings nicht! Ein Objekt wird erst gelöscht, wenn es keine Variablennamen für dieses Objekt mehr gibt (man nennt das Referenzen). Wir geben allerdings beim Erzeugen der Objekte Referenzen zurück, die der Benutzer bearbeiten kann. Daher ist nicht sicher, ob ein Stab-Objekt wirklich gelöscht wird, wenn es aus den Listen des Fachwerks entfernt wird. Daher sollte das Tragwerk für das Führen der Liste zuständig sein.
- Die Klasse Knoten bekommt ein Attribut namens _staebe, die die staebe enthält, die an den Knoten angrenzen.
- Die Klasse Knoten bekommt die Methode _addStab und _removeStab, die ein Stabobjekt der Liste hinzufügt bzw. eines entfernt
- Die Methode addStab der Klasse Fachwerk wird erweitert, sodass der neu erstellte Stab in der Nachbarschaftsliste der Endpunkte eingetragen wird
- Die Klasse Fachwerk bekommt eine Methode removeStab, die einen Stab aus der Nachbarschaftsliste seiner Endpunkte austrägt und den Stab aus der Liste der Stäbe entfernt
- Die Klasse Fachwerk bekommt eine Methode removeKnoten, die zu erst alle Stäbe, die sich in der Nachbarschaftsliste des Knotens bedinden entfernt und anschließend den Knoten selbst aus der Liste der Knoten entfernt
%% Cell type:code id:9fa5263e-8e2f-4719-9662-2c15d0899f7a tags:
``` python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
class Knoten:
def __init__(self, coo):
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
self._kraft=np.zeros(2)
self._lager=[False,False]
##############################################Neu in diesem Schritt#######################################################################################################################
self._staebe=[]
def _addStab(self,stab):
self._staebe.append(stab)
def _removeStab(self,stab):
self._staebe.remove(stab)
###############################################################################################################################################################################################
def setKraft(self,kraft):
self._kraft[:]=kraft
def setLager_x(self):
self._lager[0]=True
def setLager_y(self):
self._lager[1]=True
def removeLager_x(self):
self._lager[0]=False
def removeLager_y(self):
self._lager[1]=False
def getKoordinate(self):
return self._coo
class Stab:
def __init__(self,k1,k2):
self.k1=k1
self.k2=k2
class Fachwerk:
#####################################################################Neu in diesem Schritt#########################################################################
def removeStab(self,stab):
if stab in self._staebe:
stab.k1._removeStab(stab)
stab.k2._removeStab(stab)
self._staebe.remove(stab)
def removeKnoten(self,knoten):
if knoten in self._knoten:
for stab in knoten._staebe:
self.removeStab(stab)
self._knoten.remove(knoten)
####################################################################################################################################################################
def __init__(self):
self._knoten=[]
self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten
def addKnoten(self, coo):
neuer_knoten=Knoten(coo)
self._knoten.append(neuer_knoten)
return neuer_knoten
def addStab(self,k1,k2):
if not k1 in self._knoten:
return None
if not k2 in self._knoten:
return None
neuer_stab=Stab(k1,k2)
#####################################################################Neu in diesem Schritt#########################################################################
k1._addStab(neuer_stab)
k2._addStab(neuer_stab)
##############################################################################################################################################################
self._staebe.append(neuer_stab)
return neuer_stab
def plot(self):
fig= plt.figure(figsize=(5, 5))
ax = fig.subplots()
#zunaechst alle Knoten darstellen. Matplotlib erwartet dafür eine Darstellung in der Form [x_0,x_1,...],[y_0,y_1,..]
x_werte=[]
y_werte=[]
for knoten in self._knoten:
koordinate=knoten.getKoordinate()#die Koordinate holen
x_werte.append(koordinate[0])
y_werte.append(koordinate[1])
ax.scatter(x_werte,y_werte)#Zeichnet alle Knoten als blaue Kreise
#als nächstes alle Stäbe
for stab in self._staebe:
koordinate1=stab.k1.getKoordinate()
koordinate2=stab.k2.getKoordinate()
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
```
%% Cell type:markdown id:ced278dd-1b13-47af-8c56-80cb6f333b57 tags:
Probieren wir es aus
%% Cell type:code id:7e2dba3f-36f1-4044-bbd1-e8b7306c0779 tags:
``` python
t=Fachwerk()# Ein leeres Tragwerk erstellen
a=t.addKnoten([0.,0.])#Punkte hinzufügen
b=t.addKnoten([10.,0.])
c=t.addKnoten([5.,5.])
unten=t.addStab(a,b)#Stäbe hinzufügen
links=t.addStab(a,c)
rechts=t.addStab(b,c)
a.setKraft([-100.,0.])
b.setLager_x()
b.setLager_y()
c.setLager_x()
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:code id:eb3d7fed-312e-42b0-9f44-fe02b0f3082b tags:
``` python
t.removeStab(links)
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:markdown id:81a57552-1a97-48d6-8537-7d5d6bf5a9a3 tags:
Das sieht gut aus. Jetzt probieren wir es mit einem Knoten
%% Cell type:code id:f29f40f2-c492-4c36-b060-9bb5f0776371 tags:
``` python
t=Fachwerk()# Ein leeres Tragwerk erstellen
a=t.addKnoten([0.,0.])#Punkte hinzufügen
b=t.addKnoten([10.,0.])
c=t.addKnoten([5.,5.])
unten=t.addStab(a,b)#Stäbe hinzufügen
links=t.addStab(a,c)
rechts=t.addStab(b,c)
a.setKraft([-100.,0.])
b.setLager_x()
b.setLager_y()
c.setLager_x()
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:code id:7105c2e7-f430-44ff-ac20-628d24482e4f tags:
``` python
t.removeKnoten(c)
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:markdown id:017d5c24-3f1f-42b6-8aeb-5710e072d37a tags:
Das ist schlecht gelaufen. Ein Stab ist übrig geblieben. Was ist passiert?
Das Problem liegt in der Schleife der Funktion *removeKnoten*. Die Liste, über die wir iterieren, ist die Liste der Nachbarstäbe des Punktes. Sobald ein Stab entfernt wird, wird dadurch die Liste verändert. Ein Element wird entnommen. Die for-Schleife bekommt davon allerdings nichts mit. Über Listen zu iterieren, die sich in der Schleife verändern ist möglich, man sollte aber sehr genau wissen was passiert. Die einfachste Lösung wäre, über eine Kopie der Liste zu iterieren. Das geht. Wird eine Liste kopiert, enthält die Kopie der Liste die gleichen Elemente wie die Originalliste, also die gleichen Objekte.
%% Cell type:code id:2edc249a-f719-4d4b-8fa2-5ac5fddfd887 tags:
``` python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
class Knoten:
def __init__(self, coo):
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
self._kraft=np.zeros(2)
self._lager=[False,False]
self._staebe=[]
def _addStab(self,stab):
self._staebe.append(stab)
def _removeStab(self,stab):
self._staebe.remove(stab)
def setKraft(self,kraft):
self._kraft[:]=kraft
def setLager_x(self):
self._lager[0]=True
def setLager_y(self):
self._lager[1]=True
def removeLager_x(self):
self._lager[0]=False
def removeLager_y(self):
self._lager[1]=False
def getKoordinate(self):
return self._coo
class Stab:
def __init__(self,k1,k2):
self.k1=k1
self.k2=k2
class Fachwerk:
def removeStab(self,stab):
if stab in self._staebe:
stab.k1._removeStab(stab)
stab.k2._removeStab(stab)
self._staebe.remove(stab)
def removeKnoten(self,knoten):
if knoten in self._knoten:
############################################Die Korrektur####################################################################################
for stab in knoten._staebe[:]:
self.removeStab(stab)
self._knoten.remove(knoten)
############################################Die Korrektur####################################################################################
def __init__(self):
self._knoten=[]
self._staebe=[]# Wir erstellen erst einmal leere Listen für die Stäbe und Knoten
def addKnoten(self, coo):
neuer_knoten=Knoten(coo)
self._knoten.append(neuer_knoten)
return neuer_knoten
def addStab(self,k1,k2):
if not k1 in self._knoten:
return None
if not k2 in self._knoten:
return None
neuer_stab=Stab(k1,k2)
k1._addStab(neuer_stab)
k2._addStab(neuer_stab)
self._staebe.append(neuer_stab)
return neuer_stab
def plot(self):
fig= plt.figure(figsize=(5, 5))
ax = fig.subplots()
#zunaechst alle Knoten darstellen. Matplotlib erwartet dafür eine Darstellung in der Form [x_0,x_1,...],[y_0,y_1,..]
x_werte=[]
y_werte=[]
for knoten in self._knoten:
koordinate=knoten.getKoordinate()#die Koordinate holen
x_werte.append(koordinate[0])
y_werte.append(koordinate[1])
ax.scatter(x_werte,y_werte)#Zeichnet alle Knoten als blaue Kreise
#als nächstes alle Stäbe
for stab in self._staebe:
koordinate1=stab.k1.getKoordinate()
koordinate2=stab.k2.getKoordinate()
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
```
%% Cell type:code id:633b5f2e-a32f-406b-bab3-e620c1e1aa25 tags:
``` python
t=Fachwerk()# Ein leeres Tragwerk erstellen
a=t.addKnoten([0.,0.])#Punkte hinzufügen
b=t.addKnoten([10.,0.])
c=t.addKnoten([5.,5.])
unten=t.addStab(a,b)#Stäbe hinzufügen
links=t.addStab(a,c)
rechts=t.addStab(b,c)
a.setKraft([-100.,0.])
b.setLager_x()
b.setLager_y()
c.setLager_x()
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:code id:fe60fa93-f323-4e8c-b8a9-5acc3c83fee6 tags:
``` python
t.removeKnoten(c)
print ("Anzahl der Stäbe",len(t._staebe))
print ("Anzahl der Knoten",len(t._knoten))
t.plot()
```
%% Cell type:markdown id:ad480a8c-408b-45f6-a475-2454fd90caed tags:
So funktioniert es!
## Aufgabe zum zu Hause probieren
So weit ist uns das Bearbeiten und Darstellen eigentlich gut gelungen. Allerdings werden uns Kräfte und Lagerbedingungen nicht angezeigt. Das ist schade. Versuche die plot-Methode der Klasse Tragwerg so zu erweitern, dass auch diese Eigenschaften angezeigt werden.
Kräfte können als Pfeil in die entsprechende Richtung dargestellt werden. Auch Randbedingungen kannst du als Pfeile darstellen (vielleicht in einer anderen Farbe). Dazu kannst du die *annotate* Funktion aus dem Grundlagen-Notebook verwenden oder, wenn du eine schönere Lösung haben möchtest, dir diesen Link anschauen [https://matplotlib.org/stable/gallery/shapes_and_collections/arrow_guide.html#sphx-glr-gallery-shapes-and-collections-arrow-guide-py]
Außerdem gibt es noch einige Details, die etwas Arbeit benötigen. Versuche folgende Eigenschaften in das Programm einzubauen:
- Neue Knoten werden nur erstellt, wenn an der angegebenen Koordinate noch kein Knoten existiert. Wenn ein neuer Knoten näher als 10E-3 Einheiten an einem bestehenden Knoten liegt, werden diese als übereinanderliegend angesehen
- Zwischen zwei Knoten kann nur ein Stab existieren! Wenn ich einen Stab zwischen zwei Knoten erzeuge, zwischen denen schon ein Stab existiert erwarte ich, dass die addStab Methode None Zurückgiebt
Ein Vorschlag zu Punkt zwei:
Am einfachsten wäre es, wenn wir den *in* Operator der Listen verwenden könnten. Du könntest einen neuen Stab erzeugen und überprüfen, ob er bereits in der Liste der Stäbe vorhanden ist. Dazu wird intern der == Operator der Klasse Stab verwendet. Diesen müsstest du selbst implementieren und so anpassen, dass Stäbe gleich sind, wenn sie die gleichen Endpunkte besitzen. Allerdings unabhängig davon, in welcher Reihenfolge die Endpunkte angegeben sind. Der *in* Operator der Listen funktioniert für die Punkte bereits, da jede Klasse standardmäßig einen == Operator implementiert hat. Dieser überprüft, ob auf beiden Seiten das identische Objekt steht (ggf. unter anderem Namen). Dieser wird überschrieben, wenn du einen eigenen Operator implementierst
%% Cell type:markdown id:73f537ff-2565-427f-8e1f-37ca8dcebfc3 tags:
## Wo ist das angesprochene Architektur-Problem der Anwendung (nicht prüfungsrelevant)?
Wenn du der Übung aufmerksam gefolgt bist, ist dir vielleicht ein Problem aufgefallen. Python löscht ein Objekt erst, wenn es keien Referenzen auf dieses Objekt gibt. Das klingt einleuchtend. Was passiert aber im Fall der Stäbe und Knoten. Ein Stab besitzt Referenzen auf seine Endknoten, die Endknoten eine Referenz auf den Stab selbst. So eine zyklische Referenz würde bedeuten, dass die Objekte sich gegenseitig am Leben erhalten, auch wenn sie nicht mehr gebraucht werden. Das würde bedeuten, wenn wir das Programm lange genug Objekte anlegen lassen die wir dann nicht mehr benötigen, würde irgendwann der Arbeitsspeicher des PCs aufgebraucht sein. Kann das passieren, bzw. müssen wir beim Python-Programmieren auf zyklische Referenzen besonders achten?
Die Antwort ist Jein.
Python kann zyklische Referenzen entdecken, auf die von außerhalb des Zyklus keine Referenzen mehr existieren. Also toter Arbeitsspeicher. Python kann diesen Zyklus automatisch brechen und die Objekte löschen. Allerdings gibt es eine Besonderheit. Wenn Objekte, die innerhalb der zyklischen Referenz liegen ein \_\_del\_\_ Methode besitzen wird diese evtl. nicht ausgeführt, obwohl das Objekt gelöscht wird. Das in der Übung über Objektorientierte Programmierung gezeigte Beispiel mit dem Zähler, wie viele Objekte gerade existieren, ist also nicht zwangsweise zuverlässig. Bei Python vor der Version 3.4. konnten die Objekte tatsächlich nicht gelöscht werden!
%% Cell type:code id:4acb5039-db1e-436f-9329-85cb18bfcb9e tags:
``` python
```
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment