Praktikum zu Vorlesung Modellreduktion parametrisierter Systeme
Mario Ohlberger, Felix Schindler
Aktivieren Sie die virtuelle Umgebung zum Praktikum und starten Sie den Notebook server. Zur Erinnerung:
Terminal starten (super-Taste/Windows-Taste, terminal eingeben, Enter)
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
grid_interpolations in grid_visualization um.Erstellen Sie ein neues Python 3 notebook und benennen Sie es in interpolations um.
Importieren Sie numpy und pymor.basic und machen Sie matplotlib für das Notebook nutzbar.
%matplotlib notebook
import numpy as np
from pymor.basic import *
Schreiben Sie eine Python Funktion visualize_fv(v_h, grid, title), die für eine diskrete Funktion $v_h = $ v_h (gegeben als VectorArray der Länge 1), Gitter $\mathcal{T}_h = $ grid und Titel title, die Funktion $v_h \in V_h^0$ visualisiert, wobei wie in [Blatt 00]
$$V_h^0 := \big\{ v \in L^2(\Omega) \;\big|\; v|_K \in \mathbb{P}_0(K)\quad\forall K \in \mathcal{T}_h\big\}$$
den Raum der stückweise konstanten Funktionen bzgl. des Gitters (Finite Volumen Raum) bezeichnet ($\mathbb{P}_p(\omega)$ ist dabei der Raum der Polynome vom Grad kleiner gleich $p \in \mathbb{N}$ über einem Gebiet $\omega \subset \mathbb{R}^d$).
PatchVisualizer anzulegen und dessen visualize Methode zu nuzten.v_h die richtige Länge und Dimension hat.from pymor.gui.visualizers import PatchVisualizer
def visualize_fv(v_h, grid, title):
fv_visualizer = PatchVisualizer(grid, bounding_box=grid.bounding_box, codim=0)
fv_visualizer.visualize(v_h, d=None, legend=title)
Schreiben Sie eine Python Funktion visualize_grid(grid), die ein gegebenes Gitter $\mathcal{T}_h = $ grid visualisiert.
grid zu erhalten, einen entsprechenden fv_space als NumpyVectorSpace anzulegen, die Funktion $v_h$ als Element von $V_h^0$ mit Hilfe von fv_space zu erhalten.visualize_fv Funktion.def visualize_grid(grid):
vertices = grid.centers(grid.dim)
fv_space = NumpyVectorSpace(dim=grid.size(0), id_='FV')
v_h = fv_space.from_data(np.arange(fv_space.dim))
visualize_fv(v_h, grid, 'element indices of a TriaGrid with {} elements'.format(grid.size(0)))
Testen Sie Ihre Funktionen, indem Sie ein Dreicksgitter $\mathcal{T}_h$ mit $1^2$ Intervallen des Gebiets $\Omega = [0, 1]^2$ visualisieren.
visualize_grid Funktion auf.omega = RectDomain(([0, 0], [1, 1]))
grid = TriaGrid(num_intervals=(1, 1), domain=omega.domain)
visualize_grid(grid)
Zur Visualisierung einer Funktion $f: \Omega \to \mathbb{R}$ auf einem gegebenen Gitter $\mathcal{T}_h$ von $\Omega \subset \mathbb{R}^d$, $d = 1, 2, 3$, benötigen wir eine geeignete Approximation $f_h$. Für stetige Funktionen $f \in C^0$ bietet sich dazu die Lagrange-Interpolation an. Sei dazu
$$S_h^1 := \big\{ v \in C^0(\Omega) \;\big|\; v|_K \in \mathbb{P}_1(K)\quad\forall K \in \mathcal{T}_h\big\} \subset C^0(\Omega)$$der Raum der stetigen und stückweisen linearen Funktionen bezüglich des Gitters.
Sei $\mathcal{T}_h^d$ die Menge aller Knotenpunkte des Gitters (Entitäten mit Kodimension $d$) und $I := |\mathcal{T}_h^d| \in \mathbb{N}$ die Anzahl der Knotenpunkte. Dann ist eine Basis von $S_h^1$ gegeben durch
$$\big\{ \varphi_0, \dots, \varphi_{I - 1} \big\}$$wobei die Hütchenfunktion $\varphi_i$ zu einem Knotenpunkt $\nu_i \in \mathcal{T}_h^d$ eindeutig definiert ist durch
$$\varphi_i(\nu_j) := \delta_{i, j} := \begin{cases}1, & \text{falls }i = j,\\0, &\text{sonst}\end{cases}$$(diese Basis wird Lagrange-Basis genannt).
Damit ist jede Funktion $v_h \in S_h^1$ eindeutig definiert durch die Angabe ihrer Werte in den Knotenpunkten des Gitters, denn aus der Basisdarstellung
$$v_h(x) = \sum_{i = 0}^{I - 1} \underline{v_h}_i \varphi_i(x)$$und der Definition der Basis folgt, dass $\underline{v_h}_i = v_h(\nu_i)$.
Wir können also $S_h^1$ isomorph mit $\mathbb{R}^I$ identifizieren und jede Funktion $v_h \in S_h^1$ mit ihrem DoF Vektor (Degree of Freedom: Freiheitsgrad) $\underline{v_h} \in \mathbb{R}^I$, wobei $\underline{v_h}_i := v_h(\nu_i)$ für alle $0 \leq i_ < I$.
Um für eine beliebige Funktion $f: \Omega \to \mathbb{R}$ eine Approximation in $S_h^1$ zu erhalten, betrachten wir die Lagrange-Interpolation
$$\Pi_{S_h^1}: C^0(\Omega) \rightarrow S_h^1,\quad\quad f \mapsto \Pi_{S_h^1}[f],$$die eindeutig definiert ist durch die Angabe ihrer Werte in den Knotenpunkten,
$$\Pi_{S_h^1}[f](\nu_i) := f(\nu_i) \text{ für } 0 \leq i < I.$$Als Approximation von $f$ erhalten wir also $f_h \in S_h^1$ als $f_h := \Pi_{S_h^1}[f]$ und den dazugehörigen DoF Vektor $\underline{f_h} \in \mathbb{R}^I$ durch
$$\underline{f_h}_i := f(\nu_i), \text{ für alle } 0 \leq 1 < I.$$Bemerkung: Die Lagrange-Interpolation ist eine Projektion, denn $(\Pi_{S_h^1} \circ \Pi_{S_h^1})[v_h] = \Pi_{S_h^1}[v_h]$ für alle $v_h \in S_h^1$.
Schreiben Sie eine Python Funktion interpolate_lagrange_p1(f, grid), die für eine gegebene Funktion $f = $ f und Gitter $\mathcal{T}_h = $ grid die Lagrange-Interpolierte $f_h := \Pi_{S_h^1}[f]$ als geeignetes VectorArray zurück gibt.
NumpyVectorSpace entsprechender Dimension, mit der Kennung CG (continuous Galerkin) und legen Sie ein entsprechendes Objekt cg_space an.f an allen Knotenpunkten des Gitters aus, um $\underline{f_h}$ zu erhalten.cg_space nutzen.def interpolate_lagrange_p1(f, grid):
cg_space = NumpyVectorSpace(dim=grid.size(grid.dim), id_='CG')
vertices = grid.centers(grid.dim)
f_h = f.evaluate(vertices)
return cg_space.from_data(f_h)
Testen Sie Ihre interpolate_lagrange_p1 Funktion, indem Sie die wie in [Blatt 00, Aufgabe 1] gegebene Funktion $f: \Omega \to \mathbb{R}$, $f(x_0, x_1) := x_0 \cdot x_1$ auf $\Omega = [0, 1]^2$ interpolieren.
ExpressionFunktion.interpolate_lagrange_p1 mit dieser Funktion und dem Gitter aus Aufgabe 1 auf und geben Sie die Länge und Dimension des resultierenden VectorArrays, sowie die Anzahl der Knotenpunkte des Gitters aus.f = ExpressionFunction('x[..., 0]*x[..., 1]', dim_domain=2, shape_range=())
f_h = interpolate_lagrange_p1(f, grid)
print(len(f_h))
print(f_h.dim)
print(grid.size(grid.dim))
Schreiben Sie eine Funktion visualize_lagrange_p1(v_h, grid, title), die für eine diskrete Funtion $v_h = $ v_h (gegeben als VectorArray der Länge 1), Gitter $\mathcal{T}_h = $ grid und Titel title, die Funktion $v_h \in S_h^1$ visualisiert.
v_h die richtige Länge und Dimension hat.cg_visualizer vom Typ PatchVisualizer, achten Sie auf die korrekte Kodimension!visualize Methode, geben Sie mit legend= einen Titel an, setzen Sie bei Bedarf d=None.def visualize_lagrange_p1(v_h, grid, title):
assert len(v_h) == 1
assert v_h.dim == grid.size(grid.dim)
cg_visualizer = PatchVisualizer(grid, bounding_box=grid.bounding_box, codim=2)
cg_visualizer.visualize(v_h, d=None, legend=title)
Testen Sie Ihre visualize_lagrange_p1 Funktion, indem Sie die Funktion $f$ für verschiede Gitter interpolieren und visualisieren, messen Sie dabei jeweils die Zeit für
Testen Sie mindestens folgende Gitter:
import time
t = time.time()
grid = TriaGrid(num_intervals=(1, 1), domain=omega.domain)
print('creating simplex grid with {} elements took {}s'.format(grid.size(0), time.time() - t))
t = time.time()
f_h = interpolate_lagrange_p1(f, grid)
print('interpolation took {}s'.format(time.time() - t))
t = time.time()
visualize_lagrange_p1(f_h, grid, 'TriaGrid with {} elements'.format(grid.size(0)))
print('visualization took {}s'.format(time.time() - t))
t = time.time()
grid = TriaGrid(num_intervals=(512, 512), domain=omega.domain)
print('creating simplex grid with {} elements took {}s'.format(grid.size(0), time.time() - t))
t = time.time()
f_h = interpolate_lagrange_p1(f, grid)
print('interpolation took {}s'.format(time.time() - t))
t = time.time()
visualize_lagrange_p1(f_h, grid, 'TriaGrid with {} elements'.format(grid.size(0)))
print('visualization took {}s'.format(time.time() - t))
t = time.time()
grid = RectGrid(num_intervals=(2, 2), domain=omega.domain)
print('creating cubic grid with {} elements took {}s'.format(grid.size(0), time.time() - t))
t = time.time()
f_h = interpolate_lagrange_p1(f, grid)
print('interpolation took {}s'.format(time.time() - t))
t = time.time()
visualize_lagrange_p1(f_h, grid, 'RectGrid with {} elements'.format(grid.size(0)))
print('visualization took {}s'.format(time.time() - t))
t = time.time()
grid = RectGrid(num_intervals=(1024, 1024), domain=omega.domain)
print('creating cubic grid with {} elements took {}s'.format(grid.size(0), time.time() - t))
t = time.time()
f_h = interpolate_lagrange_p1(f, grid)
print('interpolation took {}s'.format(time.time() - t))
t = time.time()
visualize_lagrange_p1(f_h, grid, 'RectGrid with {} elements'.format(grid.size(0)))
print('visualization took {}s'.format(time.time() - t))
Für allgemeine (unstetige Funktionen) $f: \Omega \to \mathbb{R}$ bietet sich außerdem die Approximation (und Visualisierung) als Finite Volumen Funktion an, die wir schon zur Visualisierung des Gitters genutzt haben. Sei $\mathcal{T}_h$ ein Gitter mit $I := |\mathcal{T}_h| \in \mathbb{N}$ Elementen (Entitäten der Kodimension 0). Eine Basis des Finite Volumen Raumes $V_h^0$ ist durch
$$\big\{ \chi_{K_0}, \dots, \chi_{K_{I - 1}} \big\}$$gegeben, wobei
$$\chi_{K_i} := \begin{cases}1, &\text{für } x \in K_i\\0, &\text{sonst}\end{cases}$$die Indikatorfunktion bezüglich des Gitterelementes $K_i \in \mathcal{T}_h$ sei, für $0 \leq i < I$. Da jede Funktion $v_h \in V_h^0$ stückweise konstant bezüglich des Gitters $\mathcal{T}_h$ ist (vergleiche die Definition von $V_h^0$ oben), ist Sie eindeutig durch die Angabe eines Wertes $\underline{v_h}_i \in \mathbb{R}$ auf jedem Gitterelement $K_i \in \mathcal{T}_h$ gegeben, was aus der Basisdarstellung
$$v_h(x) := \sum_{i = 0}^{I - 1} \underline{v_h}_i \chi_{K_i}(x)$$und der Definition der Basis und $V_h^0$ folgt.
Um für eine beliebige Funktion $f \in L^2(\Omega)$ eine Approximation in $V_h^0$ zu erhalten, betrachten wir die Finite Volumen-Iterpolation
$$\Pi_{V_h^0}: L^2(\Omega) \rightarrow V_h^0,\quad\quad f \mapsto \Pi_{V_h^0}[f],$$die eindeutig definiert ist durch die Angabe ihrer Werte auf den Gitterelementen als Mittelwert der Funktion $f$,
$$\Pi_{V_h^0}[f]\Big|_{K_i} := |K_i|^{-1} \int_{K_i} f(x) \,\text{d}x \text{ für } 0 \leq i < I.$$Als Approximation von $f$ erhalten wir also $f_h \in V_h^0$ als $f_h := \Pi_{V_h^0}[f]$ und den dazugehörigen DoF Vektor $\underline{f_h} \in \mathbb{R}^I$ durch
$$\underline{f_h}_i := |K_i|^{-1} \int_{K_i} f(x) \,\text{d}x \text{ für } 0 \leq i < I.$$Bemerkung: Die Finite Volumen-Interpolation ist eine Projektion (genauer gesagt eine $L^2$-Projektion), denn $(\Pi_{V_h^0} \circ \Pi_{V_h^0})[v_h] = \Pi_{V_h^0}[v_h]$ für alle $v_h \in V_h^0$.
Schreiben Sie eine Python Funktion interpolate_fv(f, grid), die für eine gegebene Funktion $f = $ f und Gitter $\mathcal{T}_h = $ grid die Finite Volumen-Interpolierte $f_h := \Pi_{V_h^0}[f]$ als geeignetes VectorArray zurück gibt.
NumpyVectorSpace entsprechender Dimension, mit der Kennung FV und legen Sie ein entsprechendes Objekt fv_space an.fv_space nutzen.def interpolate_fv(f, grid):
fv_space = NumpyVectorSpace(dim=grid.size(0), id_='FV')
centers = grid.centers(0)
f_h = f.evaluate(centers)
return fv_space.from_data(f_h)
interpolate_fv Funktion, indem Sie die Funktionauf $\Omega = [0, 1]^2$ interpolieren.
ExpressionFunktion.interpolate_fv mit dieser Funktion und dem groben Dreiecksgitter auf und geben Sie die Länge und Dimension des resultierenden VectorArrays, sowie die Anzahl der Elements des Gitters aus.grid = TriaGrid(domain=omega.domain, num_intervals=(2, 2))
f = ExpressionFunction('(0.25 <= x[..., 0]) * (x[..., 0] <= 0.5) * (0.25 <= x[..., 1]) * (x[..., 1] <= 0.5) * 1.', dim_domain=2, shape_range=())
f_h = interpolate_fv(f, grid)
print(len(f_h))
print(f_h.dim)
print(grid.size(0))
Testen Sie Ihre interpolate_fv und visualize_fv Funktion von oben, indem Sie die Funktion $f$ für verschiede Gitter interpolieren und visualisieren, testen Sie mindestens folgende Gitter:
grid = TriaGrid(num_intervals=(2, 2), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'TriaGrid with {} elements'.format(grid.size(0)))
grid = TriaGrid(num_intervals=(3, 3), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'TriaGrid with {} elements'.format(grid.size(0)))
grid = TriaGrid(num_intervals=(4, 4), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'TriaGrid with {} elements'.format(grid.size(0)))
grid = RectGrid(num_intervals=(2, 2), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'RectGrid with {} elements'.format(grid.size(0)))
grid = RectGrid(num_intervals=(3, 3), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'RectGrid with {} elements'.format(grid.size(0)))
grid = RectGrid(num_intervals=(4, 4), domain=omega.domain)
f_h = interpolate_fv(f, grid)
visualize_fv(f_h, grid, 'RectGrid with {} elements'.format(grid.size(0)))