Zweites interaktives Übungsblatt

Vorgeplänkel


In [ ]:
import sys
# Diese Zeile muss angepasst werden!
sys.path.append("/home/moser/MG_2016/pyMG-2016/")
# sys.path.append("/home/dima/lectures/MG/pyMG/")
import scipy as sp
import scipy.sparse.linalg as sprsla
import numpy as np
import matplotlib as mplt
import matplotlib.pyplot as plt
%matplotlib inline

In [ ]:
import pyamg
from project.gauss_seidel import GaussSeidel
from project.weighted_jacobi import WeightedJacobi
from project.pfasst.plot_tools import eigvalue_plot_list, matrix_plot, matrix_row_plot,heat_map
from project.pfasst.transfer_tools import to_dense
from project.pfasst.matrix_method_tools import matrix_power
from pymg.collocation_classes import CollGaussRadau_Right, CollSplineRight

from project.mymultigrid import MyMultigrid
from project.poisson1d import Poisson1D
from project.linear_transfer import LinearTransfer
from project.weighted_jacobi import WeightedJacobi

In [ ]:
def plot_mat_and_eigs(A):
    A = to_dense(A) 
    fig1, (ax2, ax) = plt.subplots(ncols=2,figsize=(15,5))
    
    plt_mat = ax.imshow(A, cmap=plt.cm.jet, interpolation='nearest')
    ax.spines['left'].set_position(('outward', 10))
    ax.spines['bottom'].set_position(('outward', 10))
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)

    ax.yaxis.set_ticks_position('left')
    ax.xaxis.set_ticks_position('bottom')

    plt.colorbar(plt_mat)
    
    eig=sp.linalg.eigvals(to_dense(A))
    real_part = np.real(eig)
    img_part = np.imag(eig)
    
    ax2.plot(real_part,img_part,'bo')
    ax2.set_xlabel("real part")
    ax2.set_ylabel("img part")
    ax2.set_title('eigenvalues')
    
    fig1.tight_layout()
    plt.show()

def plot_solution(sol,N_t):
    heat_map(sol.reshape(N_t,-1))

def lineplot(vertices, indices, linewidths=1):
    """Plot 2D line segments"""
    vertices = np.asarray(vertices)
    indices = np.asarray(indices)
    
    #3d tensor [segment index][vertex index][x/y value]
    lines = vertices[np.ravel(indices),:].reshape((indices.shape[0],2,2))
    
    col = mplt.collections.LineCollection(lines)
    col.set_color('k')
    col.set_linewidth(linewidths)

    sub = mplt.pylab.gca()
    sub.add_collection(col,autolim=True)
    sub.autoscale_view()

In [ ]:
def system_matrix_poisson1d(n):
    hh1d = Poisson1D(n)
    return hh1d.A

def spec_rad(A):
    return np.max(np.abs(sp.linalg.eigvals(to_dense(A))))

Erste Schritte in pyAMG

Wir beginnnen mit einem Vergleich zwischen geometrischem und algebraischen Multigrid. Zunächst geometrisches Multigrid.


In [ ]:
ndofs = 15
niter_list = []
nlevels = int(np.log2(ndofs+1))

prob = Poisson1D(ndofs=ndofs)

mymg = MyMultigrid(ndofs=ndofs, nlevels=nlevels)
mymg.attach_transfer(LinearTransfer)
mymg.attach_smoother(WeightedJacobi,prob.A,omega=2.0/3.0)
# mymg.attach_smoother(GaussSeidel,prob.A)

k = 6
xvalues = np.array([(i+1) * prob.dx for i in range(prob.ndofs)])
prob.rhs = (np.pi*k)**2 * np.sin(np.pi*k*xvalues)

uex = sprsla.spsolve(prob.A, prob.rhs)

res = 1
niter = 0
err = []
u = np.zeros(uex.size)
res_list_mg = []

print nlevels

while res > 1E-10 and niter < 10:
    niter += 1
    u = mymg.do_v_cycle(u, prob.rhs, 2, 2, 0)
    res = np.linalg.norm(prob.A.dot(u)-prob.rhs, np.inf)
    res_list_mg.append(res)
    err.append(np.linalg.norm(u-uex, np.inf))
    print(niter,res,err[-1])

