6. Übung

Beispiel 6.1

Ein "Wörterbuch" für Python

Bevor wir uns an die nächste Aufgabe heranwagen, wollen wir eine weitere Datenstruktur kennenlernen, die Python uns bietet: das dictionary (Wörterbuch, Lexikon). Nachschlagewerke wie eben z.B. Wörterbücher, Telefonbücher oder andere Verzeichnisse basieren auf der Idee, dass man über einen definierten Schlüssel auf das Verzeichnis zugreift und daraufhin die unter dem Schlüssel hinterlegten Informationen ermitteln kann. Dies ist auch bei einem Python-dictionary nicht anders, wie wir an diesem Beispiel sehen:

# Wir definieren uns eine kleines Telefonbuch in Form eines 'dictonaries' ...
telefonbuch = {
  # Schlüssel   : Wert
  "Adam"        : 1234,
  "Eva"         : 4321,
  "Max"         : 9191,
  "Moritz"      : 1919,
  "Pippi"       : 1001,
  "Wickie"      : 2002
}

# ... dann können wir über alle Schlüssel iterieren ...
for key in telefonbuch:
  print(key, 'hat die Rufnummer', telefonbuch[key])

# ... können die Werte natürlich auch direkt ändern ...
telefonbuch["Adam"] = 2345
# ... und dabei auch einen anderen Datentyp zuweisen ...
telefonbuch["Eva"] = 'geheim'

# ... und geben das zur Kontrolle nochmal aus
for key in telefonbuch:
  print(key, 'hat die Rufnummer', telefonbuch[key])

Die dictonaries bestehen also immer aus (Schlüssel, Wert)-Paaren, wobei der Wert - wie in Python nicht anders zu erwarten - ein beliebiger Datentyp sein kann, also z.B. auch eine Liste oder ein weiteres Dictionary:

# Ein 'dictionary' mit Werten unterschiedlichen Datentyps definieren ...
vw_kaefer = {
  "Leistung" : 34, # in [PS]
  "Konstrukteur" : 'F. Porsche',
  "Spitzname" : 'Herbie',
  "Abmessungen" : [4.09, 1.55, 1.5]  # L B H in [m]
}

# ... einmal zur Kontrolle darüber iterieren ...
for key in vw_kaefer:
  print(key, vw_kaefer[key])

# ... und den Schlüssel 'Abmessungen' einer Variablen zuweisen ...
a = vw_kaefer["Abmessungen"]

# ... und damit dann etwas berechnen
print("Einhüllendes Volumen eines Käfers: ", a[0] * a[1] * a[2], 'qm')

Auch Dictionaries haben vielfältige Verwendungsmöglichkeiten insbesondere auch in Verbindung mit dem Lesen oder Schreiben von strukturierten Daten. Wir werden das später in Verbindung mit dem JSON(JavaScript Objekt Notation)-Datenformat noch näher kennenlernen. Vorerst wollen wir Dictionaries dazu nutzen, um einige Konfigurationsdaten in unseren Beispielen und Aufgaben nicht mehr direkt fest in den Code einzuprogrammieren, sondern in Form einer separaten "Konfigurationsdatei" übersichtlich und leicht änderbar zu definieren. Wir schauen uns das am Beispiel der Parameter zur Festlegung der Größe unseres Plot-Fensters einmal an:

# Dieses Dictionary enthält die Konfigurationsparameter für die Größe des
# Figure-Fensters
cfg_plt_fig = {
  "width"  : 8, # [in]
  "height" : 10 # [in]
}

... andere Anweisungen

# Anlegen eines Figures zum Plotten ...
fig, ax = plt.subplots()
# ... und festlegen der Fenstergröße anhand der Konfigurationsparameter
fig.set_size_inches(cfg_plt_fig['width'], cfg_plt_fig['height'])

... andere Anweisungen

Diese Methode funktioniert zwar, allerdings stehen unsere Konfigurationsparameter jetzt immer noch in der gleichen Datei wie unser Programmcode. Deutlich schicker wäre es aber, wenn wir eine separate Konfigurationsdatei hätten, in der nur die möglichen Parameter zu unserem Programm in übersichtlicher Form stünden.

Module

