deutsch     english     français     Drucken

 

8.7 KOMPLEXE ZAHLEN & FRAKTALE

 

 

EINFÜHRUNG

 

Komplexe Zahlen sind in der Mathematik von grosser Wichtigkeit, da sie die Menge der reellen Zahlen so erweitern, dass viele Sätze einfacher und allgemeiner formuliert werden können. Sie spielen darüber hinaus in Naturwissenschaften und Technik eine wichtige Rolle, insbesondere in der Physik und Elektrotechnik [mehr... Die Quantentheorie spielt sich gänzlich in der Welt der komplexen
Zahlen ab, in der Elektrotechnik wird die Wechselstromlehre durch
komplexe Widerstände (Impedanzen) stark vereinfacht
]. Glücklicherweise sind komplexe Zahlen in Python ein fest eingebauter Datentyp und es stehen die arithmetischen Operatoren für Addition, Subtraktion, Multiplikation und Division zur Verfügung. Zudem gibt es im Modul cmath viele bekannte Funktionen mit komplexen Argumenten.

Komplexe Zahlen können in der Gaussschen Zahlenebene als Zeiger oder als Punkte dargestellt werden. Damit sich auch ein Turtlefenster, ein GPanel und eine Pixelgitter von JGameGrid als Gausssche Zahlenebene auffassen lassen, stehen in den entsprechenden Bibliotheken alle Funktionen mit Koordinatenparametern (x, y) auch direkt für komplexe Zahlen zur Verfügung.

PROGRAMMIERKONZEPTE: Datentyp complex, Konforme Abbildung, Mandelbrot-Fraktal

 

 

GRUNDOPERATIONEN MIT KOMPLEXEN ZAHLEN

 

In Python verwendest du für die imaginäre Einheit an Stelle von i das in der Elektrotechnik übliche Symbol j. Du kannst die komplexe Zahl mit Realteil 2 und Imaginärteil 3 auf mehrere Arten definieren:

z = 2 + 3j   
oder
z = 2 + 3 * 1j      
oder
z  = complex(2, 3)

Verwende für die folgenden Beispiele die praktische TigerJython-Konsole.


Den Real- und Imaginärteil erhältst du mit z.real und z.imag. Beachte, dass es sich nicht um einen Funktionsaufruf handelt, sondern um einen Variablenwert (den du nur lesen kannst). Real- und Imaginärteil sind immer Floats.  

Das Quadrat der imaginären Einheit 1j ist -1. Anders gesagt, ist die imaginäre Einheit 1j gleich der Quadratwurzel aus -1. Um die Quadratwurzel aus komplexen Zahlen zu ziehen, musst du an Stelle von math das Modul cmath importieren.  

Die Standardfunktion abs() liefert nicht nur den Betrag für Integer und Floats, sondern auch für komplexe Zahlen Du kannst für komplexe Zahlen die üblichen Operationszeichen +, -, *, / und den Potenzoperator ** verwenden. Es gelten dieselben Rangordnungen wie für Floats.  


In deinem Programm bildest du Potenzen einer komplexen Zahl z = 0.9 + 0.3j. Diese hat einen Betrag von etwas kleiner als 1. Da bei der Multiplikation von zwei komplexen Zahlen die Beträge multipliziert und die Phasen addiert werden, bewegen sich die Potenzen offenbar auf einer Spirale, die du in einem GPanel sehr einfach einzeichnen kannst. Beachte, dass du das Füllen vor dem Zeichnen des Gitter machen musst, da mit fill() immer geschlossene Gebiete gefüllt werden.

 



from gpanel import *
makeGPanel(-1.2, 1.2, -1.2, 1.2)
title("Complex plane")

z = 0.9 + 0.3j
for n in range(1, 60):
    y = z**n
    draw(y)
fill(0.2, 0, "white", "red")
fill(0.0, 0.2, "white", "green")
drawGrid(-1.0, 1.0, -1.0, 1.0)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

draw(z) bewirkt das gleiche wie draw(z.real, z.imag), ist aber einfacher. Du verwendest GPanel-Koordinaten, die auf allen Seiten 10% grösser als der verwendete Koordinatenbereich ist. In drawGrid() musst du die Koordinaten als Float angeben, damit das Gitter mit Floats angeschrieben wird.

 

 

KONFORME ABBILDUNGEN

 