In [ ]:
plot_mat_and_eigs(prob.A)

In [ ]:
mls = pyamg.ruge_stuben_solver(prob.A,max_levels = 10, max_coarse = 1,CF='RS',keep=True)
print mls
res_list_amg = []
sol = mls.solve(b=prob.rhs, x0=np.zeros(ndofs),tol=1e-10,residuals=res_list_amg)

In [ ]:
plt.semilogy(res_list_amg)
plt.semilogy(res_list_mg)

In [ ]:
# The CF splitting, 1 == C-node and 0 == F-node
splitting = mls.levels[0].splitting
C_nodes = splitting == 1
F_nodes = splitting == 0
plt.figure(figsize=(10,2))
V = np.zeros((ndofs,2))
V[:,0] = prob.domain
plt.scatter(V[:,0][C_nodes], V[:,1][C_nodes], c='r', s=200.0)  #plot C-nodes in red
plt.scatter(V[:,0][F_nodes], V[:,1][F_nodes], c='b', s=200.0)  #plot F-nodes in blue
plt.show()

Frage

AMG führt hier zum selben Ergebnis wie ein einfaches geometrischen Multigrid. Warum sollte man also AMG verwenden?

Zeitabhängigkeit

Anstatt

$$ \Delta u(x) = 0,\quad u(0)=u(1)=0 $$

betrachten wir

$$ u_t(t,x)=\Delta u(t,x) ,\quad u(t,0)=u(t,1)=0, u(0,x) = u_0. $$

Wir verwenden die Rothe Methode um in jedem Schritt wieder eine elliptische PDGL zu lösen.

$$ u_{i+1}(x)-u_{i}(x) = \Delta t \Delta u_{i+1}(x) $$

Wir diskretisieren weiter ...

$$ u_{i+1}-u_{i} = \Delta t \mathbf{A} u_{i+1} \\ (\mathbf{I} - \Delta t \mathbf{A})u_{i+1} = u_i $$

Mehrere Schritte dieser Art können als ein System geschrieben werden.


In [ ]:
def impl_euler_system(n_steps,dt,A):
    I_L = sp.sparse.eye(n_steps,format='csc')
    I_N = sp.sparse.eye(A.shape[0],format='csc')    
    
    if n_steps == 1:
        return I_N - dt*A
    else:
        E_mat = sp.sparse.diags([1], [-1], shape=(n_steps, n_steps)).tocsc()   

        As = [[I_L,I_N],[-I_L*dt,-A],[-E_mat,I_N]]

        return reduce(lambda a, b: a + b, map(lambda a_row: reduce(lambda a, b: sp.sparse.kron(a, b), a_row), As))
    
def impl_euler_rhs(n_steps,domain,k):
    rhs = np.zeros(n_steps*domain.size)
    init = np.sin(domain*k*np.pi)
    rhs[:domain.size] = init
    return rhs

def impl_euler_x0(n_steps,domain,k):
    init = np.sin(domain*k*np.pi)
    return np.kron(np.ones(n_steps), init)

In [ ]:
plot_mat_and_eigs(impl_euler_system(2,0.1,prob.A))

In [ ]:
n_steps = 20
dt = 1e-2
k = 2
M = impl_euler_system(n_steps,dt,prob.A)
rhs = impl_euler_rhs(n_steps,prob.domain,k)
x0 = impl_euler_x0(n_steps,prob.domain,k)
sol=sprsla.spsolve(M,rhs)
plot_solution(sol,n_steps)

In [ ]:
mls = pyamg.ruge_stuben_solver(M,max_levels = 10, max_coarse = 1,CF='RS',keep=True)
print mls
res_amg_list = []
sol = mls.solve(b=rhs, x0=x0,tol=1e-10,residuals=res_amg_list)
plt.semilogy(res_amg_list)

In [ ]:
# The CF splitting, 1 == C-node and 0 == F-node
V = np.zeros((ndofs*n_steps,2))
t_space = np.array([(i) * dt for i in range(n_steps)])
x_space = prob.domain

