-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Umberto Zerbinati <[email protected]>
- Loading branch information
Umberto Zerbinati
committed
Jun 2, 2024
1 parent
07de3d5
commit 911db81
Showing
2 changed files
with
98 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
Preconditioning the Poisson problem | ||
===================================== | ||
|
||
In this tutorial, we explore using `SLEPc EPS` to solve the Laplace eigenvalue problem in different formulations. | ||
We begin considering the Poisson eigenvalue problem in primal form, i.e. | ||
|
||
.. math:: | ||
\text{find } (u,\lambda) in H^1_0(\Omega)\times\mathbb{R} \text{ s.t. } a(u,v) := \int_{\Omega} \nabla u\cdot \nabla v \; d\vec{x} = \lambda (u,v)_{L^2(\Omega)} = \lambda \int_{\Omega} uv\; d\vec{x}\qquad v\in H^1_0(\Omega). | ||
Such a discretisation can easily be constructed using NGSolve as follows: :: | ||
|
||
from ngsolve import * | ||
import netgen.gui | ||
import netgen.meshing as ngm | ||
import numpy as np | ||
from mpi4py.MPI import COMM_WORLD | ||
|
||
if COMM_WORLD.rank == 0: | ||
mesh = Mesh(unit_square.GenerateMesh(maxh=0.1).Distribute(COMM_WORLD)) | ||
else: | ||
mesh = Mesh(ngm.Mesh.Receive(COMM_WORLD)) | ||
|
||
order = 3 | ||
fes = H1(mesh, order=order, dirichlet="left|right|top|bottom") | ||
print("Number of degrees of freedom: ", fes.ndof) | ||
u,v = fes.TnT() | ||
a = BilinearForm(grad(u)*grad(v)*dx, symmetric=True) | ||
a.Assemble() | ||
m = BilinearForm(-1*u*v*dx, symmetric=True) | ||
m.Assemble() | ||
|
||
We then proceed to solve the eigenvalue problem using `SLEPc EPS` using ngsPETSc's `EigenSolver` class. | ||
In particular, we will use SLEPc implementation of a locally optimal block preconditioned conjugate gradient. | ||
Notice that we have assembled the mass matrix so that it is symmetric negative definite, this is because ngsPETSc `Eigensolver` requires you to write all eigenvalue problems as a polynomial eigenvalue problem, i.e. | ||
|
||
.. math:: | ||
A\vec{U} - \lambda M\vec{U} = 0 | ||
where :math:`A` and :math:`M` are the stiffness and mass matrices, respectively. :: | ||
|
||
from ngsPETSc import EigenSolver | ||
solver = EigenSolver((m, a), fes, 10, solverParameters={"eps_type":"lobpcg", "st_type":"precond"}) | ||
solver.solve() | ||
for i in range(10): | ||
print("Normalised (by pi^2) Eigenvalue ", i, " = ", solver.eigenValue(i)/(np.pi*np.pi)) | ||
modes, _ = solver.eigenFunctions(list(range(10))) | ||
Draw(modes) | ||
|
||
Notice that we can access a single eigenvalue using `solver.eigenValue(i)` and the corresponding eigenfunction using `solver.eigenFunction(i)`. | ||
If we use `solver.eigenFunctions(indices)` we can access the first 10 eigenfunctions as a multi-dimensional array and we can obtain the corresponding eigenvalues using `solver.eigenValues(indices)`. | ||
We now consider a mixed formulation of the Laplace eigenvalue problem, i.e. | ||
|
||
.. math:: | ||
\text{find } (\vec{\sigma}, u, lambda) \in H(\text{div},\Omega)\times L^2(\Omega)\times \mathbb{R} \text{ s.t. } \\ | ||
\begin{cases} | ||
\int_{\Omega} \vec{\sigma}\cdot\vec{\tau} \; d\vec{x} - \int_{\Omega} \nabla u \cdot \vec{\tau} \; d\vec{x} = 0 & \forall \vec{\tau}\in H(\text{div},\Omega)\\ | ||
\int_{\Omega} \nabla\cdot\vec{\sigma}v \; d\vec{x} = \lambda \int_{\Omega} uv \; d\vec{x} & \forall v\in L^2(\Omega) | ||
\end{cases} | ||
We can discretise this problem using NGSolve as follows: :: | ||
|
||
V = HDiv(mesh, order=order) | ||
Q = L2(mesh, order=order-1) | ||
W = FESpace([V,Q]) | ||
print("Number of degrees of freedom: ", W.ndof) | ||
sigma, u = W.TrialFunction() | ||
tau, v = W.TestFunction() | ||
a = BilinearForm(sigma*tau*dx + div(tau)*u*dx + div(sigma)*v*dx) | ||
a.Assemble() | ||
|
||
m = BilinearForm(1*u*v*dx) | ||
m.Assemble() | ||
|
||
We can then solve the eigenvalue problem using `SLEPc EPS` using ngsPETSc's `EigenSolver` class. | ||
Since the mass matrix now has a large kernel, hence is no longer symmetric positive definite, we can not use LOBPCG as a solver. | ||
Instead, we will use a Krylov-Schur solver with a shift-and-invert spectral transformation to target the smallest eigenvalues. | ||
Notice that because we are using a shift-and-invert spectral transformation we only need to invert the stiffness matrix which has a trivial kernel because we are using an inf-sup discretisation. | ||
If we tried to use an simple shift transformation to target the largest eigenvalues we would have run into the error caused by trying to invert a singular matrix.:: | ||
solver = EigenSolver((m, a), W, 10, solverParameters={"eps_type":"krylovschur", | ||
"st_type":"sinvert", | ||
"eps_target": 0, | ||
"st_pc_type": "lu", | ||
"st_pc_factor_mat_solver_type": "mumps"}) | ||
solver.solve() | ||
for i in range(10): | ||
print("Normalised (by pi^2) Eigenvalue ", i, " = ", solver.eigenValue(i)/(np.pi*np.pi)) | ||
mode, _ = solver.eigenFunction(0) | ||
modeSigma, modeU = mode.components | ||
Draw(modeSigma, mesh, "sigma") | ||
Draw(modeU, mesh, "u") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters