Peter Hrenka, TÜBIX 2015
Repository auf github
IPython ist eine interaktive Shell für Python. Startet man IPython mit
> ipython notebook
wird ein lokaler Webserver gestartet, so daß man mit einem Browser neue Notebooks erstellen und editieren kann.
Es wird das asynchrone Python-Webserver tornado verwendet, der eine Websocket-Verbindung mit dem Webbrowser herstellt.
Für die Darstellung mathematischer Formeln wird MathJax verwendet, welches einen nützlichen Teil der LaTeX-Syntax versteht.
Das Notebook wird über ipython zu einem Kernel
verbunden, der in einem separaten laufenden Prozess läuft und die Python-Befehle ausführt. Es sind auch Kernel für Julia, R und Clang und viele andere vorhanden.
Ein Notebook besteht aus mehreren "Zellen" (Cells) die Python-Code oder Markdown-formatierten Text enthalten können.
In [2]:
import numpy as np
# from numpy import *
print(np.__version__)
np
Out[2]:
Code-Zellen enthalten eine auch mehrzeilige Eingabe.
Ausgeführt wird der Code in der Zelle mit CTRL + RETURN
.
Eine neue Zelle erhält man mit ALT + RETURN
.
Eventuelle Ausgaben auf sys.stdout
oder sys.stderr
erscheinen direkt nach der Eingabebereich,
und der letzte Ausdruck wird im gesonderten Ausgabebereich angezeigt.
Wie vi
unterscheidet ipython
einen Kommando- und Edit-Modus:
dd
die aktelle Zelle löschenRETURN
oder Maus-Doppelklick kommt man in den Edit-Modus, den man mit ESC
oder CTRL + RETURN
verlassen kann
In [3]:
# Interaktive Hilfe
np?
numpy
ist eine Python-Bibliothek die effiziente Arrays und darauf operierende Funktionen, die in C, C++ oder FORTRAN implementiert sind, in Python verfügbar macht. Sie steht unter der BSD-Lizenz. Mittlerweile ist sie (konzeptionell) ein Bestandteil von SciPy, kann aber meist separat installiert werden.
numpy
ist recht umfangreich und leider nicht sehr gut modularisiert, so dass man sich beim ersten Start nicht über einige Gedenksekunden wundern sollte.
array
Das zentrale Objekt in numpy
ist das array
(in der Dokumentation auch oft noch ndarray
genannt).
Es ist ein mehrdimensionaler Container für Elemente des gleichen Datentyps.
In [4]:
np.array?
In [5]:
a = np.array([1,2,3,4], dtype=np.float32)
a
Out[5]:
In [6]:
a.shape, a.dtype
Out[6]:
In [7]:
a.flags
Out[7]:
In [8]:
b = a.reshape( (2,2) )
b
Out[8]:
In [9]:
b.flags
Out[9]:
Man hieran erkennen, dass a
eigene Daten besitzt (OWNDATA: True
) und b
nicht.
Das spart natürlich Speicher, kann aber auch leicht zu schwer lokalisierbaren Fehlern führen,
denn bei Änderungen an einem Objekt ändert sich auch das andere.
In [10]:
c = np.array( np.reshape(a, (2,2)) )
c.flags.owndata
Out[10]:
Lineare Bereiche kann man mit linspace(start, stop, num)
erstellen.
Man beachte, dass der Parameter num
einen etwas willkürlichen Standardwert von 50 hat,
und man daher besser immer die gewünsche Anzahl eingeben sollte.
In [11]:
lin = np.linspace(1.0, 42.0, 42)
lin
Out[11]:
Arrays können über eckige Klammern indiziert werden. Pro Dimension kann die Slice-Syntax wie bei Python-Listen verwendet werden, a[start:stop]
oder a[start:stop:step]
. Der Wert dieses Ausdrucks ist wieder ein Array der entsprechenden Dimension.
Solche Ausdrücke können auch das Ziel einer Zuweisung sein.
In [12]:
rect = np.reshape(lin, (6,7))
rect
Out[12]:
In [13]:
# Alle Einträge von Zeile 1
rect[1, :]
Out[13]:
In [14]:
# jeder zweite Eintrag von Spalte 3
rect[::2, 2]
Out[14]:
In [15]:
cuboid = np.reshape(lin, (2,3,7))
cuboid
Out[15]:
Zusätzlich zu konkreten Zahlen und Slices kann bei arrays
auch die Ellipsis
... verwendet werden (die Offenbar nur für numpy
in die Python-Grammatik eingebaut wurde). Diese entspricht der maximalen möglichen Anzahl an :
-Slices und darf auch nur einmal vorkommen (analog zu ::
in IPv6).
In [16]:
cuboid[1,...]
Out[16]:
In [17]:
cuboid[...,0]
Out[17]:
In [18]:
lin + 1
Out[18]:
Was ist da passiert? Die 1
wurde zu jedem Array-Element hinzuaddiert!
Das Verhalten nennt sich broadcasting
: Numpy versucht, die Dimension(en) der Operanden aneinander anzugleichen.
In diesem Fall wird die 1
als array aufgefasst und die Dimension auf 42
erweitert.
In [19]:
np.broadcast(lin, 1).shape
Out[19]:
In [20]:
lin*lin
Out[20]:
In [21]:
np.sin(lin)
Out[21]:
Im Regelfall sind alle Operationen in arrays
komponentenweise.
In [22]:
# Schachbrett
chess = np.zeros((10,10))
chess
Out[22]:
Betrachte die Folgen $z_{n+1} = z_n^2 + c$ für alle $c\in \mathbb{C}$, $-2 < \textrm{Re } c < 1$, $-1 < \textrm{Im } c < 1$
Die 'Mandelbrot-Menge' ist die Menge aller $c$, für die $z_n$ beschränkt bleibt.
Eine Python-Funktion, welche die Folge berechnet, kann man so schreiben:
In [23]:
def mandel_series(c, numIter):
series = np.zeros(shape=(numIter,), dtype=np.complex128)
z = 0+0j
for i in range(numIter):
z = z**2 + c
series[i] = z
return series
mandel_series(c=0.5+0.4j, numIter=10) # numIter=20
Out[23]:
Das funktioniert, ist aber wenig anschaulich.
Im ipython
-Umfeld ist es sinnvoll, matplotlib
über die zugehörige Direktive einzubinden
und einige Abkürzungssymbole zu definieren.
Matplotlib ist eine Python-Bibliothek, die unter einer BSD-artigen Lizenz steht und eine Reihe von Backends (wxPython, GTK+, Qt, …) unterstützt. Sie bietet eine ähnliche Funktionalität wie Gnuplot an, verwendet aber Python-Syntax und hat eine Reihe von Abhängigkeiten, zuvorderst numpy
.
In [24]:
%matplotlib inline
%config InlineBackend.figure_format = 'png' # für github viewer, 'svg' hübscher für Interaktion
import matplotlib as mp
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = 8,6 # Größe in Inches (!!!)
Die Funktion plt.plot
generiert einen Plot, der direkt im Notebook angezeigt wird.
In [25]:
s1 = mandel_series(0.2+0.5j, 100)
plt.plot(s1.real, s1.imag)
plt.show()
In [26]:
plt.plot(np.abs(s1));
In [27]:
s1 = mandel_series(0.2+0.5j, 100)
plt.figure(figsize=(8,4))
plt.subplot(1,2,1) # nrows, ncols, plot_number
plt.title("Folge der $z_n$")
plt.plot(s1.real, s1.imag)
plt.xlabel("$\mathrm{Re} (z)$")
plt.ylabel("$\mathrm{Im} (z)$")
plt.subplot(1,2,2)
plt.title("Betrag von $z_n$")
plt.plot(np.abs(s1))
plt.xlabel("Iteration")
plt.ylabel("$\mathrm{abs} (z)$")
plt.show()
In [28]:
s1 = mandel_series(0.37-0.3j, 100) # oder vielleicht 0.39-0.3j ?
plt.plot(s1.real, s1.imag)
plt.show()
Damit können wir individuell feststellen, welche $c$s zur Mandelbrot-Menge gehören.
Aber vielleicht erkennt man ja ein Muster, wenn man mehrere $c$ gleichzeitig berechnet…
In [29]:
c = np.linspace(-2, 1, 800)+np.linspace(-1, 1, 600).reshape(600,1)*(0+1j)
c.shape, c[0,0], c[599,799]
Out[29]:
In [30]:
def mandel(c):
z = np.zeros_like(c)
for i in range(10):
z = z**2 + c
return z
z = mandel(c)
z[0,0]
Out[30]:
In [31]:
%timeit mandel(c)
In [32]:
(np.abs(z)<100.0)
Out[32]:
In [33]:
ax = plt.imshow(np.log(abs(z)), extent=(-2,1,-1,1));
plt.colorbar(ax)
plt.show()
In [34]:
plt.imshow(np.angle(z), extent=(-2,1,-1,1));
In [35]:
def mandel2(c, numIter=10, maxabs=5):
z = c
num = np.zeros_like(c, dtype=np.int16)
for i in range(numIter):
mask = (abs(z)<maxabs)
z = mask*(z**2 + c)
num += (abs(z)<maxabs)
return z, num
In [36]:
z, num = mandel2(c, 100, 5)
plt.figure(figsize=(12,9))
plt.imshow(num, extent=(-2,1,-1,1));
In [37]:
plt.imshow(num, extent=(-2,1,-1,1));
plt.axhline(0.2)
plt.axvline(-0.75)
plt.show()
In [ ]:
In [38]:
import sympy as sp
sp.init_printing()
a, b, x, y = sp.symbols("a b x y")
a, b, x, y
Out[38]:
Die Variablen $a$, $b$, $x$ und $y$ sind nun symbolische Werte, mit denen man in Python-Syntax Ausdrücke und Gleichungen eingeben kann:
In [39]:
greekSyms = sp.symbols("alpha beta gamma delta lamda Delta Sigma")
greekSyms
Out[39]:
In [40]:
alpha, beta, gamma = greekSyms[:3]
In [41]:
f, g, h = sp.symbols("f g h", cls=sp.Function)
type(f)
Out[41]:
In [42]:
a+b, sp.diff(f(alpha)), sp.sqrt(a**2+b/gamma), sp.Eq(x+y,gamma), sp.Sum(x**a, (a, 1, b)), sp.Integral((1/x)**2,(x,1,sp.oo))
Out[42]:
Für Ausdrücke wie Integrale, die eventuell ausgewertet werden können, kann man mit der Methode .doit()
diese Auswertung anstossen:
In [43]:
intExpr = sp.Integral((1/x)**2,(x,1,sp.oo))
sp.Eq(intExpr, intExpr.doit())
Out[43]:
In [44]:
rootExpr = sp.sqrt(a**2+b/gamma)
sp.Eq(sp.Derivative(rootExpr, a), rootExpr.diff(a))
Out[44]:
Merke: Großgeschriebene Bezeichner (Sum
, Derivative
, Integral
) erhalten die symbolische Operation,
kleingeschriebene (sum
, diff
, integrate
) Verben führen die Operation gleich durch.
In SymPy-Ausdrücken kann man mit sp.subst
konkrete Werte einsetzen:
In [45]:
valDict = dict(a=sp.pi, b=sp.E)
(a+b).subs(valDict)
Out[45]:
Wer es noch konkreter mag, kann evalf
verwenden:
In [46]:
(a+b).subs(valDict).evalf(100)
Out[46]:
In [47]:
binom = (a+b)**5
binom.doit()
Out[47]:
doit()
führt nur Berechnungen aus (wie Summen, Ableitungen und Integrale), für Umformungen stehen unter anderem Folgende Funktionen zur Verfügung:
In [48]:
polynom = sp.expand(binom)
polynom
Out[48]:
In [49]:
sp.factor(polynom)
Out[49]:
In [50]:
sp.tan(x).rewrite(sp.sin)
Out[50]:
Gleichungen kann man mittels solve
nach einer Variable auflösen:
In [51]:
equ = x**2+a*x+b
quad = sp.Eq(equ, 0)
sp.solve(quad, x)
Out[51]:
In [52]:
sp.sin(x).series(x,n=10) #.removeO()
Out[52]:
In [53]:
def toLaTeX(symExpr):
return "$"+sp.latex(symExpr)+"$"
domain = np.linspace(-4, 4, 1000)
origFunc = sp.sin(x)
numFunc = sp.lambdify(x, origFunc, "numpy")
plt.plot(domain, numFunc(domain), label=toLaTeX(origFunc))
plt.ylim([-1.0, 1.0])
plt.axvline(x=0, color='black')
plt.axhline(y=0, color='black')
plt.legend()
plt.show()
In [54]:
domain = np.linspace(-4, 4, 1000)
origFunc = sp.sin(x)
numFunc = sp.lambdify(x, origFunc, "numpy")
plt.plot(domain, numFunc(domain), label=toLaTeX(origFunc))
for i in range(11)[3::2]:
symFunc = origFunc.series(x, n=i).removeO()
numFunc = sp.lambdify(x, symFunc, "numpy")
plt.plot(domain, numFunc(domain), label=toLaTeX(symFunc))
plt.ylim([-2.0, 2.0])
plt.axvline(x=0, color='black')
plt.axhline(y=0, color='black')
plt.legend(loc=(0.5,0))
plt.show()
In [ ]:
Die Notebooks werden im im JSON-Format in Dateien mit der Endung .ipynb
gespeichert.
Diese lassen sich per
> ipython nbconvert --to latex|pdf|html
Dateiname.ipynb
nach LaTeX, PDF, HTML und weitere Formate exportieren.
Allerdings wird dafür eine funktionierende LaTeX-Installation sowie pandoc vorausgesetzt.
Bilder und Formeln werden in einer String-Codierung gespeichert, wodurch Notebook-Files schnell anwachsen können. Um die Dateien klein zu halten und reproduzierbare Ergebnisse zu erhalten, kann man vor dem Speichern über Cell -> All Output -> clear
die Ausgaben löschen und nach dem Laden Cell -> Run All
ausführen.