for j in range(n_steps):
    for k in  range(ndofs):
        V[ndofs*j+k,0]=x_space[k]
        V[ndofs*j+k,1]=t_space[j]

E = np.vstack((M.tocoo().row,M.tocoo().col)).T

splitting = mls.levels[0].splitting
C_nodes = splitting == 1
F_nodes = splitting == 0
plt.figure(figsize=(10,10))

lineplot(V,E,0.1)

plt.scatter(V[:,0][C_nodes], V[:,1][C_nodes], c='r', s=200.0)  #plot C-nodes in red
plt.scatter(V[:,0][F_nodes], V[:,1][F_nodes], c='b', s=200.0)  #plot F-nodes in blue
plt.show()

Aufgabe

Probieren Sie verschiedene $dt$ aus, wie verändert sich die Vergröberungsstrategien von AMG? Welche Vergröberungsstrategien erkennt man?

Das Q

Wichtiger Bestandteil für unseren Formalismus ist die Quadratur-Matrix $Q$. Sei $v$ der Vektor der die Funktionsauswertungen der Funktion $f$ an den Quadratur-Knoten beinhaltet, so entspricht $Qv$ einer numerischen Approximation von $F(t)-F(t_0)$ ausgewertet an


In [ ]:
def Q_mat_and_nodes(N, which='radau_r'):
    if which is 'radau_r':
        cgr = CollGaussRadau_Right(N,0.0,1.0)
        return cgr.Qmat[1:,1:], cgr.nodes
    elif which is 'spline':
        spl = CollSplineRight(N,0,1,4)
        return spl.Qmat[1:,1:], spl.nodes
    elif which is 'radau_delta':
        cgr = CollGaussRadau_Right(N,0.0,1.0)
        return cgr.QDmat, cgr.nodes
    elif which is 'spline_delta':
        spl = CollSplineRight(N,0,1,1)
        return spl.QDmat, spl.nodes

Aufgabe:

Plotten Sie mithilfe von plot_mat_and_eigs die Q-Matrix für verschiedene $N$ mit und ohne splines an. Wann geht das ganze schief?


In [ ]:

Aufgabe

Testen sie nun für verschiedene Konfigurationen, wie gut Monome bis zur Ordnung 20 integriert werden.


In [ ]:
# Als Beispiel der lineare Fall
N = 3
Q,nodes = Q_mat_and_nodes(N)
v = nodes.copy()
e =  Q.dot(v)-v**2/2
print np.linalg.norm(e)

In [ ]:
def test_monom_quadrature(Q,nodes,order):
    v = nodes.copy()**order
    e =  # Zeile hier einfügen
    return np.linalg.norm(e)

In [ ]:
Q_5,nodes_5 = Q_mat_and_nodes(5)
Q_10,nodes_10 = Q_mat_and_nodes(10)
Q_50,nodes_50 = Q_mat_and_nodes(50)

Q_s_5,nodes_s_5 = Q_mat_and_nodes(5,'spline')
Q_s_10,nodes_s_10 = Q_mat_and_nodes(10,'spline')
Q_s_50,nodes_s_50 = Q_mat_and_nodes(50,'spline')

In [ ]:
fig1, (ax1,ax2) = plt.subplots(ncols=2,figsize=(15,5),sharey=True)
ax1.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_5,nodes_5,order),range(1,20)))
ax1.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_10,nodes_10,order),range(1,20)))
ax1.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_50,nodes_50,order),range(1,20)))
ax1.set_title("Gauss Radau")
ax2.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_s_5,nodes_s_5,order),range(1,20)))
ax2.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_s_10,nodes_s_10,order),range(1,20)))
ax2.semilogy(range(1,20),map(lambda order:test_monom_quadrature(Q_s_50,nodes_s_50,order),range(1,20)))
ax2.set_title("Splines")
plt.show()

Frage

Wo ist der Unterschied zwischen GaussRadau und Splines?

Das Kollokationsproblem

Wie wir in der Vorlesung gesehen haben, kann die Q-Matrix dazu genutzt werden um das Kollokationsproblem

