JGU Logo JGU Logo JGU Logo JGU Logo

Institut für Informatik

Markus Blumenstock
Sommersemester 2024

Angewandte Mathematik am Rechner

Übungsblatt 3: Die Entstehung komplexer Struktur (und etwas Mengenlehre)
Letzte Änderung : 17:13 Uhr, 19 June 2024
Abgabetermin : 05. Juni 2024, 12 Uhr
Abnahmetermin : 06. Juni 2024



Aufgabe 3.1: Operationen mit endlichen Mengen

In der Mathematik haben wir es ständig mit Mengen aller Art zu tun, die wir auf verschiedenste Weise konstruieren und kombinieren. Man kommt bei den ganzen verschiedenen Operationen (Potenzmengen, Produktmengen, Potenzen von Mengen bzw. Abbildugen, Relationen zwischen Mengen usw.) durchaus schnell durcheinander. Daher ist es eine gute Übung, die verschiedenen Operationen auf Mengen einmal als Bibliothek in einer Programmiersprache zu implementieren. Man sieht dabei nochmal gut die kombinatorischen und strukturellen Eigenschaften.
In dieser Aufgabe implementieren wir eine (neue; es gibt ja schon eine) Mengenklasse in Python. In unserer Klasse stellen wir dann diverse Operationen zur Verknüpfung von Mengen \(A\) und \(B\) bereit.
Ziel dieser Aufgabe ist es, ein besseres Gefühl dafür zu bekommen, wie die Mathematik strukturiert ist, besonders soll dabei der Begriff der Gleichheit veranschaulicht werden. Auf dem Weg dorthin werden wir die definierten Strukturen auch dafür verwenden, die Kongruenzrechnung zu implementieren oder die Koeffizienten der allgemeinen Binomischen Formel \((a+b)^n\) (Binomialkoeffizienten) zu bestimmen.
Tipp: Viele der Methoden können kurz und kompakt mit den Python-typischen „List Comprehensions“ formuliert werden.
Noch ein Tipp: Die hier definierten Objekte laden zum Ausprobieren ein. In Python können Sie ihre Python-Skripte auch einlesen lassen und dann deren Funktionalität aus einer iteraktiven Konsole heraus verwenden. Falls Sie Python direkt verwenden können Sie dies mit dem Option -i erreichen (z.B. python -i meinSkript.py). In PyCharm können Sie Ihren Code markieren Ctrl+A und dann per „Execute Selection in Console“ in die interaktive Konsole laden.

Aufgabe 3.1.1: Mengenklasse.

[15 Punkte]


Als erstes definieren wir die Mengenklasse selbst (selbstverständlich ohne die Standardklasse set aus Python zu verwenden!).

Aufgabe 3.1.2: Arbeiten mit der Mengenklasse.

[15 Punkte]


Wir benutzen unsere neue Mengenklasse nun, um einige Anwendungen zu realisieren. Wir beginnen mit einem eher abstrakten Gedankenspiel:


Aufgabe 3.1.3: Kartesisches Produkt

[10 Punkte]


Mengen definieren keine Ordnung ihrer Elemente. Um Anordnungen von Elementen, bei denen jede Position eine andere Semantik hat, zu konstruieren, benötigen wir das Konzept des kartesischen Produktes (Menge aller Paare zweier Mengen):

\[A \times B:= \left\{ (a, b) \mid a \in A, b \in B \right\}\]
Nur für Interessenten: Aufgabe 3.1.4: Funktionen.

