Processing math: 100%

Praktikum zu Vorlesung Modellreduktion parametrisierter Systeme

Mario Ohlberger, Felix Schindler

Blatt 00, 03.04.2019

Aufgabe 0

(i) einmalige Einrichtung

Dazu müssen auf Ihrem Rechner die Befehle python3 und virtualenv verfügbar sein. Das können Sie überprüfen, indem Sie ein Terminal starten (unter Ubuntu: Super/Windows Taste, terminal eingeben, Return) und die folgenden Befehle eingeben:

python3 --version
virtualenv --version

Beide Befehle sollten Ihnen die jeweilige Version des Programms anzeigen (und keine Fehlermeldung). Zur Einrichtung gehen Sie folgendermaßen vor:

  • Ins HOME Verzeichnis wechseln und einen Projekt-Ordner anlegen
cd      # ins HOME Verzeichnis wechseln
pwd     # Ort des aktuellen Verzeichnises anzeigen
ls -lh  # Inhalt des aktuellen Verzeichnisses anzeigen
mkdir vorlesung_modellreduktion # ein neues Verzeichnis erstellen
cd vorlesung_modellreduktion    # in dieses Verzeichnis wechseln
pwd
ls -lh
  • eine virtuelle Umgebung für die Programmierung mit Python schaffen
virtualenv --python=python3 python_umgebung_praktikum_modellreduktion
ls -lh
  • die virtuelle Umgebung aktivieren
source python_umgebung_praktikum_modellreduktion/bin/activate
  • benötigte Software in der virtuellen Umgebung installieren
pip install --upgrade pip
pip install pymor[full]
pip install notebook
du -sh python_umgebung_praktikum_modellreduktion

Dabei installiert pip install pymor[full] das neueste Release von pyMOR (momentan 0.5.2). Mehr dazu finden Sie auf https://pymor.org (gehen Sie dort auf View on Github und wählen anstelle von branch: master links oben Tags: 0.5.2 aus, um die entsprechende README.md anzuzeigen).

  • Arbeitsverzeichnis erstellen
mkdir notebooks

(ii) ab jetzt jedes mal

  • ins Projektverzeichnis wechseln
cd ~/vorlesung_modellreduktion
  • die virtuelle Umgebung aktivieren
source python_umgebung_praktikum_modellreduktion/bin/activate
  • Notebook server starten
jupyter-notebook --notebook-dir=notebooks

Aufgabe 1: Gebiete und Gitter

Aktivieren Sie die virtuelle Umgebung zum Praktikum und starten Sie den Notebook server (falls noch nicht geschehen)

  1. Erstellen Sie ein neues Python 3 notebook und benennen Sie es in grid_interpolations um.

  2. Geben Sie folgende Befehle in die erste Zelle ein und führen Sie sie aus (mit STRG + Return):

    %matplotlib notebook
    import numpy as np
    from pymor.basic import *
    

    Ab jetzt ist die numpy Bibliothek unter np verfügbar und alles aus pymor.basic direkt verfügbar.

In [1]:
%matplotlib notebook
import numpy as np
from pymor.basic import *
  1. Suchen Sie in der pyMOR Dokumentation auf docs.pymor.org/ nach einer Klasse, die das Gebiet Ω=[0,1]2 modellieren kann und legen Sie ein entsprechendes Objekt mit Namen omega an. Nutzen Sie dazu auch die Möglichkeit des jupyter Notebooks, sich zu einem beliebigen Objekt mit Hilfe des ?-postfix die Dokumentation anzeigen zu lassen (z.B. np.array?).
In [2]:
# => API Dokumentation http://docs.pymor.org/en/0.5.1/index.html#api-documentation
# => Modul "domaindescriptions": http://docs.pymor.org/en/0.5.1/generated/pymor.domaindescriptions.html
# => Klasse RectDomain, diese ist Dank des * imports direkt verfügbar
# => Hilfe zu RectDomain anzeigen lassen: RectDomain?

omega = RectDomain(([0, 0], [1, 1]))
  1. Suchen Sie nach einer Möglichkeit ein konformes Dreiecksgitter Th als Partitionierung des Gebiets Ω mit 16 Dreiecken zu erstellen, und geben Sie folgende Informationen aus:

    • Dimension d des Gitters
    • Anzahl der Elemente (Kodimension 0 Entitäten, in diesem Fall Dreiecke) im Gitter
    • Anzahl der Kanten (Kodimension 1 Entitäten, in diesem Fall Intervalle) im Gitter
    • Anzahl der Knoten (Kodimension d Entitäten, Punkte) im Gitter
    • die Position eines jeden Knoten des Gitters mit Hilfe einer for-Schleife

    Vergleichen Sie die Positionen der Knoten mit der Dokumentation.

In [3]:
# => API Dokumentation, Modul grid, Klasse TriaGrid
# => Hilfe anzeigen lassen: TriaGrid?

