Kapitel 1 - Programmiergrundlagen

Was ist Programmieren?

Unter Programmieren versteht man ganz allgemein die Tätigkeit, einem Computer Anweisungen zu geben. Das Eintippen einer Rechenanweisung in einen Taschenrechner lässt sich also schon als Programmieren betrachten. Man erklärt dem Computer die Berechnung, die man gerne durchführen möchte, und der Computer erledigt die Rechenarbeit. Einfache Taschenrechner kennen jedoch nur wenige Befehle, meist nur Grundrechenarten. Aufwendigere kennen zum Beispiel Winkelfunktionen oder die Möglichkeit, Zahlen zu speichern und wieder abzurufen. Will man noch komplexere Berechnungen durchführen, ist man auf „echte“ Computer angewiesen.

Im Unterschied zum Taschenrechner mit seinen Tasten für jede Grundrechenart, muss dem Computer nun allerdings der Umstand, dass zum Beispiel die Wurzel einer Zahl berechnet werden soll, mithilfe einer Programmiersprache mitgeteilt werden. Dies könnte zum Beispiel der Befehl sqrt(x) sein, den es wohl – abhängig von der so genannten Syntax einer Sprache – in der einen oder anderen Form in jeder Programmiersprache gibt. Das hat den Vorteil, dass die Kenntnis einer Programmiersprache in der Regel dafür sorgt, dass auch andere Programmiersprachen leicht erlernt werden. Darüber hinaus steigern Programmierkenntnisse die allgemeine Problemlösungskompetenz, die Abstraktionsfähigkeit und die präzise Ausdrucksweise im wissenschaftlichen Kontext.

Die Programmiersprache Python

Die Vorlesung – und damit dieses Skriptum – beziehen sich auf die Programmiersprache Python. Dies hat mehrere Gründe: Python ( https://www.python.org ) ist eine universelle, vielseitig einsetzbare Programmiersprache, die

  1. in ihrer klaren und übersichtlichen Syntax als leicht erlernbar ist,
  2. damit in vielen Disziplinen mittlerweile zu einem wissenschaftlichen Standard geworden ist
  3. in all ihren Grundlagen offen und damit kostenlos zu verwenden ist
  4. eine umfangreiche Standardbibliothek und zahlreiche frei zugängliche Spezialmodule umfasst
  5. eine leicht installierbare und komfortabel im Browser laufende Programmierumgebung zur Verfügung stellt
  6. und von einer sehr großen Community beständig weiterentwickelt wird.

Anaconda

Anaconda (https://www.anaconda.com/) ist eine sogenannte Python-Distribution, die eine Vielzahl von Werkzeugen vereint, die für das Programmieren mit Python notwendig sind. Anaconda ist frei (d.h. kostenlos), sehr leicht zu installieren, und verfügbar für Windows, Mac-OS und Linux. Anaconda enthält mit über 150 mitgelieferten Modulen (packages) im Prinzip alles (und viel mehr), was zum Programmieren im Bereich der USW-Systemwissenschaften nötig ist.

Vor allem enthält Anaconda bereits auch eine sehr komfortable Ein- und Ausgabe-Umgebung (d.h. eine Entwicklungsumgebung) namens Jupyter Notebook , die einfach im gewohnten Browser läuft, wie er für das tägliche Surfen im Internet verwendet wird (Google-Chrome, Firefox, Internetexplorer ...). Das heißt, nach Installation von Anaconda sind im Prinzip keine zusätzlichen oder ungewohnten Programme mehr notwendig, um sofort mit Python zu arbeiten zu beginnen. Im Folgenden wird nun der Installationsvorgang für Anaconda mit Python 3.7. auf einem Windows-Computer beschrieben.

Eine weitere Windows-Installationsbeschreibung findet sich hier: https://docs.continuum.io/anaconda/install/windows

Für die Mac-OS-Installation siehe hier: https://docs.continuum.io/anaconda/install/mac-os

Für Linux siehe hier: https://docs.continuum.io/anaconda/install/linux

Anaconda Installation für Python 3.7

screenshot

  • Klicken Sie, wenn Sie einen entsprechenden Computer haben, auf den Button „64-BIT INSTALLER“ um Python 3.7 Version zu erhalten. Ansonsten wählen Sie die 32-BIT-Version. Daraufhin startet der Download der Installationsdatei. Dies kann einige Minuten in Anspruch nehmen. Achten Sie darauf, dass der Pfad zu dem Ordner, in dem Sie Ihre Downloads speichern, und auch der Ordnername selbst keine Sonderzeichen (wie Umlaute etc.) oder Leerzeichen enthält. Sollte dies der Fall sein, so kopieren Sie die heruntergeladene Datei bevor Sie sie ausführen in einen entsprechenden Ordner.
  • Doppelklicken Sie auf die .exe Datei, die Sie heruntergeladen haben.
  • Klicken Sie im automatisch neu geöffneten Fenster auf den Button „Ausführen“.
  • Folgen Sie den weiteren Dialog-Fenstern.
  • Im Licence Agreement-Fenster klicken Sie auf den Button „I Agree“, wenn Sie den Lizenzbedingungen zustimmen.
  • Folgen sie erneut den Dialog-Fenstern. Wir empfehlen die jeweils vorgeschlagenen Angaben zu übernehmen. Im Fenster Choose Install Location sollten sie erneut darauf achten, dass der Pfad zu dem Ordner, in den sie Anaconda installieren wollen, und auch der Ordnername selbst keine Sonderzeichen (Umlaute etc.) oder Leerzeichen enthalten. Mit einem Klick auf den Button „Browse...“ können Sie den automatisch vorgeschlagenen Ordner ändern und Ihren gewünschten Speicherort auswählen. Klicken sie erneut „Next >“ um mit der Installation fortzufahren.
  • Klicken Sie auf den Button „Install“ und schließlich „Finish“ um die Installation fertigzustellen.

Nach erfolgreicher Installation sollten sie in Ihrem Windows Startmenü einen Menüpunkt Anaconda sehen , der seinerseits einen Menü - Unterpunkt Jupyter Notebook enthält.

Jupyter Notebook

Das Jupyter Notebook ist eine sehr komfortable Ein-und Ausgabe-Umgebung für die Programmiersprache Python , d.h. eine Entwicklungsumgebung, die im bereits auf ihrem Computer installierten Browser läuft. Alle in diesem Skriptum vorkommenden Beispiele wurden mit dem Jupyter Notebook geschrieben und werden auch in diesem angezeigt.

Klicken Sie , um das Jupyter Notebook zu starten, im Windows-Startmenü unter Anaconda auf den Menüpunkt Jupyter Notebook.

Es öffnet sich ein Fenster, das zuerst schwarz ist, und sich dann mit weißem Text füllt. Dieses Fenster darf nicht geschlossen werden, solange Sie mit dem Jupyter Notebook arbeiten möchten. Es handelt sich hierbei um eine Anzeige für den so genannten Kernel , also den Prozess, in dem die eigentlichen Berechnungen durchgeführt werden. Zusätzlich öffnet sich Ihr Browser, bzw. ein neuer Tab in Ihrem Browser.

Um ein neues Jupyter Notbook zu öffnen, klicken Sie im rechten Bereich dieses Browser-Tabs auf das Dropdown-Menü „New“ und wählen Sie „Python 3“ aus.

Kapitel 2 – Einführung in Python – Der Froschteich

Verwenden von Jupyter-Notebooks

Jupyter-Notebooks bestehen aus Zellen. Es gibt drei Typen von Zellen:

  • Textzellen, so wie diese hier, in welche man Texte schreiben und formatieren kann
  • In-Zellen, in die man Python-Code schreiben kann
  • Out-Zellen, die das Ergebnis der darüberstehenden In-Zelle ausgeben

Um eine In-Zelle auszuführen klickt man einfach in die gewünschte Zelle und drückt die Shift- und die Enter-Taste gleichzeitig. Es wird automatisch eine Out-Zelle produziert, in der das Ergebnis der In-Zelle steht.

Auch ohne jegliche Programmierkenntnisse kann man so schon Python benutzen, wenn auch nur als Taschenrechner:

In [1]:
7 * 7 - 7
Out[1]:
42
In [2]:
((12 + 144 + 20 + 3 * 4**0.5) / 7) + 5 * 11
# Exponenten werden in Python als x**y angegeben. 4**0.5 bedeutet also 4 hoch 0.5. 
# Der Text hinter einer Raute (#) wird vom Computer ignoriert und ist als Information für den Menschen gedacht.
Out[2]:
81.0

Python kann aber natürlich viel mehr als ein Taschenrechner. In eine Zelle kann man nicht nur einzelne Rechnungen schreiben, sondern ganze Programme oder vollständige Simulationen. Versuchen wir eine ganz simple Simulation zu erstellen, um die Grundbefehle von Python zu lernen. Nehmen wir an, wir wollen die Populationsentwicklung in einem Froschteich simulieren. In allererster Näherung gehen wir davon aus, das sich jedes Jahr 3 neue Frösche im Teich ansiedeln.

Variablen definieren

Um Zahlen (wie z.B. die Anzahl der Frösche in einem Teich) zu speichern muss eine so genannte Variable angelegt werden. Das ist sozusagen der Name unter dem die Zahl gespeichert ist. Die Zuweisung von Variablenname zur Zahl passiert in Python mit dem = Zeichen. Wichtig ist, dass der Name links vom = Zeichen steht, die Zahl die dort gespeichert werden soll, rechts. Wir starten unsere Teichsimulation also indem wir die Anzahl der Frösche auf 0 setzen.

In [3]:
froschanzahl = 0

Variablen verändern

Um eine Variable zu ändern kann man einfach den aktuellen Wert mit dem neuen Wert überschreiben. Auch das geschieht mit dem = Zeichen.

In [4]:
# Jahr 1:
froschanzahl = 3

Variablen abrufen

Um Variablen wieder anzuzeigen benutzt man den Befehl print. Dieser Befehl schreibt den aktuellen Wert der abgefragten Variable in die Ausgabezeile. Schreiben wir also print(froschanzahl), sollte das das Ergebnis 3 bringen.

In [5]:
print(froschanzahl)
3

Das ist sehr hilfreich für unsere weiter Simulation, denn so können wir die aktuelle Zahl der Frösche benutzen, um die neue zu berechnen. Laut unserer Annahme ist die Zahl der Frösche in jedem Jahr um 3 höher als zuvor, mathematisch ausgedrückt also froschanzahl + 3. Somit sieht unsere Froschsimulation so aus:

In [6]:
froschanzahl = 0
#Jahr 1
froschanzahl = froschanzahl + 3
#Jahr 2
froschanzahl = froschanzahl + 3
#Jahr 3
froschanzahl = froschanzahl + 3
#Jahr 4
froschanzahl = froschanzahl + 3
#Jahr 5
froschanzahl = froschanzahl + 3
#Um ein Ergebnis auszugeben benutzen wir den Befehl "print"
print(froschanzahl)
15

In dieser Version ist unsere Froschsimulation nicht nur sehr unspektakulär, sondern auch recht umständlich. Sie ist jedoch ein guter Ausgangpunkt für eine besser Simulation. Als ersten Schritt wäre es schön, wenn wir die Anzahl der Jahre, die simuliert werden sollen, einstellen könnten und nicht immer exakt 5 Jahre simulieren müssen. Dazu benötigen wir ein wichtiges Konzept: die For-Schleife.

For-Schleifen

For-Schleifen werden benutzt um einen Befehl mehrmals hintereinander ausführen zu lassen, in unserem Fall das Hinzuzählen von Fröschen. So würde unser Programm mit einer For-Schleife aussehen:

In [7]:
froschanzahl = 0
for it in range(5):
    froschanzahl = froschanzahl + 3
print(froschanzahl)
15

Das ist deutlich kompakter, dadurch aber auch ein wenig komplizierter. Die erste Zeile ist exakt gleich, wir setzen froschanzahl auf 0. In der nächsten Zeile leitet der Befehl for die For-Schleife ein. Lesen könnte man diese Zeile als: Mache folgenden Befehl für jede ganze Zahl it, die kleiner ist als 5, also ingesamt 5 mal (für 0,1,2,3,4). Nach dem Doppelpunkt kommt in der nächsten Zeile der Befehl, der ausgeführt werden soll. Zum Schluss lassen wir uns wieder das Ergebnis ausgeben.

Einrücken in Python

Woher weiß Python jetzt aber, das wir den letzten Befehl (das print) nicht auch 5 mal ausgeführt haben wollen? Das passiert mittels Einrückungen (tab-Taste). Befehle, die direkt untereinander stehen gehören für Python zusammen. Das macht Pythoncode automatisch gut lesbar und man benötigt keine Klammern. Würden wir die Ausgabe wirklich gerne nach jedem Jahr, und nicht erst am Schluss haben, können wir den print-Befehl mit der Tabulatortaste einrücken. So gehört er zur For-Schleife:

In [8]:
froschanzahl = 0
for it in range(5):
    froschanzahl = froschanzahl + 3
    print(froschanzahl)
3
6
9
12
15

Ob Befehle innerhalb oder außerhalb einer For-Schleife stehen macht einen großen Unterschied und ist eine häufige Fehlerquelle. Würde man beispielsweise auch die Zeile froschanzahl = 0 in die For-Schleife einbauen, würde die Zahl der Frösche bei jedem Durchlauf der Schleife auf 0 gesetzt werden:

In [9]:
for it in range(5):
    froschanzahl = 0
    froschanzahl = froschanzahl + 3
    print(froschanzahl)
3
3
3
3
3

Dennoch bietet das Verwenden einer For-Schleife einen riesigen Vorteil: Wir müssen nur mehr eine einzige Zahl ändern um die Zahl der simulierten Jahre zu verändern. Wollen wir beispielweise 10 Jahre simulieren:

In [10]:
froschanzahl = 0
for it in range(10):
    froschanzahl = froschanzahl + 3
print(froschanzahl)
30

Zu einem schönen Stil beim Programmieren gehört es, solche Zahlen, die man öfter ändern möchte an den Anfang des Programms zu stellen. Wir definieren also eine neue Variable, damit wir die Zahl nicht mitten im Code ändern müssen, sondern schön übersichtlich am Anfang. Mit einem Kommentar wissen auch zukünftige Benutzer, was diese Variable genau macht.

In [11]:
simulationszeit = 10
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird

froschanzahl = 0
for it in range(simulationszeit):
    froschanzahl = froschanzahl + 3
print(froschanzahl)
30

Mit diesem Programm können wir nun auch sehr lange Zeitbereiche simulieren:

In [12]:
simulationszeit = 1000
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird

froschanzahl = 0
for it in range(simulationszeit):
    froschanzahl = froschanzahl + 3
print(froschanzahl)
3000

In dieser Simulation ist das Endergebnis weniger spannend als die zeitliche Entwicklung. Es wäre also interessant die Froschanzahl zu jedem Zeitpunkt auszugeben. Am einfachsten verschieben wir den print Befehl in die For-Schleife:

In [3]:
simulationszeit = 10
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird

froschanzahl = 0
for it in range(simulationszeit):
    froschanzahl = froschanzahl + 3
    print(froschanzahl)
3
6
9
12
15
18
21
24
27
30

Auf diese Weise sieht man wie sich die Froschpopulation entwickelt, da wir jeden Wert einzeln ausgegeben haben. Der Nachteil an dieser Variante: Wirklich gespeichert haben wir immer nur ein Zahl. Am Ende der Simulation sehen wir zwar viele Zahlen, gespeichert (und somit zum Weiterarbeiten verfügbar) ist aber nur die letzte. Eine schönere Lösung ist das Verwenden von einer Liste.

Listen in Python

Listen bestehen aus mehreren aufeinanderfolgenden Werten. Um eine Liste zu erstellen, fängt man am besten mit einer leeren Liste an und hängt dann immer wieder neue Elemente an. Auch Listen bekommen, so wie Variablen, einen eindeutigen Namen. Leere Listen erstellt man mit name=[] und neu Einträge fügt man mit dem append-Befehl hinzu. Für unser Beispiel:

In [14]:
simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird

froschanzahl = 0
froschanzahl_liste = [] #leere liste wird erstellt
froschanzahl_liste.append(froschanzahl) #erster Eintrag (0 Frösche) wird angehängt

for it in range(simulationszeit):    
    froschanzahl = froschanzahl + 3
    #am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die liste angehängt
    froschanzahl_liste.append(froschanzahl) 
print(froschanzahl_liste)
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90]

Nun kann man schon grob erkennen was passiert, viel vorstellen kann man sich aber noch nicht. Schön wäre eine grafische Darstellung. Und das ist in Python zum Glück sehr einfach.

Grafiken in Python

Der Befehl zum Erstellen von Plots lautet "plot". Dieser Befehl ist jedoch nicht standardmäßg in jedem Pythonprogramm vorhanden, sondern muss in der Regel erst aus einem sogenannten Paket importiert werden. Pakete sind sozusagen Sammlungen von Befehlen.

Der plot Befehl selbst funktioniert dann ganz einfach: In Klammer schreibt man einfach die Liste von Zahlen, die man darstellen möchte.

In [2]:
import matplotlib.pyplot as plt #wir importieren das Modul matplotlib.pyplot und geben ihm die Abkürzung plt
# die nächste Zeile bewirkt, dass die Grafiken direkt in der Zelle angezeigt werden
%matplotlib inline 

simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird

froschanzahl = 0
froschanzahl_liste = [] #leere Liste wird erstellt
froschanzahl_liste.append(froschanzahl) #erster Eintrag (0 Frösche) wird angehängt

for it in range(simulationszeit):    
    froschanzahl = froschanzahl + 3
    #am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
    froschanzahl_liste.append(froschanzahl) 

plt.plot(froschanzahl_liste)
Out[2]:
[<matplotlib.lines.Line2D at 0x1ef1f2a71d0>]

Eine Grafik ist schon viel anschaulicher als eine Liste von Zahlen. Natürlich kann man diese Grafik noch deutlich verbessern. Bei Fröschen würde es sich beispielsweise anbieten, die Linie grün darzustellen. Solche zusätzlichen Optionen kann man im Plot-Befehl einfach nach einem Beistrich anfügen:

In [16]:
plt.plot(froschanzahl_liste, color = 'green')
Out[16]:
[<matplotlib.lines.Line2D at 0xb0f9828>]

Man beachte, dass man die Farbe selbst unter Anführungsstrichen schreiben muss, da das Programm sonst nach einer Variable suchen würde, die green heißt.

Eine weitere mögliche Verbesserung wäre eine Beschriftung der Achsen:

In [3]:
plt.plot(froschanzahl_liste, color = 'green')
plt.xlabel('Zeit (Jahre)')
plt.ylabel('Frösche')
Out[3]:
<matplotlib.text.Text at 0x1ef1f2d64e0>

Zusammenfassung

Variablen

Durch Variablen können wir Werte einem Namen zuordnen. Mit

varname = 42

weisen wir der Variable varname den Wert 42 zu und überschreiben den aktuell dort gespeichert Wert, wenn dort schon etwas gespeichert ist.

Mit

varname = varname + 1

erhöhen wir den Wert, der unter varname gespeichert ist um 1.

Den aktuellen Wert der Variable varname kann mit

print(varname)

angezeigt werden.

For-Schleifen

Mit For-Schleifen ist es möglich gleiche oder ähnliche Befehl oftmals hintereinander auszuführen. Um einen Befehl also beispielsweise 100 mal abarbeiten zu lassen, verwenden wir:

for it in range(100):

befehl

Man beachte die Einrückung des Befehls, die zeigt, das sich der Befehl innerhalb der Schleife befindet.

Listen

In Listen können mehrere Werte unter nur einem Namen abgespeichert werden. Leere Listen erstellt man mit

listenname = []

Einen neuen Eintrag (z.B. neueselement) zur Liste hinzufügen kann man dann mit

listenname.append(neueselement)

Grafiken

Um innerhalb eines Jupyter-Notebooks den Plotbefehl benutzen zu können, verwenden wir die Zeilen

import matplotlib.pyplot as plt

%matplotlib inline

am Anfang des Programms. Danach können wir mit dem Befehl

plt.plot(listenname)

die Liste mit dem Namen listenname grafisch darstellen. Zusatzoptionen wie Farbe können nach einem Beistrich übergeben werden:

plt.plot(listenname, color='green')

Kapitel 3 - Populationsentwicklungen

In dieser Einheit möchten wir uns genauer mit verschiedenen Möglichkeiten der Populationsentwicklung auseinandersetzen. Mit dem Beispiel des Froschteiches haben wir bereits eine sehr einfache Form der Populationsentwicklung kennengelernt, so genanntes:

Lineares Wachstum

Wir ziehen nun als weiteres Beispiel die Vermehrung von Kaninchen heran.

In [2]:
import matplotlib.pyplot as plt #wir importieren das paket matplotlib.pyplot und geben ihm die abkürzung plt
#die nächste Zeile bewirkt, dass die Grafiken direkt in der Zelle angezeigt werden
%matplotlib inline 

simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden

kaninchenanzahl = 0
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (0 Kaninchen) wird angehängt

for it in range(simulationszeit):    
    kaninchenanzahl = kaninchenanzahl + 1
    #am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[2]:
[<matplotlib.lines.Line2D at 0x246c4dacba8>]

Diese Form von Wachstum war für einen Froschteich zwar eine gute Näherung, wenn wir annehmen, dass die Frösche dort einfach zuwandern. Unsere Kaninchenpopulation vermehrt sich aber durch Fortpflanzung. Das heißt, je mehr Kaninchen es gibt, um so schneller kommen neue hinzu.

Dies führt zu sogenanntem exponentiellen Wachstum.

Exponentielles Wachstum

Von exponentellem Wachstum spricht man, wenn das Wachstum einer Größe proportional zu der Größe selbst ist. In Python ausgedrückt also:

kaninchen = kaninchen + x * kaninchen

wobei x ein Wachstumsparameter ist, den wir beliebig wählen können. Möchten wir zum Beispiel, dass sich die Anzahl der Kanichen in jedem Zeitschritt verdoppelt, können wir als Wachstumsparameter 1 wählen:

kaninchen = kaninchen + 1 * kaninchen

was man auch als kaninchen = 2 * kaninchen schreiben könnte. Möchten wir jeden Zeitschritt ein Wachstum von 10% wählen wir x als 0.1:

kaninchen = kaninchen + 0.1 * kaninchen

Machen wir dazu eine Simulation:

In [1]:
import matplotlib.pyplot as plt 
%matplotlib inline 

simulationszeit = 10
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden

kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt

for it in range(simulationszeit):    
    kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
    #am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[1]:
[<matplotlib.lines.Line2D at 0x23810428a58>]

Exponentielles Wachstum ist eine recht gute Näherung an das echte Verhalten einer Population. Ein Problem entsteht aber, wenn wir sehr lange Zeiträume betrachten. Setzen wir die Simulationszeit einmal auf 150 und sehen was passiert:

In [4]:
import matplotlib.pyplot as plt 
%matplotlib inline 

simulationszeit = 150
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden

kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt

for it in range(simulationszeit):    
    kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
    #am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[4]:
[<matplotlib.lines.Line2D at 0x23810648c50>]

Das Wachstum wird immer schneller und die Kaninchenpopulation explodiert. Das ist ein unrealistisches Verhalten, denn jedes Ökosystem hat eine gewisse Kapazitätsgrenze, also eine maximale Anzahl an Individuen, die im System leben können (aufgrund von Nahrung- oder Unterschlupfangebot). Auch das sollten wir unser Programm einbauen. Nehmen wir an es kann in unserem System nie mehr als 1000 Kaninchen geben. Wenn diese Zahl überschritten ist, sollen keine neuen Kaninchen dazukommen. Um das ins Programm einzubauen, brauchen wir eine neue Struktur, die so genannte If-Abfrage.

If- Abfrage

If-Abfragen sind "Wenn-Dann-Fragen", mit denen wir Befehle an Bedingungen knüpfen können. Sie haben stets folgenden Aufbau:

if BEDINGUNG:
     BEFEHL

BEFEHL meint hierbei irgendeine Anweisung an Python, also zum Beispiel das Ändern einer Variable, einen Printbefehl oder etwas Ähnliches. Eine If-Abfrage kann auch mehrere Befehle enthalten.

Eine BEDINGUNG kann alles sein, was entweder wahr oder falsch sein kann. Oftmal benutzt man zur Definition einer Bedingung eine Gleichung. Man überprüft also etwa, ob es wahr ist, dass eine Variable x gleich groß ist wie eine Variable y. Noch häufiger werden Ungleichungen benutzt. Man überprüft also, ob es wahr ist, dass eine Variable x größer (oder kleiner) als eine Variable y ist.

Im folgenden Beispiel betrachten wir eine If-Abfrage in einer For-Schleife. Die For-Schleife erhöht die Zahl der Kaninchen fortlaufend um 1, die If-Abfrage führt einen Printbefehl aus, allerdings nur dann, wenn die Zahl der Hasen größer ist als 100.

In [5]:
kaninchen = 0 # Variablen-Definition, d.h. Zuweisung des Wertes 0 and die Variable hasen

for it in range(105):
    kaninchen = kaninchen + 1
    if kaninchen == 100:  # Vergleich des Wertes der Variable hasen mit dem Wert 100
        print("Es gibt jetzt EXAKT 100 Kaninchen!")
Es gibt jetzt EXAKT 100 Kaninchen!

Dieses Beispiel demonstriert den wichtigen Unterschied zwischen dem einfachen = Zeichen und dem doppelten = Zeichen noch einmal: Das einfache = wird benutzt um einer Variable (kaninchen) einen Wert zu zuweisen. Das doppelte = Zeichen wird dagegen für einen Vergleich verwendet.

Es können auch mehrere Befehle unter eine If-Abfrage gestellt werden. Ähnlich wie bei der For-Schleife wird hier durch Einrücken signalisiert, ob ein Befehl Teil der If-Abfrage ist, oder nicht:

In [6]:
kaninchen = 95
for it in range(10):
    kaninchen = kaninchen + 1
    if kaninchen > 100:
        print("Es gibt jetzt mehr als 100 Kaninchen!")
        print("Die exakte Zahl der Kaninchen ist ")
        print(kaninchen)
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
101
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
102
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
103
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
104
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
105

Wenn wir die beiden zuletzt stehenden Befehle nicht einrücken, dann sind sie nicht mehr Teil der If-Abfrage und werden in jedem Fall ausgeführt, egal wie viele Kaninchen es gibt:

In [8]:
kaninchen = 95
for it in range(10):
    kaninchen = kaninchen + 1
    if kaninchen > 100:
        print("Es gibt jetzt mehr als 100 Kaninchen!")
    print("Die exakte Zahl der Kaninchen ist ")
    print(kaninchen)
Die exakte Zahl der Kaninchen ist 
96
Die exakte Zahl der Kaninchen ist 
97
Die exakte Zahl der Kaninchen ist 
98
Die exakte Zahl der Kaninchen ist 
99
Die exakte Zahl der Kaninchen ist 
100
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
101
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
102
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
103
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
104
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
105

Wenn wir die beiden Befehle noch einmal nach außen verschieben, stehen sie auch außerhalb der For-Schleife und werden somit nur einmal ausgeführt, und zwar nachdem die For-Schleife abgeschlossen ist:

In [10]:
kaninchen = 95
for it in range(10):
    kaninchen = kaninchen + 1
    if kaninchen > 100:
        print("Es gibt jetzt mehr als 100 Kaninchen!")
print("Die exakte Zahl der Kaninchen ist ")
print(kaninchen)
Es gibt jetzt mehr als 100 Kaninchen!
Es gibt jetzt mehr als 100 Kaninchen!
Es gibt jetzt mehr als 100 Kaninchen!
Es gibt jetzt mehr als 100 Kaninchen!
Es gibt jetzt mehr als 100 Kaninchen!
Die exakte Zahl der Kaninchen ist 
105

Nun, da wird If-Abfragen verstehen können wir die Beschränkung auf 1000 Individuen in unser Programm einbauen: Nur wenn es noch weniger als 1000 Kaninchen gibt soll die Population weiter anwachsen:

In [15]:
import matplotlib.pyplot as plt 
%matplotlib inline 

simulationszeit = 100
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden

kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt

for it in range(simulationszeit):
    if kaninchenanzahl < 1000: #NUR WENN es weniger als 1000 Kaninchen gibt
        kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
    #am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[15]:
[<matplotlib.lines.Line2D at 0x2381099eb00>]

Neben dem exponentiellen Wachstum gibt es aber noch andere Methoden um das Wachstum einer Population zu beschreiben. Mann kann auch von der Idee ausgehen, dass die momentane Population einerseits von der Population aus dem letzten Zeitschritt, andererseits aber auch von der Population aus dem vorletzten Zeitschritt abhängt.

Dies führt zur sogenannten

Fibonacci-Folge

In diesem Model errechnet sich die Anzahl der Kaninchen im aktuellen Jahr aus der Anzahl der Kaninchen aus dem Vorjahr plus der Anzahl der Kaninchen aus dem vorletzen Jahr. Um so etwas zu programmieren, müssen wir aber zuerst mehr über Listen in Python lernen:

Listen und Schleifen für Fortgeschrittene

Bislang wissen wir, wie man leere Listen erstellt und Elemente zu Listen hinzufügt. Man kann aber natürlich auch spezifische Listeneinträge abfragen. Möchte man beispielsweise das siebente Element der Liste kaninchenanzahl_liste wissen, kann man es mit kaninchenanzahl_liste[6] abrufen. Warum nicht 7? In Programmiersprachen ist es üblich, das erste Element einer Liste stets mit der Nummer 0 zu speichern, in diesem Fall also als kaninchenanzahl_liste[0]. Das ist anfangs zwar verwirrend, für spätere Anwendungen jedoch eine recht sinnvolle Konvention.

In [5]:
kaninchenanzahl_liste[0]
Out[5]:
0

For-Schleifen haben wir schon kennen gelernt, eine wichtige Eigenschaft haben wir aber bislang ignoriert. Die Zahl it, die sozusagen mitzählt wie oft ein Befehl schon ausgeführt worden ist, darf auch innerhalb eines Befehls benutzt werden:

In [3]:
for it in range(5):
    print(it)
0
1
2
3
4

Wenn wir diese beiden Kenntnisse nun kombinieren, haben wir eine Möglichkeit gefunden, wie wir innerhalb einer Schleife auf vorherige Listeneinträge zugreifen können. Das ist genau das, was wir für unsere Fibonacci-Folge benötigen.

Eine weitere wichtige Eigenschaft von for-Schleifen ist, dass it nicht unbedingt bei 0 anfangen muss. Möchten wir beispielsweise erst ab Jahr 2 simulieren, so können wir die Schleife auch erst bei Jahr zwei beginnen lassen:

In [4]:
for it in range(2,5):
    print(it)
2
3
4
In [5]:
import matplotlib.pyplot as plt 
%matplotlib inline 

simulationszeit = 10
# Simulationszeit: Die Zeit in Jahren, die die Kaninchenfortpflanzung simuliert wird

kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
kaninchenanzahl = 1 
kaninchenanzahl_liste.append(kaninchenanzahl)#zweiter Eintrag (1 Kaninchen) wird angehängt
#wir benötigen hier zwei Kaninchen, damit sie sich auch vermehren können

for it in range(2, simulationszeit):
    letztesjahr = kaninchenanzahl_liste[it - 1] # Kaninchenanzahl vom letzten jahr wird berechnet
    vorletztesjahr = kaninchenanzahl_liste[it - 2] # Kaninchenanzahl vom vorletzten jahr wird berechnet
    kaninchenanzahl = letztesjahr + vorletztesjahr # neue Kaninchenanzahl wird berechnet
    # am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[5]:
[<matplotlib.lines.Line2D at 0xb05ff28>]

Natürlich können wir auch hier eine Beschränkung des Wachstums durch eine If-Abfrage einbauen, ganz gleich wie beim exponentiellen Wachstum:

In [19]:
import matplotlib.pyplot as plt 
%matplotlib inline 

simulationszeit = 20
# Simulationszeit: Die Zeit in Jahren, die die Kaninchenfortpflanzung simuliert wird

kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
kaninchenanzahl = 1 
kaninchenanzahl_liste.append(kaninchenanzahl)#zweiter Eintrag (1 Kaninchen) wird angehängt
#wir benötigen hier zwei Kaninchen, damit sie sich auch vermehren können

for it in range(2, simulationszeit):
    if kaninchenanzahl < 1000: #NUR WENN es weniger als 1000 Kaninchen gibt
        letztesjahr = kaninchenanzahl_liste[it - 1] # Kaninchenanzahl vom letzten jahr wird berechnet
        vorletztesjahr = kaninchenanzahl_liste[it - 2] # Kaninchenanzahl vom vorletzten jahr wird berechnet
        kaninchenanzahl = letztesjahr + vorletztesjahr # neue Kaninchenanzahl wird berechnet
    # am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
    kaninchenanzahl_liste.append(kaninchenanzahl) 

plt.plot(kaninchenanzahl_liste)
Out[19]:
[<matplotlib.lines.Line2D at 0x23810c19ef0>]

Somit haben wir mehrere Möglichkeiten kennengelernt, wie man das Wachstum von einer Population modellieren kann. Möchte man realistischere Modelle erstellen, ist man meist auf Differenzengleichungen angewiesen, die sowohl die Geburt von neuen Individuen, als auch das Sterben von alten Individuen berücksichtigt. Aber auch das ist nicht deutlich schwieriger, wie wir im nächsten Kapitel sehen werden.

Zusammenfassung

Lineares Wachstum

Bei linearem Wachstum kommt zu einer Population in jedem Zeitschritt eine konstante Anzahl hinzu:

kaninchen = kaninchen + x

wobei x angibt wie viele Individuen hinzukommen, also wie schnell die Population wächst.

Exponentielles Wachstum

Bei exponentiellem Wachstum kommt zur Population kein konstanter Wert hinzu, sondern ein Wert, der direkt von der aktuellen Population abhängt. Die genaue Abhängigkeit von Wachstum und momentaner Population gibt der so genannte Wachstumsparameter an:

kaninchen = kaninchen + kaninchen * wachstumsparameter

If-Abfragen

If-Abfragen können benutzt werden, um Anweisungen und Befehle nur unter gewissen Bedingungen ausführen zu lassen. Sie haben den Aufbau

if BEDINGUNG:
     BEFEHL

Die Bedingungen sind meist Ungleichungen (hasen >= 100) oder Gleichungen (hasen == 100), die entweder wahr oder falsch sein können. Wichtig ist, bei einem Vergleich das doppelte =-Zeichen zu verwenden.

For-Schleifen für Fortgeschrittene

Die Laufvariable (im Beispiel it) kann innerhalb der Schleife benutzt werden. So liefert die Schleife

for it in range(3):

print(it)

das Ergebnis

0

1

2

Listen für Fortgeschrittene

Um die einzelnen Elemente einer Liste zu verwenden oder auszugeben, benutzt man eckige Klammern:

listenname[2]

liefert zum Beispiel das dritte(!) Element. Achtung: Das erste Element in der Liste hat stets den Index 0, das zweite den Index 1, und so weiter.

Kapitel 4 - Wachstum mit Feedback

Während wir im vorhergehenden Kapitel Wachstumsprozesse von nur einer Tierpopulation betrachtet haben, wollen wir uns nun ein vereinfachtes Ökosystem, das aus zwei Tierarten besteht, ansehen. Es ist aber noch kein richtiges Räuber-Beute-System, wie wir es später in diesem Skriptum kennenlernen werden. Hier betrachten wir fürs erste ein stark vereinfachtes Modell , das aber trotzdem viele wichtige Eigenschaften eines echten Räuber-Beute-Systems zeigt.

Wir beginnen damit, dass wir zwei Tierarten betrachten, Hasen und Luchse, die sich beide exponentiell vermehren. Beide tun dies von unterschiedlichen Anfangswerten aus und mit unterschiedlichen Wachstumsraten. Die Simulationszeit soll 1 Jahr betragen, mit einer Genauigkeit von einzelnen Tagen.

In [1]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # Wachstumsrate, wie schnell vermehren sich die Hasen


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Luchse


for it in range(365): # Schleife über 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum 
    # population = population + wachstumrate * population
    hasen =  hasen + hasen_wachstum * hasen  
    luchse = luchse + luchs_wachstum * luchse 
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[1]:
[<matplotlib.lines.Line2D at 0x98bea90>]

Das exponentielle Wachstum dieser Populationen ist eher unrealistisch. Es berücksichtigt nicht, dass Tiere auch sterben. Wir bauen deshalb, zusätzlich zur Wachstumsrate, auch eine Sterberate in unsere Gleichungen ein:

$$Population = Population + Wachstumsrate * Population - Sterberate * Population$$

In [2]:
#Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # Sterberate, wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # Sterberate, wie viele Luchse sterben

for it in range(365): #Schleife über 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
 
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

#Listen werden geplottet    
plt.plot(hasen_liste,color = "gray", lw = 2)
plt.plot(luchs_liste,color = "brown", lw = 2)
Out[2]:
[<matplotlib.lines.Line2D at 0xa519b38>]

Die sich ergebenden Kurven sehen zwar ähnlich aus, wie zuvor, wachsen aber nicht so hoch an, wie der Blick auf die Werte an der y-Achse zeigt.

In dieser Variante unserer Gleichungen leben Hasen und Luchse noch vollkommen unabhängig voneinander. Wir könnten nun aber berücksichtigen, dass sich Luchse von Hasen ernähren und sich damit schneller vermehren, wenn es gerade viele Hasen gibt. Dazu benötigen wir die Programmierstruktur, die wir im vorherigen Kapitel kennengelernt haben: Die If-Abfrage.

Wir können nun unsere neuen Erkentnisse über If-Abfragen in unsere Gleichung einbauen. Wir wollen dabei berücksichtigen, dass sich die Luchse besser vermehren können, wenn es sehr viele Hasen gibt. Dazu nehmen wir an, dass sich die Wachstumsrate der Luchse auf 0.01 erhöht, wenn es 1500 oder mehr Hasen gibt.

In [8]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(365): #Schleife über 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
    
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[8]:
[<matplotlib.lines.Line2D at 0xacfef60>]

Mit dieser Erweiterung wächst die Zahl der Luchse ab einer gewissen Hasen-Menge stärker an. Was wird unter diesen Bedingungen nun mit den Hasen passieren? Die Hasen werden stärker bejagt, ihre Sterberate wird sich also erhöhen. In den Systemwissenschaften nennt man einen solchen Effekt Feedback.

Feedbacks

Ein Feedback liegt immer dann vor, wenn die Änderung einer Größe direkt oder indirekt zu einer weiteren Änderung der Größe führt. Unser Beispiel impliziert gleich mehere Feedbacks: Einerseits kann man die Vermehrung der Hasen und Luchse für sich bereits als Feedback betrachten: je mehr Hasen (Luchse) es gibt, desto mehr Hasen (Luchse) werden geboren. Diesbezüglich spricht man von einem positiven Feedback.

Es gibt allerdings auch negative Feedbacks. Diese kommen zu tragen, wenn eine positive Änderung einer Variable im Weiteren zu einer negativen Änderung der gleichen Variable führt. Auch ein solches negatives Feedback ist in unserem Beispiel vertreten: Wenn es viele Hasen gibt, gibt es auch viele Luchse. Viele Luchse führen aber dazu, dass mehr Hasen gefressen werden. Indirekt führt die positive Änderung der Hasenpopulation zu einer negativen Änderung der Hasenpopulation.

Bauen wir diesen Effekt nun (wieder mit einer If-Abfrage) in unser Modell ein. Wir nehmen dazu an, dass sich die Sterberate der Hasen auf 0.02 erhöht, wenn die Luchs-Population auf den Wert 200 (und darüber) anwächst.

In [9]:
#Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(365): #Schleife über 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
        
    #viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[9]:
[<matplotlib.lines.Line2D at 0xb1b7630>]

Hier passiert nun offensichtlich etwas Spannendes, das wir aus unseren bisherigen Wachstums-Betrachtungen so noch nicht kennen: Die Hasen-Population beginnt plötzlich wieder abzunehmen. Wenn es sehr viele Luchse gibt, werden mehr Hasen gefressen als geboren werden. Die Population schrumpft. Die Luchse werden jedoch immer mehr. Am Ende der Simulation gibt es sogar mehr Luchse als Hasen. Was ist hier passiert?

Zuerst steigt die Anzahl der Hasen. Sobald eine kritische Größe (1500) überschritten wird, beginnen sich die Luchse stärker zu vermehren und die Sterberate der Hasen erhöht sich ebenfalls, wenn es 200 und mehr Luchse gibt. So weit, so gut. Das haben wir in unseren Überlegungen bedacht.

Die erhöhte Vermehrungsrate der Luchse bleibt allerdings nun bestehen, auch wenn es, später in der Simulation, wieder weniger Hasen gibt. Wir sollten also genau genommen eine weitere If-Abfrage einbauen, die überprüft, ob es irgendwann wieder weniger als 1500 Hasen gibt und damit die Vermehrungsrate der Luchse wieder auf den normalen Wert zurückfallen sollte. Es gibt für diesen Fall, in dem wir hasen >= 1500 und hasen < 1500 unterscheiden, eine elegante Methode, die so genannte:

If-else-Abfrage

Die If-else-Abfrage ist eine Erweiterung der If-Abfrage. Sie erweitert diese Struktur um zusätzliche Befehle, die ausgeführt werden, wenn die Bedingung 'nicht wahr' (also falsch) ist. Der Aufbau sieht so aus:

if BEDINGUNG:
      BEFEHL WENN WAHR
else:
      BEFEHL WENN FALSCH

Diese Struktur können wir nun verwenden, um die Wachstums- und Sterberaten wider auf ihre ursprünglichen Werte zurückzusetzen, wenn die Hasen- und Luchs-Populationen wieder unter ihre kritischen Werte fallen.

In [10]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(365): #Schleife über 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen  =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
        
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    else:                       # SONST
        luchs_wachstum = 0.005    # vermehren sie sich normal
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    else:                       # SONST
        hasen_sterben = 0.001     # ist das Hasensterben normal groß
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[10]:
[<matplotlib.lines.Line2D at 0xb480320>]

Mit dieser Änderung normalisiert sich das Wachstum der Luchse wieder, wenn die Hasen-Population zu schrumpfen beginnt.

Ein wichtiger Effekt fehlt uns aber noch in unserem Modell: Wenn es wieder weniger Hasen gibt, dann werden auch die Luchse wieder weniger Beute machen. Einige von ihnen werden nicht genug Nahrung finden. Ihre Sterberate erhöht sich. Auch diesen Effekt können wir mit einer If-Else-Bedingung in unser Modell einbauen:

Wir nehmen dazu an, dass sich die Sterberate der Luchse auf 0.01 erhöht, wenn es 500 oder weniger Hasen gibt.

In [11]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(365): #Schleife über 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
        
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    else:                       # SONST
        luchs_wachstum = 0.005    # vermehren sie sich normal
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    else:                       # SONST
        hasen_sterben = 0.001     # ist das Hasensterben normal groß
    
    # wenig Hasen
    if hasen <= 500:              # WENN es 500 oder weniger Hasen gibt
        luchs_sterben = 0.01      # verhungern viele Luchse
    else:                       # SONST
        luchs_sterben = 0.0005    # ist das Luchssterben normal groß
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[11]:
[<matplotlib.lines.Line2D at 0x3cb0e48>]

Das Verhalten dieser beiden voneinander abhängigen Populationen ist nun schon einigermaßen komplex: Zuerst steigt die Zahl der Hasen, bis ihre Zahl nach etwa 100 Tagen ein Maximum erreicht und wieder abzunehmen bginnt. In Folge dessen wird nach etwa 250 Tagen das Nahrungsangebot für die Luchse knapp. Auch sie erreichen ihr Maximum. Beide Populationen weisen nun ein negatives Wachstum auf (d.h. sie schrumpfen).

Es stellt sich an dieser Stelle die Frage, ob Hasen und Luchse somit aussterben werden. Um diese Frage zu beantworten, erhöhen wir in unserem Modell einfach die Simulationszeit von einem auf sieben Jahre und beobachten, wie sich das System weiterentwickelt.

In [12]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(7*365): #Schleife über 7 * 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
    
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    else:                       # SONST
        luchs_wachstum = 0.005    # vermehren sie sich normal
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    else:                       # SONST
        hasen_sterben = 0.001     # ist das Hasensterben normal groß
    
    # wenig Hasen
    if hasen <= 500:              # WENN es 500 oder weniger Hasen gibt
        luchs_sterben = 0.01      # verhungern viele Luchse
    else:                       # SONST
        luchs_sterben = 0.0005    # ist das Luchssterben normal groß
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[12]:
[<matplotlib.lines.Line2D at 0xaf7f5c0>]

Wie wir sehen können, überleben in unserem Modell beide Arten, und das auch auf lange Sicht. Immer wenn die Hasen-Population einen niedrigen Wert erreicht, beginnt auch die Population der Luchse wieder zu schrumpfen und die Hasenpopulation kann sich wieder erholen. Beide Populationen pendeln zwischen einem Minimum und einem Maximum hin und her. wir werden sehen, dass dies ein Verhalten ist, das für viele einfache Räuber-Beute Systeme charakteristisch ist.

Einen weiteren Effekt möchten wir noch einbauen: Wenn es sehr wenige Luchse gibt, fühlen sich die Hasen weniger bedroht und vermehren sich stärker. Wieder benutzen wir eine If-Else-Abfrage:

Wir nehmen dazu an, dass die Vermehrungs- oder Wachstumsrate der Hasen auf 0.011 steigt, wenn es weniger als 100 Luchse gibt.

In [13]:
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(7*365): #Schleife über 7 * 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
    
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    else:                       # SONST
        luchs_wachstum = 0.005    # vermehren sie sich normal
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    else:                       # SONST
        hasen_sterben = 0.001     # ist das Hasensterben normal groß
    
    # wenig Hasen
    if hasen <= 500:              # WENN es 500 oder weniger Hasen gibt
        luchs_sterben = 0.01      # verhungern viele Luchse
    else:                       # SONST
        luchs_sterben = 0.0005    # ist das Luchssterben normal groß
        
    # wenig Luchse    
    if luchse <= 100:             # WENN es 100 oder weniger Luchse gibt
        hasen_wachstum = 0.011    # können sich die Hasen schneller vermehren
    else:                       # SONST
        hasen_wachstum = 0.01     # vermehren sie sich normal
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[13]:
[<matplotlib.lines.Line2D at 0xbaef5f8>]

Dieses System scheint relativ stabil zu sein: Die Populationen oszillieren in ihrer Größe, aber keine Art ist vom Aussterben bedroht. Wir könnten nun aber auch Einflüsse von außen betrachten. Was würde zum Beispiel passieren, wenn die Hasen über einen bestimmten Zeitraum hinweg von Menschen bejagt werden.

Nehmen wir an, dass für einen kurzen Zeitraum, Jagd auf Hasen gemacht wird, und in diesem Zeitraum damit die Hasenpopulation zusätzlich sinkt. Wir nehmen diesen Zeitraum von Tag 1500 bis Tag 1580 an. Wir müssen also in unser Modell eine Abfrage einbauen, die überprüft, ob der je aktuelle Tag mindestens der 1500ste ist, aber gleichzeitig auch höchstens der 1580ste.

Dafür können wir den Umstand ausnutzen, dass man If-Abfragen verschachteln kann, dass man also If-Abfragen auch innerhalb von If-Abfragen stellen kann. Schematisch könnte dies zum Beispiel so aussehen:

if it >= 1500:
      if it <= 1580:
           HASENSAISON!

Für den Fall, dass zwei Bedingungen in dieser Weise gleichzeitig vorliegen, gibt es allerdings auch eine elegantere Methode. Wir können die beiden Bedingungen mit and verknüpfen, und so festlegen, dass beide Bedingungen gleichzeitig erfüllt sein müssen.

Darüber hinaus lassen sich Bedingungen auch mit or verknüpfen, und so sicher stellen, dass zumindest eine der beiden Bedingungen erfüllt, also wahr, sein muss.

Benutzen wir nun diese elegantere Methode, um die Hasenjagd in unsere Simulation einzubauen.

In [21]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # wie schnell vermehren sich die Hasen
hasen_sterben = 0.001  # wie viele Hasen sterben

# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # wie schnell vermehren sich die Luchse
luchs_sterben = 0.0005 # wie viele Luchse sterben

for it in range(7*365): #Schleife über 7 * 365 Tage    
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    # population = population + wachstumrate * population - sterberate * population
    hasen =  hasen + hasen_wachstum * hasen  - hasen_sterben * hasen
    luchse = luchse + luchs_wachstum * luchse - luchs_sterben * luchse
     
    # viele Hasen
    if hasen >= 1500:             # WENN es 1500 oder mehr Hasen gibt
        luchs_wachstum = 0.01     # können sich die Luchse besser vermehren
    else:                       # SONST
        luchs_wachstum = 0.005    # vermehren sie sich normal
    
    # viele Luchse
    if luchse >= 200:           # WENN es 200 oder mehr Luchse gibt
        hasen_sterben = 0.02      # werden mehr Hasen gefressen
    else:                       # SONST
        hasen_sterben = 0.001     # ist das Hasensterben normal groß
    
    # wenig Hasen
    if hasen <= 500:              # WENN es 500 oder weniger Hasen gibt
        luchs_sterben = 0.01      # verhungern viele Luchse
    else:                       # SONST
        luchs_sterben = 0.0005    # ist das Luchssterben normal groß
        
    # wenig Luchse    
    if luchse <= 100:             # WENN es 100 oder weniger Luchse gibt
        hasen_wachstum = 0.011    # können sich die Hasen schneller vermehren
    else:                       # SONST
        hasen_wachstum = 0.01     # vermehren sie sich normal     
        
    #Hasensaison
    #WENN  der Tag 'it' 1500 oder größer ist UND GLEICHZEITIG 1580 oder kleiner
    if it >= 1500 and it <= 1580:
        hasen = hasen - 10
    
    
    # die je aktuellen Populationen werden an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[21]:
[<matplotlib.lines.Line2D at 0x238109c0860>]

Der Effekt, den eine solche Jagd auf das Ökosystem hat, ist nun gut zu sehen. Die Population der Hasen und Luchse wird damit nicht nur kurzzeitig reduziert, sondern kann sich auf lange Sicht nicht mehr erholen. Der ursprüngliche Bestand wird auch nach Ende der Jagd, also nach dem 1580sten Tag, nicht mehr erreicht.

Was, wenn wir nun auf einen gewissen Teil eines Plots hineinzoomen möchten? Wenn wir beispielsweise nur den relevanten Teil zwischen Tag 500 und Tag 2000 zeigen wollen. Um das zu bewerkstelligen ist es notwendig, dass iw runs besser mit dem Auswählen einzelner Listenelemente auseinandersetzen.

Auswählen von Listenelementen

Wie wir einzelne Listenelemente auswählen, um sie zum Beispiel zu printen, wissen wir schon. Für das elfte Element der Hasenliste schreiben wir:

In [22]:
print(hasen_liste[10])
1093.733872802527

Wir können aber auch mehrere Elemente auf einmal auswählen. Dazu schreiben wir in eckige Klammern das erste Element das wir haben möchten, dann einen Doppelpunkt, und dann das erste Element das wir nicht mehr haben wollen. Das Element mit dem Index 10 und das mit dem Index 11 bekommen wir also mit

In [24]:
print(hasen_liste[10:12])
[1093.733872802527, 1103.5774776577498]

Wird vor dem Doppelpunkt keine Zahl geschrieben, wird beim Element mit dem Index Null begonnen. Wird nach dem Doppelpunkt keine Zahl geschrieben, wird bis zum letzen Element alles ausgewählt.

In [28]:
print(hasen_liste[:3])
print(hasen_liste[2554:])
[1000, 1009.0, 1018.081]
[674.7248803814014, 680.797404304834]

Python akzeptiert auch negative Zahlen als Index. Der Index -1 bezeichnet dabei das letzte Element einer Liste, der Index -2 das vorletzte und so weiter. Das ist besonders praktisch, wenn man z.b. die letzten 5 Einträge der Liste auswählen möchte:

In [29]:
print(hasen_liste[-5:])
[656.830371734887, 662.7418450805009, 668.7065216862253, 674.7248803814014, 680.797404304834]

Das Auswählen von Listenteilen funktioniert natürlich nicht nur in Kombination mit dem print-Befehl, sondern auch mit allen anderen Befehlen, zum Beispiel plot. Wir können also nun auf den relevanten Teil unserer Grafik zoomen:

In [32]:
# Listen werden geplottet    
plt.plot(hasen_liste[500:2001], color = "gray", lw = 2)
plt.plot(luchs_liste[500:2001], color = "brown", lw = 2)
Out[32]:
[<matplotlib.lines.Line2D at 0x23811d86c18>]

Was ist hier aber mit der Beschriftung der x-Achse passiert? Warum beginnt sie nicht bei 500, sondern bei 0? Dazu müssen wir uns ein wenig mit dem Plot-Befehl auseinandersetzen.

Plot für Fortgeschrittene

Bisher haben wir den Plot-Befehl immer so verwendet:

plt.plot(liste_mit_y_werten)

Das erzeugt einen Plot mit den Werten aus der Liste. Die x-Achse wird aber immer automatisch mit der Anzahl der Listenwerte beschriftet. Wenn wir eine andere Beschriftung haben wollen, müssen wir den Plot-Befehl anders benutzen:

plt.plot(liste_mit_x_werten,liste_mit_y_werten)

Man beachte dass die Reihenfolge der Listen wesentlich ist: Wenn nur eine Liste in der Klammer steht, wird es als y-Liste interpretiert, wenn zwei Listen in der Klammer stehen (= als Argumente übergeben werden) wird die erste Liste als x-Werte und erst die zweite als y-Werte interpretiert. Um die Beschriftung nun also auf 500 bis 2000 zu korrigieren können wir eine Liste anlegen, die die korrekten Werte enthält:

In [37]:
#x-Liste wird erstellt:
zeit = []
for it in range(500,2001):
    zeit.append(it)

# Listen werden geplottet    
plt.plot(zeit,hasen_liste[500:2001], color = "gray", lw = 2)
plt.plot(zeit,luchs_liste[500:2001], color = "brown", lw = 2)
Out[37]:
[<matplotlib.lines.Line2D at 0x23811ef0b38>]

Wir können so natürlich auch andere Einheiten für die Achse wählen. Wollen wir zum Beispiel Jahre anstelle von Tagen haben, können wir die einzelnen Einträge einfach durch 365.25 dividieren:

In [38]:
#x-Liste wird erstellt:
zeit = []
for it in range(500,2001):
    zeit.append(it/365.25)

# Listen werden geplottet    
plt.plot(zeit,hasen_liste[500:2001], color = "gray", lw = 2)
plt.plot(zeit,luchs_liste[500:2001], color = "brown", lw = 2)
Out[38]:
[<matplotlib.lines.Line2D at 0x23811f49198>]

Zusammenfassung

If-Else Abfragen

Die If-Else-Abfrage ist eine Erweiterung der If-Abfrage. Sie erweitert diese Struktur um zusätzliche Befehle, die ausgeführt werden sollen, wenn die Bedingung nicht wahr ist. Sie hat den Aufbau:

if BEDINGUNG:
      BEFEHL WENN WAHR
else:
      BEFEHL WENN FALSCH

and / or

Mit and und or kann man mehrere Bedingungen miteinander verknüpfen, entweder so, dass alle Bedingungen erfüllt sein müssen, oder so, dass nur eine erfüllt werden muss.

  • wahr and wahr = wahr
  • wahr and falsch = falsch
  • wahr or wahr = wahr
  • wahr or falsch = wahr
  • falsch or falsch = falsch

Auswählen von Teilen einer Liste

Wenn wir mehrere Elemente einer Liste auswählen möchten, können wir

listenname[a:b]

verwenden, wobei a das erste Element ist, das wir haben möchten und b das erste, das nicht mehr Teil der Auswahl sein soll. Wenn wir a frei lassen, wird beim Element mit Index 0 begonnen, wenn wir b frei lassen wird bis zum letzten Element ausgewählt. Der Index -1 meint immer das letze Element einer Liste, -2 das vorletzte und so weiter.

x-Werte im Plotbefehl

Den Befehl plot kann man auf mehrere Arten verwenden. Mit einem Argument wird die Liste als y-Werte interpretiert:

plt.plot(y_werte)

Möchten wir bestimmte x-Werte, können wir auch eine zweite Liste übergeben. Achtung, dann ist die erste Liste x und die zweite Liste y:

plt.plot(x_werte,y_werte)

Kapitel 5 - Gekoppelte Differentialgleichungen: Räuber-Beute-Systeme

Wir haben im vorherigen Kapitel dieses Skriptums bereits die Entwicklung zweier Tierpopulationen betrachtet, deren Wachstum voneinander abhängig war. Solche Abhängigkeiten und Beinflussungen unterschiedlicher Dynamiken sind gewissermaßen der Normalfall in den Systemen, die für die Systemwissenschaften interessant sind. Der Systembegriff geht ja eben davon aus, dass interagierende, also sich wechselseitig beeinflussende Dynamiken in ihrem Zusammenwirken etwas generieren, das ohne dieses Zusammenwirken nicht, oder zumindest nicht so, beobachtet werden kann.

In Bezug auf diese sich wechselseitig beeinflussenden Dynamiken spricht man von gekoppelten Dynamiken. Und die Mathematik kennt zu ihrer Analyse die Methode der gekoppelten Differentialgleichungen. Das systemwissenschaftliche Standardbeispiel für solche gekoppelten Differentialgleichungssysteme sind Räuber-Beute-Systeme, für die es in der Regel allerdings keinen analytischen (rein mathematischen) Lösungsweg gibt. Im Folgenden wollen wir deshalb ein historisches Beispiel eines solchen Räuber-Beute-Systems mit Hilfe von Python simulieren.

Beginnen wir ähnlich, wie wir in einem früheren Kapitel Hasen und Luchse simuliert haben. Innerhalb einer For-Schleife, wächste die Population von Hasen und Luchsen exponentiell:

In [1]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01  # Wachstumsrate, wie schnell vermehren sich die Hasen


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Luchse


for it in range(365): # Schleife über 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum 
    # population = population + wachstumrate * population
    hasen =  hasen + hasen_wachstum * hasen  
    luchse = luchse + luchs_wachstum * luchse 
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[1]:
[<matplotlib.lines.Line2D at 0x26d15b5bc18>]

Nun müssen wir auch noch den Term in die Gleichung einbauen, der beschreibt, dass Tiere sterben. Früher haben wir hierfür einen fixen Faktor angenommen, den wir dann mit einigen If-Abfragen daran angepasst haben, ob es gerade viel oder wenig Nahrungsangebot bzw. viel oder wenig Jäger gab. Nun möchten wir aber eine elegantere Lösung wählen.

Wir wollen folgende Informationen in die Gleichung einbauen:

Wenn es viele Hasen gibt, können sich die Luchse schneller vermehren.

Wenn es viele Luchse gibt, sterben mehr Hasen.

Wie bauen wir das nun mathematisch ein? Früher hatten wir

$$\Delta\ Hasen = a * Hasen - {b} * Hasen $$ $$\Delta\ Luchse = {c} * Luchse - d * Luchse $$

Nun möchten wir diese zusätzliche Abhängigkeit einbauen:

$$\Delta\ Hasen = a * Hasen - \color{darkred}{ \tilde b * Hasen * Luchse}$$ $$\Delta\ Luchse = \color{darkred}{ \tilde c * Luchse * Hasen} - d * Luchse $$

Wenn wir unsere Gleichung so anpassen, wird in jedem Zeitschritt die aktuelle Population der jeweils anderen Tierart mitberücksichtigt, und wir benötigen keine If-Abfragen, die uns die Parameter a, b, c und d anpassen.

In [36]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005  # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01


for it in range(10 * 365): # Schleife über 10 * 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    hasen =  hasen + hasen_wachstum * hasen - hasen_sterben * hasen * luchse  
    luchse = luchse + luchs_wachstum * luchse * hasen - luchs_sterben * luchse
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[36]:
[<matplotlib.lines.Line2D at 0x26d250d65f8>]

Einen kleinen Fehler machen wir hierbei aber noch: Wenn wir die neue Hasenpopulation ausrechnen, verwenden wir korrekterweise die Hasen und Luchspopulation aus dem letzten Zeitschritt. Dann überschreiben wir die Hasenzahl. Wenn wir dann also in der nächsten Zeile die Luchse berechnen, verwenden wir schon die neue Zahl der Hasen. Hier sollten wir eigentlich die Zahl vom Vortag benutzen. Also ersetzen wir hasen durch hasen_liste[it-1] und luchse durch luchs_liste[it-1].

Zusätzlich müssen wir mit unserer Schleife nicht mehr bei 0, sondern bei 1 beginnen.

In [40]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005  # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01


for it in range(1,10 * 365): # Schleife über 10 * 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    hasen =  hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]  
    luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)

# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Out[40]:
[<matplotlib.lines.Line2D at 0x26d15b5b588>]

Diese Berechnung ist nun tagesgenau. Wenn wir genauere Ergebnisse haben möchten, könnten wir unsere Zeitschritte verkürzen, also auf Stunden, Minuten oder Sekunden. Wir könnten auch unendlich kleine Zeitschritte machen. Die Differenzengleichung würde dann zu einer Differentialgleichung werden.

Dieses System aus gekoppelten Differentialgleichungen ist allgemein bekannt. Es heißt Lotka-Volterra-System:

$$\frac{dH}{dt}=\alpha*H-\beta*H*L$$

$$\frac{dL}{dt}=\gamma*L*H-\phi*L$$

Fürs erste sind wir aber mit der Rechengenauigkeit unserer Differenzengleichungen zufrieden. Für ein Lotka-Volterra-System gibt es 3 Regeln. Wir können im Folgenden überprüfen, ob diese Regeln auch für unser System aus Differenzengleichungen gelten.

Lotka-Volterra-Regel 1

Die Räuber- und die Beute-Populationen oszillieren periodisch und zueinander zeitlich versetzt. Die Räuber-Population läuft der Beute-Population zeitlich etwas hinterher.

Um das zu überprüfen, müssen wir unsere grafische Darstellung ein wenig verbessern. Da es sehr viel weniger Luchse als Hasen gibt, ist es schwer die Werte zu vergleichen. Besser wäre es, wenn wir nicht die absolute Population, sondern die relative Population, also die aktuelle Population dividiert durch die Startpopulation darstellen.