Mithilfe des kartesischen Produktes können wir nun Funktionen als Teilmengen \(F\subset A\times B\) definieren, bei der die linken Teilelemente eineindeutig sind (das heißt jedem \(a\in A\) wird genau ein \(b\in B\) zugeordnet).
Mathematisch ausgedrückt: \[f:A\rightarrow B, a \mapsto f(a)\].
  • Definieren Sie die Funktionsklasse auf Basis des kartesischen Produktes. Hierzu ist es sinnvoll, eine eigene Unterklasse der Klasse CartesianProduct zu definieren (sofern Sie eine erstellt haben).

    Das ganze Produkt selbst kann dabei keine Funktion darstellen (Warum?).
    Implementieren Sie also die Konstruktion so, dass sie eine Auswahlfunktion übergeben bekommt, die wie bei der subset-Methode auch angibt, wann ein Element Teil der Struktur ist und wann nicht.
    (Auch hier sei die Auswahlfunktion als sprachliches Merkmal zu verstehen als als Funktion. Andererseits würden wir Funktionen über Funktionen definieren. Die „richtige“ Auswahlfunktion ist erst bei unendlichen Mengen nötig, die wir hier jedoch nicht betrachten.)

    Wichtig: Prüfen Sie bei der Konstruktion, ob das resultierende Objekt eine Funktion ist und werfen Sie im Falle, dass sie es nicht ist, eine Fehlermeldung aus.
  • Die Darstellung über eine Teilmenge des kartesischen Produktes ist jedoch nicht sehr bequem in der Handhabung. Überladen Sie für die von Ihnen definierten Klassen daher den call-Operator. Sie können dies erreichen, indem Sie eine Methode __call__(self,a) anfügen, die zu jedem \(a\) das entsprechende \(b\) zurückgibt. Die Funktion können Sie dann wie gewöhnliche Python-Methoden auch aufrufen.
    
    A = Set(["hund","katze","maus"])
    F = ...
    
    # Aufruf (als Werte sind jedoch nur Elemente aus A erlaubt!)
    F(A[1])
    
  • Testen Sie Ihren Code durch Definition und Auswerten:
    • Definieren Sie die Delta-Funktion, die genau für ein ausgewähltes Element 1 ist, und sonst 0.
    • Definieren Sie ein Polynom auf den ganzen Zahlen zwischen -25 und 25.
    • Fügen Sie eine Methode an, die das Plotten Ihrer Funktion ermöglicht. (Die simpelste Art und Weise, die Ihnen dazu einfällt)
    Hinweis: Wenn die Funktionsklasse, wie vorgeschlagen, von Set erbt, können Sie die Funktion selbst als Menge ausgeben lassen.
  • Wie viele Funktionen gibt es, die von einer Menge A auf die andere Menge B abbilden?
    Lösen Sie diese Aufgabe (numerisch) mithilfe der hier definierten Funktionsklassen.
    Überlegen Sie sich theoretisch, wie viele Funktionen es geben muss und überprüfen Sie damit die Python-Lösung.

Aufgabe 3.1.5: (Äquivalenz-)Relationen.

[20 Punkte]


Relationen sind Verallgemeinerungen von Funktionen (bzw. Funktionen sind spezielle Relationen). Eine Funktion ordnet jedem Element einer Definitionsmenge \(A\) ein Element der Wertemenge \(B\) zu. Relationen verallgemeinern dies: Wir können jedem Element aus der Definitionsmenge beliebig viele Elemente der Wertemenge zuordnen (weiterhin kann jedes Element von \(A\) auch vielen verschiedenen Elementen aus \(B\) zugeordnet werden). Anders gesagt: Wir merken uns lediglich für jedes Paar \((a,b), a \in A, b \in B\), ob es in der Relation enthalten ist oder nicht. Relationen sind also Strukturen, die auf dem kartesischen Produkt zweier Mengen agieren (etwa \(A\times B\)).


Wie der Name schon sagt, bringen Sie die Elemente dieser beiden Mengen zu einander in „Relation“:
Die Mengenrelation zwischen den Mengen \(A\) und \(B\) legt für alle Paare \((a,b)\) fest, ob die Elementrelation zwischen Elementen \(a\) und \(b\) entweder besteht oder nicht besteht. Man kann eine Relation also als Teilmenge des kartesischen Produktes definieren, \(R \subseteq A \times B\).
Alternativ kann man dies auch als Funktion \(R: A \times B \rightarrow \{0,1\}\) beschreiben.
Warum ist das so? Erklären Sie dies in der Vorstellung Ihrer Lösung!
(Ganz nebenbei: Durch rekursive Anwendung des kartesischen Produktes, \(A\times (B\times C)\), können auch Relationen auf mehr als zwei Mengen definiert werden; hier schauen wir uns aber nur Relationen zwischen zwei Mengen an.)


Beispiel (einfache Relationen): Sei \(A\) die Menge der in Mainz wohnenden Studenten und \(B\) die Menge der aus Wiesbaden anreisenden Studenten. Wir sagen \(a\in A\) und \(b\in B\) stehen genau dann zu einander in Relation, wenn sie sich kennen.


Axiome für spezielle Typen von Relationen

Relationen helfen uns dabei, komplexere Strukturen zu definieren. Hierzu hat man in der Mathematik Relationen charakterisiert, die in vielen Anwendungen immer wieder auftauchen. Interessant sind dabei oft homogene Relationen \(R\) auf \(A^2 = A\times A\):