Bei zweidimensionalen Abbildungen wird jedem Punkt P(x, y) ein Bildpunkt P'(x', y') zugeordnet.
Du kannst die Punkte auch als komplexe Zahlen auffassen und sagen, dass bei der Abbildung jeder Zahl z ein Funktionswert z' zugeordnet wird und dafür z' = f(z) schreiben.

Du wählst im Folgenden die Funktion z' = f(z) = 1/z (Inversion) und bildest ein rechtwinkliges Koordinatengitter ab.

Dazu wählst du in der Gaussschen Zahlenebene den Bereich von -5 bis 5 und denkst dir 201 Gitterlinien im Abstand von 1 / 20. Die Bilder der horizontalen Linien zeichnest du grün, die der vertikalen Linien rot ein. Es entsteht ein hübsches Bild.

 


from gpanel import *

# function f(z) = 1/z
def f(z):
    if z == 0:
        return 0
    return 1 / z

min_value = -5.0
max_value = 5.0
step = 1 / 20
reStep = complex(step, 0)
imStep = complex(0, step)

makeGPanel(min_value, max_value, min_value, max_value)
title("Conformal mapping for f(z) = 1 / z")
line(min_value, 0, max_value, 0)  # Real axis
line(0, min_value, 0, max_value) # Imaginary axis

# Transform horizontal line per line
setColor("green")
z = complex(min_value, min_value)
while z.imag < max_value:
    z = complex(min_value, z.imag) # left
    move(f(z))
    while z.real < max_value: # move along horz. line
        draw(f(z))
        z = z + reStep
    z = z + imStep

# Transform vertical line per line
setColor("red")
z = complex(min_value, min_value)
while z.real < max_value:
    z = complex(z.real, min_value) # bottom
    move(f(z))
    while z.imag < max_value:  # move along vert. line
        draw(f(z))
        z = z + imStep
    z = z + reStep
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Wie du aus der Grafik entnimmst, schneiden sich die Bilder der Gitterlinien wieder rechtwinklig. Eine Abbildung, bei der der Winkel zwischen sich schneidenden Linien erhalten bleibt, nennt man winkeltreu und man spricht von einer konformen Abbildung [mehr... Man kann natürlich mathematisch exakt beweisen, dass die Inversion eine konforme Abbildung ist] .

 

 

MANDELBROT-FRAKTALE

 


Fraktale Bilder sind sehr typisch und vielen Menschen bekannt. Wahrscheinlich ist dir das Apfelmännchen auch schon begegnet, das du hier selbst programmieren wirst.  Der Algorithmus für viele Fraktale beruht auf komplexen Zahlen und es ist schon deswegen lohnend, sich mit ihnen zu befassen. Als Vater der Fraktalen Geometrie gilt der Mathematiker Benoit Mandelbrot (1924-2010).

  Mandelbrot an der Einführungsvorlesung
der Légion d'honneur (2006) (© Wiki)

Um Mandelbrot-Fraktale zu erstellen, betrachtest du zu der vorgegebenen komplexen Zahl c eine (rekursiv definierte) Folge von komplexen Zahlen nach dem Bildungsgesetz:

z'  =  z2  +  c      mit dem Anfangswert    z0  =  0

Falls der Betrag der Folgenglieder in einem beschränkten Bereich bleibt, also nicht über alle Grenzen wächst, gehört c zur Mandelbrot-Menge.

Um dich mit dem Algorithmus vertraut zu machen,  zeichnest du die Folgenglieder für zwei komplexe Zahlen c1 = 0.35 + 0.35j und c2 = 0.36 +  0.36j. Dabei siehst du sofort, dass c1 zur Mandelbrot-Menge gehört, c2 hingegen nicht.



 

from gpanel import *

def f(z):
    return z * z + c

makeGPanel(-1.2, 1.2, -1.2, 1.2)
title("Mandelbrot iteration")

drawGrid(-1, 1.0, -1, 1.0, 4, 4, "gray")

isMandelbrot = askYesNo("c in Mandelbrot set?")
if isMandelbrot:
    c = 0.35 + 0.35j
    setColor("black")
else:
    c = 0.36+ 0.36j
    setColor("red")

title("Mandelbrot iteration with c = " + str(c))
move(c)
fillCircle(0.03)

z = 0j
while True:
    if z == 0:
        move(z)
    else:
        draw(z)
    z = f(z)
    delay(100)         
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