Was wir also machen möchten, ist jeden Eintrag der Populationslisten durch 1000 bzw. durch 100 zu dividieren. Mit Listen funktioniert das leider nicht so einfach, wohl aber mit numpy-arrays. Wir konvertieren unsere Listen also zu numpy-arrays, die wir dann ohne Probleme dividieren können:

In [44]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005  # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01


for it in range(1,10 * 365): # Schleife über 10 * 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    hasen =  hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]  
    luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)
    
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000

luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_array, color = "gray", lw = 2)
plt.plot(luchs_array, color = "brown", lw = 2)
Out[44]:
[<matplotlib.lines.Line2D at 0x26d2543dfd0>]

Hier sehen wir eindeutig: Es gibt Oszillationen und das Maximum der Jägerpopulation is immer etwas nach dem Maximum der Beutepopulation. Die erste Lotka-Volterra-Regel wird also korrekt in unserem Modell wiedergegeben.

Lotka-Volterra-Regel 2

Die durchschnittlichen Größen der beiden Populationen bleiben über längere Zeiträume konstant, auch wenn die Maxima und Minima sehr unterschiedlich sind.

Um das zu überprüfen müssen wir längere Zeiträume betrachten. Innerhalb der ersten zwar Oszillationen sieht es zwar so aus, als würde die durchschnittliche Population konstant bleiben, wir sollten usn aber sicherheitshalber längere Zeiträume ansehen. Erhöhen wir den Simulationszeitraum auf 100 Jahre.