$$ \left( \mathbf{I} - \Delta t \cdot \mathbf{Q}\otimes\mathbf{A} \right)U = U_0 $$

aufzustellen. Wobei A hierbei die Systemmatrix für das periodische Problem ist.

Aufgabe

Schreiben Sie eine Funktion colloc_mat(Nt,Nx,sig,dt,typ='radau_r'), welche Kollokationsmatrix abhängig von $\sigma,dt,N_x,$ und $N_t$ aufstellt.


In [ ]:
def colloc_mat(Nt,Nx,dt,typ='radau_r'):
    I = # Zeile hier einfügen
    Q,nod = Q_mat_and_nodes(Nt,typ)
    A = -system_matrix_poisson1d(Nx)
    return # Zeile hier einfügen

Buntebilderaufgabe

Plotten Sie mithilfe von plot_mat_and_eigs einige Konfigurationen. Welche Strukturen sind zu erkennen?


In [ ]:
plot_mat_and_eigs(to_dense(colloc_mat(3,10,0.1)))

Nun brauchen wir noch eine rechte Seite um das lineare Gleichungssystem zu vervollständigen. Die rechte Seite entsteht durch einen "spread" auf die einzelnen Quadraturknoten. Wir werden nun Funktion $s_k(x) = \sin(k\pi x)$, ausgewertet auf den Raumknoten als Anfangswert verwenden.


In [ ]:
def colloc_rhs(Nt,Nx,k):
    dx = 1.0/(Nx+1)
    domain = np.array([(i+1) * dx for i in range(Nx)])
    init = np.sin(domain*k*np.pi)
    return np.kron(np.ones(Nt),init)

In [ ]:
plot_solution(colloc_rhs(10,16,2),10)

Aufgabe

Finden sie mithilfe von scipy die Lößung des Kollokationsproblems und plotten Sie diese für $N_t=10, N_x=32, dt=0.05$. Probieren sie verschiedene Konfigurationen aus. Was passiert für unterschiedliche $k$?


In [ ]:
Nx = 24
Nt = 10
k=2

M = # Zeile hier einfügen
rhs = # Zeile hier einfügen
sol = sprsla.spsolve(M,rhs)
plot_solution(sol,Nt)

Desweiteren wollen wir nun mehrere dieser Kollokationsprobleme hintereinander aufreihen, wir stellen dementsprechend die Matrix $$ \mathbf{I}_L\otimes \mathbf{I}_M \otimes \mathbf{I}_N -\mathbf{I}_L\otimes\mathbf{Q}\otimes\mathbf{A}-\mathbf{E}\otimes\mathbf{N}\otimes\mathbf{I}_N $$ auf, wobei $L$ die Anzahl der Subintervale, $N$ die Anzahl der Freiheitsgraden im Raum und $M$ die Anzahl der Quadraturknoten ist.


In [ ]:
def compound_colloc_mat(N,M,L,dt,typ='radau_r'):
    
    if L == 1:
        return colloc_mat(M,N,dt,typ)
    else:
        I_N = sp.sparse.eye(N,format='csc')
        I_M = sp.sparse.eye(M,format='csc')
        I_L = sp.sparse.eye(L,format='csc')
        
        E_mat = sp.sparse.diags([1], [-1], shape=(L, L)).tocsc()
        N_mat = np.zeros((M,M))
        N_mat[:,-1] = 1.0
        N_mat = N_mat
        Q,nod = Q_mat_and_nodes(M,typ)
        
        A = -system_matrix_poisson1d(N)
        
        As = [[I_L,I_M,I_N],[-I_L*dt,Q,A],[-E_mat,N_mat,I_N]]

        return reduce(lambda a, b: a + b, map(lambda a_row: reduce(lambda a, b: sp.sparse.kron(a, b), a_row), As))

In [ ]:
def compound_colloc_rhs(N,M,L,k):
    rhs = np.zeros(N*M*L)
    rhs[0:N*M] = colloc_rhs(M,N,k)
    return rhs

In [ ]:
def compound_x0(N,M,L,rhs):
    x0 = rhs.copy()
    for k in range(L-1):
        x0[(k+1)*N*M:(k+2)*N*M] = rhs[:N*M]
    return x0

