{
"cells": [
{
"cell_type": "markdown",
"id": "9a77f718-716d-4081-b751-4ab20e598651",
"metadata": {},
"source": [
"# Algebraic Multigrid Methods\n",
"\n",
"Algebraic multigrid methods (AMG) build a multigrid hierarchy from the given matrix. In contrast to geometric multigrid methods, they do not need a mesh hierarchy. Just one finite element mesh is enough. This makes them useful for a wide range of of applications.\n",
"\n",
"AMG takes profit from providing the type of problem (Poisson equation, elasticity, Maxwell, ...).\n",
"\n",
"NGSolve comes with builtin AMG solvers for scalar equations, and for Maxwell equations. It provides also interfaces to external, parallel AMG solvers (hypre, gamg, ...)"
]
},
{
"cell_type": "markdown",
"id": "2c14230a-c5c0-4f0b-9a61-1f69da0c877c",
"metadata": {},
"source": [
"## Agglomeration based scalar AMG\n",
"\n",
"The coarsening of degrees of freedom is steered by the strength of connections between dofs, one may think of a network of resistors. For this, one finds edge-based weights $w_E$ such that the energy norm is equivalent to the weighted sum of squared differences:\n",
"\n",
"$$\n",
"u^T A u \\approx \\sum_{{\\text edges} E} w_E \\, (u_{E_1} - u_{E_2})^2,\n",
"$$\n",
"\n",
"where $w_E$ is the edge-weight (i.e. the conductivity of each resistor), and $E_1$ and $E_2$ are the vertex numbers of the end-points of the edge $E$. The right hand side is a norm represented by a surrogate matrix $\\tilde A$. \n",
"\n",
"\n",
"\n",
"Coarse grid vertices are defined by the index mapping\n",
"\n",
"$$\n",
"Ind : \\text{Vertex} \\rightarrow \\text{Cluster}\n",
"$$\n",
"\n",
"All vertices having the same index are combined to a cluster. Each cluster is a degree of freedom on the coarse grid.\n",
"\n",
"\n",
"\n",
"\n",
"How to select the agglomerates ? The basic principle is to combine vertices to clusters which are connected by a strong conductivity.\n",
"For each edge we compute the coarsening weight\n",
"\n",
"$$\n",
"\\frac { w_{E_{ij}} ^2 } { \\sum_{k \\neq j} w_{E_{ik}} \\sum_{k \\neq i} w_{E_{ik}} }\n",
"$$\n",
"\n",
"which is the ratio of the edge-weight, to the geometric mean of the sum of all edge-weights connected to vertices i, and j. One sorts the edges by the coarsening weights, and selects all edges for collapsing whose vertices don't belong to edges selected before."
]
},
{
"cell_type": "markdown",
"id": "65b5eec5-1304-4052-a6b8-e45b9fbac43f",
"metadata": {},
"source": [
"## The builtin *h1amg* preconditioner\n",
"\n",
"The h1amg preconditioner works for symmetric, scalar problems with nodal degrees of freedom. It uses unsmoothed agglomeration for the generation of coarse spaces.\n",
"\n",
"The first task is to determine the edge-weights. If one has access to element-matrices (instead of the assembled matrix), one has better possibilities. One may compute Schur complements with respect to all edges of each element, which gives a surrogate matrix for each element. Then sum up the weights (conductivities) of all elements sharing the edge.\n",
"\n",
"To have access to element matrices, the setup of the surrogate matrix is included into the assembling loop. Thus, the workflow is to\n",
"\n",
"* define the biliear-form\n",
"* define the h1amg preconditioner, which registers at the bilinear-form\n",
"* finally assemble the bilinear-form, which also runs the setup of the preconditioner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d416edf0-6ed3-4bf2-92f2-65673a625222",
"metadata": {},
"outputs": [],
"source": [
"from ngsolve import *\n",
"from ngsolve.la import EigenValues_Preconditioner\n",
"\n",
"with TaskManager():\n",
" mesh = Mesh(unit_cube.GenerateMesh(maxh=0.1))\n",
" for l in range(3): mesh.Refine() "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56fc4e59-798f-4728-a2c1-c7cf78c895df",
"metadata": {},
"outputs": [],
"source": [
"# ngsglobals.msg_level=3\n",
"fes = FESpace(\"nodal\", mesh, order=1)\n",
"print (\"ndof=\", fes.ndof)\n",
"u,v = fes.TnT()\n",
"a = BilinearForm(grad(u)*grad(v)*dx + 1e-3*u*v*dx)\n",
"pre = Preconditioner(a, \"h1amg\")\n",
"with TaskManager():\n",
" a.Assemble();\n",
" lam = EigenValues_Preconditioner(a.mat, pre.mat)\n",
" print (list(lam[0:3]), '...', list(lam[-3:-1]))"
]
},
{
"cell_type": "markdown",
"id": "324f60ab-cfe5-4485-848f-e967635fcd12",
"metadata": {},
"source": [
"## low order AMG, high order smoothing "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f74994e-5afb-4051-b973-8f90e96d6e69",
"metadata": {},
"outputs": [],
"source": [
"from ngsolve import *\n",
"from ngsolve.la import EigenValues_Preconditioner\n",
"ngsglobals.msg_level=0\n",
"\n",
"with TaskManager():\n",
" mesh = Mesh(unit_cube.GenerateMesh(maxh=0.05))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f20e994-a4a2-4ea7-81ea-9995d4f5b16f",
"metadata": {},
"outputs": [],
"source": [
"# ngsglobals.msg_level=3\n",
"\n",
"fes = H1(mesh, order=3) \n",
"print (\"ndof=\", fes.ndof)\n",
"u,v = fes.TnT()\n",
"a = BilinearForm(grad(u)*grad(v)*dx + 1e-3*u*v*dx)\n",
"pre = Preconditioner(a, \"multigrid\", coarsetype=\"h1amg\")\n",
"with TaskManager():\n",
" a.Assemble()\n",
" lam = EigenValues_Preconditioner(a.mat, pre.mat)\n",
" print (list(lam[0:3]), '...', list(lam[-3:-1]))"
]
},
{
"cell_type": "markdown",
"id": "b1ccb71e-1950-4fa4-99e0-8a461c08c39a",
"metadata": {},
"source": [
"Exercises:\n",
"* test the `h1amg` preconditioner with more complicated domains, varying coefficients across sub-domains, and additional $L_2(\\Omega)$ and $L_2(\\Gamma)$-terms"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ccdb0fb8-226f-469b-9565-fea23c8451dc",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}