Deshalb gehen wir zunächst noch einen Schritt weiter und schauen uns an, wie wir unser Programm über mehrere Dateien verteilen. In Python spricht man dann von sog. "Modulen". Im Prinzip haben wir schon die ganze Zeit mit (jeweils) einem Modul gearbeitet, wenn wir ein Programm geschrieben haben. In Python ist nämlich jedes Programm bzw. jede Programmdatei auch ein Modul! Um jetzt in einem Programm auf Inhalte wie Daten oder Funktionen in einem anderen Modul zuzugreifen müssen wir dieses lediglich importieren und DAS kennen wir ja schon lange. Wir packen unser Konfigurations-Dictionary also in eine separate Datei, die wir beispielsweise "cfg_example_6_1.py" nennen:

# Datei 'cfg_example_6_1.py'
# Konfigurationsdaten zum Beispiel 6.1

# Dieses Dictionary enthält die Konfigurationsparameter für die Größe des
# Figure-Fensters
plt_fig = {             # ACHTUNG - der Name ist hier geändert!
  "width"  : 8, # [in]
  "height" : 10 # [in]
}

Im unserer eigentlichen Programmdatei können wir dann unser erstes (bewusst) selbstgeschriebenes Modul so wie auch andere Module einfach importieren und nutzen:

# Datei 'example_6_1.py'
# Unser Hauptprogramm

# Wir importieren unser Konfigurationsmodul und kürzen den Namen mit 'cfg' ab
import cfg_example_6_1 as cfg

... andere Anweisungen

# Anlegen eines Figures zum Plotten ...
fig, ax = plt.subplots()
# ... und festlegen der Fenstergröße anhand der Konfigurationsparameter
# Mit 'cfg.' greifen wir auf die Konfigurationsparameter zu
fig.set_size_inches(cfg.plt_fig['width'], cfg.plt_fig['height'])

... andere Anweisungen

In gleicher Weise wie unsere Konfigurationsparameter könnten wir auch andere Daten, Funktionen oder Klassen in eigene Module auslagern. Darüberhinaus können wir mehrere Module zu sogenannten Paketen zusammenfassen, aber dazu später mehr.

Module und der Python-Pfad

Grundsätzlich sind selbsterstellte Module und Pakete vollkommen gleichwertig zu den Modulen, die wir mit pip (oder auf andere Weise) herunterladen und auf unserem Rechner installieren. Trotzdem kann es manchmal sein, dass unsere eigenen Module nicht gefunden werden - vor allem, wenn diese nicht im selben Verzeichnis stehen, wie das Modul in dem der Aufruf erfolgt. Das liegt daran, dass Python Module und Pakete im Python-Pfad sucht, zu dem i.d.R. auch das aktuelle Arbeitsverzeichnis zählt. Um das Problem zu beseitigen, gibt es drei Möglichkeiten:

  1. Das Modul in einem Verzeichnis im (vorhandenen) Python-Pfad bereitstellen.

  2. Den Python-Pfad in unserem (virtuellen) Environment statisch um das Modulverzeichnis erweitern.

  3. Den Python-Pfad dynamisch, d.h. während der Ausführung, um das Modulverzeichnis erweitern.

Die dritte Variante werden wir später bei Gelegenheit einsetzen, im Augenblick sollte unser Modul auch so gefunden werden.

Aufgabe 6.1

Im Rahmen der letzten Aufgabe haben wir angenommen, dass wir die Beschleunigungsänderungen als (Ruck-)Impulse auf unser System geben. Ein solches Verhalten entspricht nicht dem realer Systeme, da diese über - im Falle der Bewegung - über Massen verfügen, die beschleunigt werden müssen, außerdem treten oft Reibungs- und andere Widerstandskräfte auf, die berücksichtigt werden müssten. Bei einem Auto beispielsweise geben wir mittels der Gaspedalstellung auch nicht die Beschleunigung vor, sondern die Geschwindigkeit. Deshalb wollen wir jetzt einmal ein etwas realitätsgetreueres Modell betrachten.

Jedes reale physikalische System hat aufgrund der vorhandenen "Energiespeicher" wie etwa Massen, Federn, Kondensatoren oder sonstigen Elementen eine gewisse Trägheit. Im Fall der von uns betrachteten Bewegung eines Objekts wollen wir uns auf die Masse als Energiespeicher beschränken. Wenn wir bei einem Auto eine bestimmte Gaspedalstellung vorgeben (und halten), dann korrespondiert diese - je nach Übersetzung des Antriebsstrangs - mit einer gewissen Geschwindigkeit. Diese wird aber erst nach einiger Zeit erreicht, je nach Leistung des Motors und der Masse der Autos. In erster Näherung kann man das Verhalten ähnlich wie in Abbildung Sprungantwort eines PT1-Gliedes darstellen. Ein solches Übertragungsverhalten bezeichnet man als Verzögerungsglied 1. Ordnung (PT1-Glied), auch als Tiefpass bezeichnet.

