2.2. A first example in NGSove#
This first notebook introduces in few basic steps how to solve a simple PDE problem using NGSolve; each step contains different objects that will be explained in detail in the following notebooks.
To solve the problem one need to consider the following steps:
Define the domain \(\Omega\), name the boundaries and mesh it.
Define the finite element space and reserve space for the solution.
Define the linear and bilinear forms and assemble respective matrix and a vector.
Solve the linear system. (We may need homogeneization techniques)
2.2.1. Poisson problem on the unit square#
The first problem we are going to solve a problem on the unit square \(\Omega = (0,1)^2\), we call the boundaries \(\Gamma_b, \Gamma_t, \Gamma_l, \Gamma_r\) the bottom, top, left and right boundaries respectively.
Find \(u\) such that
2.2.1.1. Step 0: Import the necessary libraries#
ngsolve
is the library that contains the finite element methods and the solvers.Draw
is a function that allows us to visualize the results in the notebook.
from ngsolve import *
from ngsolve.webgui import Draw # Drawing in Jupyter notebook. More about this later.
2.2.1.2. Step 1: Define the domain#
netgen_mesh = unit_square.GenerateMesh(maxh=0.2) # generate a netgen mesh from the OCCGeometry class
mesh = Mesh(netgen_mesh) # create a NGSolve mesh class
Draw(mesh); # names are predefined for this case
2.2.1.3. Step 2: Define the finite element space and solution vector#
We need to create the Lagrange finite element space of order 1 on the mesh, to do so me need to pass a mesh and the essential boundary conditions.
The essential boundaries are selected using the dirichlet
flag. In particular we select the bottom and (united with) the left boundary using the regex bottom|left
.
fes = H1(mesh, order = 1, dirichlet="bottom|left") # define the finite element space
With the use of a finite element space one can create a GridFunction
that will be used to store the solution.
For now you can understand the GridFunction
as a vector that stores the values of the solution at the nodes of the mesh.
gfu = GridFunction(fes) # define the grid function: store a function on the finite element space
# help(gfu) # query the grid function
# print(gfu.vec) # print the vector of the grid function
2.2.1.4. Step 3: Define the symbolic forms#
Our problem is to find \(u \) such that
For this specific problem we need to define the bilinear form \(a(u,v)\) and the linear form \(f(v)\). To write it in a math-like way we make use of the ProxyFunctions
:
u
, trial function.v
, test function.
u = fes.TrialFunction() # define the trial function
v = fes.TestFunction() # define the test function
# u, v = fes.TnT() # one-liner to define the trial and test functions
a = BilinearForm(fes) # define the bilinear form
a += grad(u) * grad(v) * dx
a += u*v * dx
# print(a.mat) # print it ... does not work!
a.Assemble();
#print(a.mat) # print the matrix of the bilinear form
in the biliear form we have the dx
that represents the integration over the domain \(\Omega\), it is short hand for DifferentialSymbol(VOL)
.
and grad
that represents the gradient of the symbolic test/trial functions.
We want to declare the right hand side as follows:
$\(
f(v) = \int_{\Omega} 20 \,xy^2\, v \, dx.
\)$
To do so we use the CoefficientFunction
s x
and y
that represent the first two coordinate
f = LinearForm(fes)
f += 20 * x * y ** 2 * v * dx
f.Assemble();
# print(f.vec) # print the vector of the linear form. RHS is zero so far.
2.2.1.5. Step 4: Solve the linear system#
So far our problem was defined only symbolically, the only computation that was done was in the creation of the mesh. To actively solve the problem we need to assemble the linear system.
We can now invert the matrix and solve the linear system.
inv = a.mat.Inverse(freedofs=fes.FreeDofs()) # compute the inverse of the matrix
gfu.vec.data = inv * f.vec # solve the system
What freedofs does is to select the degrees of freedom that are not in the essential boundary conditions.
Draw(gfu); # draw the solution
2.2.2. Interact with NGSolve#
A jupyter notebook (like this one) gives you one way to interact with NGSolve. When you have a complex sequence of tasks to perform, the notebook may not be adequate.
You can write an entire python module in a text editor and call python on the command line. (A script of the above is provided in
poisson.py
.)python3 poisson.py
If you want the Netgen GUI, then use
netgen
on the command line:netgen poisson.py
You can then ask for a python shell from the GUI’s menu options (
Solve -> Python shell
).One can use the Netgen GUI within a jupyter notebook:
import netgen.gui
.import netgen.gui from ngsolve import * mesh = Mesh(unit_square.GenerateMesh(maxh=0.2)) # generate a mesh Draw(mesh); # draw the mesh
# import netgen.gui
# from ngsolve import *
# mesh = Mesh(unit_square.GenerateMesh(maxh=0.2)) # generate a mesh
# Draw(mesh); # draw the mesh