Um herauszufinden, welche Zahlen eines bestimmten Bereichs der Gaussschen Zahlenebene zur Mandelbrot-Menge gehören, führst du für komplexe Zahlen c in einem bestimmten Gitterbereich die Iteration durch.

Dabei nimmst du stark vereinfachend an, dass c nicht zur Mandelbrot-Menge gehört, wenn der Betrag eines Folgenglieds in den ersten 50 Iterationen grösser als R = 2 wird. Bleibt der Betrag von z bis am Ende der 50 Iterationen kleiner als 2, so so nimmst du an, dass c zur Mandelbrot-Menge gehört und zeichnest dort einen schwarzen Punkt.

 

 


from gpanel import *

def isInSet(c):
    z = 0
    for n in range(maxIterations):
        z = z*z + c
        if abs(z) > R: # diverging
            return False
    return True

maxIterations = 50
R = 2
xmin = -2
xmax = 1
xstep = 0.03
ymin = -1.5
ymax = 1.5
ystep = 0.03

makeGPanel(xmin, xmax, ymin, ymax)
line(xmin, 0, xmax, 0)  # real axis
line(0, ymin, 0, ymax) # imaginary axis
title("Mandelbrot set")
y = ymin
while y <= ymax:
    x = xmin
    while x <= xmax:
        c = x + y*1j
        inSet = isInSet(c)
        if inSet:
            move(c)
            fillCircle(0.01)
        x += xstep
    y += ystep
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

Das Apfelmännchen wird in der Grafik bereits sichtbar. Noch schönere Figuren kannst du zeichnen, wenn du für Zahlen c, die nicht zur Mandelbrot-Menge gehörten, einen Farbpunkt zeichnest, dessen Farbe etwas darüber aussagt, wie schnell die Folge divergiert. Als Mass für die Divergenz wird dabei die Anzahl itCount der Iterationen genommen, bei der der Betrag von z das erste Mal grösser als R = 2 wird.

Für die Zuordnung von itCount zu einer Farbe kannst du in getIterationColor() irgend eine Auswahl treffen, die nach deinem ästhetischen Geschmack ein besonders schönes Fraktal erzeugt.

 

 

 


from gpanel import *

def getIterationColor(it):
    color = makeColor((30 * it) % 256, 
                      (4 * it) % 256, 
                      (255 - (30 * it)) % 256)
    return color
    
def mandelbrot(c):
    z = 0
    for it in range(maxIterations):
        z = z*z + c
        if abs(z) > R: # diverging
            return it
    return maxIterations

maxIterations = 50
R = 2
xmin = -2
xmax = 1
xstep = 0.003
ymin = -1.5
ymax = 1.5
ystep = 0.003

makeGPanel(xmin, xmax, ymin, ymax)
title("Mandelbrot set")
enableRepaint(False)
y = ymin
while y <= ymax:
    x = xmin
    while x <= xmax:
        c = x + y*1j
        itCount = mandelbrot(c)
        if itCount == maxIterations: # inside Mandelbrot set
            setColor("black")
        else: # outside Mandelbrot set
           setColor(getIterationColor(itCount))
        point(c)
        x += xstep
    y += ystep
    repaint()
        
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Um das Zeichnen etwas zu beschleunigen, wird enableRepaint(False) gesetzt und nur am Ende jeder Zeile mit repaint() neu gerendert.

Das Mandelbrot-Fraktal besitzt die bemerkenswerte Eigenschaft, dass bei einer Vergrösserung eines Ausschnitts wieder eine ähnliche Struktur erscheint [mehr... Allerdings ist die Struktur nicht exakt gleich und damit ist das Fraktal nicht strikt selbstähnlich] .

 

 

AUFGABEN

 

1.


Ein hübsches Fraktal kannst du erhalten, wenn du für ein Gitter in der Gaussschen Zahlenebene im Bereich -20 bis 20 nur diejenigen Gitterpunkte z einzeichnest, für welche das gerundete Betragsquadrat eine gerade Zahl ist, also  int(abs(z) * abs(z)) % 2 == 0 gilt. Wähle eine Schrittweite von 0.1.

2.

Untersuche die Abbildungen der Gaussschen Zahlenebene

a)   z' = f(z) = z2

b)   z' = f(z) = a * z   mit komplexem a = 2 + 1j