Äquivalenzrelation: Ein wichtiges Konzept ist das einer Äquivalenzrelation. Hiermit definieren wir einen vergröberten Gleichheitsoperator „\(=\)“ (auch gerne als „\(\equiv\)“ geschrieben), der die folgenden Eigenschaften erfüllt:


Erfüllt eine Relation diese drei Eigenschaften spricht man von einer Äquivalenzrelation.
Das besondere dabei ist, dass man alle Elemente der Grundmenge so in Teilmengen aufteilen kann, dass alle Elemente jeweils in Relation zu einander stehen, jedoch nicht zu den Elementen der anderen Mengen. Diese Mengen nennen wir (Äquivalenz-)Klassen.
Da die Äquivalenzrelation die Klassenelemente eindeutig zuordnet, stellt man eine Klasse häufig (um Schreibarbeit zu sparen) nur durch einen einzigen (beliebigen, aber eindeutig bestimmten) Repräsentanten dar (siehe Beispiel).
Restklassen werden als Beispiel für Äquivalenzklassen in der nächsten Aufgabe an einem konkreten Beispiel erklärt.



Algorithmische Umsetzung des Findens von Äquivalenzklassen: Programmatisch würde man so vorgehen, dass man mit einem Element startet und es als neue Klasse definiert. Für alle neuen Elemente der Basismenge \(A\) prüft man nun, ob das Element mit dem ersten(!, beliebig aber festen) Element einer der existierenden Klassen in Relation steht. (Warum reicht es aus nur ein Element der Klasse mit dem neuen Element auf Relation zu prüfen?) Falls ja, fügt man das Element der Klasse an, falls es keine passende Klasse gibt, bildet das Element eine neue Klasse.


Ordnungsrelationen auf Mengen (Verallgemeinerung von \(\le\)): Die natürlichen Zahlen sind geordnet, d.h. zu jedem Paar von Zahlen kann man sagen welche Zahl größer (oder gleich) ist. Ordnungsrelationen sind eine (abstrakte) Klasse von Relationen, die einer (bislang ungeordneten) Menge \(A\) eine Reihenfolge gibt. Eine abstrakte Ordnungsrelation muss immer folgende Eigenschaften erfüllen:


Beispiel Beziehungen: Auf der Menge der Personen \(\{\text{Alice},\text{Bob},\text{Charles},\text{Denise}, \text{Eric}\}\) definieren wir eine Relation: X mag Y. (Überlegen Sie sich an dieser Stelle, wer wen mag).


Beispiel Ordnungen: Sei \(N\) eine Menge von Zahlen. Wir definieren die Relation durch \((a,b) \in R_1 \Leftrightarrow a\le b\). Für dieselbe Menge definieren wir zwei weitere Relationen durch \((a,b) \in R_2 \Leftrightarrow a < b\) und \((a,b) \in R_3 \Leftrightarrow a = b\).


Beispiel Restklassen: Sei \(N\) eine Menge von Zahlen und \(m \in N\) eine Zahl (z.B. 5). Wir definieren die Relation durch \((a,b) \in R_1 \Leftrightarrow a - b \text{ ganzzahlig Teilbar durch } m\). Als Repräsentant \(r\) der Restklasse wählen wir stets die kleinste Zahl der Klasse und schreiben die Klasse dann als \([r]\).


Fragen / Aufgaben:


Aufgabe 3.1.6: Restklassenring

[10 Punkte]


Nun können wir uns endlich dem widmen, was wir zu Beginn versprochen haben — der Implementierung der Kongruenzrechnung, basierend auf der von uns implementierten Arithmetik.


Nun zur eigentlichen Aufgabe:


Hinweis: Um besser mit den Äquivalenzklassen arbeiten zu können, empfehlen wir Ihnen eine neue Klasse zu erstellen (z.B. EquivalenceClass), die von Set erbt und lediglich die __str__-Methode überschreibt, um nur einen Repräsentanten auszugeben statt der ganzen Menge.
Sofern Sie möchten können sie auch eine statische Variable einführen, die die Ausgabe steuert, um bei Bedarf doch alle Elemente zu inspizieren.

Aufgabe 3.2: Die Mandelbrotmenge („Apfelmännchen“)

Abbildung 3.1: Verschiedene Zoom-Stufen des „Apfelmännchens“. (In den beiden Reihen sind jeweils andere Colorschemes in Verwendung.)