grid = TriaGrid(num_intervals=(2, 2), domain=omega.domain)
print(grid)
print()
print('grid lives in a {}-dimensional space'.format(grid.dim))
print('grid has {} simplices'.format(grid.size(0)))
print('grid has {} edges'.format(grid.size(1)))
print('grid has {} vertices'.format(grid.size(2)))

vertices = grid.centers(grid.dim)
print()
print(type(vertices))
print(vertices.shape)

print()
print('vertices:')
counter = 0
for vertex in vertices:
    print('vertex {} has position {}'.format(counter, vertex))
    counter += 1
Tria-Grid on domain [0,1] x [0,1]
x0-intervals: 2, x1-intervals: 2
elements: 16, edges: 28, vertices: 13

grid lives in a 2-dimensional space
grid has 16 simplices
grid has 28 edges
grid has 13 vertices

<class 'numpy.ndarray'>
(13, 2)

vertices:
vertex 0 has position [5.55111512e-17 5.55111512e-17]
vertex 1 has position [5.00000000e-01 5.55111512e-17]
vertex 2 has position [1.00000000e+00 5.55111512e-17]
vertex 3 has position [5.55111512e-17 5.00000000e-01]
vertex 4 has position [0.5 0.5]
vertex 5 has position [1.  0.5]
vertex 6 has position [5.55111512e-17 1.00000000e+00]
vertex 7 has position [0.5 1. ]
vertex 8 has position [1. 1.]
vertex 9 has position [0.25 0.25]
vertex 10 has position [0.75 0.25]
vertex 11 has position [0.25 0.75]
vertex 12 has position [0.75 0.75]
  1. Visualisieren Sie "das Gitter", indem Sie die Funktion

    vhV0h,vh|K:=index of K in the grid,KTh

    visualisieren, wobei

    V0h:={vL2(Ω)|v|KP0(K)KTh}

    den Raum der stückweise konstanten Funktionen bzgl. des Gitters bezeichnet (Pp(ω) ist dabei der Raum der Polynome vom Grad kleiner gleich pN über einem Gebiet ωRd). Der Raum V0h spielt insbesondere bei Finite Volumen Verfahren eine Rolle.

    • Definieren Sie dazu V0h mit Hilfe des NumpyVectorSpace und legen Sie ein entsprechendes Objekt fv_space an.
    • Erstellen Sie mit Hilfe von numpy den zu vh assoziierten DoF-Vektor mit den Werten 0,,15
    • Erstellen Sie vh aus diesem DoF-Vektor mit Hilfe von fv_space.from_data.
    • Was ist vh?
    • Erstellen Sie einen fv_visualizer vom Typ PatchVisualizer, achten Sie auf die korrekte Kodimension! Importieren Sie dazu das nötige Modul (nicht Teil von pymor.basic, also noch nicht verfügbar).
    • Nutzen Sie die visualize Methode, geben Sie mit legend= einen Titel an, setzen Sie bei Bedarf d=None.
In [4]:
fv_space = NumpyVectorSpace(dim=grid.size(0), id_='FV')

print(fv_space)

v_h = np.arange(15)
print()
print(v_h)

v_h = fv_space.from_data(np.arange(fv_space.dim))
print()
print(type(v_h))
print(v_h)
print(len(v_h))
print(v_h.dim)

# => v_h ist ein NumpyVectorArray (http://docs.pymor.org/en/0.5.1/generated/pymor.vectorarrays.html#module-pymor.vectorarrays.numpy),
#    also insbesondere vom Typ VectorArrayInterface (http://docs.pymor.org/en/0.5.1/generated/pymor.vectorarrays.html#pymor.vectorarrays.interfaces.VectorArrayInterface)
# siehe auch http://docs.pymor.org/en/0.5.1/technical_overview.html#three-central-classes

from pymor.gui.visualizers import PatchVisualizer
fv_visualizer = PatchVisualizer(grid, bounding_box=grid.bounding_box, codim=0)
fv_visualizer.visualize(v_h, d=None, legend='element indices of a TriaGrid with {} elements'.format(grid.size(0)))
NumpyVectorSpace(16, FV)

[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14]

<class 'pymor.vectorarrays.numpy.NumpyVectorArray'>
[[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15]]
1
16

Aufgabe 2: analytische Funktionen vektorisiert implementieren

Sei f:ΩR gegeben durch f(x0,x1):=x0x1.

  1. Schreiben Sie eine Funktion f, die die Auswertung von f and einem Punkt x modelliert und werten Sie die Funktion f für jeden Knotenpunkt des Gitters einzeln mit Hilfe einer for-Schleife aus.
In [5]:
def f(x):
    return x[0]*x[1]

for x in vertices:
    print('f({}) = {}'.format(x, f(x)))