c)   z' = f(z) = ez

d) z' = f(z) = (1 - z)/ 1 + z (Möbius-Transformation)

Bilde das rechtwinklige Koordinatengitter im Bereich von -5 bis 5 mit einer Schrittweite von 1/10 ab. Beschreibe die Abbildung in Worten und mutmasse, ob sie konform ist.

3.

Zeichne einige Mandelbrot-Fraktal mit anderer Farbzuordnung, beispielsweise:

 Anzahl Iterationen   Farbe
  < 3   dunkelgrau
  < 5   grün
  < 8   rot
  < 10   blau
  < 100   gelb
  sonst   schwarz

 

   

ZUSATZSTOFF


 

WECHSELSTROM UND IMPEDANZ

 

Elektrische Schaltungen für sinusförmige Wechselspannungen und Wechselströmen, die aus passiven Bauelementen (Widerständen, Kondensatoren, Spulen) aufgebaut sind, können wie Gleichstromschaltungen behandelt werden, wenn du für Spannungen, Ströme und Widerstände komplexe Grössen verwendest. Ein allgemeiner komplexer Widerstand heisst auch Impedanz und wird oft mit Z und für rein imaginäre Widerstände mit X bezeichnet. Die Impedanz eines ohmschen Widerstands ist R, einer Spule XL = jωL (L: Induktivität) und eines Kondensators XC = 1 / jωC (C: Kapazität), wobei ω = 2πf (f: Frequenz) ist.

Eine komplexe Wechselspannung u = u(t) läuft in der Gaussschen Zahlenebene gleichförmig auf einem Kreis. Wird sie an eine Impedanz Z angelegt, so fliesst der Strom i(t) und nach dem Ohmschen Gesetz gilt u = Z * i.  Da bei der Multiplikation von komplexen Zahlen die Phasen addiert und die Beträgt multipliziert werden, läuft u um die Phase von Z phasenverschoben vor i

phase(u) = phase(Z) + phase(i)

Also läuft i auch auf einem Kreis. Für die Beträge (Amplituden) gilt:

| u | = | Z | * | i |

In deinem Programm stellst du diese Beziehungen in der Gaussschen Zahlenebene anschaulich dar, wobei du von den Werten  

| u | = 5V und Z = 2 +3j

und einer Frequenz von f = 10 Hz ausgehst. Du verwendest für die Animation ein GPanel mit enableRepaint(False), da die Grafik in jedem Zeitschritt vollständig gelöscht, neu aufgebaut und mit repaint() gerendert wird.

 


from gpanel import *
import math

def drawAxis():
   line(min, 0, max, 0)  # real axis
   line(0, min, 0, max) # imaginary axis


def cdraw(z, color, label):
    oldColor = setColor(color)
    line(0j, z)
    fillCircle(0.2)
    z1 = z + 0.5 * z / abs(z) - (0.1 + 0.2j)
    text(z1, label)
    setColor(oldColor)

min = -10
max = 10
dt = 0.001

makeGPanel(min, max, min, max)
enableRepaint(False)
bgColor("gray")
title("Complex voltages and currents")

f = 10 # Frequency
omega = 2 * math.pi * f

t = 0
uA = 5
Z = 2 + 3j

while True:
    u = uA * (math.cos(omega * t) + 1j * math.sin(omega * t))
    i = u / Z
    clear()
    drawAxis()
    cdraw(u, "green", "U")
    cdraw(i, "red", "I")
    cdraw(Z, "blue", "Z")
    repaint()
    t += dt
    delay(100)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Elektrische Schaltungen mit passiven Bauelementen lassen wie Gleichstromschaltungen behandeln, wenn man Spannung, Strom und Widerstand als komplexe Zahlen auffasst.

 
Du wendest diese Erkenntnis auf eine einfache Schaltung an, die lediglich aus einem Widerstand und einem Kondensator besteht. Dabei interessierst du dich für die Ausgangsspannung u1 in Abhängigkeit von der Frequenz f, wobei du die Eingangsspannung uo, sowie R und C als gegeben betrachtest.
 

Die Berechnung ist einfach: Für die Serieschaltung von R und C ergibt sich die Impedanz Z = R + XC und damit der Strom i = uo / Z, also wiederum mit dem Ohmschen Gesetz die Ausgangspannung

  u1 = Xc * i = (  Xc  )/ R + Xc * u0 =  oder   u1 = v * u0   mit   v = (  Xc  )/ R + Xc  