In dieser Aufgabe wollen wir das berühmte „Apfelmännchen“ implementieren.
Dies ist ein Beispiel dafür, wie mit wenigen, sehr einfachen arithmetischen Operationen eine sehr vielfältige und komplexe Struktur entstehen kann. Man nennt dieses Phänomen im Allgemeinen Fall emergente Komplexität — es ist nicht nötig besonders komplexe Regeln zu definieren, um mit diesen sehr komplexe Resultate zu erhalten. Die Turing-mächtige Maschine ist der Extremfall — sie ist universell und kann, mit einem geeigneten Programm, alles erzeugen.
Mandelbrotmengen sind keine Turing-Maschinen; dennoch entsteht eine sehr interessante Struktur aus einer harmlos und simpel erscheinenden Rechenvorschrift.


Wiederholung der komplexen Zahlen:

Bevor wir uns der Mandelbrotmenge selbst widmen, sollen an dieser Stelle nochmals die komplexen Zahlen wiederholt werden.


Die komplexen Zahlen sind die Menge \[\mathbb{C} := \mathbb{R} + i\cdot \mathbb{R} := \{a+ib \,|\, a,b\in \mathbb{R}\},\] die mit der folgenden Struktur versehen sind: Eine komplexe Zahl \(z = a+ib\) besteht damit aus zwei reellen Zahlen \(a,b\) und der imaginären Einheit \(i\), die die Eigenschaft \(i^2=-1\) besitzt. Aus dieser Eigenschaft und der herkömmlichen Multiplikation und Addition auf reellen Zahlen folgen die Rechenregeln: \[\begin{aligned} \text{Addition:}\quad &(a+ib)+(c+id) &= \quad &\, (a+c) + i(b+d)\\ \text{Multiplikation:}\quad &(a+ib)\cdot(c+id) &= \quad &\, (ac-bd) + i(bc+ad)\\ \text{Absolutbetrag (entspr. Länge im } \mathbb{R}^2\text{):} \quad &|(a+ib)| &= \quad &\, \sqrt{a^2 + b^2} \end{aligned}\] Komplexe Zahlen sind glücklicherweise bereits fest in Python und Numpy implementiert. Es unterscheidet sich lediglich das Symbol der komplexen Einheit \(i\): Für Python ist diese nämlich durch j gekennzeichnet.

Mandelbrotmenge:

Wir beginnen mit einer beliebigen komplexen Zahl \(c\in \mathbb{C}\) und definieren die Folge

\[\begin{aligned} z_0 &= 0\\ z_{n+1} &= z_n^2 + c, \quad \text{ für } n\ge 0. \end{aligned}\]

Die Mandelbrotmenge \(M\) ist die Menge, für die der Absolutbetrag dieser Folge nach oben hin beschränkt ist, mathematisch ausgedrückt:

\[c \in M \Leftrightarrow \limsup_{n\rightarrow \infty} |z_n| \le 2. \]

Aufgabe 3.2.1: Die Mandelbrotmenge (aka: das Apfelmännchen).

[30 Punkte]


Nun werden wir die Mandelbrotmenge darstellen.
Wichtig: An dieser Stelle der Hinweis, dass die Berechnung der Darstellung mit reinem Python nur langsam von statten gehen würde.
Daher sind die Aufgaben so formuliert, dass Sie Numpy verwenden sollten, damit die „Interaktion“ noch Sinn macht. Andernfalls macht der Teil mit dem „Zoomen“ wenig Spaß.
Hinweis: Verwenden Sie dennoch eine geringere Auflösung, damit die Berechnungen noch schneller sind.



Beispielcode: Farbschemata mit Matplotlib

# importieren Sie zu Beginn Ihres Codes das Matplotlib-Paket
import matplotlib.pyplot as plt


# ... in ihrer update- bzw. draw-Methode ...


# Normalisieren von count (Werte zwischen 0 und 1)
max = np.max(count)
min = np.min(count)
if max==0:
  max = 1
count = (count - min) / max

# für große n können die Farbübergänge zu stark sein. Dies kann z.B. mit
# count = count**0.25
# umgewichtet werden

# Farbschema anwenden
# (eine Auswahl finden Sie unter: http://matplotlib.org/users/colormaps.html )
colormap = plt.cm.RdYlBu # z.B. in Ihrer GUI Auswählbar
colfloat = colormap(count)

# Qt erwartet Werte zwischen 0 und 255 für RGB-Bilder
colint = np.asarray(colfloat*255, dtype=np.uint8)

# Konvertieren in ein QImage & Anzeigen
img = QImage(colint.data, self.width, self.height, QImage.Format_RGBA8888)
pixmap = QPixmap.fromImage(img)
self.display.setPixmap(pixmap)