../_images/exer_06_matplotlib_04A.PNG

Sprungantwort eines PT1-Gliedes

Mathematisch wird dieses Verhalten wie folgt als Differenzengleichung beschrieben (im Folgenden schreiben wir abkürzend \(v_k anstelle von v(t_k)\) und weitere):

\[v_{ist,k+1} = v_{ist,k} + (K_v \cdot v_{soll,k}-v_{ist,k}) \cdot \frac{\Delta t}{T_v} \tag{6.1}\]

mit

\[\begin{split}\begin{eqnarray} v_{ist} &=& \text{Ist-Geschwindigkeit} \\ v_{soll} &=& \text{Soll-Geschwindigkeit} \\ K_v &=& \text{Verstärkungsfaktor} \\ \Delta t &=& \text{Zeitschritt} \\ T_v &=& \text{Zeitkonstante} \end{eqnarray}\end{split}\]

Den Weg erhalten wir dann in der uns schon bekannten Form als Differenzengleichung:

\[s_{k+1} = s_k + \Delta t \cdot v_{ist,k} \tag{6.2}\]

Die Beschleunigung ergibt sich in diesem Fall als Ableitung der Ist-Geschwindigkeit:

\[a_{k+1} = \frac{v_{ist,k+1}-v_{ist,k}}{\Delta t} \tag{6.3}\]

Da wir - anders als in Aufgabe 5.2 - nun die Soll-Geschwindigkeit vorgeben, muss die Tabelle, die wir wieder aus einer Datei einlesen wollen, nach einem etwas anderen Muster aufgebaut sein:

\(t\ [s]\)

\(v_{soll}\ [m/s]\)

Kommentar

0.0

12.5

15.5

0.0

28.5

31.0

43.0

22.5

52.0

0.0

60.0

0.0

Endzeitpunkt

To Do

Es ist ein Programm zu erstellen, dass:

  1. die Soll-Geschwindigkeit für mindestens 5 Zeitintervalle aus einer Datei einliest. (Die Datei ist ebenfalls selbst zu erstellen.)

  2. unter Nutzung der NumPy-Bibliothek die Ist-Geschwindigkeit \(v_{ist}\), die Beschleunigung \(a\) und den zurückgelegtem Weg \(s\) über alle Zeitintervalle und alle Zeitschritte \(k\) implementiert.

  3. die Verläufe für die Elemente von \(s, (v_{ist}, v_{soll}), a\) mittels der Matplotlib untereinander in 3 einzelnen Subplots darstellt. (Grid, Achsenbezeichnungen, Legenden)

  4. die folgenden Konfigurationsparameter aus einem separaten Modul in Form eines Dictionaries einliest:

    1. Zeitschritt \(dt\) (Standardwert = \(0.05s\))

    2. Verstärkungsfaktor \(K_v\) (Standardwert = \(1\))

    3. Zeitkonstante \(T_v\) (Standardwert = \(2.5s\))

    4. Größe des Plot-Fensters

    5. Plot-Stil (Farbe, Linienart) für jedes darzustellende Signal

    6. Name der Datei mit den Soll-Geschwindigkeiten

  5. die verschiedenen Aufgaben sinnvoll in Funktionen gliedert, Meldungen an die Nutzer ausgibt und Kommentare enthält.

Lösungshinweise:

  1. Auch in diesem Fall muss die Lösung wieder iterativ berechnet werden. Anders als bei Aufgabe 5.2 können wir in diesem Fall nicht mit Matrizen arbeiten, sondern müssen die gesuchten Werte jeweils separat ermitteln.

  2. Da die Konfigurationsparameter unterschiedliche Aspekte betreffen, ist es sinnvoll, diese nicht in einem gemeinsamen Dictionary abzulegen, sondern diese thematisch zu bündeln, z.B. 'model' (1.-3.), 'plot' (4.-5.) und 'data' (6.).

Auch hier wieder als Beispiel, wie das Ergebnis aussehen sollte, wenn wir die o.a. Beispieltabelle verwenden:

../_images/exer_06_matplotlib_03.PNG