In [49]:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np

# Startbedingungen für Hasen
hasen = 1000           # wie viele Hasen sind am Anfang vorhanden
hasen_liste = []       # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005  # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100


# Startbedingungen für Luchse
luchse = 100           # wie viele Luchse sind am Anfang vorhanden
luchs_liste = []       # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01


for it in range(1,100 * 365): # Schleife über 10 * 365 Tage   
    # Populuationsgleichungen:
    # population = population + wachstum - sterben
    hasen =  hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]  
    luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
      
    # je aktuelle Population wird an die Listen angefügt
    hasen_liste.append(hasen)
    luchs_liste.append(luchse)
    
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000

luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
# Listen werden geplottet (lw gibt die Stärke der Linien an)    
plt.plot(hasen_array, color = "gray", lw = 2)
plt.plot(luchs_array, color = "brown", lw = 2)
Out[49]:
[<matplotlib.lines.Line2D at 0x26d253ed208>]

Hier sehen wir sehr deutlich, dass das Maximum immer größer wird. Die zweite Lotka-Volterra-Regel wird in unserem Modell also verletzt. Woran liegt das? Der erste Verdacht ist unsere Vereinfachung, dass wir anstelle einer Differentialgleichung nur eine tagesgenaue Differentialgleichung benutzen. Um festzustellen ob wirklich das für dieses Verhalten verantwortlich ist, müssen wir eine bessere Methode finden, um längere Zeitentwicklungen grafisch darzustellen. Wir werden im Folgenden die sogenannte Phasenraumdarstellung benutzen.

Phasenraumdarstellung

In unseren üblichen Grafiken tragen wir immer die Zeit auf der einen Achse und die Populationen auf der anderen Achse auf. Das ist aber nicht die einzige Möglichkeit, die wir haben. Wir könnten auch die eine Achse für die Jäger und die andere Achse für die Beutetiere verwenden. Dann stellen wir nicht mehr die Zeitentwicklung darn, sondern viel mehr die Population der Jäger und Beute, die uns während der ganzen Zeitentwicklung untergekommen sind.

Sollten wir immer konstante Maxima und Miminma haben, sollte die Phasenraumdarstellung eine geschlossene Form haben, ähnlich einem Kreis. Wenn wir immer größere Werte bekommen, sehen wir eine Spirale. Machen wir nun also zur Probe einen Phasenraumplot:

In [50]:
plt.plot(hasen_array,luchs_array)
plt.title("Phasenraumdarstellung")
plt.xlabel("Beute")
plt.ylabel("Räuber")
Out[50]:
<matplotlib.text.Text at 0x26d26a399b0>