f([5.55111512e-17 5.55111512e-17]) = 3.0814879110195774e-33
f([5.00000000e-01 5.55111512e-17]) = 2.775557561562891e-17
f([1.00000000e+00 5.55111512e-17]) = 5.551115123125783e-17
f([5.55111512e-17 5.00000000e-01]) = 2.775557561562891e-17
f([0.5 0.5]) = 0.24999999999999994
f([1.  0.5]) = 0.49999999999999994
f([5.55111512e-17 1.00000000e+00]) = 5.551115123125783e-17
f([0.5 1. ]) = 0.4999999999999999
f([1. 1.]) = 0.9999999999999999
f([0.25 0.25]) = 0.0625
f([0.75 0.25]) = 0.1875
f([0.25 0.75]) = 0.1875
f([0.75 0.75]) = 0.5625
  1. Erstellen Sie ein feineres Gitter mit 10242 Intervallen und messen Sie die benötigte Zeit, um f an jedem Knotenpunkt des feineren Gitters auszuwerten.
In [6]:
fine_grid = TriaGrid(num_intervals=(1024, 1024), domain=omega.domain)
print(fine_grid)

import time
tic = time.time()
values = f(fine_grid.centers(grid.dim))
toc = time.time() - tic
print()
print('evaluating non-vectorized function took {}s'.format(toc))
Tria-Grid on domain [0,1] x [0,1]
x0-intervals: 1024, x1-intervals: 1024
elements: 4194304, edges: 6293504, vertices: 2099201

evaluating non-vectorized function took 14.28770661354065s
  1. Versuchen Sie, f an allen Knotenpunkten des groben Gitters gleichzeitig auszuwerten. Warum funktioniert das nicht?
In [7]:
# => an allen Punkten gleichzeitig auswerten
values = f(vertices)
print(values)
print(len(vertices))
print(len(values))
[2.77555756e-17 3.08148791e-33]
13
2
In [8]:
# Warum kommt das raus?
# Beim Aufruf f(vertices) passiert
vertices[0]*vertices[1]
# also eine elementweise Multiplikation des 0ten und 1ten Knotens
Out[8]:
array([2.77555756e-17, 3.08148791e-33])
  1. Schreiben Sie eine vektorisierte Variante von f, welche die elementweisen Operationen des numpy.ndarray ausnutzt.
  • Werten Sie diese Variante für alle Knotenpunkte des feineren Gitters gleichzeitig aus und messen Sie die benötigte Zeit.
In [9]:
# Gegeben eine Menge von Punkten als Matrix
print(vertices)
print(vertices.shape)

# extrahiere 0te und 1te Komponente für alle Punkte gleichzeitig
x_0s = vertices[..., 0] # 0te Spalte
x_1s = vertices[..., 1] # 1te Spalte
print()
print(type(x_0s))
print(x_0s)
print(x_1s)
[[5.55111512e-17 5.55111512e-17]
 [5.00000000e-01 5.55111512e-17]
 [1.00000000e+00 5.55111512e-17]
 [5.55111512e-17 5.00000000e-01]
 [5.00000000e-01 5.00000000e-01]
 [1.00000000e+00 5.00000000e-01]
 [5.55111512e-17 1.00000000e+00]
 [5.00000000e-01 1.00000000e+00]
 [1.00000000e+00 1.00000000e+00]
 [2.50000000e-01 2.50000000e-01]
 [7.50000000e-01 2.50000000e-01]
 [2.50000000e-01 7.50000000e-01]
 [7.50000000e-01 7.50000000e-01]]
(13, 2)

<class 'numpy.ndarray'>
[5.55111512e-17 5.00000000e-01 1.00000000e+00 5.55111512e-17
 5.00000000e-01 1.00000000e+00 5.55111512e-17 5.00000000e-01
 1.00000000e+00 2.50000000e-01 7.50000000e-01 2.50000000e-01
 7.50000000e-01]
[5.55111512e-17 5.55111512e-17 5.55111512e-17 5.00000000e-01
 5.00000000e-01 5.00000000e-01 1.00000000e+00 1.00000000e+00
 1.00000000e+00 2.50000000e-01 2.50000000e-01 7.50000000e-01
 7.50000000e-01]
In [10]:
# Das ganze kompakt in einer Funktion
def f(x):
    return x[..., 0]*x[..., 1]

# Kann an einem Punkt ausgewertet werden ...
print(f(vertices[0]))
# ... oder an allen Punkten gleichzeitig
print(f(vertices))
3.0814879110195774e-33
[3.08148791e-33 2.77555756e-17 5.55111512e-17 2.77555756e-17
 2.50000000e-01 5.00000000e-01 5.55111512e-17 5.00000000e-01
 1.00000000e+00 6.25000000e-02 1.87500000e-01 1.87500000e-01
 5.62500000e-01]
In [11]:
tic = time.time()
values = f(fine_grid.centers(grid.dim))
toc = time.time() - tic
print('took {}s'.format(toc))
took 0.013637065887451172s
  1. Erstellen Sie eine ExpressionFunction, um f zu modellieren, werten Sie diese an allen Knotenpunkten des feinen Gitters aus und messen Sie die benötigte Zeit.
In [12]:
f = ExpressionFunction('x[..., 0]*x[..., 1]', dim_domain=2, shape_range=())
tic = time.time()
values = f.evaluate(fine_grid.centers(grid.dim))
toc = time.time() - tic
print('took {}s'.format(toc))
took 0.013749361038208008s