In [ ]:
plot_mat_and_eigs(to_dense(compound_colloc_mat(8,3,4,0.1)))

Und jetzt: pyAMG

PyAMG bietet diverse Möglichkeiten zum Lösen des Systems (https://github.com/pyamg/pyamg/wiki/Examples).


In [ ]:
# Baue Kollokationsmatrix
N = 255
M = 5
L = 10
dt = 5e-5

A = compound_colloc_mat(N,M,L,dt,"radau_r")
rhs = compound_colloc_rhs(N,M,L,16)
x0 = compound_x0(N,M,L,rhs)

sol = sprsla.spsolve(A,rhs)
plot_solution(sol,M*L)

Blackboxsmoother


In [ ]:
bb_solution = pyamg.solve(A,rhs,verb=False, tol=1e-11,maxiter=10)

In [ ]:
plot_solution(bb_solution-sol,M*L)

Classical AMG


In [ ]:
mls_rs = pyamg.ruge_stuben_solver(A, max_levels=10, CF='RS',keep=True)
print mls_rs
rs_residuals = []
rs_solution = mls_rs.solve(b=rhs,x0=x0, tol=1e-10, residuals=rs_residuals )
plt.semilogy(rs_residuals)
plot_solution(rs_solution,M*L)

Aufgabe

Finden Sie experimentell heraus wo die Grenzen der AMG Löser sind, probieren Sie insbesondere verschiedene $\Delta t$ aus. Finden Sie ein Setup welches den Kollokationsansatz und den Ansatz mit dem impl. Euler vergleichbar macht. Was fällt beim Vergleich auf?


In [ ]:
# Baue Kollokationsmatrix
N = 255
M = 5
L = 10
dt = 1e-2
k=2

A = compound_colloc_mat(N,M,L,dt,"radau_r")
rhs = compound_colloc_rhs(N,M,L,k)
x0 = compound_x0(N,M,L,rhs)

A_impl_euler = # Zeile hier einfügen
dx = 1.0/(N+1)
domain = np.asarray([(i + 1) * dx for i in range(N)])
rhs_impl_euler = impl_euler_rhs(L*M,domain,k)

sol = sprsla.spsolve(A,rhs)
sol_impl_euler = sprsla.spsolve(A_impl_euler,rhs_impl_euler)

plot_solution(sol_impl_euler,M*L)
plot_solution(sol,M*L)

print np.linalg.norm(sol_impl_euler[-N:] - sol[-N:])

In [ ]:
mls_rs = pyamg.ruge_stuben_solver(A, max_levels=10, CF='RS',keep=True)
print mls_rs
rs_residuals = []
rs_solution = mls_rs.solve(b=rhs,x0=x0, tol=1e-10, residuals=rs_residuals )
plt.semilogy(rs_residuals)
plot_solution(rs_solution,M*L)

In [ ]:
mls_rs = pyamg.ruge_stuben_solver(A_impl_euler, max_levels=10, CF='RS',keep=True)
print mls_rs
rs_residuals = []
rs_solution = mls_rs.solve(b=rhs_impl_euler,x0=x0, tol=1e-10, residuals=rs_residuals )
plt.semilogy(rs_residuals)
plot_solution(rs_solution,M*L)

Feines und grobes Gitter

Nun wollen wir uns etwas genauer anschauen was AMG macht.


In [ ]:
def compound_collocation_vertices(n_space,n_comp,q_nodes,dt=1e-2,plot_it=True):
    n_time = q_nodes.shape[0]
    V = np.zeros((n_space*n_time*n_comp,2))
    dx = 1.0/(n_space+1)
    x_space = np.array([(i+1) * dx for i in range(n_space)])
    t_space = q_nodes.copy() * dt
    
    for i in range(n_comp):
        for j in range(n_time):
            for k in  range(n_space):
                V[n_time*n_space*i+n_space*j+k,0]=x_space[k]
                V[n_time*n_space*i+n_space*j+k,1]=t_space[j]+i*dt
                
    if plot_it:
        plt.figure(figsize=(10,10))
        plt.scatter(V[:,0], V[:,1], c='r', s=100.0)
#         if not with_initial_val:
#             pylab.scatter(x_space,np.zeros(n_space),c='b',s=100.0)
        plt.show()
    return V

def CF_plotter_classical(N,M,L,dt,typ='radau_r',symmetric=False):
    Q,nodes = Q_mat_and_nodes(M,typ)
    A = compound_colloc_mat(N,M,L,dt,typ)
    V = compound_collocation_vertices(N,L,nodes,dt,plot_it=False)
    E = np.vstack((A.tocoo().row,A.tocoo().col)).T
    
    if symmetric:
        A = A.T.dot(A)
    
    # Use Ruge-Stuben Splitting Algorithm (use 'keep' in order to retain the splitting)
    mls = pyamg.ruge_stuben_solver(A, max_levels=2, max_coarse=1, CF='RS',keep=True)
#     print mls

    # The CF splitting, 1 == C-node and 0 == F-node
    splitting = mls.levels[0].splitting
    C_nodes = splitting == 1
    F_nodes = splitting == 0
    plt.figure(figsize=(20,10))
    lineplot(V, E,linewidths=.05)

    plt.scatter(V[:,0][C_nodes], V[:,1][C_nodes], c='r', s=200.0)  #plot C-nodes in red
    plt.scatter(V[:,0][F_nodes], V[:,1][F_nodes], c='b', s=200.0)  #plot F-nodes in blue
    plt.show()
    return mls

Aufgabe

Probieren Sie die Funktion CF_plotter_classical mit verschiedenen Parametern aus. Was fällt im Vergleich zum impliziten Euler Vorgehen auf?


In [ ]:
mls=CF_plotter_classical(7,5,2,1e1,typ='radau_r')
print mls

Das symmetrisierte Problem

In der Vorlesung haben wir gelernt, dass die Theorie zu AMG auf sog. $\mathbf{M}$ Matrizen aufgebaut ist. Das Kollokationsproblem erfüllt die meisten Eigenschaften davon nicht. Zudem sehen wir die schlechten Konvergenzeigenschaften von AMG für das kombinierte Kollokationsproblem.

Aufgabe

Symmetrisieren Sie das Problem und wenden Sie SmoothedAggregation und Classical AMG an.


In [ ]:
# Baue Kollokationsmatrix
N = 255
M = 5
L = 4
dt = 1e-4

A = compound_colloc_mat(N,M,L,dt,"radau_r")
rhs = compound_colloc_rhs(N,M,L,16)
x0 = compound_x0(N,M,L,rhs)

A_sym =   # Zeile hier einfügen
rhs_sym = # Zeile hier einfügen
x0_sym =  # Zeile hier einfügen

sol = sprsla.spsolve(A_sym,rhs_sym)
plot_solution(sol,M*L)

In [ ]:
# Count of the non-zero entries 
print A_sym.nnz, A.nnz, (N*M*L)**2

In [ ]:
mls_sa = pyamg.ruge_stuben_solver(A_sym, max_levels=5, max_coarse=1, CF='RS',keep=True)
print mls_sa
sa_residuals = []
sa_solution = mls_sa.solve(b=rhs_sym,x0=x0_sym, tol=1e-10, residuals=sa_residuals )
plt.semilogy(sa_residuals)
plot_solution(sa_solution, M*L)

In [ ]:
mls_sa = pyamg.ruge_stuben_solver(A, max_levels=5, max_coarse=1, CF='RS',keep=True)
print mls_sa
sa_residuals = []
sa_solution = mls_sa.solve(b=rhs,x0=x0, tol=1e-10, residuals=sa_residuals )
plt.semilogy(sa_residuals)
plot_solution(sa_solution, M*L)

Aufgabe

Vergleichen Sie die Wahl der Grobgitterpunkte zwischen dem Kollokationionsproblem und seiner symmetrisierten Version.


In [ ]:
N = 7 
M = 5
L = 2
dt =1e-3

In [ ]:
mls=CF_plotter_classical(N,M,L,dt)
print mls

In [ ]:
mls=CF_plotter_classical(N,M,L,dt,symmetric=True)
print mls