v nennt man den komplexen Verstärkungsfaktor. Du kannst ihn zur Veranschaulichung in der komplexen Ebene für verschiedene Werte von f auftragen und siehst, dass der Betrag vom Wert 1 bei der Frequenz 0 mit zunehmender Frequenz abnimmt. Tiefe Frequenzen werden also gut übertragen, hohe hingegen schlecht. Man nennt diese Schaltung daher einen Tiefpass.

 


from gpanel import *
from math import pi

def drawAxis():
   line(-1, 0, 1, 0)  # Real axis
   line(0, -1, 0, 1) # Imaginary axis

makeGPanel(-1.2, 1.2, -1.2, 1.2)
drawGrid(-1.0, 1.0, -1.0, 1.0, "gray")
setColor("black")
drawAxis()
title("Complex gain factor – low pass")

R = 10
C = 0.001
def v(f):
    if f == 0:
        return 1 + 0j
    omega = 2 * pi * f
    XC = 1 / (1j * omega * C)
    return XC / (R + XC)

f = 0 # Frequency
while f <= 100:
    if f == 0:
        move(v(f))
    else:
        draw(v(f))
    if f % 10 == 0:
        text(str(f)) 
    f += 1
    delay(10)
    
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Im Bode-Plot wird der Verstärkungsfaktor nach Betrag und Phase aufgeteilt und in Funktion der Frequenz aufgetragen (üblich sind allerdings logarithmische Skalen).



 

from gpanel import *
import math
import cmath

R = 10
C = 0.001

def v(f):
    if f == 0:
        return 1 + 0j
    omega = 2 * math.pi * f
    XC = 1 / (1j * omega * C)
    return XC / (R + XC)

p1 = GPanel(-10, 110, -0.1, 1.1)
drawPanelGrid(p1, 0, 100, 0, 1.0, "gray")
p1.title("Bode Plot - Low Pass, Gain")
p1.setColor("blue")
f = 0
while f <= 100:
    if f == 0:
        p1.move(f, abs(v(f)))
    else:
        p1.draw(f, abs(v(f)))
    f += 1

p2 = GPanel(-10, 110, 9, -99)
drawPanelGrid(p2, 0, 100, 0, -90, 10, 9, "gray")
p2.title("Bode Plot - Low Pass, Phase")
p2.setColor("red")
f = 0
while f <= 100:
    if f == 0:
        p2.move(f, math.degrees(cmath.phase(v(f))))
    else:
        p2.draw(f, math.degrees(cmath.phase(v(f))))
    f += 1    
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Im Bode-Plot wird nochmals besonders deutlich, dass die vorliegende Schaltung tiefe Frequenzen gut und hohe Frequenzen schlecht überträgt. Zwischen Eingang- und Ausgangssignal besteht zudem eine Phasenverschiebung im Bereich 0 bis -90 Grad. Man sagt, dass die Ausgangsspannung der Eingangsspannung "nachhinkt" bzw. dass die Eingangsspannung der Ausgangsspannung "vorausläuft".

 

 

AUFGABEN

 

1.


Die Frequenz

  fc = (   1   )/ 2 π R C  

heisst Grenzfrequenz (cutoff frequency). Zeige für den RC-Tiefpass mit R = 10 Ohm und C = 0.001 F dass bei dieser Frequenz der Betrag des Verstärkungsfaktors
1 / √2 ist.

2.

Oft gibt man den Betrag des Verstärkungsfaktors in Dezibel (dB) an. Man definiert dB = 20 log |v| (Zehnerlogarithmus). Zeichne für den RC-Tiefpass mit R = 10 Ohm und C = 0.001 F das Bode-Diagramm mit einer dB-Skala bis -100 dB und einer logarithmischen Frequenzskala im Bereich 1Hz..100 kHz.

Bestätige mit der grafischen Darstellung, dass für höhere Frequenzen der Abfall 20 dB/Frequenzdekade beträgt.

3.

Die folgende Schaltung ist ein Hochpassfilter (R = 10 Ohm, C = 0.001 F).

Zeichne wie in Aufgabe 2 den Bode-Plot für den Verstärkungsfaktor und diskutiere das Frequenzverhalten.