Kurze Einführung in Python

Diese Einführung fokusiert das wichtigste und ersetzt nicht weitere Unterlagen, wie zum Beispiel die auf der einführenden Seite verwiesenen Tutorials.

Ziel der Einführung in die Numerik ist der Fokus auf die

  • mathematischen Ansätze

  • numerische Umsetzung

  • Algorithmik.

Es werden daher so wenig wie nötig Bibliotheksfunktionalität vorgestellt. Diese kann später leicht selber erarbeitet und mit dem vermittelten wissen erfolgreich angewandt werden.

Die beiden wichtigsten Unterschiede zu Matlab:

  • Der Index startet in Python bei 0.

  • Blöcke werden mit „:“ gestartet und mit Space-Zeichen eingerückt (es gibt kein „end“).

Python

  • Die Markierung eines Blocks geschieht durch Einrücken.

  • Index startet bei 0

Listen

Listen erstellen:

[1]:
liste = [1,2,3,4]

oder in einer for-Schlaufe mit if-Abfrage:

[2]:
liste = []
for k in range(5):
    print('k=',k)
    if k < 3:
        liste.append(k)
    else:
        liste.append(-k)
liste
k= 0
k= 1
k= 2
k= 3
k= 4
[2]:
[0, 1, 2, -3, -4]

Ein weiteres Beispiel: es kann auch über die Elemente iteriert werden

[3]:
stringliste = ['erster String', 'zweiter String', 'dritter String']
for s in stringliste:
    print(s)
erster String
zweiter String
dritter String

hat man mehrere Listen, auf welche man zurückgreifen will, so können diese „gezippt“ werden:

[4]:
for s, k in zip(stringliste, liste):
    print('k='+str(k)+'\t'+s)
k=0     erster String
k=1     zweiter String
k=2     dritter String

Die kürzeste Liste definiert die Anzahl Durchgänge. Die Listen sind unterschiedlich lang, wie man im folgenden Tupel sieht.

[5]:
(len(stringliste), len(liste))
[5]:
(3, 5)

Tupel werden gerne für die Rückgabe von mehreren Element von Funktionen benutzt.

Funktionen

Optionale Parameter werden am Schluss der Parameterliste aufgeführt.

[6]:
def meineFunktion(x, n=5):
    y = []
    for k in range(n):
        y.append(x**k)
    return y
[7]:
meineFunktion(2)
[7]:
[1, 2, 4, 8, 16]
[8]:
meineFunktion(2,3)
[8]:
[1, 2, 4]

oder

[9]:
meineFunktion(2,n=4)
[9]:
[1, 2, 4, 8]

Numpy

Wir starten direkt mit NumPy. Der klassische Import (defacto Standard in der Notation ‚np‘) des Moduls geschieht mit Hilfe des Befehls:

[10]:
import numpy as np

Das Rechnen mit Matrizen, Vektoren etc. werden wir mit Hilfe der numpy.ndarray Klasse durchführen. Das Initialisieren geschieht mit Hilfe einer Liste:

Vektoren

[11]:
x = np.array([1,2])
[12]:
type(x)
[12]:
numpy.ndarray

oder evtl. etwas flexibler:

[13]:
x = np.array([i**2 for i in range(5)])
print(x)
[ 0  1  4  9 16]

Elementweise Operationen +, -, *, /, etc. können direkt benutzt werden

[14]:
x*x
[14]:
array([  0,   1,  16,  81, 256])
[15]:
x+x
[15]:
array([ 0,  2,  8, 18, 32])
[16]:
(np.sin(x)+np.exp(x))*x
[16]:
array([0.00000000e+00, 3.55975281e+00, 2.15365390e+02, 7.29314644e+04,
       1.42177764e+08])

Matrizen

Für Matrizen benutzen wir ebenfalls die numpy.ndarray Klasse. Das ist bedeutend flexibler als die abgeleitete Matrix Klasse.

[17]:
A = np.ones((5,5))
[18]:
A
[18]:
array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]])

Elementweise Operationen können wieder direkt durchgeführt werden:

[19]:
5*A
[19]:
array([[5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5.]])
[20]:
np.sin(A)
[20]:
array([[0.84147098, 0.84147098, 0.84147098, 0.84147098, 0.84147098],
       [0.84147098, 0.84147098, 0.84147098, 0.84147098, 0.84147098],
       [0.84147098, 0.84147098, 0.84147098, 0.84147098, 0.84147098],
       [0.84147098, 0.84147098, 0.84147098, 0.84147098, 0.84147098],
       [0.84147098, 0.84147098, 0.84147098, 0.84147098, 0.84147098]])

Der Zugriff auf die Elemente geschieht wie folgt:

[21]:
A[0,0]
[21]:
1.0
[22]:
A[1,3] = 5
[23]:
A
[23]:
array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 5., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]])

Matrix Multiplikation

Zur Erinnerung: x ist ein Zeilenvektor, daher erhalten wir wieder einen Zeilenvektor.

[24]:
A@x
[24]:
array([30., 66., 30., 30., 30.])

äquivalent

[25]:
A.dot(x)
[25]:
array([30., 66., 30., 30., 30.])

oder als Spaltenvektor

[26]:
x1 = x.reshape((5,1))
x1
[26]:
array([[ 0],
       [ 1],
       [ 4],
       [ 9],
       [16]])
[27]:
A@x1
[27]:
array([[30.],
       [66.],
       [30.],
       [30.],
       [30.]])

Mit Hilfe von ‚fancy indexing‘ kann auch Teilmengen eines Arrays zugegriffen werden. Im folgenden Beispiel erstellen wir ein Array mit Zahlen zwischen -2 und 2, berechnen das Quadrat dieser und wählen wie Werte grösser als 1:

[28]:
x = np.linspace(-2,2,50) # 100 Werte zwischen -2 und 2
y = x**2 # Elementweise quadrieren
ind = y>1
ind
[28]:
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True])
[29]:
y[ind]
[29]:
array([4.        , 3.68013328, 3.37359434, 3.08038317, 2.80049979,
       2.53394419, 2.28071637, 2.04081633, 1.81424406, 1.60099958,
       1.40108288, 1.21449396, 1.04123282, 1.04123282, 1.21449396,
       1.40108288, 1.60099958, 1.81424406, 2.04081633, 2.28071637,
       2.53394419, 2.80049979, 3.08038317, 3.37359434, 3.68013328,
       4.        ])

Die zugehörigen \(x\) Werte sind gegeben durch:

[30]:
x[ind]
[30]:
array([-2.        , -1.91836735, -1.83673469, -1.75510204, -1.67346939,
       -1.59183673, -1.51020408, -1.42857143, -1.34693878, -1.26530612,
       -1.18367347, -1.10204082, -1.02040816,  1.02040816,  1.10204082,
        1.18367347,  1.26530612,  1.34693878,  1.42857143,  1.51020408,
        1.59183673,  1.67346939,  1.75510204,  1.83673469,  1.91836735,
        2.        ])

Matplotlib

Die Matplotlib beinhaltet eine sehr ähnliche Notation und Funktionalität wie Matlab. Viele Beispiele sind auf der Website von Matplotlib zu finden.

[31]:
import matplotlib.pyplot as plt

Visualisieren wir obiges Beispiel mit ‚fancy indexing‘:

[32]:
plt.plot(x,y,label='original array')
plt.plot(x[ind],y[ind],'o',label='subset array')
plt.legend()
plt.grid()
plt.show()
../_images/introduction_einfuehrungpython_58_0.png

Viele Methoden aus Matlab sind in NumPy und matplotlib unter gleichem Namen verfügbar.

[33]:
x = np.linspace(0,2*np.pi,200) # erzeugt 200 Punkte im Intervall [0,2pi]
                               # Kommentar mit #-Zeichen
[34]:
plt.plot(x,np.sin(x),label='sin(x)')
plt.plot(x,np.cos(x),label='cos(x)')
plt.legend()
plt.grid()
plt.xlabel('x')
plt.ylabel('y')
plt.show()
../_images/introduction_einfuehrungpython_61_0.png

Performance

Skript Programmiersprachen sind sehr flexibel und beliebt. Es muss jedoch zwingend im Bereich des wissenschaftlichen Rechnens sorgfältig damit umgegangen werden. Loops mit viel Rechenaufwand müssen möglichst mit Hilfe der Bibliotheksfunktionalität und daher dem C++ Code ausgeführt werden.

Beispiel: Berechnung des Skalarprodukts eines grossen Vektors.

[35]:
x = np.linspace(0,1,int(1e8))
[36]:
x.shape
[36]:
(100000000,)

Langsame - verbotene - Version: (in Python noch ineffizienter als matlab!)

[37]:
import time
start = time.time()
sum = 0;
for xi in x:
    sum += xi*xi;
end = time.time()
print('CPU-Time:', end-start,'seconds')
sum
CPU-Time: 19.128199100494385 seconds
[37]:
33333333.499996684

Schnell im C++ Code:

[38]:
x = np.linspace(0,1,int(1e9))
[39]:
start = time.time()
x.dot(x)
end = time.time()
print('CPU-Time:', end-start,'seconds')
CPU-Time: 0.4200723171234131 seconds

Mit %timeit können Performancetests in python relativ einfach umgesetzt werden. Eine einmalige Durchführung bei kurzen Rechenzeiten liefert mehr die Interrupt Zeit als CPU-Zeit.

[40]:
%timeit x.dot(x)
409 ms ± 3.73 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)