FreeFEM Documentation
FreeFEM Documentation
FreeFEM Documentation
Release 4.8
Frederic Hecht
i
ii
CONTENTS
1 Introduction 3
1.1 Version 4.5: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Version 4.6: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Version 4.7: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 Version 4.7-1: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.5 Version 4.8: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 Version 4.9: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.7 Version 4.10: new features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.8 Version 4.11: new features (4 apr 2022) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.9 Installation guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.10 Download . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.11 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.12 Citation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.13 Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.14 Contributing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2 Learning by Examples 31
2.1 Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2 Classification of partial differential equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.3 Membrane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.4 Heat Exchanger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.5 Acoustics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.6 Thermal Conduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.7 Irrotational Fan Blade Flow and Thermal effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.8 Pure Convection : The Rotating Hill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.9 The System of elasticity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.10 The System of Stokes for Fluids . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.11 A projection algorithm for the Navier-Stokes equations . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.12 Newton Method for the Steady Navier-Stokes equations . . . . . . . . . . . . . . . . . . . . . . . . 71
2.13 A Large Fluid Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
2.14 An Example with Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
2.15 Optimal Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
2.16 A Flow with Shocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
2.17 Time dependent schema optimization for heat equations . . . . . . . . . . . . . . . . . . . . . . . . 88
2.18 Tutorial to write a transient Stokes solver in matrix form . . . . . . . . . . . . . . . . . . . . . . . . 91
2.19 Wifi Propagation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
2.20 Plotting in Matlab and Octave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
3 Documentation 105
3.1 Notations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
iii
3.2 Mesh Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3.3 Finite element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
3.4 Visualization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
3.5 Algorithms & Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
3.6 Parallelization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
3.7 PETSc and SLEPc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
3.8 The Boundary Element Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
3.9 Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
3.10 Developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
3.11 ffddm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
6 Examples 641
6.1 Misc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
6.2 Mesh Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648
6.3 Finite Element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
6.4 Visualization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
6.5 Algorithms & Optimizations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
6.6 Parallelization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 682
6.7 Developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Bibliography 727
iv
FreeFEM Documentation, Release 4.8
CONTENTS 1
FreeFEM Documentation, Release 4.8
2 CONTENTS
CHAPTER
ONE
INTRODUCTION
FreeFEM is a partial differential equation solver for non-linear multi-physics systems in 1D, 2D, 3D and 3D border
domains (surface and curve).
Problems involving partial differential equations from several branches of physics, such as fluid-structure interactions,
require interpolations of data on several meshes and their manipulation within one program. FreeFEM includes a fast
interpolation algorithm and a language for the manipulation of data on multiple meshes.
FreeFEM is written in C++ and its language is a C++ idiom.
3
FreeFEM Documentation, Release 4.8
4 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
• Since the version 4.5, the FreeFEM binary packages provides with a compiled PETSc library.
• FreeFEM is now interfaced with ParMmg.
After Surface FEM, Line FEM is possible with a new mesh type meshL, P0 P1 P2 P1dc FE, basic FEM, mesh generation.
This new development allows to treat a 1d problem, such as a problem described on a 3d curve.
Allows to define and solve a 2d/3d BEM formulation and rebuild the associated potential. The document is in con-
struction.
• Added
– new search algorithm for the element containing a point (more safe) in mesh of type mesh3, meshS, or
meshL.
– new function hasType to know if a PETSc component has been installed, e.g., hasType("PC", "hypre")
– eigenvalue problems on linear elements, cf. examples/eigen/LapEigen1DBeltrami.edp or
examples/hpddm/laplace-beltrami-3d-line-SLEPc.edp
– –download-cmake in PETSc configure if there is no CMake available
– flags –with-[slepc|slepccomplex]-include and –with-[slepc|slepccomplex]-ldflags for when SLEPc has
been built outside of FreeFEM or PETSc
– interface to KSPSetResidualHistory and KSPGetIterationNumber
– interface to mpiWaitAll
– new function extract, allows to build a curve mesh from a 2d mesh (can extract a labeled boundary, apply
a geometric transformation)
– ffglut can plot a vectorial FE function in surface 3d
– distributed ParMmg interface, cf. examples/hpddm/distributed-parmmg.edp or examples/hpddm/
laplace-adapt-dist-3d-PETSc.edp
– new parallel interpolator on non-matching meshes, cf. examples/hpddm/transfer.edp
– ability to solve problems in single precision or with 64 bit integers
– tool to read data form vtk file only in 3d (cf. plugin iovtk a first example examples/plugin/iovtk.edp)
– tool to read/wrile ply file of meshL, mesh3, MeshS
[Polygon File Format / Stanford Triangle Format do load “ioply”] see examples//3dSurf/
operatorsOnMeshS.edp
• Changed
– new tgv values: -10 => zero row, -20 => zero row/column
– Windows binary now shipped with PETSc/SLEPc
– BEM examples are now in examples/mpi
– plot border type is now in 3d (border 2d and 3d)
– PETSc version 3.13.0
• Fixed
6 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
• Added
– new way to build matrix between 2d Finite element 2d and Curve finite element to do mortar (Thank to
Axel ) , see first example examples/tutorial/mortar-DN-4-v4.5.edp
– add Ns normal vector in R^3 on meshS (normal of the surface) of current point (to day Ns of [x,y,0] plan
is [0,0,-1]) no be compatible to exterior normal.
– add Tl tangent vector in R^3 on meshL (tangent vector of the line/curve) of current point
– compile ffmaster / ffslave example under windows (thanks to [email protected])
– Boolean parameter spiltpbedge in buildmesh to split in to edge with two boundary vertices
– interface to PETSc DMPlex, see examples/hpddm/DMPlex-PETSc.edp
– function MatDestroy
– function MatPtAP and transferMat for parallel interpolation between non-matching grids, see
examples/hpddm/PtAP-2d-PETSc.edp or examples/hpddm/diffusion-mg-2d-PETSc.edp
– preliminary interface to SVDSolve from SLEPc to compute singular value decompositions, see
examples/hpddm/mf-2d-SLEPc.edp or examples/hpddm/helmholtz-2d-SLEPc-complex.edp
– preliminary interface to NEPSolve from SLEPc to solve nonlinear eigenvalue problems, see
examples/hpddm/nonlinear-2d-SLEPc-complex.edp
– transpose parameter when constructing a Mat for defining a matrix-free transposed operation
– interface to PetscMemoryGetCurrentUsage
– add P2b, RT0, RT1 surface FE (P2bS, RT0S, RT1S))
– add operator interpolate (2d->3d surface)
– add operator x = A’*b; where x, b are array and A 2 dim array (full matrix) and generate an error in case
of b’*A or b’*A expression
– function MatLoad to load a PETSc Mat from disk, see examples/hpddm/MatLoad-PETSc.edp
– possibility to assemble a symmetric HMatrix<complex> and to densify a HMatrix<complex> into a
Mat<complex>
• Changed
– moved Htool to its new GitHub location
– ScaLAPACK and MUMPS are not compiled by PETSc anymore if there is no Fortran compiler
• Changed
– change the language definition to use type as a construction function with named arguments for bem plugin
– PETSc version 3.14.0
– ARPACK compiled by SLEPc
– Mmg version 5.5.0
– -std=c++14 instead of -std=c++11 when possible
• Removed
– plugins thresholdings, symmetrizeCSR, and fflapack and associed example
• Fixed
– problem compilation with gfortran-10 of arpack and mumps (add -fallow-argument-mismatch flags)
8 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
• Added
– Bilaplacian example using Morley FE with PETSc, see examples/hpddm/bilaplacian-2d-PETSc.edp
– Oseen problem preconditioned by PCD, see examples/hpddm/oseen-2d-PETSc.edp
– SLEPc polynomial eigenvalue solver PEPSolve()
– add trivial example to check periodic boundary condition on meshS , meshL , mesh3 exam-
ples/3d/periodic3.edp examples/3dSurf/periodicS.edp examples/3dCurve/periodicL.edp
• Changed
– PETSc version 3.14.2
– Mmg version 5.5.2
– link of ffglut so change in configure.ac and Makefile.am LIBS -> FF_LIBS and LIBS become empty to
remove default libs
– change number of save plot in ffglut from 10 to 20 for O. Pironneau
• Fixed
– some memory leaks
– the periodic boundary condition have wrong before first a sementic level of MeshS and MeshL case.
the new syntexe is for example: meshL Tl=segment(10); fespace Vl(Tl,P1,periodic=[[1],[2]]); meshS
Th=square3(10,10,[x*2*pi,y*2*pi]); fespace Vh2(Th,P1,periodic=[[1,x],[3,x],[2,y],[4,y]]);
– fixed ‘*’ keyboard trick, to keep the viewpoint in ffglut or not.
• Added
– add P3 lagrange finite element on meshS and meshS
– add new plugin meshtool`to add tool to compute the number of connected components
of a all kind of mesh (mesh,mesh3,meshS,meshL) with 2 kind of connected
components ones on interior part of the mesh (default) ans secondly on the
closure of the mesh (see :freefem:`examples/hpddm/bConnectedComponents.edp )
add functions int[int] In=iminP1K(Th,u) or int[int] Ix=imaxP1K(Th,u) get the array min/max of value u[i]
where i is vertex number on each element k, so we have u[Im[k]] = min u[i]/ i in k;
– add in plugin bfstream to to read binary int (4 bytes) to read fortran file and try to pull tools to share the
endiannes in progress
– add gluemesh of array of MeshL and MeshS type
– interface to PC_MG_GALERKIN_BOTH
– Kronecker product of two sparse matrices matrix C = kron(A, B)
– add lot of finite element on Mesh3, MeshS, MeshL of Discontinous Galerling Element in 3d : P1dc3d,
P2dc3d, P3dc3d, P4dc3d , P0edge3d ,P0edgedc3d , P0face3d ,P0facedc3d , P0VF3d ,P0VFdc3d , on Sur-
face : P1dcS, P2dcS, P3dcS, P4dcS , P0edgeS ,P0edgedcS , P0VFS ,P0VFdcS, on Curve : P1dcL, P2dcL,
P3dcL, P4dcL , P0VFL ,P0VFdcL remark; the associated generic name existe of P1dc, P2dc, P0edge, P0VF
and all dc finite element corresponding to no continuity across element.
• Added
– ridgeangle named parameter in ExtractMeshL in msh3 plugin
– DG formulation in 1d : add integral of all border of element : intallBE(ThL) and unified the notation
by adding intallBE(ThS) , intallBE(Th2), intallBE(Th3) nuVertex of now the vertex number
of element in intallBE0d integral BoundaryBE, InternalBE to know if border element (BE) is on true
boundary of not. update nElementonB in case on no manifold data (value greater > 2) in meshL, MeshS
case .. add code to use jump, mean of test functuon on MeshL case. ( not in mesh3 ) to compute RHS.
– add getcwd() function in shell plugin to get the current working dir
– add nuVertex to get the vextex on element in some int?
• Changed
– PETSc 3.16.1
• Deprecated
– SLEPc and SLEPc-complex have been part of PETSc and PETSc-complex for multiple releases and are
now deprecated
• Fixed
– examples/potential.edp correct problem in times loops and BC
– tutorial/mortar-DN-4.edp correct problem of region number in meshL
10 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
• Added
– add computation scalar product of R3 example : ( N’*Tl)
– add tools to do compution with R3 vector see tutorial/calculus.edp
– add an example tutorial/tgv-test.edp see see what tgv do on matrix build.
– add R3 Th.be(k).N to get the normal of boundary element (in all mesh type)
– add R3 Th.be(k)[i].P to get the point (R3) of boundary vertices
– add R3 Th.be(k).measure to get the measure of the boundary elment
– add projection function to a mesh , meshL, MeshS or mesh3 with return a R3 point
– see new example dist-projection.edp example in exemples
– add dxx, dyy, dzz, dxy, .. on P2L finite element
– add tools to compute solid angle
[let R3 O; a given point, Th3 a mesh3 and ThS a meshS.] solidangle(O,Th3.be(ke)) // triangular face
is the boundary face solidangle(O,Th3[k],nuface) // triangular face is face nuface of tet Th3[k] sol-
idangle(O,ThS[k]) // triangular face is ThS[k] solidangle(O,A,B,C) // triangular face i (A,B,C) Vol-
ume(O,Th3.be(ke)) // O, triangular face is the boundary face Volume(O,Th3[k],nuface) // O, triangular
face is face nuface of tet Th3[k] Volume(O,ThS[k]) // O, triangular face is ThS[k] Volume(O,A,B,C)
// (O,A,B,C) tet ..
– in bem pluging add array of HMatrix
– examples/3d/Connectivite-3d.edp or /3dSurf/Connectivite-S.edp of test.
– 3 function mapk, mapkk, mapkk to set a function in fourier space with k parametre
R3 K; // le fourier variable allway 3d (sorry) int n1=16,n2=8, n3=4; real[int]
tab1(nx,tab2(nx*ny),tab3(nx*ny*nz); mapk(tab1,K,sqr(K.x)); mapkk(tab2,ny,K,K.norm2); map-
kkk(tab3,ny,nz,K,K.norm2); // Remark you can change K by P (current point)
– in SurfaceMesh.ipd fonction to build a Isocaedron and a Sphere from this Isocaedron
– new finite element on MeshS this finite element is the ortogonal of RT0 on surface, or Nelelec Finite
Element on triangle with one DoF per mesh edge and where the DoF is the current on Edge in orientate
edge by number of vertices.
– plugin Element_P3pnc for new 2d finite element P3pnc (P3 + 2 bulles) noncoforming (continuite of P2
mod)
and add 2 examples with this new finite element
examples/plugin/cavityNewtowP3pnc.edp examples/plugin/testFE-P3pnc.edp
– function to set dirichlet Boundary conditon on matrix A (real ou compex) trought an real[int]
(if none zero => set BC )
setBC(A,au1[],-2); and the example
examples/3d/Elasticity-simple-support-BC.edp
• Changed
– the beaviour of linear solver UMFPACK, CHOLMOD in case of error , now FreeFEm exit on ExecError
like in MUMPS
– PETSc 3.17.0
• Removed
-map function in plugin dfft
• Fixed
– pow(int,int) now call int version not complex version..
– correct the normal the N implicite variable on meshL case
– correct version dump in banner FreeFem++ - version 4.10 (V . . .
– correct in CPU time on big mesh due to do bad HCode in HashTable.hpp
– bug in array of finite element on meshhS, meshL (ie. fespace Vh(ThS,[P1,P1]); )
To use FreeFEM, two installation methods are available: user access (binary package) and access developers (from the
source code). Follow the section corresponding to your type of installation.
Note: Since the version 4.5, FreeFEM relese provides with the last version of PETSc.
First, open the following web page download page and choose your platform: Linux, MacOS or Windows.
Note: Binary packages are available for Microsoft Windows, MacOS and some Linux distributions. Since the release
4.5, FreeFEM binaries provide with the current version of PETSc.
Install FreeFEM by double-clicking on the appropriate file. Under Linux and MacOS the install directory is one of
the following /usr/local/bin, /usr/local/share/freefem++, /usr/local/lib/ff++
12 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
Windows installation
Note: The windows package is build for Window 7 64bits. The support ended for all releases under Windows 32 bits
since the V4.
First download the windows installation executable, then double click to install FreeFEM. Install MSMPI for parallel
version under window64 MS MPI V10.1.2, and install both msmpisdk.msi and MSMpiSetup.exe.
In most cases just answer yes (or type return) to all questions.
Otherwise in the Additional Task windows, check the box “Add application directory to your system path.” This is
required otherwise the program ffglut.exe will not be found.
By now you should have two new icons on your desktop:
• FreeFem++ (VERSION).exe, the freefem++ application.
• FreeFem++ (VERSION) Examples, a link to the freefem++ examples folder.
where (VERSION) is the version of the files (for example 4.5).
By default, the installed files are in C:\Programs Files\FreeFem++. In this directory, you have all the .dll files
and other applications: FreeFem++-nw.exe, ffglut.exe, . . . The syntax for the command-line tools are the same
as those of FreeFem.exe.
To use FreeFEM binaries under Windows, two methods are possible:
• Use the FreeFEM launcher (launchff++.exe)
Warning: if you launch FreeFEM without filename script by double-clicking, your get a error due (it is bug of usage
GetOpenFileName in win64).
• In shell terminal (cmd, powershell, bash, . . . ):
– To launch sequential version:
macOS X installation
Download the macOS X binary version file, extract all the files by double clicking on the icon of the file, go the the
directory and put the FreeFem++.app application in the /Applications directory.
If you want terminal access to FreeFEM just copy the file FreeFem++ in a directory of your $PATH shell environment
variable.
Ubuntu installation
Beforehand, install the following dependances libraries using the apt tool:
1 dpkg -i FreeFEM_VERSION_Ubuntu_withPETSc_amd64.deb
An up-to-date package of FreeFEM for Arch is available on the Archlinux user repository.
To install it:
Fedora installation
Packages are available in the Fedora Repositories, and they are managed by the Fedora SciTech special interest group.
The packages are usually recent builds, but may not be the latest released version.
You can install them using the dnf tool, for both the serial and parallel (MPI) versions. :
FreeFEM is directly available in your terminal by the command “FreeFem++”. To use the OpenMPI version, in your
terminal first load the OpenMPI module, for example using
and then the command “FreeFem++-mpi_openmpi” will be available in your terminal. To use the MPICH version, in
your terminal first load the MPICH module using
14 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
Note: We advise you to use the package manager for macOS Homebrew to get the different packages
required avalaible here
1. Install Xcode, Xcode Command Line tools and Xcode Additional Tools from the Apple website
2. Install gfortran from Homebrew
Note: If you have installed gcc via brew, gfortran comes with it and you do not need this line
1 # to install openmpi
2 curl -L https://download.open-mpi.org/release/open-mpi/v4.0/openmpi-4.0.1.tar.gz --
˓→output openmpi-4.0.1.tar.gz
3 tar xf openmpi-4.0.1
4 cd openmpi-4.0.1/
5 # to install mpich
6 curl -L https://www.mpich.org/static/downloads/4.0.2/mpich-4.0.2.tar.gz --output␣
˓→mpich-4.0.2.tar.gz
7 tar xf mpich-4.0.2.tar.gz
8 cd mpich-4.0.2
˓→have/files/installed
˓→have/files/installed
5 make -j<nbProcs>
6 make install
5. If you want build your own configure according your system, install autoconf and automake from Homebrew
(optional, see note in step 10)
6. To use FreeFEM with its plugins, install from Homebrew suitesparse, hdf5, cmake, wget
7. Install gsl
1 curl -O https://mirror.ibcp.fr/pub/gnu/gsl/gsl-2.7.tar.gz
2 tar zxvf gsl-2.7.tar.gz
3 cd gsl-2.7
4 ./configure
5 make -j<nbProcs>
6 make install --prefix=/where/you/want/to/have/files/installed
8. Download the latest Git for Mac installer git and the FreeFEM source from the repository
1 cd FreeFem-sources
2 autoreconf -i
1 ./3rdparty/getall -a
Note: All the third party packages have their own licence
11. If you want use PETSc/SLEPc and HPDDM (High Performance Domain Decomposition Methods)
1 cd 3rdparty/ff-petsc
2 make petsc-slepc // add SUDO=sudo if your installation directory is the␣
˓→default /usr/local
3 cd -
4 ./reconfigure
16 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
1 make -j<nbProcs>
2 make -j<nbProcs> check
Note: make check is optional, but advised to check the validity of your FreeFEM build
Note: it isn’t necessary to execute this last command, FreeFEM executable is avalaible here
your_installation/src/nw/FreeFem++ and mpi executable here your_installation/src/mpi/ff-mpirun.
Compilation on Ubuntu
Warning: In the oldest distribution of Ubuntu, libgsl-dev does not exist, use libgsl2-dev instead
3. Autoconf
1 cd FreeFem-sources
2 autoreconf -i
4. Configure
1 ./3rdparty/getall -a
Note: All the third party packages have their own licence
6. If you want use PETSc/SLEPc and HPDDM (High Performance Domain Decomposition Methods) for massively
parallel computing
1 cd 3rdparty/ff-petsc
2 make petsc-slepc // add SUDO=sudo if your installation directory is the default /
˓→usr/local
3 cd -
4 ./reconfigure
1 make -j<nbProcs>
2 make -j<nbProcs> check
Note: make check is optional, but advised to check the validity of your FreeFEM build
1 make install
Note: it isn’t necessary to execute this last command, FreeFEM executable is avalaible here
your_installation/src/nw/FreeFem++ and mpi executable here your_installation/src/mpi/ff-mpirun
Warning: As Arch is in rolling release, the following information can be quickly outdated !
Warning: FreeFEM fails to compile using the newest version of gcc 8.1.0, use an older one instead.
1 pacman -Syu
2 pacman -S git openmpi gcc-fortran wget python
3 freeglut m4 make patch gmm
4 blas lapack hdf5 gsl fftw arpack suitesparse
5 gnuplot autoconf automake bison flex gdb
6 valgrind cmake texlive-most
18 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
3. Autoconf
1 cd FreeFem-sources
2 autoreconf -i
4. Configure
1 ./3rdparty/getall -a
Note: All the third party packages have their own licence
6. If you want use HPDDM (High Performance Domain Decomposition Methods) for massively parallel computing,
install PETSc/SLEPc
1 cd 3rdparty/ff-petsc
2 make petsc-slepc SUDO=sudo
3 cd -
4 ./reconfigure
1 make
Note: If your computer has many threads, you can run make in parallel using make -j16 for 16 threads, for
example.
Compilation on Fedora
3. Autoconf
1 cd FreeFem-sources
2 autoreconf -i
4. Configure
1 ./3rdparty/getall -a
Note: All the third party packages have their own licence
6. If you want use PETSc/SLEPc and HPDDM (High Performance Domain Decomposition Methods) for massively
parallel computing
1 cd 3rdparty/ff-petsc
2 make petsc-slepc // add SUDO=sudo if your installation directory is the default /
˓→usr/local
20 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
1 make -j<nbProcs>
2 make -j<nbProcs> check
Note: make check is optional, but advised to check the validity of your FreeFEM build
1 make install
Note: it isn’t necessary to execute this last command, FreeFEM executable is avalaible here
your_installation/src/nw/FreeFem++ and mpi executable here your_installation/src/mpi/ff-mpirun
Compilation on Windows
Warning: The support ended for all releases under Windows 32 bits since the V4. We assume your development
machine is 64-bit, and you want your compiler to target 64-bit windows by default.
1 pacman -Syuu
Close the MSYS2 shell once you’re asked to. There are now 3 MSYS subsystems installed: MSYS2, MinGW32
and MinGW64. They can respectively be launched from C:devmsys64msys2.exe, C:devmsys64mingw32.exe and
C:devmsys64mingw64.exe Reopen MSYS2 (doesn’t matter which version, since we’re merely installing packages).
Repeatedly run the following command until it says there are no further updates. You might have to restart your shell
again.
1 pacman -Syuu
6. Open a MingW64 terminal (or MingW32 for old 32 bit FreeFEM version) and compile the FreeFEM source
7. If you want use HPDDM (High Performance Domain Decomposition Methods) for massively parallel computing,
install PETSc/SLEPc
1 cd 3rdparty/ff-petsc
2 make petsc-slepc SUDO=sudo
3 cd -
4 ./reconfigure
8. Download the 3rd party packages and build your FreeFEM library and executable
1 ./3rdparty/getall -a
2 make
3 make check
4 make install
Note: The FreeFEM executable (and some other like ffmedit, . . . ) are in C:\msys64\mingw64\bin (or
C:\msys32\mingw32\bin).
22 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
FreeFEM reads a user’s init file named freefem++.pref to initialize global variables: verbosity, includepath,
loadpath.
Note: The variable verbosity changes the level of internal printing (0: nothing unless there are syntax errors, 1:
few, 10: lots, etc. . . . ), the default value is 2.
The included files are found in the includepath list and the load files are found in the loadpath list.
1 verbosity = 5
2 loadpath += "/Library/FreeFem++/lib"
3 loadpath += "/Users/hecht/Library/FreeFem++/lib"
4 includepath += "/Library/FreeFem++/edp"
5 includepath += "/Users/hecht/Library/FreeFem++/edp"
6 # This is a comment
7 load += "funcTemplate"
8 load += "myfunction"
9 load += "MUMPS_seq"
1 /etc/freefem++.pref
2 $(HOME)/.freefem++.pref
3 freefem++.pref
• under windows
1 freefem++.pref
We can also use shell environment variables to change verbosity and the search rule before the init files.
1 export FF_VERBOSITY=50
2 export FF_INCLUDEPATH="dir;;dir2"
3 export FF_LOADPATH="dir;;dir3"
Note: The separator between directories must be “;” and not “:” because “:” is used under Windows.
1 export FF_VERBOSITY=100;
2 ./FreeFem++-nw
Atom
In order to get the syntax highlighting in Atom, you have to install the FreeFEM language support.
You can do it directly in Atom: Edit -> Preferences -> Install, and search for language-freefem-offical.
To launch scripts directly from Atom, you have to install the atom-runner package. Once installed, modify the Atom
configuration file (Edit -> Config. . . ) to have something like that:
1 "*":
2 ...
3
4 runner:
5 extensions:
6 edp: "FreeFem++"
7 scopes:
8 "Freefem++": "FreeFem++"
Gedit
In order to get the syntax highlighting in Gedit, you have to downlaod the Gedit parser and copy it in /usr/share/
gtksourceview-3.0/language-specs/.
To use the coloring FreeFEM syntax with the Textmate 2 editor on Mac 10.7 or better, download from macromates.com
and download the textmate freefem++ syntax here (version june 2107). To install this parser, unzip Textmate2-ff++.zip
and follow the explanation given in file How_To.rtf.
rom www.freefem.org/ff++/Textmate2-ff++.zip (version june 2107) unzip Textmate2-
Emacs editor
24 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
1.10 Download
Note: The support ended for all releases under Windows 32 bits.
1.11 History
The project has evolved from MacFem, PCfem, written in Pascal. The first C version lead to freefem
3.4; it offered mesh adaptivity on a single mesh only.
A thorough rewriting in C++ led to freefem+ (freefem+ 1.2.10 was its last release), which included
interpolation over multiple meshes (functions defined on one mesh can be used on any other mesh); this
software is no longer maintained but is still in use because it handles a problem description using the strong
form of the PDEs. Implementing the interpolation from one unstructured mesh to another was not easy
because it had to be fast and non-diffusive; for each point, one had to find the containing triangle. This
is one of the basic problems of computational geometry (see [PREPARATA1985] for example). Doing it
in a minimum number of operations was the challenge. Our implementation is 𝒪(𝑛𝑙𝑜𝑔𝑛) and based on a
quadtree. This version also grew out of hand because of the evolution of the template syntax in C++.
We have been working for a few years now on FreeFEM , entirely re-written again in C++ with a thorough
usage of template and generic programming for coupled systems of unknown size at compile time. Like
1.10. Download 25
FreeFEM Documentation, Release 4.8
all versions of freefem, it has a high level user friendly input language which is not too far from the
mathematical writing of the problems.
The freefem language allows for a quick specification of any partial differential system of equa-
tions. The language syntax of FreeFEM is the result of a new design which makes use of the
STL [STROUSTRUP2000], templates, and bison for its implementation; more details can be found in
[HECHT2002]. The outcome is a versatile software in which any new finite elements can be included
in a few hours; but a recompilation is then necessary. Therefore the library of finite elements available
in FreeFEM will grow with the version number and with the number of users who program more new
elements. So far we have discontinuous 𝑃0 elements,linear 𝑃1 and quadratic 𝑃2 Lagrangian elements,
discontinuous 𝑃1 and Raviart-Thomas elements and a few others like bubble elements.
1987
MacFem/PCFem the old ones (O. Pironneau in Pascal) no free.
1992
FreeFem rewrite in C++ (P1,P0 one mesh ) O. Pironneau, D. Bernardi, F.Hecht (mesh adaptation , bamg)
, C. Prudhomme .
1996
FreeFem+ rewrite in C++ (P1,P0 more mesh) O. Pironneau, D. Bernardi, F.Hecht (algebra of function).
1998
FreeFem++ rewrite with an other finite element kernel and an new language F. Hecht, O. Pironneau,
K.Ohtsuka.
1999
FreeFem 3d (S. Del Pino), a fist 3d version base on fictitious domaine method.
2008
FreeFem++ v3 use a new finite element kernel multidimensionnels: 1d,2d,3d. . .
2014
FreeFem++ v3.34 parallel version
2017
FreeFem++ v3.57 parallel version
2018
FreeFem++ v4: New matrix type, Surface element, New Parallel tools . . .
26 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
1.12 Citation
1.12.1 If you use FreeFEM, please cite the following reference in your work:
BibTeX
1 @article{MR3043640,
2 AUTHOR = {Hecht, F.},
3 TITLE = {New development in FreeFem++},
4 JOURNAL = {J. Numer. Math.},
5 FJOURNAL = {Journal of Numerical Mathematics},
6 VOLUME = {20}, YEAR = {2012},
7 NUMBER = {3-4}, PAGES = {251--265},
8 ISSN = {1570-2820},
9 MRCLASS = {65Y15},
10 MRNUMBER = {3043640},
11 URL = {https://freefem.org/}
12 }
APA
ISO690
MLA
1.13 Authors
Frédéric Hecht
Professor at Laboratoire Jacques Louis Lions (LJLL), Sorbonne University, Paris
[email protected]
https://www.ljll.math.upmc.fr/hecht/
Sylvain Auliac
Former PhD student at LJLL, optimization interface with nlopt, ipopt, cmaes, . . .
https://www.ljll.math.upmc.fr/auliac/
1.12. Citation 27
FreeFEM Documentation, Release 4.8
Olivier Pironneau
Professor of numerical analysis at the Paris VI university and at LJLL, numerical methods in fluid
Member of the Institut Universitaire de France and Academie des Sciences
https://www.ljll.math.upmc.fr/pironneau/
Jacques Morice
Former Post-Doc at LJLL, three dimensions mesh generation and coupling with medit
Antoine Le Hyaric
CNRS research engineer at Laboratoire Jacques Louis Lions, expert in software engineering for scientific applica-
tions, electromagnetics simulations, parallel computing and three-dimensionsal visualization
https://www.ljll.math.upmc.fr/lehyaric/
Kohji Ohtsuka
Professor at Hiroshima Kokusai Gakuin University, Japan and chairman of the World Scientific and Engineering
Academy and Society, Japan. Fracture dynamic, modeling and computing
https://sites.google.com/a/comfos.org/comfos/
Pierre-Henri Tournier
CNRS research engineer at Laboratoire Jacques Louis Lions (LJLL), Sorbonne University, Paris
Pierre Jolivet
CNRS researcher, MPI interface with PETSc, HPDDM, . . .
http://jolivet.perso.enseeiht.fr/
Frédéric Nataf
CNRS senior researcher at Laboratoire Jacques Louis Lions (LJLL), Sorbonne University, Paris
https://www.ljll.math.upmc.fr/nataf/
Simon Garnotel
Reasearch engineer at Airthium
https://github.com/sgarnotel
Karla Pérez
Developer, Airthium internship
https://github.com/karlaprzbr
Loan Cannard
Web designer, Airthium internship
https://www.linkedin.com/in/loancannard
28 Chapter 1. Introduction
FreeFEM Documentation, Release 4.8
1.14 Contributing
Ask one of the contributors for Collaborator Access or make a Pull Request.
1.14. Contributing 29
FreeFEM Documentation, Release 4.8
30 Chapter 1. Introduction
CHAPTER
TWO
LEARNING BY EXAMPLES
The FreeFEM language is typed, polymorphic and reentrant with macro generation.
Every variable must be typed and declared in a statement, that is separated from the next by a semicolon ;.
The FreeFEM language is a C++ idiom with something that is more akin to LaTeX.
For the specialist, one key guideline is that FreeFEM rarely generates an internal finite element array, this was adopted
for speed and consequently FreeFEM could be hard to beat in terms of execution speed, except for the time lost in the
interpretation of the language (which can be reduced by a systematic usage of varf and matrix instead of problem).
The Development Cycle: Edit–Run/Visualize–Revise
Many examples and tutorials are given there after and in the examples section. It is better to study them and learn by
example.
If you are a beginner in the finite element method, you may also have to read a book on variational formulations.
The development cycle includes the following steps:
Modeling: From strong forms of PDE to weak forms, one must know the variational formulation to use FreeFEM;
one should also have an eye on the reusability of the variational formulation so as to keep the same internal matrices; a
typical example is the time dependent heat equation with an implicit time scheme: the internal matrix can be factorized
only once and FreeFEM can be taught to do so.
Programming: Write the code in FreeFEM language using a text editor such as the one provided in your integrated
environment.
Run: Run the code (here written in file mycode.edp). That can also be done in terminal mode by :
1 FreeFem++ mycode.edp
Visualization: Use the keyword plot directly in mycode.edp to display functions while FreeFEM is running. Use
the plot-parameter wait=1 to stop the program at each plot.
Debugging: A global variable debug (for example) can help as in wait=true to wait=false.
31
FreeFEM Documentation, Release 4.8
13 fespace Vh(Th,P2);
14 Vh f = sin(pi*x)*cos(pi*y);
15 Vh g = sin(pi*x + cos(pi*y));
16
Changing debug to false will make the plots flow continuously. Watching the flow of graphs on the screen (while
drinking coffee) can then become a pleasant experience.
Error management
Error messages are displayed in the console window. They are not always very explicit because of the template structure
of the C++ code (we did our best!). Nevertheless they are displayed at the right place. For example, if you forget
parenthesis as in:
1 2 : mesh Th = square(10,10;
2 Error line number 2, in file bb.edp, before token ;
3 parse error
4 current line = 2
5 syntax error
6 current line = 2
7 Compile error : syntax error
8 line number :2, ;
9 error Compile error : syntax error
10 line number :2, ;
11 code = 1 mpirank: 0
1 real aaa = 1;
2 real aaa;
If you find that the program isn’t doing what you want you may also use cout to display in text format on the console
window the value of variables, just as you would do in C++.
The following example works:
1 ...
2 fespace Vh(Th, P1);
3 Vh u;
4 cout << u;
5 matrix A = a(Vh, Vh);
6 cout << A;
We will compute 𝑢 with 𝑓 (𝑥, 𝑦) = 𝑥𝑦 and Ω the unit disk. The boundary 𝐶 = 𝜕Ω is defined as:
Note: In FreeFEM, the domain Ω is assumed to be described by the left side of its boundary.
11 // Define a function f
12 func f= x*y;
13
As illustrated in Fig. 2.1b, we can see the isovalue of 𝑢 by using FreeFEM plot command (see line 29 above).
Note: The qualifier solver=LU (line 18) is not required and by default a multi-frontal LU is used.
The lines containing clock are equally not required.
for all 𝑣 which are in the finite element space 𝑉ℎ and zero on the boundary 𝐶.
This first example shows how FreeFEM executes with no effort all the usual steps required by the finite element method
(FEM). Let’s go through them one by one.
On the line 2:
∑︀𝐽
The boundary Γ is described analytically by a parametric equation for 𝑥 and for 𝑦. When Γ = 𝑗=0 Γ𝑗 then each
curve Γ𝑗 must be specified and crossings of Γ𝑗 are not allowed except at end points.
The keyword label can be added to define a group of boundaries for later use (boundary conditions for instance).
Hence the circle could also have been described as two half circle with the same label:
Boundaries can be referred to either by name (Gamma1 for example) or by label (C here) or even by its internal number
here 1 for the first half circle and 2 for the second (more examples are in Meshing Examples).
On the line 5
The triangulation 𝒯ℎ of Ω is automatically generated by buildmesh(C(50)) using 50 points on C as in Fig. 2.1a.
The domain is assumed to be on the left side of the boundary which is implicitly oriented by the parametrization. So
an elliptic hole can be added by typing:
then the inside of the ellipse would be triangulated as well as the outside.
Note: Automatic mesh generation is based on the Delaunay-Voronoi algorithm. Refinement of the mesh are done by
increasing the number of points on Γ, for example buildmesh(C(100)), because inner vertices are determined by the
density of points on the boundary.
Mesh adaptation can be performed also against a given function f by calling adaptmesh(Th,f).
Now the name 𝒯ℎ (Th in FreeFEM) refers to the family {𝑇𝑘 }𝑘=1,··· ,𝑛𝑡 of triangles shown in Fig. 2.1a.
Traditionally ℎ refers to the mesh size, 𝑛𝑡 to the number of triangles in 𝒯ℎ and 𝑛𝑣 to the number of vertices, but it is
seldom that we will have to use them explicitly.
If Ω is not a polygonal domain, a “skin” remains between the exact domain Ω and its approximation Ωℎ = ∪𝑛𝑘=1
𝑡
𝑇𝑘 .
However, we notice that all corners of Γℎ = 𝜕Ωℎ are on Γ.
On line 8:
A finite element space is, usually, a space of polynomial functions on elements, triangles here only, with certain match-
ing properties at edges, vertices etc. Here fespace Vh(Th, P1) defines 𝑉ℎ to be the space of continuous functions
which are affine in 𝑥, 𝑦 on each triangle of 𝑇ℎ .
As it is a linear vector space of finite dimension, basis can be found. The canonical basis is made of functions, called
the hat function 𝜑𝑘 , which are continuous piecewise affine and are equal to 1 on one vertex and 0 on all others. A
typical hat function is shown on Fig. 2.2b.
(a) mesh Th
Then:
{︃ ⃒ 𝑀
}︃
⃒ ∑︁
𝑉ℎ (𝒯ℎ , 𝑃1 ) = 𝑤(𝑥, 𝑦) ⃒ 𝑤(𝑥, 𝑦) = 𝑤𝑘 𝜑𝑘 (𝑥, 𝑦), 𝑤𝑘 are real numbers (2.2)
⃒
⃒
𝑘=1
where 𝑀 is the dimension of 𝑉ℎ , i.e. the number of vertices. The 𝑤𝑘 are called the degrees of freedom of 𝑤 and 𝑀
the number of degree of freedom.
It is said also that the nodes of this finite element method are the vertices.
Setting the problem
On line 9, Vh u, v declares that 𝑢 and 𝑣 are approximated as above, namely:
𝑀
∑︁−1
𝑢(𝑥, 𝑦) ≃ 𝑢ℎ (𝑥, 𝑦) = 𝑢𝑘 𝜑𝑘 (𝑥, 𝑦) (2.3)
𝑘=0
On the line 12, the right hand side f is defined analytically using the keyword func.
Line 18 to 26 define the bilinear form of equation (2.1) and its Dirichlet boundary conditions.
This variational formulation is derived by multiplying (2.1) by 𝑣(𝑥, 𝑦) and integrating the result over Ω:
∫︁ ∫︁
− 𝑣∆𝑢 d𝑥d𝑦 = 𝑣𝑓 d𝑥d𝑦
Ω Ω
Then, by Green’s formula, the problem is converted into finding 𝑢 such that
with:
= ∫︀Ω ∇𝑢 · ∇𝑣 d𝑥d𝑦
∫︀
𝑎(𝑢, 𝑣)
(2.4)
ℓ(𝑓, 𝑣) = Ω 𝑓 𝑣 d𝑥d𝑦
Warning: In FreeFEM bilinear terms and linear terms should not be under the same integral indeed to
construct the linear systems FreeFEM finds out which integral contributes to the bilinear form by checking if both
terms, the unknown (here u) and test functions (here v) are present.
which is found by using (2.3) and replacing 𝑣 by 𝜑𝑖 in (2.4). The Dirichlet conditions are implemented by penalty,
namely by setting 𝐴𝑖𝑖 = 1030 and 𝐹𝑖 = 1030 * 0 if 𝑖 is a boundary degree of freedom.
Note: The number 1030 is called tgv (très grande valeur or very high value in english) and it is generally possible to
change this value, see the item :freefem`solve, tgv=`
The matrix 𝐴 = (𝐴𝑖𝑗 ) is called stiffness matrix. If the user wants to access 𝐴 directly he can do so by using (see section
Variational form, Sparse matrix, PDE data vector for details).
1 varf a(u,v)
2 = int2d(Th)(
3 dx(u)*dx(v)
(continues on next page)
1 varf l(unused,v)
2 = int2d(Th)(
3 f*v
4 )
5 + on(C, unused=0)
6 ;
7 Vh F;
8 F[] = l(0,Vh); //F[] is the vector associated to the function F
Note: Here u and F are finite element function, and u[] and F[] give the array of value associated (u[] ≡
(𝑢𝑖 )𝑖=0,...,𝑀 −1 and F[] ≡ (𝐹𝑖 )𝑖=0,...,𝑀 −1 ).
So we have:
𝑀
∑︁−1 𝑀
∑︁−1
u(𝑥, 𝑦) = u[][𝑖]𝜑𝑖 (𝑥, 𝑦), F(𝑥, 𝑦) = F[][𝑖]𝜑𝑖 (𝑥, 𝑦)
𝑖=0 𝑖=0
where 𝜑𝑖 , 𝑖 = 0..., , 𝑀 − 1 are the basis functions of Vh like in equation :eq: equation3, and 𝑀 = Vh.ndof is the
number of degree of freedom (i.e. the dimension of the space Vh).
The linear system (2.5) is solved by UMFPACK unless another option is mentioned specifically as in:
1 Vh u, v;
2 problem Poisson(u, v, solver=CG) = int2d(...
meaning that Poisson is declared only here and when it is called (by simply writing Poisson;) then (2.5) will be
solved by the Conjugate Gradient method.
Summary : It is usually not easy to determine the type of a system. Yet the approximations and algorithms suited to
the problem depend on its type:
• Finite Elements compatible (LBB conditions) for elliptic systems
• Finite difference on the parabolic variable and a time loop on each elliptic subsystem of parabolic systems; better
stability diagrams when the schemes are implicit in time.
• Upwinding, Petrov-Galerkin, Characteristics-Galerkin, Discontinuous-Galerkin, Finite Volumes for hyperbolic
systems plus, possibly, a time loop.
When the system changes type, then expect difficulties (like shock discontinuities) !
Elliptic, parabolic and hyperbolic equations
A partial differential equation (PDE) is a relation between a function of several variables and its derivatives.
𝜕2𝜙 𝜕𝑚𝜙
(︂ )︂
𝜕𝜙 𝜕𝜙
𝐹 𝜙(𝑥), (𝑥), · · · , (𝑥), 2 (𝑥), · · · , 𝑚 (𝑥) = 0, ∀𝑥 ∈ Ω ⊂ R𝑑
𝜕𝑥1 𝜕𝑥𝑑 𝜕𝑥1 𝜕𝑥𝑑
The range of 𝑥 over which the equation is taken, here Ω, is called the domain of the PDE. The highest derivation index,
here 𝑚, is called the order. If 𝐹 and 𝜙 are vector valued functions, then the PDE is actually a system of PDEs.
Unless indicated otherwise, here by convention one PDE corresponds to one scalar valued 𝐹 and 𝜙. If 𝐹 is linear with
respect to its arguments, then the PDE is said to be linear.
The general form of a second order, linear scalar PDE is
𝛼𝜙 + 𝑎 · ∇𝜙 + 𝐵 : ∇(∇𝜙) = 𝑓 in Ω ⊂ R𝑑 ,
2 ∑︀𝑑
where 𝜕𝑥𝜕𝑖 𝜕𝑥
𝜙
𝑗
and 𝐴 : 𝐵 means 𝑖,𝑗=1 𝑎𝑖𝑗 𝑏𝑖𝑗 ., 𝑓 (𝑥), 𝛼(𝑥) ∈ R, 𝑎(𝑥) ∈ R𝑑 , 𝐵(𝑥) ∈ R𝑑×𝑑 are the PDE coefficients.
If the coefficients are independent of 𝑥, the PDE is said to have constant coefficients.
To a PDE we associate a quadratic form, by replacing 𝜙 by 1, 𝜕𝜙/𝜕𝑥𝑖 by 𝑧𝑖 and 𝜕 2 𝜙/𝜕𝑥𝑖 𝜕𝑥𝑗 by 𝑧𝑖 𝑧𝑗 , where 𝑧 is a
vector in R𝑑 :
𝛼 + 𝐴 · 𝑧 + 𝑧 𝑇 𝐵𝑧 = 𝑓.
If it is the equation of an ellipse (ellipsoid if 𝑑 ≥ 2), the PDE is said to be elliptic; if it is the equation of a parabola or
a hyperbola, the PDE is said to be parabolic or hyperbolic.
If 𝐵 ≡ 0, the degree is no longer 2 but 1, and for reasons that will appear more clearly later, the PDE is still said to be
hyperbolic.
These concepts can be generalized to systems, by studying whether or not the polynomial system 𝑃 (𝑧) associated with
the PDE system has branches at infinity (ellipsoids have no branches at infinity, paraboloids have one, and hyperboloids
have several).
If the PDE is not linear, it is said to be non-linear. These are said to be locally elliptic, parabolic, or hyperbolic according
to the type of the linearized equation.
For example, for the non-linear equation
𝜕 2 𝜙 𝜕𝜙 𝜕 2 𝜙
− =1
𝜕𝑡2 𝜕𝑥 𝜕𝑥2
we have 𝑑 = 2, 𝑥1 = 𝑡, 𝑥2 = 𝑥 and its linearized form is:
𝜕 2 𝑢 𝜕𝑢 𝜕 2 𝜙 𝜕𝜙 𝜕 2 𝑢
− − =0
𝜕𝑡2 𝜕𝑥 𝜕𝑥2 𝜕𝑥 𝜕𝑥2
which for the unknown 𝑢 is locally elliptic if 𝜕𝜙
𝜕𝑥 < 0 and locally hyperbolic if 𝜕𝜙
𝜕𝑥 > 0.
∆(∆𝜙) = 𝑓 in Ω.
Boundary conditions
A relation between a function and its derivatives is not sufficient to define the function. Additional information on the
boundary Γ = 𝜕Ω of Ω, or on part of Γ is necessary. Such information is called a boundary condition.
For example:
𝜙(𝑥) given, ∀𝑥 ∈ Γ,
Here 𝑡 is time so the first condition is called an initial condition. The whole set of conditions is also called Cauchy
condition.
The wave equation is well posed with :
𝜕𝜙
𝜙 and given at 𝑡 = 0 and Dirichlet or Neumann or mixed conditions on 𝜕Ω.
𝜕𝑡
2.3 Membrane
Summary : Here we shall learn how to solve a Dirichlet and/or mixed Dirichlet Neumann problem for the Laplace
operator with application to the equilibrium of a membrane under load. We shall also check the accuracy of the method
and interface with other graphics packages
An elastic membrane Ω is attached to a planar rigid support Γ, and a force 𝑓 (𝑥)𝑑𝑥 is exerted on each surface element
d𝑥 = d𝑥1 d𝑥2 . The vertical membrane displacement, 𝜙(𝑥), is obtained by solving Laplace’s equation:
−∆𝜙 = 𝑓 in Ω
𝜙|Γ = 0
If the support wasn’t planar but had an elevation 𝑧(𝑥1 , 𝑥2 ) then the boundary conditions would be of non-homogeneous
Dirichlet type.
𝜙|Γ = 𝑧
If a part Γ2 of the membrane border Γ is not fixed to the support but is left hanging, then due to the membrane’s rigidity
the angle with the normal vector 𝑛 is zero; thus the boundary conditions are:
𝜕𝜙
𝜙|Γ1 = 𝑧, |Γ = 0
𝜕𝑛 2
𝜕2𝜙 𝜕2𝜙
∆𝜙 = +
𝜕𝑥21 𝜕𝑥22
With such “mixed boundary conditions” the problem has a unique solution (see Dautray-Lions (1988), Strang (1986)
and Raviart-Thomas (1983)). The easiest proof is to notice that 𝜙 is the state of least energy, i.e.
∫︁
1
𝐸(𝜑) = min 𝐸(𝑣), with 𝐸(𝑣) = ( |∇𝑣|2 − 𝑓 𝑣)
𝜙−𝑧∈𝑉 Ω 2
and where 𝑉 is the subspace of the Sobolev space 𝐻 1 (Ω) of functions which have zero trace on Γ1 . Recall that
(𝑥 ∈ R𝑑 , 𝑑 = 2 here):
Calculus of variation shows that the minimum must satisfy, what is known as the weak form of the PDE or its variational
formulation (also known here as the theorem of virtual work)
∫︁ ∫︁
∇𝜙 · ∇𝑤 = 𝑓 𝑤 ∀𝑤 ∈ 𝑉
Ω Ω
Next an integration by parts (Green’s formula) will show that this is equivalent to the PDE when second derivatives
exist.
2.3. Membrane 41
FreeFEM Documentation, Release 4.8
Warning: Unlike the previous version Freefem+ which had both weak and strong forms, FreeFEM implements
only weak formulations. It is not possible to go further in using this software if you don’t know the weak form
(i.e. variational formulation) of your problem: either you read a book, or ask help form a colleague or drop the
matter. Now if you want to solve a system of PDE like 𝐴(𝑢, 𝑣) = 0, 𝐵(𝑢, 𝑣) = 0 don’t close this manual, because
in weak form it is
∫︁
(𝐴(𝑢, 𝑣)𝑤1 + 𝐵(𝑢, 𝑣)𝑤2 ) = 0 ∀𝑤1 , 𝑤2 ...
Ω
Example
Let an ellipse have the length of the semimajor axis 𝑎 = 2, and unitary the semiminor axis. Let the surface force be
𝑓 = 1. Programming this case with FreeFEM gives:
1 // Parameters
2 real theta = 4.*pi/3.;
3 real a = 2.; //The length of the semimajor axis
4 real b = 1.; //The length of the semiminor axis
5 func z = x;
6
7 // Mesh
8 border Gamma1(t=0., theta){x=a*cos(t); y=b*sin(t);}
9 border Gamma2(t=theta, 2.*pi){x=a*cos(t); y=b*sin(t);}
10 mesh Th = buildmesh(Gamma1(100) + Gamma2(50));
11
12 // Fespace
13 fespace Vh(Th, P2); //P2 conforming triangular FEM
14 Vh phi, w, f=1;
15
16 // Solve
17 solve Laplace(phi, w)
18 = int2d(Th)(
19 dx(phi)*dx(w)
20 + dy(phi)*dy(w)
21 )
22 - int2d(Th)(
23 f*w
24 )
25 + on(Gamma1, phi=z)
26 ;
27
28 // Plot
29 plot(phi, wait=true, ps="membrane.eps"); //Plot phi
30 plot(Th, wait=true, ps="membraneTh.eps"); //Plot Th
31
32 // Save mesh
33 savemesh(Th,"Th.msh");
A triangulation is built by the keyword buildmesh. This keyword calls a triangulation subroutine based on the De-
launay test, which first triangulates with only the boundary points, then adds internal points by subdividing the edges.
How fine the triangulation becomes is controlled by the size of the closest boundary edges.
The PDE is then discretized using the triangular second order finite element method on the triangulation; as was briefly
(a) Mesh of the ellipse (b) Level lines of the membrane deformation
indicated in the previous chapter, a linear system is derived from the discrete formulation whose size is the number of
vertices plus the number of mid-edges in the triangulation.
The system is solved by a multi-frontal Gauss LU factorization implemented in the package UMFPACK.
The keyword plot will display both Tℎ and 𝜙 (remove Th if 𝜙 only is desired) and the qualifier fill=true replaces
the default option (colored level lines) by a full color display.
except that on Γ2 𝜕𝑛 𝜙 = 2 instead of zero. So we will consider a non-homogeneous Neumann condition and solve:
∫︁ ∫︁ ∫︁
∇𝜙 · ∇𝑤 = 𝑓𝑤 + 2𝑤 ∀𝑤 ∈ 𝑉
Ω Ω Γ2
and print the error in both cases as well as the log of their ratio an indication of the rate of convergence.
1 // Parameters
2 verbosity = 0; //to remove all default output
3 real theta = 4.*pi/3.;
4 real a=1.; //the length of the semimajor axis
(continues on next page)
2.3. Membrane 43
FreeFEM Documentation, Release 4.8
9 // Mesh
10 border Gamma1(t=0., theta){x=a*cos(t); y=b*sin(t);}
11 border Gamma2(t=theta, 2.*pi){x=a*cos(t); y=b*sin(t);}
12
13 // Error loop
14 real[int] L2error(2); //an array of two values
15 for(int n = 0; n < 2; n++){
16 // Mesh
17 mesh Th = buildmesh(Gamma1(20*(n+1)) + Gamma2(10*(n+1)));
18
19 // Fespace
20 fespace Vh(Th, P2);
21 Vh phi, w;
22
23 // Solve
24 solve Laplace(phi, w)
25 = int2d(Th)(
26 dx(phi)*dx(w)
27 + dy(phi)*dy(w)
28 )
29 - int2d(Th)(
30 f*w
31 )
32 - int1d(Th, Gamma2)(
33 2*w
34 )
35 + on(Gamma1,phi=0)
36 ;
37
38 // Plot
39 plot(Th, phi, wait=true, ps="membrane.eps");
40
41 // Error
42 L2error[n] = sqrt(int2d(Th)((phi-phiexact)^2));
43 }
44
45 // Display loop
46 for(int n = 0; n < 2; n++)
47 cout << "L2error " << n << " = " << L2error[n] << endl;
48
49 // Convergence rate
50 cout << "convergence rate = "<< log(L2error[0]/L2error[1])/log(2.) << endl;
1 L2error 0 = 0.00462991
2 L2error 1 = 0.00117128
3 convergence rate = 1.9829
(continues on next page)
We find a rate of 1.98 , which is not close enough to the 3 predicted by the theory.
The Geometry is always a polygon so we lose one order due to the geometry approximation in 𝑂(ℎ2 ).
Now if you are not satisfied with the .eps plot generated by FreeFEM and you want to use other graphic facilities,
then you must store the solution in a file very much like in C++. It will be useless if you don’t save the triangulation as
well, consequently you must do
1 {
2 ofstream ff("phi.txt");
3 ff << phi[];
4 }
5 savemesh(Th,"Th.msh");
For the triangulation the name is important: the extension determines the format.
Fig. 2.4: The 3D version drawn by gnuplot from a file generated by FreeFEM
Still that may not take you where you want. Here is an interface with gnuplot (see : web site link ) to produce the Fig.
2.4.
9 ff << Th[i][0].x << " " << Th[i][0].y << " " << phi[][Vh(i,0)] << "\n\n\n"
(continues on next page)
2.3. Membrane 45
FreeFEM Documentation, Release 4.8
We use the finite element numbering, where Wh(i,j) is the global index of 𝑗 𝑇 ℎ degrees of freedom of triangle number
𝑖.
Then open gnuplot and do:
This works with P2 and P1, but not with P1nc because the 3 first degrees of freedom of P2 or P2 are on vertices and
not with P1nc.
Summary: Here we shall learn more about geometry input and triangulation files, as well as read and write operations.
The problem Let {𝐶𝑖 }1,2 , be 2 thermal conductors within an enclosure 𝐶0 (see Fig. 2.5).
The first one is held at a constant temperature 𝑢1 the other one has a given thermal conductivity 𝜅2 3 times larger than
the one of 𝐶0 .
We assume that the border of enclosure 𝐶0 is held at temperature 20∘ 𝐶 and that we have waited long enough for thermal
equilibrium.
In order to know 𝑢(𝑥) at any point 𝑥 of the domain Ω, we must solve:
∇ · (𝜅∇𝑢) = 0 in Ω, 𝑢|Γ = 𝑔
where Ω is the interior of 𝐶0 minus the conductor 𝐶1 and Γ is the boundary of Ω, that is 𝐶0 ∪ 𝐶1 .
𝑢 = 𝑢𝑖 on 𝐶𝑖 , 𝑖 = 0, 1.
The variational formulation for this problem is in the subspace 𝐻01 (Ω) ⊂ 𝐻 1 (Ω) of functions which have zero traces
on Γ.
∫︁
1
𝑢 − 𝑔 ∈ 𝐻0 (Ω) : ∇𝑢∇𝑣 = 0∀𝑣 ∈ 𝐻01 (Ω)
Ω
Let us assume that 𝐶0 is a circle of radius 5 centered at the origin, 𝐶𝑖 are rectangles, 𝐶1 being at the constant temperature
𝑢1 = 60∘ 𝐶 (so we can only consider its boundary).
1 // Parameters
2 int C1=99;
3 int C2=98; //could be anything such that !=0 and C1!=C2
4
5 // Mesh
6 border C0(t=0., 2.*pi){x=5.*cos(t); y=5.*sin(t);}
7
23 mesh Th=buildmesh(C0(50)
24 + C11(5)+C12(20)+C13(5)+C14(20)
25 + C21(-5)+C22(-20)+C23(-5)+C24(-20));
26
27 plot(Th,wait=1);
28
29 // Fespace
30 fespace Vh(Th, P1);
31 Vh u, v;
32 Vh kappa=1 + 2*(x<-1)*(x>-2)*(y<3)*(y>-3);
33
34 // Solve
35 solve a(u, v)
36 = int2d(Th)(
37 kappa*(
38 dx(u)*dx(v)
39 + dy(u)*dy(v)
40 )
(continues on next page)
46 // Plot
47 plot(u, wait=true, value=true, fill=true, ps="HeatExchanger.eps");
Tip: Exercise :
Use the symmetry of the problem with respect to the x axes.
Triangulate only one half of the domain, and set homogeneous Neumann conditions on the horizontal axis.
Writing and reading triangulation files Suppose that at the end of the previous program we added the line
1 savemesh(Th, "condensor.msh");
and then later on we write a similar program but we wish to read the mesh from that file. Then this is how the condenser
should be computed:
1 // Mesh
2 mesh Sh = readmesh("condensor.msh");
3
4 // Fespace
5 fespace Wh(Sh, P1);
6 Wh us, vs;
7
8 // Solve
9 solve b(us, vs)
10 = int2d(Sh)(
11 dx(us)*dx(vs)
12 + dy(us)*dy(vs)
13 )
14 +on(1, us=0)
15 +on(99, us=1)
16 +on(98, us=-1)
17 ;
18
19 // Plot
20 plot(us);
Note that the names of the boundaries are lost but either their internal number (in the case of C0) or their label number
(for C1 and C2) are kept.
2.5 Acoustics
Summary : Here we go to grip with ill posed problems and eigenvalue problems
Pressure variations in air at rest are governed by the wave equation:
𝜕2𝑢
− 𝑐2 ∆𝑢 = 0
𝜕𝑡2
When the solution wave is monochromatic (and that depends on the boundary and initial conditions), 𝑢 is of the form
𝑢(𝑥, 𝑡) = 𝑅𝑒(𝑣(𝑥)𝑒𝑖𝑘𝑡 ) where 𝑣 is a solution of Helmholtz’s equation:
𝑘 2 𝑣 + 𝑐2 ∆𝑣 =0 in Ω
𝜕𝑣
𝜕𝑛 |Γ =𝑔
2.5. Acoustics 49
FreeFEM Documentation, Release 4.8
1 // Parameters
2 real kc2 = 1.;
3 func g = y*(1.-y);
4
5 // Mesh
6 border a0(t=0., 1.){x=5.; y=1.+2.*t;}
7 border a1(t=0., 1.){x=5.-2.*t; y=3.;}
8 border a2(t=0., 1.){x=3.-2.*t; y=3.-2.*t;}
9 border a3(t=0., 1.){x=1.-t; y=1.;}
10 border a4(t=0., 1.){x=0.; y=1.-t;}
11 border a5(t=0., 1.){x=t; y=0.;}
12 border a6(t=0., 1.){x=1.+4.*t; y=t;}
13
17 // Fespace
18 fespace Vh(Th, P1);
19 Vh u, v;
20
21 // Solve
22 solve sound(u, v)
23 = int2d(Th)(
24 u*v * kc2
25 - dx(u)*dx(v)
26 - dy(u)*dy(v)
27 )
28 - int1d(Th, a4)(
29 g * v
30 )
31 ;
32
33 // Plot
34 plot(u, wait=1, ps="Sound.eps");
Results are on Fig. 2.7a. But when 𝑘𝑐2 is an eigenvalue of the problem, then the solution is not unique:
• if 𝑢𝑒 ̸= 0 is an eigen state, then for any given solution 𝑢 + 𝑢𝑒 is another solution.
To find all the 𝑢𝑒 one can do the following :
1 // Parameters
2 real sigma = 20; //value of the shift
3
4 // Problem
5 // OP = A - sigma B ; // The shifted matrix
6 varf op(u1, u2)
7 = int2d(Th)(
8 dx(u1)*dx(u2)
9 + dy(u1)*dy(u2)
10 - sigma* u1*u2
11 )
12 ;
13
(a) Amplitude of an acoustic signal coming from the left ver- (b) First eigen state (𝜆 = (𝑘/𝑐)2 = 14.695) close to 15 of
tical wall. eigenvalue problem: −Δ𝜙 = 𝜆𝜙 and 𝜕𝑛 𝜕𝜙
= 0 on Γ}
23 // Eigen values
24 int nev=2; // Number of requested eigenvalues near sigma
25
32 cout << ev(0) << " 2 eigen values " << ev(1) << endl;
33 v = eV[0];
34 plot(v, wait=true, ps="eigen.eps");
Summary : Here we shall learn how to deal with a time dependent parabolic problem. We shall also show how to
treat an axisymmetric problem and show also how to deal with a nonlinear problem
How air cools a plate
We seek the temperature distribution in a plate (0, 𝐿𝑥) × (0, 𝐿𝑦) × (0, 𝐿𝑧) of rectangular cross section Ω = (0, 6) ×
(0, 1); the plate is surrounded by air at temperature 𝑢𝑒 and initially at temperature 𝑢 = 𝑢0 + 𝐿 𝑥
𝑢1 . In the plane
perpendicular to the plate at 𝑧 = 𝐿𝑧/2, the temperature varies little with the coordinate 𝑧; as a first approximation the
problem is 2D.
1 // Parameters
2 func u0 = 10. + 90.*x/6.;
3 func k = 1.8*(y<0.5) + 0.2;
4 real ue = 25.;
5 real alpha=0.25;
6 real T=5.;
7 real dt=0.1 ;
8
9 // Mesh
10 mesh Th = square(30, 5, [6.*x,y]);
11
12 // Fespace
13 fespace Vh(Th, P1);
14 Vh u=u0, v, uold;
15
16 // Problem
17 problem thermic(u, v)
18 = int2d(Th)(
19 u*v/dt
20 + k*(
21 dx(u) * dx(v)
22 + dy(u) * dy(v)
23 )
24 )
25 + int1d(Th, 1, 3)(
26 alpha*u*v
27 )
28 - int1d(Th, 1, 3)(
29 alpha*ue*v
30 )
31 - int2d(Th)(
32 uold*v/dt
33 )
34 + on(2, 4, u=u0)
35 ;
36
37 // Time iterations
(continues on next page)
Note: We must separate by hand the bilinear part from the linear one.
Note: The way we store the temperature at point (3, 0.5) for all times in file thermic.dat. Should a one dimensional
plot be required (you can use gnuplot tools), the same procedure can be used. For instance to print 𝑥 ↦→ 𝜕𝑢𝜕𝑦 (𝑥, 0.9)
one would do:
Let us now deal with a cylindrical rod instead of a flat plate. For simplicity we take 𝜅 = 1.
In cylindrical coordinates, the Laplace operator becomes (𝑟 is the distance to the axis, 𝑧 is the distance along the axis,
𝜃 polar angle in a fixed plane perpendicular to the axis):
1 1 2 2
∆𝑢 = 𝜕𝑟 (𝑟𝜕𝑟 𝑢) + 2 𝜕𝜃𝜃 𝑢 + 𝜕𝑧𝑧 .
𝑟 𝑟
Symmetry implies that we loose the dependence with respect to 𝜃; so the domain Ω is again a rectangle ]0, 𝑅[×]0, |[
. We take the convention of numbering of the edges as in square() (1 for the bottom horizontal . . . ); the problem is
now:
𝑟𝜕𝑡 𝑢 − 𝜕𝑟 (𝑟𝜕𝑟 𝑢) − 𝜕𝑧 (𝑟𝜕𝑧 𝑢) =0 in Ω
𝑢(𝑡 = 0) = 𝑢0 + 𝐿𝑧𝑧 (𝑢1 − 𝑢)
𝑢|Γ4 = 𝑢0
𝑢|Γ2 = 𝑢1
𝜕𝑢
𝛼(𝑢 − 𝑢𝑒 ) + 𝜕𝑛 |Γ1 ∪Γ3 =0
Note that the PDE has been multiplied by 𝑟.
After discretization in time with an implicit scheme, with time steps dt, in the FreeFEM syntax 𝑟 becomes 𝑥 and 𝑧
becomes 𝑦 and the problem is:
1 problem thermaxi(u, v)
2 = int2d(Th)(
3 (u*v/dt + dx(u)*dx(v) + dy(u)*dy(v))*x
4 )
5 + int1d(Th, 3)(
6 alpha*x*u*v
7 )
8 - int1d(Th, 3)(
9 alpha*x*ue*v
10 )
11 - int2d(Th)(
12 uold*v*x/dt
13 )
14 + on(2, 4, u=u0);
Note: The bilinear form degenerates at 𝑥 = 0. Still one can prove existence and uniqueness for 𝑢 and because of this
degeneracy no boundary conditions need to be imposed on Γ1 .
Heat loss through radiation is a loss proportional to the absolute temperature to the fourth power (Stefan’s Law). This
adds to the loss by convection and gives the following boundary condition:
𝜕𝑢
𝜅 + 𝛼(𝑢 − 𝑢𝑒 ) + 𝑐[(𝑢 + 273)4 − (𝑢𝑒 + 273)4 ] = 0
𝜕𝑛
The problem is nonlinear, and must be solved iteratively with fixed-point iteration where 𝑚 denotes the iteration index,
a semi-linearization of the radiation condition gives
𝜕𝑢𝑚+1
+ 𝛼(𝑢𝑚+1 − 𝑢𝑒 ) + 𝑐(𝑢𝑚+1 − 𝑢𝑒 )(𝑢𝑚 + 𝑢𝑒 + 546)((𝑢𝑚 + 273)2 + (𝑢𝑒 + 273)2 ) = 0,
𝜕𝑛
1 ...
2 // Mesh
3 fespace Vh(Th, P1);
4 Vh vold, w, v=u0-ue, b,vp;
5
6 // Problem
7 problem thermradia(v, w)
8 = int2d(Th)(
9 v*w/dt
10 + k*(dx(v) * dx(w) + dy(v) * dy(w))
11 )
12 + int1d(Th, 1, 3)(
13 b*v*w
14 )
15 - int2d(Th)(
16 vold*w/dt
17 )
18 + on(2, 4, v=u0-ue)
19 ;
20
36 plot(v);
Summary : Here we will learn how to deal with a multi-physics system of PDEs on a complex geometry, with multiple
meshes within one problem. We also learn how to manipulate the region indicator and see how smooth is the projection
operator from one mesh to another.
Incompressible flow
Without viscosity and vorticity incompressible flows have a velocity given by:
(︃ )︃
𝜕𝜓
𝑢= 𝜕𝑥
−𝜕𝜓 , where 𝜓 is solution of ∆𝜓 = 0
𝜕𝑦
One can also prescribe the normal velocity at an artificial boundary, and this translates into non constant Dirichlet data
for 𝜓.
Airfoil
Let us consider a wing profile 𝑆 in a uniform flow. Infinity will be represented by a large circle 𝐶 where the flow is
assumed to be of uniform velocity; one way to model this problem is to write:
1 // Parameters
2 int S = 99;// wing label
3 // u infty
4 real theta = 8*pi/180;// // 1 degree on incidence => lift
5 real lift = theta*0.151952/0.0872665; // lift approximation formula
6 real uinfty1= cos(theta), uinfty2= sin(theta);
7 // Mesh
8 func naca12 = 0.17735*sqrt(x) - 0.075597*x - 0.212836*(x^2) + 0.17363*(x^3) - 0.06254*(x^
˓→4);
14 // Fespace
15 fespace Xh(Th, P2);
16 Xh psi, w;
17 macro grad(u) [dx(u),dy(u)]// def of grad operator
18 // Solve
19 solve potential(psi, w)
20 = int2d(Th)(
21 grad(psi)'*grad(w) // scalar product
22 )
23 + on(C, psi = [uinfty1,uinfty2]'*[y,-x])
24 + on(S, psi=-lift) // to get a correct value
25 ;
26
27 plot(psi, wait=1);
(a) Zoom around the NACA0012 airfoil showing the stream- (b) Temperature distribution at time T=25 (now the maxi-
lines (curve 𝜓 = constant). To obtain such a plot use the mum is at 90 instead of 120).
interactive graphic command: “+” and p.
Now let us assume that the airfoil is hot and that air is there to cool it. Much like in the previous section the heat
equation for the temperature 𝑣 is
𝜕𝑣
𝜕𝑡 𝑣 − ∇ · (𝜅∇𝑣) + 𝑢 · ∇𝑣 = 0, 𝑣(𝑡 = 0) = 𝑣0 , |𝐶,𝑢·𝑛>0 = 0, 𝑣|𝐶,𝑢·𝑛<0 = 0
𝜕𝑛
But now the domain is outside AND inside 𝑆 and 𝜅 takes a different value in air and in steel. Furthermore there is
convection of heat by the flow, hence the term 𝑢 · ∇𝑣 above.
Consider the following, to be plugged at the end of the previous program:
13 // Problem
14 solve potential(psi, w)
15 = int2d(Th)(dx(psi)*dx(w)+dy(psi)*dy(w))
16 + on(C, psi = y)
17 + on(S, psi=0);
18
19 // Plot
20 plot(psi, wait=1);
21
22 /// Thermic
23 // Parameters
(continues on next page)
26 // Mesh
27 border D(t=0, 2){x=1+t; y=0;}
28 mesh Sh = buildmesh(C(25) + Splus(-90) + Sminus(-90) + D(200));
29 int steel = Sh(0.5, 0).region, air = Sh(-1, 0).region;
30 // Change label to put BC on In flow
31 // Fespace
32 fespace Wh(Sh, P1);
33 Wh vv;
34
50
Note: How steel and air are identified by the mesh parameter region which is defined when buildmesh is called and
takes an integer value corresponding to each connected component of Ω;
Note: We use the change function to put label 10 on inflow boundary, remark the trick to chanhe only label C flabel
= (label == C && [u1,u2]'*N<0) ? 10 : label
How the convection terms are added without upwinding. Upwinding is necessary when the Pecley number |𝑢|𝐿/𝜅 is
large (here is a typical length scale), The factor 10 in front of the convection terms is a quick way of multiplying the
velocity by 10 (else it is too slow to see something).
The solver is Gauss’ LU factorization and when init ̸= 0 the LU decomposition is reused so it is much faster after the
first iteration.
Summary: Here we will present two methods for upwinding for the simplest convection problem. We will learn about
Characteristics-Galerkin and Discontinuous-Galerkin Finite Element Methods.
Let Ω be the unit disk centered at (0, 0); consider the rotation vector field
u = [𝑢1, 𝑢2], 𝑢1 = 𝑦, 𝑢2 = −𝑥
Pure convection by u is
𝜕𝑡 𝑐 + u.∇𝑐 =0 in Ω × (0, 𝑇 )
𝑐(𝑡 = 0) = 𝑐0 in Ω.
𝑐(𝑥𝑡 , 𝑡) = 𝑐0 (𝑥, 0)
where 𝑥𝑡 is the particle path in the flow starting at point 𝑥 at time 0. So 𝑥𝑡 are solutions of
d(𝑥𝑡
𝑥˙𝑡 = 𝑢(𝑥𝑡 ), 𝑥𝑡=0 = 𝑥, where 𝑥˙𝑡 =
d𝑡
The ODE are reversible and we want the solution at point 𝑥 at time 𝑡 ( not at point 𝑥𝑡 ) the initial point is 𝑥−𝑡 , and we
have
𝑐(𝑥, 𝑡) = 𝑐0 (𝑥−𝑡 , 0)
The game consists in solving the equation until 𝑇 = 2𝜋, that is for a full revolution and to compare the final solution
with the initial one; they should be equal.
In FreeFEM there is an operator called convect([u1,u2], dt, c) which compute 𝑐 ∘ 𝑋 with 𝑋 is the convect
field defined by 𝑋(𝑥) = 𝑥𝑑𝑡 and where 𝑥𝜏 is particule path in the steady state velocity field u = [𝑢1, 𝑢2] starting at
point 𝑥 at time 𝜏 = 0, so 𝑥𝜏 is solution of the following ODE:
𝑥˙ 𝜏 = 𝑢(𝑥𝜏 ), 𝑥𝜏 =0 = 𝑥.
When u is piecewise constant; this is possible because 𝑥𝜏 is then a polygonal curve which can be computed exactly
and the solution exists always when u is divergence free; convect returns 𝑐(𝑥𝑑𝑓 ) = 𝐶 ∘ 𝑋.
1 // Parameters
2 real dt = 0.17;
3
4 // Mesh
5 border C(t=0., 2.*pi) {x=cos(t); y=sin(t);};
6 mesh Th = buildmesh(C(100));
7
8 // Fespace
9 fespace Uh(Th, P1);
10 Uh cold, c = exp(-10*((x-0.3)^2 +(y-0.3)^2));
11 Uh u1 = y, u2 = -x;
12
Note: 3D plots can be done by adding the qualifyer dim=3 to the plot instruction.
Discontinuous Galerkin methods take advantage of the discontinuities of 𝑐 at the edges to build upwinding. There are
may formulations possible. We shall implement here the so-called dual-𝑃1𝐷𝐶 formulation (see [ERN2006]):
𝑐𝑛+1 − 𝑐𝑛
∫︁ ∫︁ ∫︁
1
( + 𝑢 · ∇𝑐)𝑤 + (𝛼|𝑛 · 𝑢| − 𝑛 · 𝑢)[𝑐]𝑤 = |𝑛 · 𝑢|𝑐𝑤 ∀𝑤
Ω 𝛿𝑡 𝐸 2 −
𝐸Γ
where 𝐸 is the set of inner edges and 𝐸Γ− is the set of boundary edges where 𝑢 · 𝑛 < 0 (in our case there is no such
edges). Finally [𝑐] is the jump of 𝑐 across an edge with the convention that 𝑐+ refers to the value on the right of the
oriented edge.
1 // Parameters
2 real al=0.5;
3 real dt = 0.05;
4
5 // Mesh
6 border C(t=0., 2.*pi) {x=cos(t); y=sin(t);};
7 mesh Th = buildmesh(C(100));
8
9 // Fespace
10 fespace Vh(Th,P1dc);
11 Vh w, ccold, v1 = y, v2 = -x, cc = exp(-10*((x-0.3)^2 +(y-0.3)^2));
12
13 // Macro
14 macro n() (N.x*v1 + N.y*v2) // Macro without parameter
15
16 // Problem
17 problem Adual(cc, w)
18 = int2d(Th)(
19 (cc/dt+(v1*dx(cc)+v2*dy(cc)))*w
20 )
21 + intalledges(Th)(
22 (1-nTonEdge)*w*(al*abs(n)-n/2)*jump(cc)
(continues on next page)
29 // Time iterations
30 for (real t = 0.; t < 2.*pi; t += dt){
31 ccold = cc;
32 Adual;
33 plot(cc, fill=1, cmm="t="+t+", min="+cc[].min+", max="+ cc[].max);
34 }
35
36 // Plot
37 real [int] viso = [-0.2, -0.1, 0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 1.1];
38 plot(cc, wait=1, fill=1, ps="ConvectCG.eps", viso=viso);
39 plot(cc, wait=1, fill=1, ps="ConvectDG.eps", viso=viso);
(so all internal edges are see two times), nTonEdge which is one if the triangle has a boundary edge and two otherwise,
jump to implement [𝑐].
Results of both methods are shown on Fig. 2.10a nad Fig. 2.10b with identical levels for the level line; this is done with
the plot-modifier viso.
Notice also the macro where the parameter u is not used (but the syntax needs one) and which ends with a //; it simply
replaces the name n by (N.x*v1+N.y*v2). As easily guessed N.x,N.y is the normal to the edge.
Now if you think that DG is too slow try this:
1 // Mesh
2 border C(t=0., 2.*pi) {x=cos(t); y=sin(t);};
3 mesh Th = buildmesh(C(100));
4
(a) The rotating hill after one revolution with (b) The rotating hill after one revolution with Discontinuous
Characteristics-Galerkin 𝑃1 Galerkin
22 real[int] viso=[-0.1,0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1,1.1];
23
24 matrix AA=Adual(Vh,Vh,solver=GMRES);
25 matrix BB=rhs(Vh,Vh);
26
Elasticity
Solid objects deform under the action of applied forces:
a point in the solid, originally at (𝑥, 𝑦, 𝑧) will come to (𝑋, 𝑌, 𝑍) after some time; the vector u = (𝑢1 , 𝑢2 , 𝑢3 ) =
(𝑋 − 𝑥, 𝑌 − 𝑦, 𝑍 − 𝑧) is called the displacement. When the displacement is small and the solid is elastic, Hooke’s law
gives a relationship between the stress tensor 𝜎(𝑢) = (𝜎𝑖𝑗 (𝑢)) and the strain tensor 𝜖(𝑢) = 𝜖𝑖𝑗 (𝑢)
−𝜇∆u − (𝜇 + 𝜆)∇(∇.u) = f in Ω,
−𝑑𝑖𝑣(𝜎) = f in Ω
Tip: Consider an elastic plate with the undeformed rectangle shape [0, 20] × [−1, 1].
The body force is the gravity force f and the boundary force g is zero on lower, upper and right sides. The left vertical
side of the beam is fixed. The boundary conditions are:
𝜎.n =g =0 on Γ1 , Γ4 , Γ3 ,
u =0 on Γ2
1 // Parameters
2 real E = 21e5;
3 real nu = 0.28;
4
5 real f = -1;
6
7 // Mesh
(continues on next page)
10 // Fespace
11 fespace Vh(Th, P2);
12 Vh u, v;
13 Vh uu, vv;
14
15 // Macro
16 real sqrt2=sqrt(2.);
17 macro epsilon(u1,u2) [dx(u1),dy(u2),(dy(u1)+dx(u2))/sqrt2] //
18 // The sqrt2 is because we want: epsilon(u1,u2)'* epsilon(v1,v2) = epsilon(u): epsilon(v)
19 macro div(u,v) ( dx(u)+dy(v) ) //
20
21 // Problem
22 real mu= E/(2*(1+nu));
23 real lambda = E*nu/((1+nu)*(1-2*nu));
24
36 // Plot
37 real coef=100;
38 plot([u, v], wait=1, ps="lamevect.eps", coef=coef);
39
40 // Move mesh
41 mesh th1 = movemesh(Th, [x+u*coef, y+v*coef]);
42 plot(th1,wait=1,ps="lamedeform.eps");
43
44 // Output
45 real dxmin = u[].min;
46 real dymin = v[].min;
47
48 cout << " - dep. max x = "<< dxmin << " y=" << dymin << endl;
49 cout << " dep. (20, 0) = " << u(20, 0) << " " << v(20, 0) << endl;
Solution of Lamé’s equations for elasticity for a 2D beam deflected by its own weight and clamped by its left vertical
side is shown Fig. 2.11a and Fig. 2.11b. Result are shown with a amplification factor equal to 100. The size of the
arrow is automatically bound, but the color gives the real length.
In the case of a flow invariant with respect to the third coordinate (two-dimensional flow), flows at low Reynolds number
(for instance micro-organisms) satisfy,
−∆u + ∇𝑝 = 0
∇·u = 0
1 // Parameters
2 int nn = 30;
3
4 // Mesh
5 mesh Th = square(nn, nn);
6
7 // Fespace
8 fespace Uh(Th, P1b);
9 Uh u, v;
10 Uh uu, vv;
11
15 // Problem
16 solve stokes ([u, v, p], [uu, vv, pp])
17 = int2d(Th)(
18 dx(u)*dx(uu)
19 + dy(u)*dy(uu)
20 + dx(v)*dx(vv)
21 + dy(v)*dy(vv)
22 + dx(p)*uu
23 + dy(p)*vv
24 + pp*(dx(u) + dy(v))
(continues on next page)
31 // Plot
32 plot([u, v], p, wait=1);
Note: We add a stabilization term −10e − 10 * p * pp to fix the constant part of the pressure.
Summary : Fluid flows require good algorithms and good triangultions. We show here an example of a complex
algorithm and or first example of mesh adaptation.
An incompressible viscous fluid satisfies:
𝜕𝑡 u + u · ∇u + ∇𝑝 − 𝜈∆u =0 in Ω×]0, 𝑇 [
∇·u =0 in Ω×]0, 𝑇 [
u|𝑡=0 = u0
u|Γ = uΓ
−∆𝑝𝑚+1 = −∇ · u𝑚 𝑜X𝑚
𝜕𝑛 𝑝𝑚+1 =0 on Γ
𝑝𝑚+1 =0 on Γ𝑜𝑢𝑡
where u𝑜X(𝑥) = u(x − u(x)𝛿𝑡) since 𝜕𝑡 u + u · ∇u is approximated by the method of characteristics, as in the
previous section.
We use the Chorin’s algorithm with free boundary condition at outlet (i.e. 𝑝 = 0, 𝜈𝜕𝑛 𝑢 = 0), to compute a correction,
q, to the pressure.
−∆𝑞 =∇·u
𝑞 = 0 on Γ𝑜𝑢𝑡
and define
u𝑚+1 = ũ + 𝑃 ∇𝑞𝛿𝑡
𝑝𝑚+1 = 𝑝𝑚 − 𝑞
where ũ is the (u𝑚+1 , 𝑣 𝑚+1 ) of Chorin’s algorithm, and where 𝑃 is the 𝐿2 projection with mass lumping ( a sparse
matrix).
The backward facing step
Fig. 2.12: Solution of Stokes’ equations for the driven cavity problem, showing the velocity field and the pressure level
lines.
The geometry is that of a channel with a backward facing step so that the inflow section is smaller than the outflow
section. This geometry produces a fluid recirculation zone that must be captured correctly.
This can only be done if the triangulation is sufficiently fine, or well adapted to the flow.
Note: There is a technical difficulty in the example: the output B.C. Here we put 𝑝 = 0 and 𝜈𝜕𝑛 𝑢 = 0.
1 // Parameters
2 verbosity = 0;
3 int nn = 1;
4 real nu = 0.0025;
5 real dt = 0.2;
6 real epsv = 1e-6;
7 real epsu = 1e-6;
8 real epsp = 1e-6;
9
10 // Mesh
11 border a0(t=1, 0){x=-2; y=t; label=1;}
12 border a1(t=-2, 0){x=t; y=0; label=2;}
13 border a2(t=0, -0.5){x=0; y=t; label=2;}
14 border a3(t=0, 1){x=18*t^1.2; y=-0.5; label=2;}
15 border a4(t=-0.5, 1){x=18; y=t; label=3;}
16 border a5(t=1, 0){x=-2+20*t; y=1; label=4;}
17
19 plot(Th);
20
21 // Fespace
22 fespace Vh(Th, P1);
23 Vh w;
24 Vh u=0, v=0;
25 Vh p=0;
26 Vh q=0;
27
31 // Macro
32 macro BuildMat()
33 { /* for memory managenemt */
34 varf vM(unused, v) = int2d(Th)(v);
35 varf vdx(u, v) = int2d(Th)(v*dx(u)*dt);
36 varf vdy(u, v) = int2d(Th)(v*dy(u)*dt);
37
48 // Build matrices
49 BuildMat
50
51 // Time iterations
52 real err = 1.;
53 real outflux = 1.;
54 for(int n = 0; n < 300; n++){
55 // Update
56 Vh uold=u, vold=v, pold=p;
57
58 // Solve
59 solve pb4u (u, w, init=n, solver=CG, eps=epsu)
60 = int2d(Th)(
61 u*w/dt
62 + nu*(dx(u)*dx(w) + dy(u)*dy(w))
63 )
64 -int2d(Th)(
65 convect([uold, vold], -dt, uold)/dt*w
66 - dx(p)*w
67 )
68 + on(1, u=4*y*(1-y))
69 + on(2, 4, u=0)
70 ;
71
72 plot(u);
73
(b) Pressure
(c) Velocity
101 p = pold-q;
102 u[] += dtM1x*q[];
103 v[] += dtM1y*q[];
104
119 // Verification
120 assert(abs(outflux)< 2e-3);
121
122 // Plot
123 plot(p, wait=1, ps="NSprojP.eps");
124 plot(u, wait=1, ps="NSprojU.eps");
Rannacher’s projection algorithm: result on an adapted mesh, Fig. 2.13a, showing the pressure, Fig. 2.13b, and the
horizontal velocity Fig. 2.13c for a Reynolds number of 400 where mesh adaptation is done after 50 iterations on the
first mesh.
The problem is find the velocity field u = (𝑢𝑖 )𝑑𝑖=1 and the pressure 𝑝 of a Flow satisfying in the domain Ω ⊂ R𝑑 (𝑑 =
2, 3):
(u · ∇)u − 𝜈∆u + ∇𝑝 = 0
∇·u = 0
where 𝜈 is the viscosity of the fluid, ∇ = (𝜕𝑖 )𝑑𝑖=1 , the dot product is ·, and ∆ = ∇ · ∇ with the same boundary
conditions (u is given on Γ).
The weak form is find u, 𝑝 such that for ∀v (zero on Γ), and ∀𝑞:
∫︁
((u · ∇)u).v + 𝜈∇u : ∇v − 𝑝∇ · v − 𝑞∇ · u = 0
Ω
1 // Parameters
2 real R = 5.;
3 real L = 15.;
4
5 real nu = 1./50.;
6 real nufinal = 1/200.;
7 real cnu = 0.5;
8
11 verbosity = 0;
12
13 // Mesh
14 border cc(t=0, 2*pi){x=cos(t)/2.; y=sin(t)/2.; label=1;}
15 border ce(t=pi/2, 3*pi/2){x=cos(t)*R; y=sin(t)*R; label=1;}
16 border beb(tt=0, 1){real t=tt^1.2; x=t*L; y=-R; label=1;}
(continues on next page)
26 // Fespace
27 fespace Xh(Th, P2);
28 Xh u1, u2;
29 Xh v1,v2;
30 Xh du1,du2;
31 Xh u1p,u2p;
32
33 fespace Mh(Th,P1);
34 Mh p;
35 Mh q;
36 Mh dp;
37 Mh pp;
38
39 // Macro
40 macro Grad(u1,u2) [dx(u1), dy(u1), dx(u2),dy(u2)] //
41 macro UgradV(u1,u2,v1,v2) [[u1,u2]'*[dx(v1),dy(v1)],
42 [u1,u2]'*[dx(v2),dy(v2)]] //
43 macro div(u1,u2) (dx(u1) + dy(u2)) //
44
45 // Initialization
46 u1 = (x^2+y^2) > 2;
47 u2 = 0;
48
49 // Viscosity loop
50 while(1){
51 int n;
52 real err=0;
53 // Newton loop
54 for (n = 0; n < 15; n++){
55 // Newton
56 solve Oseen ([du1, du2, dp], [v1, v2, q])
57 = int2d(Th)(
58 nu * (Grad(du1,du2)' * Grad(v1,v2))
59 + UgradV(du1,du2, u1, u2)' * [v1,v2]
60 + UgradV( u1, u2,du1,du2)' * [v1,v2]
61 - div(du1,du2) * q
62 - div(v1,v2) * dp
63 - 1e-8*dp*q //stabilization term
64 )
65 - int2d(Th) (
66 nu * (Grad(u1,u2)' * Grad(v1,v2))
67 + UgradV(u1,u2, u1, u2)' * [v1,v2]
68 - div(u1,u2) * q
(continues on next page)
74 u1[] -= du1[];
75 u2[] -= du2[];
76 p[] -= dp[];
77
81 cout << n << " err = " << err << " " << eps << " rey = " << 1./nu << endl;
82 if(err < eps) break; //converge
83 if( n>3 && err > 10.) break; //blowup
84 }
85
90 // Change nu
91 if( nu == nufinal) break;
92 if( n < 4) cnu = cnu^1.5; //fast converge => change faster
93 nu = max(nufinal, nu* cnu); //new viscosity
94
95 // Update
96 u1p = u1;
97 u2p = u2;
98 pp = p;
99 }
100 else{ //blowup: increase $\nu$ (more simple)
101 assert(cnu< 0.95); //the method finally blowup
102
103 // Recover nu
104 nu = nu/cnu;
105 cnu= cnu^(1./1.5); //no conv. => change lower
106 nu = nu* cnu; //new viscosity
107 cout << " restart nu = " << nu << " Rey = " << 1./nu << " (cnu = " << cnu << " )␣
˓→ \n";
108
Note: We use a trick to make continuation on the viscosity 𝜈, because the Newton method blowup owe start with the
final viscosity 𝜈.
A friend of one of us in Auroville-India was building a ramp to access an air conditioned room. As I was visiting the
construction site he told me that he expected to cool air escaping by the door to the room to slide down the ramp and
refrigerate the feet of the coming visitors. I told him “no way” and decided to check numerically.
The fluid velocity and pressure are solution of the Navier-Stokes equations with varying density function of the tem-
perature.
The geometry is trapezoidal with prescribed inflow made of cool air at the bottom and warm air above and so are the
initial conditions; there is free outflow, slip velocity at the top (artificial) boundary and no-slip at the bottom. However
the Navier-Stokes cum temperature equations have a RANS 𝑘 − 𝜖 model and a Boussinesq approximation for the
buoyancy. This comes to :
We use a time discretization which preserves positivity and uses the method of characteristics (𝑋 𝑚 (𝑥) ≈ 𝑥−𝑢𝑚 (𝑥)𝛿𝑡)
1 𝑚+1
𝛿𝑡 (𝜃 − 𝜃𝑚 ∘ 𝑋 𝑚 ) − ∇ · (𝜅𝑚
𝑇 ∇𝜃
𝑚+1
) = 0
1 𝑚+1 𝑚 𝑚 𝑚 𝑚+1 𝑚+1 𝑚+1
𝛿𝑡 (𝑢 −𝑢 ∘𝑋 )−∇· (𝜇𝑇 ∇𝑢 ) + ∇𝑝 + 𝑒(𝜃 − 𝜃0 )⃗𝑒2 = 0
𝑚+1
∇·𝑢 = 0
𝑚+1 𝜖𝑚 𝜇𝑚
1
𝛿𝑡 (𝑘 𝑚+1
− 𝑘 𝑚
∘ 𝑋 𝑚
) + 𝑘 𝑘𝑚𝑚 − ∇ · (𝜇 𝑚
𝑇 ∇𝑘 𝑚+1
) = 2𝑇 |∇𝑢𝑚 + ∇𝑢𝑚 𝑇 |2
1 𝑚+1
− 𝜖𝑚 ∘ 𝑋 𝑚 ) + 𝑐2 𝜖𝑚+1 𝑘𝜖 𝑚 − 𝑐𝑐𝜇𝜖 ∇(𝜇˙ 𝑚 ∇𝜖𝑚+1 ) = 𝑐1 𝑘 𝑚 |∇𝑢𝑚 + ∇𝑢𝑚 𝑇 |2
𝛿𝑡 (𝜖 𝑇 2
𝑚+1 2
𝜇𝑚+1
𝑇 = 𝑐𝜇 𝑘𝜖𝑚+1
𝜅𝑚+1
𝑇 = 𝜅𝜇𝑚+1
𝑇
1 load "iovtk"
2
3 verbosity=0;
4
5 // Parameters
6 int nn = 15;
7 int nnPlus = 5;
8 real l = 1.;
9 real L = 15.;
10 real hSlope = 0.1;
11 real H = 6.;
12 real h = 0.5;
13
25 // Mesh
26 border b1(t=0, l){x=t; y=0;}
27 border b2(t=0, L-l){x=1.+t; y=-hSlope*t;}
28 border b3(t=-hSlope*(L-l), H){x=L; y=t;}
29 border b4(t=L, 0){x=t; y=H;}
30 border b5(t=H, h){x=0; y=t;}
31 border b6(t=h, 0){x=0; y=t;}
32
34 plot(Th);
35
36 // Fespaces
37 fespace Vh2(Th, P1b);
38 Vh2 Ux, Uy;
39 Vh2 Vx, Vy;
40 Vh2 Upx, Upy;
41
42 fespace Vh(Th,P1);
43 Vh p=0, q;
44 Vh Tp, T=35;
45 Vh k=0.0001, kp=k;
46 Vh ep=0.0001, epp=ep;
47
48 fespace V0h(Th,P0);
49 V0h muT=1;
50 V0h prodk, prode;
51 Vh kappa=0.25e-4, stress;
52
(continues on next page)
58 // Functions
59 func g = (x) * (1-x) * 4;
60
61 // Problem
62 real alpha = 0.;
63
64 problem Temperature(T, q)
65 = int2d(Th)(
66 alpha * T * q
67 + kappa* grad(T)' * grad(q)
68 )
69 + int2d(Th)(
70 - alpha*convect([Upx, Upy], -dt, Tp)*q
71 )
72 + on(b6, T=25)
73 + on(b1, b2, T=30)
74 ;
75
76 problem KineticTurbulence(k, q)
77 = int2d(Th)(
78 (epp/kp + alpha) * k * q
79 + muT* grad(k)' * grad(q)
80 )
81 + int2d(Th)(
82 prodk * q
83 - alpha*convect([Upx, Upy], -dt, kp)*q
84 )
85 + on(b5, b6, k=0.00001)
86 + on(b1, b2, k=beta*numu*stress)
87 ;
88
89 problem ViscosityTurbulence(ep, q)
90 = int2d(Th)(
91 (1.92*epp/kp + alpha) * ep * q
92 + muT * grad(ep)' * grad(q)
93 )
94 + int1d(Th, b1, b2)(
95 T * q * 0.001
96 )
97 + int2d(Th)(
98 prode * q
99 - alpha*convect([Upx, Upy], -dt, epp)*q
100 )
101 + on(b5, b6, ep=0.00001)
102 + on(b1, b2, ep=beta*nuep*pow(stress,1.5))
103 ;
104
131 {
132 real[int] xx(21), yy(21), pp(21);
133 for (int i = 0 ; i < 21; i++){
134 yy[i] = i/20.;
135 xx[i] = Ux(0.5,i/20.);
136 pp[i] = p(i/20.,0.999);
137 }
138 cout << " " << yy << endl;
139 plot([xx, yy], wait=true, cmm="Ux x=0.5 cup");
140 plot([yy, pp], wait=true, cmm="p y=0.999 cup");
141 }
142
143 // Initialization
144 dt = 0.1; //probably too big
145 int nbiter = 3;
146 real coefdt = 0.25^(1./nbiter);
147 real coefcut = 0.25^(1./nbiter);
148 real cut = 0.01;
149 real tol = 0.5;
150 real coeftol = 0.5^(1./nbiter);
151 nu = 1./reylnods;
152
167 // Update
168 Upx = Ux;
169 Upy = Uy;
170 kp = k;
171 epp = ep;
172 Tp = max(T, 25); //for beauty only should be removed
173 Tp = min(Tp, 35); //for security only should be removed
174 kp = max(k, 0.0001); epp = max(ep, 0.0001); // to be secure: should not be active
175 muT = 0.09*kp*kp/epp;
176
177 // Solve NS
178 NavierStokes;
179
180 // Update
181 prode = -0.126*kp*(pow(2*dx(Ux),2)+pow(2*dy(Uy),2)+2*pow(dx(Uy)+dy(Ux),2))/2;
182 prodk = -prode*kp/epp*0.09/0.126;
183 kappa = muT/0.41;
184 stress = abs(dy(Ux));
185
191 // Plot
192 plot(T, value=true, fill=true);
193 plot([Ux, Uy], p, coef=0.2, cmm=" [Ux, Uy] - p", WindowIndex=1);
194
195 // Time
196 cout << "\tTime = " << clock()-T0 << endl;
197 }
198
199 // Check
200 if (iter >= nbiter) break;
201
202 // Adaptmesh
203 Th = adaptmesh(Th, [dx(Ux), dy(Ux), dx(Ux), dy(Uy)], splitpbedge=1, abserror=0,␣
˓→cutoff=cut, err=tol, inquire=0, ratio=1.5, hmin=1./1000);
204 plot(Th);
205
206 // Update
207 dt = dt * coefdt;
(continues on next page)
In a microwave oven heat comes from molecular excitation by an electromagnetic field. For a plane monochromatic
wave, amplitude is given by Helmholtz’s equation:
𝛽𝑣 + ∆𝑣 = 0.
We consider a rectangular oven where the wave is emitted by part of the upper wall. So the boundary of)︂the domain is
𝑦−𝑐
(︂
made up of a part Γ1 where 𝑣 = 0 and of another part Γ2 = [𝑐, 𝑑] where for instance 𝑣 = sin 𝜋 .
𝑐−𝑑
Within an object to be cooked, denoted by 𝐵, the heat source is proportional to 𝑣 2 . At equilibrium, one has :
−∆𝜃 = 𝑣 2 𝐼𝐵
𝜃Γ = 0
1 // Parameters
2 int nn = 2;
3 real a = 20.;
4 real b = 20.;
5 real c = 15.;
6 real d = 8.;
7 real e = 2.;
8 real l = 12.;
9 real f = 2.;
10 real g = 2.;
11
12 // Mesh
13 border a0(t=0, 1){x=a*t; y=0; label=1;}
14 border a1(t=1, 2){x=a; y=b*(t-1); label=1;}
15 border a2(t=2, 3){ x=a*(3-t); y=b; label=1;}
16 border a3(t=3, 4){x=0; y=b-(b-c)*(t-3); label=1;}
17 border a4(t=4, 5){x=0; y=c-(c-d)*(t-4); label=2;}
18 border a5(t=5, 6){x=0; y=d*(6-t); label=1;}
19
(a) Temperature at time step 100 (b) Velocity at time step 100
(c) Temperature at time step 200 (d) Velocity at time step 200
(e) Temperature at time step 300 (f) Velocity at time step 300
(g) Temperature at time step 400 (h) Velocity at time step 400
(i) Temperature at time step 500 (j) Velocity at time step 500
31 // Fespace
32 fespace Vh(Th, P1);
33 Vh R=(region-air)/(meat-air);
34 Vh<complex> v, w;
35 Vh vr, vi;
36
40 // Problem
41 solve muwave(v, w)
42 = int2d(Th)(
43 v*w*(1+R)
44 - (dx(v)*dx(w) + dy(v)*dy(w))*(1 - 0.5i)
45 )
46 + on(1, v=0)
47 + on(2, v=sin(pi*(y-c)/(c-d)))
48 ;
49
50 vr = real(v);
51 vi = imag(v);
52
53 // Plot
54 plot(vr, wait=1, ps="rmuonde.ps", fill=true);
55 plot(vi, wait=1, ps="imuonde.ps", fill=true);
56
57 // Problem (temperature)
58 ff=1e5*(vr^2 + vi^2)*R;
59
70 // Plot
71 plot(u, wait=1, ps="tempmuonde.ps", fill=true);
Results are shown on Fig. 2.16a, Fig. 2.16b and Fig. 2.16c.
(c) Temperature
Thanks to the function BFGS it is possible to solve complex nonlinear optimization problem within FreeFEM. For
example consider the following inverse problem
min𝑏,𝑐,𝑑∈𝑅 𝐽 = 𝐸 (𝑢 − 𝑢𝑑 )2
∫︀
−∇(𝜅(𝑏, 𝑐, 𝑑) · ∇𝑢) = 0
𝑢|Γ = 𝑢Γ
where the desired state 𝑢𝑑 , the boundary data 𝑢Γ and the observation set 𝐸 ⊂ Ω are all given. Furthermore let us
assume that:
1 // Mesh
2 border aa(t=0, 2*pi){x=5*cos(t); y=5*sin(t);};
3 border bb(t=0, 2*pi){x=cos(t); y=sin(t);};
4 border cc(t=0, 2*pi){x=-3+cos(t); y=sin(t);};
5 border dd(t=0, 2*pi){x=cos(t); y =-3+sin(t);};
6
9 // Fespace
10 fespace Vh(th, P1);
11 Vh Ib=((x^2+y^2)<1.0001),
12 Ic=(((x+3)^2+ y^2)<1.0001),
13 Id=((x^2+(y+3)^2)<1.0001),
14 Ie=(((x-1)^2+ y^2)<=4),
15 ud, u, uh, du;
16
17 // Problem
18 real[int] z(3);
19 problem A(u, uh)
20 = int2d(th)(
21 (1+z[0]*Ib+z[1]*Ic+z[2]*Id)*(dx(u)*dx(uh) + dy(u)*dy(uh))
22 )
23 + on(aa, u=x^3-y^3)
24 ;
25
26 // Solve
27 z[0]=2; z[1]=3; z[2]=4;
28 A;
(continues on next page)
31 ofstream f("J.txt");
32 func real J(real[int] & Z){
33 for (int i = 0; i < z.n; i++)
34 z[i] =Z[i];
35 A;
36 real s = int2d(th)(Ie*(u-ud)^2);
37 f << s << " ";
38 return s;
39 }
40
41 // Problem BFGS
42 real[int] dz(3), dJdz(3);
43 problem B (du, uh)
44 = int2d(th)(
45 (1+z[0]*Ib+z[1]*Ic+z[2]*Id)*(dx(du)*dx(uh) + dy(du)*dy(uh))
46 )
47 + int2d(th)(
48 (dz[0]*Ib+dz[1]*Ic+dz[2]*Id)*(dx(u)*dx(uh) + dy(u)*dy(uh))
49 )
50 +on(aa, du=0)
51 ;
52
64 real[int] Z(3);
65 for(int j = 0; j < z.n; j++)
66 Z[j]=1;
67
73 // Plot
74 plot(ud, value=1, ps="u.eps");
In this example the sets 𝐵, 𝐶, 𝐷, 𝐸 are circles of boundaries 𝑏𝑏, 𝑐𝑐, 𝑑𝑑, 𝑒𝑒 and the domain Ω is the circle of boundary
𝑎𝑎.
The desired state 𝑢𝑑 is the solution of the PDE for 𝑏 = 2, 𝑐 = 3, 𝑑 = 4. The unknowns are packed into array 𝑧.
Note: It is necessary to recopy 𝑍 into 𝑧 because one is a local variable while the other one is global.
−∇ · (𝜅∇𝑝) = 2𝐼𝐸 (𝑢 − 𝑢𝑑 )
𝑝|Γ = 0
Consequently:
∫︀
𝛿𝐽 = −
∫︀ Ω (∇ · (𝜅∇𝑝))𝛿𝑢
= Ω∫︀(𝜅∇𝑝 · ∇𝛿𝑢)
= − Ω (𝛿𝜅∇𝑝 · ∇𝑢)
𝐽𝑏′ = − ∫︀𝐵 ∇𝑝 · ∇𝑢
∫︀
𝐽𝑐′ = − ∫︀𝐶 ∇𝑝 · ∇𝑢
𝐽𝑑′ = − 𝐷 ∇𝑝 · ∇𝑢
Note: As BFGS stores an 𝑀 × 𝑀 matrix where 𝑀 is the number of unknowns, it is dangerously expensive to use this
method when the unknown 𝑥 is a Finite Element Function. One should use another optimizer such as the NonLinear
Conjugate Gradient NLCG (also a key word of FreeFEM).
Compressible Euler equations should be discretized with Finite Volumes or FEM with flux up-winding scheme but these
are not implemented in FreeFEM. Nevertheless acceptable results can be obtained with the method of characteristics
1 (︀ +
provided that the mean values 𝑓¯ = 𝑓 + 𝑓 − are used at shocks in the scheme, and finally mesh adaptation.
)︀
2
¯∇𝜌 + 𝜌¯∇ · 𝑢 = 0
𝜕𝑡 𝜌 + 𝑢
𝜌¯(𝜕𝑡 𝑢 + 𝜌𝑢
𝜌¯ ∇𝑢 + ∇𝑝 = 0
¯∇𝑝 + (𝛾 − 1)¯
𝜕𝑡 𝑝 + 𝑢 𝑝∇ · 𝑢 = 0
One possibility is to couple 𝑢, 𝑝 and then update 𝜌, i.e.:
1 𝑚+1
(𝛾−1)𝛿𝑡𝑝¯𝑚 (𝑝 − 𝑝𝑚 ∘ 𝑋 𝑚 ) + ∇ · 𝑢𝑚+1 = 0
𝑚
𝜌¯ 𝑚+1 ˜ 𝑚 ) + ∇𝑝𝑚+1
𝛿𝑡 (𝑢 − 𝑢𝑚 ∘ 𝑋 = 0
𝑚+1 𝑚 𝑚 𝜌¯𝑚 𝑚+1
𝜌 = 𝜌 ∘ 𝑋 + (𝛾−1) 𝑝¯𝑚 (𝑝 − 𝑝𝑚 ∘ 𝑋 𝑚 )
1 // Parameters
2 verbosity = 1;
3 int anew = 1;
4 int m = 5;
5 real x0 = 0.5;
6 real y0 = 0.;
7 real rr = 0.2;
8 real dt = 0.01;
9 real u0 = 2.;
10 real err0 = 0.00625;
11 real pena = 2.;
12
13 // Mesh
14 border ccc(t=0, 2){x=2-t; y=1;};
15 border ddd(t=0, 1){x=0; y=1-t;};
16 border aaa1(t=0, x0-rr){x=t; y=0;};
17 border cercle(t=pi, 0){x=x0+rr*cos(t); y=y0+rr*sin(t);}
18 border aaa2(t=x0+rr, 2){x=t; y=0;};
19 border bbb(t=0, 1){x=2; y=t;};
20
21 mesh Th;
22 if(anew)
23 Th = buildmesh (ccc(5*m) + ddd(3*m) + aaa1(2*m) + cercle(5*m) + aaa2(5*m) + bbb(2*m));
24 else
25 Th = readmesh("Th_circle.mesh"); plot(Th);
26
27 // fespace
28 fespace Wh(Th, P1);
29 Wh u, v;
30 Wh u1, v1;
31 Wh uh, vh;
32
39 // Initialization
40 if(anew){
41 u1 = u0;
42 v1 = 0;
43 r1 = 1;
44 }
45 else{
46 ifstream g("u.txt"); g >> u1[];
47 ifstream gg("v.txt"); gg >> v1[];
48 ifstream ggg("r.txt"); ggg >> r1[];
49 plot(u1, ps="eta.eps", value=1, wait=1);
50 err0 = err0/10;
51 dt = dt/10;
52 }
53
54 // Problem
55 problem euler(u, v, r, uh, vh, rh)
56 = int2d(Th)(
57 (u*uh + v*vh + r*rh)/dt
58 + ((dx(r)*uh + dy(r)*vh) - (dx(rh)*u + dy(rh)*v))
59 )
60 + int2d(Th)(
61 - (
62 rh*convect([u1,v1],-dt,r1)
63 + uh*convect([u1,v1],-dt,u1)
64 + vh*convect([u1,v1],-dt,v1)
65 )/dt
66 )
67 +int1d(Th, 6)(
68 rh*u
69 )
70 + on(2, r=0)
71 + on(2, u=u0)
72 + on(2, v=0)
73 ;
74
75 // Iterations
76 int j = 80;
77 for(int k = 0; k < 3; k++){
78 if(k==20){
79 err0 = err0/10;
80 dt = dt/10;
81 j = 5;
82 }
83
84 // Solve
85 for(int i = 0; i < j; i++){
86 euler;
87 u1=u;
(continues on next page)
94 // Mesh adaptation
95 Th = adaptmesh (Th, r, nbvx=40000, err=err0, abserror=1, nbjacoby=2, omega=1.8,␣
˓→ratio=1.8, nbsmooth=3, splitpbedge=1, maxsubdiv=5, rescaling=1);
96 plot(Th);
97 u = u;
98 v = v;
99 r = r;
100
101 // Save
102 savemesh(Th, "Th_circle.mesh");
103 ofstream f("u.txt"); f << u[];
104 ofstream ff("v.txt"); ff << v[];
105 ofstream fff("r.txt"); fff << r[];
106 r1 = sqrt(u*u+v*v);
107 plot(r1, ps="mach.eps", value=1);
108 r1 = r;
109 }
First, it is possible to define variational forms, and use this forms to build matrix and vector to make very fast script (4
times faster here).
For example solve the ThermalConduction problem, we must solve the temperature equation in Ω in a time interval
(0,T).
𝜕𝑡 𝑢 − ∇ · (𝜅∇𝑢) = 0 in Ω × (0, 𝑇 )
𝑢(𝑥, 𝑦, 0) = 𝑢0 + 𝑥𝑢1
𝑢 = 30 on Γ24 × (0, 𝑇 )
𝜕𝑢
𝜅 𝜕𝑛 + 𝛼(𝑢 − 𝑢𝑒 ) = 0 on Γ × (0, 𝑇 )
𝑏𝑖 if 𝑖 ∈ Γ24
{︂ ′′
𝑛 −1 𝑛 ′ 𝑛−1 1 𝑛
𝑢 = 𝐴 𝑏 , 𝑏 = 𝑏0 + 𝑀 𝑢 , 𝑏” = 𝑏𝑐𝑙 , 𝑏𝑖 =
𝜀 𝑏′𝑖 else
Fig. 2.18: Pressure for a Euler flow around a disk at Mach 2 computed by (2.6)
Where with 1
𝜀 = tgv = 1030 :
1
if 𝑖 ∈ Γ24 , and
⎧
⎨ ∫︁ 𝜀 ∫︁ 𝑗=𝑖
𝐴𝑖𝑗 =
⎩ 𝑤𝑗 𝑤𝑖 /𝑑𝑡 + 𝑘(∇𝑤𝑗 .∇𝑤𝑖 ) + 𝛼𝑤𝑗 𝑤𝑖 else
⎧ Ω Γ13
⎨ ∫︁ 1𝜀 if 𝑖 ∈ Γ24 , and 𝑗 = 𝑖
𝑀𝑖𝑗 =
⎩ 𝑛 𝑤𝑗 𝑤𝑖 /𝑑𝑡 else
∫︀ Ω
𝑏0,𝑖 = 𝑛 Γ13 𝛼𝑢𝑢𝑒 𝑤𝑖
𝑏𝑐𝑙 = 𝑢0 the initial data
1 ...
2 Vh u0=fu0, u=u0;
Consider the following script to solve a time dependent Stokes problem in a cavity
1 // Parameters
2 real nu = 0.1;
3 real T=1.;
4 real dt = 0.1;
5
6 // Mesh
7 mesh Th = square(10, 10);
8
9 // Fespace
10 fespace Vh(Th, P2)
11 Vh u, v;
12 Vh uu, vv;
13 Vh uold=0, vold=0;
14
19 // Problem
20 problem stokes (u, v, p, uu, vv, pp)
21 = int2d(Th)(
22 (u*uu+v*vv)/dt
23 + nu*(dx(u)*dx(uu) + dy(u)*dy(uu) + dx(v)*dx(vv) + dy(v)*dy(vv))
24 - p*pp*1.e-6
25 - p*(dx(uu) + dy(vv))
26 - pp*(dx(u) + dy(v))
27 )
28 - int2d(Th)(
29 (uold*uu+vold*vv)/dt
30 )
31 + on(1, 2, 4, u=0, v=0)
32 + on(3, u=1, v=0)
33 ;
34
35 // Time loop
36 int m, M = T/dt;
37 for(m = 0; m < M; m++){
38 stokes;
39 uold = u;
40 vold = v;
(continues on next page)
43 // Plot
44 plot(p, [u, v], value=true, wait=true, cmm="t="+m*dt);
Every iteration is in fact of the form 𝐴[𝑢, 𝑣, 𝑝] = 𝐵[𝑢𝑜𝑙𝑑, 𝑣𝑜𝑙𝑑, 𝑝𝑜𝑙𝑑] + 𝑏 where 𝐴, 𝐵 are matrices and 𝑏 is a vector
containing the boundary conditions. 𝐴, 𝐵, 𝑏 are constructed by:
Note that the boundary conditions are not specified in 𝑏𝑏. Removing the comment // would cause the compiler to
multiply the diagonal terms corresponding to a Dirichlet degree of freedom by a very large term (tgv); if so 𝑏 would
not be needed, on the condition that 𝑢𝑜𝑙𝑑 = 1 on boundary 3 initially. Note also that b has a tgv on the Dirichlet nodes,
by construction, and so does A.
The loop will then be:
There is yet a difficulty with the initialization of sol and with the solution from sol. For this we need a temporary
vector in 𝑋ℎ and here is a solution:
The freefem team agrees that the line sol=w1[]; is mysterious as it copies also w2 and wp into sol. Structured data
such as vectors of 𝑋ℎ here cannot be written component by component. Hence w1=u is not allowed.
2.19.1 Summary
In this tutorial, we will study the wifi signal power in a flat. An awesome flat is especially designed for the experiment,
with 2 walls:
Even if the flat seems small enough to be covered by wifi everywhere, it is still interesting to study where the signal’s
power is the lowest. We will study where to put the hotspot to get the best coverage, and as we’re a bit lazy we will
only put it next to the left wall.
2.19.2 Physics
In a nutshell, the Wifi is a electromagnetic wave that contains a signal : Internet data. Electromagnetic waves are well
know by physicists and are ruled by the 4 Maxwell equations which give you the solution for E, the electrical field,
and B, the magnetic field, in space but also in time.
We don’t care about the time here, because the signal period is really short so our internet quality will not change
with time. Without time, we’re looking for stationaries solutions, and the Maxwell equations can be simplified to one
equation, the Helmholtz one :
𝑘2
∇2 𝐸 + 𝐸=0
𝑛2
Where k is the angular wavenumber of the wifi signal, and n the refractive index of the material the wave is in.
Indeed, the main point of this study is the impact of walls on the signal’s power, where the n is different from air (where
it is 1). In walls, the refractive index is a complex number in which the two parts have a physic interpretation:
• The real part defines the reflexion of the wall (the amount of signal that doesn’t pass).
• The imaginary part defines the absorption of the wall (the amount that disappears).
The wifi hotspot (simulated by a simple circle) will be the boundary condition, with a non null value for our electrical
field.
2.19.3 Coding
The domain
In order to create the domain of experimentation, we need to create border objects, like this :
1 int n=13;
2 mesh Sh = buildmesh(a00(10*n) + a10(10*n) + a20(10*n) + a30(10*n)
3 + a01(10*n) + a11(10*n) + a21(10*n) + a31(10*n)
4 + b00(5*n) + b10(5*n) + b20(5*n) + b30(5*n)
5 + c00(5*n) + c10(5*n) + c20(5*n) + c30(5*n));
6 plot(Sh, wait=1);
1 int bx;
2 for (bx = 1; bx <= 7; bx++){
3 border C(t=0, 2*pi){x=2+cos(t); y=bx*5+sin(t); label=2;}
4
The border C is our hotspot and as you can see a simple circle. Th is our final mesh, with all borders and the hotspot.
Let’s resolve this equation !
4 else { return 0; }
5 }
6
7 Vh<complex> v,w;
8
9 randinit(900);
10 Vh wallreflexion = randreal1();
11 Vh<complex> wallabsorption = randreal1()*0.5i;
12 Vh k = 6;
13
14 cout << "Reflexion of walls min/max: " << wallreflexion[].min << " " << wallreflexion[].
˓→max << "\n";
15 cout << "Absorption of walls min/max: " << wallabsorption[].min << " "<< ␣
˓→wallabsorption[].max << "\n";
17 problem muwave(v,w) =
18 int2d(Th)(
19 (v*w*k^2)/(1+(wallreflexion+wallabsorption)*wall())^2
20 - (dx(v)*dx(w)+dy(v)*dy(w))
21 )
22 + on(2, v=1)
23 ;
24
25 muwave;
26 Vh vm = log(real(v)^2 + imag(v)^2);
27 plot(vm, wait=1, fill=true, value=0, nbiso=65);
28 }
Beautiful isn’t it ? This is the first position for the hotspot, but there are 6 others, and the electrical field is evolving
depending on the position. You can see the other positions here :
2.20.1 Overview
In order to create a plot of a FreeFEM simulation in Matlab© or Octave two steps are necessary:
• The mesh, the finite element space connectivity and the simulation data must be exported into files
• The files must be imported into the Matlab / Octave workspace. Then the data can be visualized with the ffmatlib
library
The steps are explained in more detail below using the example of a stripline capacitor.
Note: Finite element variables must be in P1 or P2. The simulation data can be 2D or 3D.
2.20.2 2D Problem
Consider a stripline capacitor problem which is also shown in Fig. 2.23. On the two boundaries (the electrodes) 𝐶𝐴 ,
𝐶𝐾 a Dirichlet condition and on the enclosure 𝐶𝐵 a Neumann condition is set. The electrostatic potential 𝑢 between
the two electrodes is given by the Laplace equation
∆𝑢(𝑥, 𝑦) = 0
E = −∇𝑢
16 int n=15;
17 mesh Th = buildmesh(enclosure(3*n)+
18 bottomA(-w2*n)+topA(-w2*n)+rightA(-h*n)+leftA(-h*n)+
19 bottomK(-w2*n)+topK(-w2*n)+rightK(-h*n)+leftK(-h*n));
20
21 fespace Vh(Th,P1);
22
23 Vh u,v;
24 real u0=2.0;
(continues on next page)
26 problem Laplace(u,v,solver=LU) =
27 int2d(Th)(dx(u)*dx(v) + dy(u)*dy(v))
28 + on(CA,u=u0)+on(CK,u=0);
29
30 real error=0.01;
31 for (int i=0;i<1;i++){
32 Laplace;
33 Th=adaptmesh(Th,u,err=error);
34 error=error/2.0;
35 }
36 Laplace;
37
38 Vh Ex, Ey;
39 Ex = -dx(u);
40 Ey = -dy(u);
41
42 plot(u,[Ex,Ey],wait=true);
The mesh is stored with the FreeFEM command savemesh(), while the connectivity of the finite element space and
the simulation data are stored with the macro commands ffSaveVh() and ffSaveData(). These two commands are
located in the ffmatlib.idp file which is included in the ffmatlib. Therefore, to export the stripline capacitor data
the following statement sequence must be added to the FreeFEM code:
1 include "ffmatlib.idp"
2
3 //Save mesh
4 savemesh(Th,"capacitor.msh");
5 //Save finite element space connectivity
6 ffSaveVh(Th,Vh,"capacitor_vh.txt");
7 //Save some scalar data
8 ffSaveData(u,"capacitor_potential.txt");
9 //Save a 2D vector field
10 ffSaveData2(Ex,Ey,"capacitor_field.txt");
The mesh file can be loaded into the Matlab / Octave workspace using the ffreadmesh() command. A mesh file
consists of three main sections:
1. The mesh points as nodal coordinates
2. A list of boundary edges including boundary labels
3. List of triangles defining the mesh in terms of connectivity
The three data sections mentioned are returned in the variables p, b and t. The finite element space connectivity and the
simulation data can be loaded using the ffreaddata() command. Therefore, to load the example data the following
statement sequence must be executed in Matlab / Octave:
ffpdeplot() is a plot solution for creating patch, contour, quiver, mesh, border, and region plots of 2D geometries.
The basic syntax is:
1 [handles,varargout] = ffpdeplot(p,b,t,varargin)
varargin specifies parameter name / value pairs to control the plot behaviour. A table showing all options can be
found in the ffmatlib documentation. A small selection of possible plot commands is given as follows:
• Plot of the boundary and the mesh:
1 ffpdeplot(p,b,t,'Mesh','on','Boundary','on');
• Patch plot (2D map or density plot) including mesh and boundary:
1 ffpdeplot(p,b,t,'VhSeq',vh,'XYData',u,'Mesh','on','Boundary','on', ...
2 'XLim',[-2 2],'YLim',[-2 2]);
• 3D surf plot:
1 ffpdeplot(p,b,t,'VhSeq',vh,'XYData',u,'ZStyle','continuous', ...
2 'Mesh','off');
(continues on next page)
1 ffpdeplot(p,b,t,'VhSeq',vh,'XYData',u,'Mesh','off','Boundary','on', ...
2 'XLim',[-2 2],'YLim',[-2 2],'Contour','on','CColor','b', ...
3 'XYStyle','off', 'CGridParam',[150, 150],'ColorBar','off', ...
4 'FlowData',[Ex,Ey],'FGridParam',[24, 24]);
3D problems are handled by the ffpdeplot3D() command, which works similarly to the ffpdeplot() command.
In particular in three-dimensions cross sections of the solution can be created. The following example shows a cross-
sectional problem of a three-dimensional parallel plate capacitor.
2.20.7 References
• Octave
• Matlab
• ffmatlib
THREE
DOCUMENTATION
The fruit of a long maturing process, freefem, in its last avatar, FreeFEM , is a high level integrated development
environment (IDE) to solve numerically systems of partial differential equations (PDE) in dimension 1,2 3 and surfaces
embedded in a 3D domain and lines embedded in a 2D or 3D. It is the ideal tool for teaching the finite element method
but it is also perfect for research to quickly prototype and test new algorithmic ideas or solve multi-physics complex
applications.
FreeFEM has an advanced automatic mesh generator, capable of a posteriori mesh adaptivity; it has a general purpose
elliptic solver interfaced with fast algorithms, such as the multi-frontal method UMFPACK, SuperLU, MUMPS etc.
Hyperbolic and parabolic problems are solved by iterative algorithms prescribed by the user with the high level language
of FreeFEM. It has several triangular or tetraedral finite elements, including discontinuous elements. Everything is
there in FreeFEM to prepare research quality reports with online color display, zooming and other features as well as
postscript printouts, from within or using an external application like paraview.
This manual is meant for students at a Masters level or more, for researchers at any level, and for engineers (including
financial engineering) with some understanding of variational methods for partial differential equations.
Introduction
A partial differential equation is a relation between a function of several variables and its (partial) derivatives. Many
problems in physics, engineering, mathematics and even banking are modeled by one or several partial differential
equations.
FreeFEM is a software to solve these equations numerically, based on the Finite Element Method. As its name implies,
it is a free software (see the copyrights for full detail) it is not a package, it is an integrated product with its own high
level programming language, referred below as a :index freefem script. This software runs on all UNIX OS (with g++
3.3 or later, and OpenGL), on Window XP, Vista and 7, 8, 10 and 11 and on MacOS 10 intel and arm.
Moreover FreeFEM is highly adaptive. Many phenomena involve several coupled systems, such as: Fluid-structure
interactions, Lorentz forces for aluminum casting and ocean-atmosphere problems, etc. These require different finite
element approximations and polynomial degrees, possibly on different meshes. Some algorithms like the Schwarz’
domain decomposition method also requires data interpolation on multiple meshes within one program. FreeFEM
can handle these difficulties, i.e. arbitrary finite element spaces on arbitrary unstructured and adapted bi and three
dimensional meshes.
The characteristics of FreeFEM are:
• Problem description (real or complex valued) by their variational formulations, with access to the internal vectors
and matrices if needed.
• Multi-variables, multi-equations, bi and three-dimensional static or time dependent, linear or nonlinear coupled
systems; however the user is required to describe the iterative procedures which reduce the problem to a set of
linear problems.
• Easy geometric input by analytic description of boundaries by pieces; however this part is not a CAD system; for
instance when two boundaries intersect, the user must specify the intersection points.
105
FreeFEM Documentation, Release 4.8
• Automatic mesh generator, based on the Delaunay-Voronoi algorithm; the inner point density is proportional to
the density of points on the boundaries [GEORGE1996].
• Metric-based anisotropic mesh adaptation. The metric can be computed automatically from the Hessian of any
FreeFEM function [HECHT1998].
• High level user friendly typed input language with an algebra of analytic and finite element functions.
• Multiple finite element meshes within one application with automatic interpolation of data on different meshes
and possible storage of the interpolation matrices.
• A large variety of triangular finite elements: linear, quadratic Lagrangian elements and more, discontinuous P1
and Raviart-Thomas elements, elements of a non-scalar type, the mini-element,. . . (but no quadrangles).
• Tools to define discontinuous Galerkin finite element formulations P0, P1dc, P2dc and keywords: jump, mean,
intalledges.
• A large variety of linear direct and iterative solvers (LU, Cholesky, Crout, CG, GMRES, UMFPACK, MUMPS,
SuperLU, . . . ) and eigenvalue and eigenvector solvers (ARPARK) .
• Near optimal execution speed (compared with compiled C++ implementations programmed directly).
• Online graphics, generation of ,.txt,.eps,.gnu, mesh files for further manipulations of input and output data.
• Many examples and tutorials: elliptic, parabolic and hyperbolic problems, Navier-Stokes flows, elasticity, fluid
structure interactions, Schwarz’s domain decomposition method, eigenvalue problem, residual error indicator,
...
• A parallel version using MPI
3.1 Notations
3.1.1 Generalities
3.1.3 Numbers
3.1.5 Meshes
(︂∫︁ )︂1/2
norm: ‖𝑤‖0,Ω = |𝑤(𝑥, 𝑦)| d𝑥d𝑦
2
Ω
∫︁
scalar product: (𝑣, 𝑤) = 𝑣𝑤
Ω
{︂ ⃒ ∫︁ }︂
• [𝐻 (Ω)] the set 𝑤 ∈ 𝐿 (Ω) ⃒ 2
|𝜕𝑤/𝜕𝑥| + |𝜕𝑤/𝜕𝑦| d𝑥d𝑦 < ∞
2 2
1
⃒ (︀ )︀
⃒
Ω
)︀1/2
norm: ‖𝑤‖1,Ω = ‖𝑤‖20,Ω + ‖∇𝑢‖20.Ω
(︀
𝜕 |𝛼| 𝑤
{︂ ⃒ ∫︁ }︂
• [𝐻 (Ω)] the set 𝑤 ∈ 𝐿 (Ω) ⃒⃒ 2
∈ 𝐿2 (Ω) 2
𝑚
⃒
𝛼1 𝛼2
∀𝛼 = (𝛼1 , 𝛼2 ) ∈ N , |𝛼| = 𝛼1 + 𝛼2
Ω 𝜕𝑥 𝜕𝑦
∑︁ ∫︁
scalar product: (𝑣, 𝑤)1,Ω = 𝐷𝛼 𝑣𝐷𝛼 𝑤
|𝛼|≤𝑚 Ω
[𝐿2 (Ω)2 ] denotes 𝐿2 (Ω) × 𝐿2 (Ω), and also 𝐻 1 (Ω)2 = 𝐻 1 (Ω) × 𝐻 1 (Ω)
• [𝑉ℎ ] denotes the finite element space created by fespace Vh(Th, *) in FreeFEM (see Finite Elements for *)
• [Πℎ 𝑓 ] the projection of the function 𝑓 into 𝑉ℎ (func f=x^2*y^3; Vh v = f;) means 𝑣 = 𝑃 𝑖ℎ (𝑓 ) * [{𝑣}] for
FE-function 𝑣 in 𝑉ℎ means the column vector (𝑣1 , · · · , 𝑣𝑀 )𝑇 if 𝑣 = 𝑣1 𝜑1 + · · · + 𝑣𝑀 𝜑𝑀 , which is shown by
fespace Vh(Th, P2); Vh v; cout << v[] << endl;
1 real x0 = 1.2;
2 real x1 = 1.8;
3 real y0 = 0;
4 real y1 = 1;
5 int n = 5;
6 real m = 20;
7 mesh Th = square(n, m, [x0+(x1-x0)*x, y0+(y1-y0)*y]);
0. will produce a mesh where all quads are split with diagonal 𝑥 − 𝑦 = 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡
1. will produce a Union Jack flag type of mesh
2. will produce a mesh where all quads are split with diagonal 𝑥 + 𝑦 = 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡
3. same as in case 0, except two corners where the triangles are the same as case 2, to avoid having 3 vertices on
the boundary
4. same as in case 2, except two corners where the triangles are the same as case 0, to avoid having 3 vertices on
the boundary
Note: Adding the named parameter label=labs will change the 4 default label numbers to labs[i-1], for example
int[int] labs=[11, 12, 13, 14], and adding the named parameter region=10 will change the region number
to 10, for instance (v 3.8).
To see all of these flags at work, check Square mesh example:
1 int upper = 1;
2 int others = 2;
3 int inner = 3;
4
15 int n = 10;
16 plot(C01(-n) + C02(-n) + C03(-n) + C04(-n) + C05(-n)
17 + C06(-n) + C11(n) + C12(n) + C13(n), wait=true);
18
22 plot(Th, wait=true);
23
24 cout << "Part 1 has region number " << Th(0.75, -0.25).region << endl;
25 cout << "Part 2 has redion number " << Th(0.25, -0.25).region << endl;
Borders and mesh are respectively shown in Fig. 3.2a and Fig. 3.2b.
Triangulation keywords assume that the domain is defined as being on the left (resp right) of its oriented parameterized
boundary
To check the orientation plot 𝑡 ↦→ (𝜙𝑥 (𝑡), 𝜙𝑦 (𝑡)), 𝑡0 ≤ 𝑡 ≤ 𝑡1 . If it is as in Fig. 3.3, then the domain lies on the shaded
area, otherwise it lies on the opposite side.
where 𝑚𝑗 are positive or negative numbers to indicate how many vertices should be on Γ𝑗 , Γ = ∪𝐽𝑗=1 Γ𝐽 , and the
optional parameter (see also References), separated with a comma, can be:
• nbvx= int, to set the maximum number of vertices in the mesh.
• fixedborder= bool, to say if the mesh generator can change the boundary mesh or not (by default the boundary
mesh can change; beware that with periodic boundary conditions (see. Finite Element), it can be dangerous.
The orientation of boundaries can be changed by changing the sign of 𝑚𝑗 .
The following example shows how to change the orientation. The example generates the unit disk with a small circular
hole, and assigns “1” to the unit disk (“2” to the circle inside). The boundary label must be non-zero, but it can also
be omitted.
Note: Notice that the orientation is changed by b(-30) in the 5th line. In the 7th line, ps="fileName" is used to
generate a postscript file with identification shown on the figure.
Note: Borders are evaluated only at the time plot or buildmesh is called so the global variables are defined at this
time. In this case, since 𝑟 is changed between the two border calls, the following code will not work because the first
border will be computed with r=0.3:
1 real r=1;
2 border a(t=0, 2*pi){x=r*cos(t); y=r*sin(t); label=1;}
3 r=0.3;
4 border b(t=0, 2*pi){x=r*cos(t); y=r*sin(t); label=1;}
5 mesh Thwithhole = buildmesh(a(50) + b(-30)); // bug (a trap) because
6 // the two circles have the same radius = :math:`0.3`
1 // Mesh
2 mesh Th = square(2, 2);
3
38 cout << "// Method to find information of point (0.55,0.6)" << endl;
39 {
40 int TNumber = Th(0.55, 0.6).nuTriangle; //the triangle number
41 int RLabel = Th(0.55, 0.6).region; //the region label
42
43 cout << "Triangle number in point (0.55, 0.6): " << TNumber << endl;
44 cout << "Region label in point (0.55, 0.6): " << RLabel << endl;
45 }
46
54 cout << "Area of triangle " << TNumber << ": " << TArea << endl;
55 cout << "Region of triangle " << TNumber << ": " << TRegion << endl;
56 cout << "Label of triangle " << TNumber << ": " << TLabel << endl;
57 }
58
59 cout << "// Hack to get a triangle containing point x, y or region number (old method)" <
˓→< endl;
60 {
61 fespace femp0(Th, P0);
62 femp0 TNumbers; //a P0 function to get triangle numbering
63 for (int i = 0; i < Th.nt; i++)
64 TNumbers[][i] = i;
65 femp0 RNumbers = region; //a P0 function to get the region number
66
67 int TNumber = TNumbers(0.55, 0.6); // Number of the triangle containing (0.55, 0,6)
68 int RNumber = RNumbers(0.55, 0.6); // Number of the region containing (0.55, 0,6)
69
75 cout << "// New method to get boundary information and mesh adjacent" << endl;
76 {
77 int k = 0;
(continues on next page)
98 cout << "Adjacent triangle of the triangle " << k << " by edge " << e << " = " <<␣
˓→Adjacent << endl;
106 cout << "// Print mesh connectivity " << endl;
107 {
108 int NbTriangles = Th.nt;
109 for (int k = 0; k < NbTriangles; k++)
110 cout << k << " : " << int(Th[k][0]) << " " << int(Th[k][1])
111 << " " << int(Th[k][2])
112 << ", label " << Th[k].label << endl;
113
FreeFEM is able to build a triangulation from a set of points. This triangulation is a Delaunay mesh of the convex hull
of the set of points. It can be useful to build a mesh from a table function.
The coordinates of the points and the value of the table function are defined separately with rows of the form: x y
f(x,y) in a file such as:
1 0.51387 0.175741 0.636237
2 0.308652 0.534534 0.746765
3 0.947628 0.171736 0.899823
4 0.702231 0.226431 0.800819
5 0.494773 0.12472 0.580623
6 0.0838988 0.389647 0.456045
7 ...............
The third column of each line is left untouched by the triangulate command. But you can use this third value to
define a table function with rows of the form: x y f(x,y).
The following example shows how to make a mesh from the file xyf with the format stated just above. The command
triangulate only uses the 1st and 2nd columns.
1 // Build the Delaunay mesh of the convex hull
2 mesh Thxy=triangulate("xyf"); //points are defined by the first 2 columns of file `xyf`
3
20 // Plot
21 plot(fxy);
One new way to build a mesh is to have two arrays: one for the 𝑥 values and the other for the 𝑦 values.
(a) The empty mesh with boundary (b) An empty mesh defined from a pseudo region numbering
of triangle
To define a Finite Element space on a boundary, we came up with the idea of a mesh with no internal points (called
empty mesh). It can be useful to handle Lagrange multipliers in mixed and mortar methods.
So the function emptymesh removes all the internal points of a mesh except points on internal boundaries.
1 {
2 border a(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
3 mesh Th = buildmesh(a(20));
4 Th = emptymesh(Th);
5 plot(Th);
6 }
It is also possible to build an empty mesh of a pseudo subregion with emptymesh(Th, ssd) using the set of edges
from the mesh Th; an edge 𝑒 is in this set when, with the two adjacent triangles 𝑒 = 𝑡1 ∩ 𝑡2 and 𝑠𝑠𝑑[𝑇 1] ̸= 𝑠𝑠𝑑[𝑇 2]
where 𝑠𝑠𝑑 refers to the pseudo region numbering of triangles, they are stored in the int[int] array of size “the number
of triangles”.
1 {
2 mesh Th = square(10, 10);
3 int[int] ssd(Th.nt);
4 //build the pseudo region numbering
5 for(int i = 0; i < ssd.n; i++){
6 int iq = i/2; //because 2 triangles per quad
7 int ix = iq%10;
8 int iy = iq/10;
9 ssd[i] = 1 + (ix>=5) + (iy>=5)*2;
10 }
11 //build emtpy with all edges $e=T1 \cap T2$ and $ssd[T1] \neq ssd[T2]$
12 Th = emptymesh(Th, ssd);
13 //plot
14 plot(Th);
15 savemesh(Th, "emptymesh.msh");
16 }
Remeshing
Meshes can be translated, rotated, and deformed by movemesh; this is useful for elasticity to watch the deformation
due to the displacement Φ(𝑥, 𝑦) = (Φ1 (𝑥, 𝑦), Φ2 (𝑥, 𝑦)) of shape.
It is also useful to handle free boundary problems or optimal shape problems.
If Ω is triangulated as 𝑇ℎ (Ω), and Φ is a displacement vector then Φ(𝑇ℎ ) is obtained by:
Sometimes the transformed mesh is invalid because some triangles have flipped over (meaning it now has a negative
area). To spot such problems, one may check the minimum triangle area in the transformed mesh with checkmovemesh
before any real transformation.
For example:
1 verbosity = 4;
2
3 // Parameters
4 real coef = 1;
5
6 // Mesh
7 border a(t=0, 1){x=t; y=0; label=1;};
8 border b(t=0, 0.5){x=1; y=t; label=1;};
9 border c(t=0, 0.5){x=1-t; y=0.5; label=1;};
10 border d(t=0.5, 1){x=0.5; y=t; label=1;};
11 border e(t=0.5, 1){x=1-t; y=1; label=1;};
12 border f(t=0, 1){x=0; y=1-t; label=1;};
13 mesh Th = buildmesh(a(6) + b(4) + c(4) + d(4) + e(4) + f(6));
14 plot(Th, wait=true, fill=true, ps="Lshape.eps");
15
16 // Function
17 func uu = sin(y*pi)/10;
18 func vv = cos(x*pi)/10;
19
20 // Checkmovemesh
21 real minT0 = checkmovemesh(Th, [x, y]); //return the min triangle area
22 while(1){ // find a correct move mesh
23 real minT = checkmovemesh(Th, [x+coef*uu, y+coef*vv]);
24 if (minT > minT0/5) break; //if big enough
25 coef /= 1.5;
26 }
27
28 // Movemesh
29 Th = movemesh(Th, [x+coef*uu, y+coef*vv]);
30 plot(Th, wait=true, fill=true, ps="MovedMesh.eps");
Note: Consider a function 𝑢 defined on a mesh Th. A statement like Th=movemesh(Th...) does not change 𝑢 and
so the old mesh still exists. It will be destroyed when no function uses it. A statement like 𝑢 = 𝑢 redefines 𝑢 on the
new mesh Th with interpolation and therefore destroys the old Th, if 𝑢 was the only function using it.
Now, we give an example of moving a mesh with a Lagrangian function 𝑢 defined on the moving mesh.
1 // Parameters
2 int nn = 10;
3 real dt = 0.1;
4
5 // Mesh
6 mesh Th = square(nn, nn);
7
8 // Fespace
9 fespace Vh(Th, P1);
10 Vh u=y;
11
12 // Loop
13 real t=0;
14 for (int i = 0; i < 4; i++){
15 t = i*dt;
16 Vh f=x*t;
17 real minarea = checkmovemesh(Th, [x, y+f]);
18 if (minarea > 0) //movemesh will be ok
19 Th = movemesh(Th, [x, y+f]);
20
21 cout << " Min area = " << minarea << endl;
22
23 real[int] tmp(u[].n);
24 tmp = u[]; //save the value
(continues on next page)
This section presents the way to obtain a regular triangulation with FreeFEM.
For a set 𝑆, we define the diameter of 𝑆 by
The sequence {𝒯ℎ }ℎ→0 of Ω is called regular if they satisfy the following:
1. limℎ→0 max{diam(𝑇𝑘 )| 𝑇𝑘 ∈ 𝒯ℎ } = 0
𝜌(𝑇𝑘 )
2. There is a number 𝜎 > 0 independent of ℎ such that diam(𝑇𝑘 ) ≥𝜎 for all 𝑇𝑘 ∈ 𝒯ℎ where 𝜌(𝑇𝑘 ) are the diameter
of the inscribed circle of 𝑇𝑘 .
We put ℎ(𝒯ℎ ) = max{diam(𝑇𝑘 )| 𝑇𝑘 ∈ 𝒯ℎ }, which is obtained by
1 mesh Th = ......;
2 fespace Ph(Th, P0);
3 Ph h = hTriangle;
4 cout << "size of mesh = " << h[].max << endl;
The function:
sharply varies in value and the initial mesh given by one of the commands in the Mesh Generation part cannot reflect
its sharp variations.
1 // Parameters
2 real eps = 0.0001;
3 real h = 1;
4 real hmin = 0.05;
5 func f = 10.0*x^3 + y^3 + h*atan2(eps, sin(5.0*y)-2.0*x);
6
7 // Mesh
8 mesh Th = square(5, 5, [-1+2*x, -1+2*y]);
9
10 // Fespace
11 fespace Vh(Th,P1);
12 Vh fh = f;
13 plot(fh);
14
Fig. 3.8: 3D graphs for the initial mesh and 1st and 2nd mesh adaptations
Tip: The solution has the singularity 𝑟3/2 , 𝑟 = |𝑥 − 𝛾| at the point 𝛾 of the intersection of two lines 𝑏𝑐 and 𝑏𝑑 (see
Fig. 3.9a).
1 // Parameters
2 real error = 0.1;
3
4 // Mesh
5 border ba(t=0, 1){x=t; y=0; label=1;}
6 border bb(t=0, 0.5){x=1; y=t; label=1;}
7 border bc(t=0, 0.5){x=1-t; y=0.5; label=1;}
8 border bd(t=0.5, 1){x=0.5; y=t; label=1;}
9 border be(t=0.5, 1){x=1-t; y=1; label=1;}
10 border bf(t=0, 1){x=0; y=1-t; label=1;}
11 mesh Th = buildmesh(ba(6) + bb(4) + bc(4) + bd(4) + be(4) + bf(6));
12
13 // Fespace
14 fespace Vh(Th, P1);
15 Vh u, v;
16
17 // Function
18 func f = 1;
19
20 // Problem
21 problem Poisson(u, v, solver=CG, eps=1.e-6)
22 = int2d(Th)(
23 dx(u)*dx(v)
24 + dy(u)*dy(v)
25 )
26 - int2d(Th)(
27 f*v
28 )
29 + on(1, u=0);
30
38 // Plot
39 plot(u);
To speed up the adaptation, the default parameter err of adaptmesh is changed by hand; it specifies the required
precision, so as to make the new mesh finer or coarser.
The problem is coercive and symmetric, so the linear system can be solved with the conjugate gradient method (pa-
rameter solver=CG) with the stopping criteria on the residual, here eps=1.e-6).
By adaptmesh, the slope of the final solution is correctly computed near the point of intersection of 𝑏𝑐 and 𝑏𝑑 as in
Fig. 3.9b.
This method is described in detail in [HECHT1998]. It has a number of default parameters which can be modified.
If f1,f2 are functions and thold, Thnew are meshes:
Note: As ratio gets closer to 1, the number of generated vertices increases. This may be useful to control
the thickness of refined regions near shocks or boundary layers.
• omega= relaxation parameter for the smoothing procedure. 1.0 is the default.
• cutoff= lower limit for the relative error evaluation. 1.0e-6 is the default.
• verbosity= informational messages level (can be chosen between 0 and ∞).
Also changes the value of the global variable verbosity (obsolete).
• inquire= To inquire graphically about the mesh. false is the default.
• splitpbedge= If true, splits all internal edges in half with two boundary vertices.
true is the default.
• maxsubdiv= Changes the metric such that the maximum subdivision of a background edge is bound by
val.
Always limited by 10, and 10 is also the default.
• rescaling= if true, the function, with respect to which the mesh is adapted, is rescaled to be between 0
and 1.
true is the default.
• keepbackvertices= if true, tries to keep as many vertices from the original mesh as possible.
true is the default.
• IsMetric= if true, the metric is defined explicitly.
false is the default. If the 3 functions 𝑚11 , 𝑚12 , 𝑚22 are given, they directly define a symmetric matrix
field whose Hessian is computed to define a metric. If only one function is given, then it represents the
isotropic mesh size at every point.
For example, if the partial derivatives fxx (= 𝜕 2 𝑓 /𝜕𝑥2 ), fxy (= 𝜕 2 𝑓 /𝜕𝑥𝜕𝑦), fyy (= 𝜕 2 𝑓 /𝜕𝑦 2 ) are given,
we can set Th = adaptmesh(Th, fxx, fxy, fyy, IsMetric=1, nbvx=10000, hmin=hmin);
• power= exponent power of the Hessian used to compute the metric.
1 is the default.
• thetamax= minimum corner angle in degrees.
Default is 10∘ where the corner is 𝐴𝐵𝐶 and the angle is the angle of the two vectors 𝐴𝐵, 𝐵𝐶, (0 imply
no corner, 90 imply perpendicular corner, . . . ).
• splitin2= boolean value.
If true, splits all triangles of the final mesh into 4 sub-triangles.
• metric= an array of 3 real arrays to set or get metric data information.
The size of these three arrays must be the number of vertices. So if m11,m12,m22 are three P1 fi-
nite elements related to the mesh to adapt, you can write: metric=[m11[],m12[],m22[]] (see file
convect-apt.edp for a full example)
• nomeshgeneration= If true, no adapted mesh is generated (useful to compute only a metric).
• periodic= Writing periodic=[[4,y],[2,y],[1,x],[3,x]]; builds an adapted periodic mesh.
The sample builds a biperiodic mesh of a square. (see periodic finite element spaces, and see the Sphere
example for a full example)
We can use the command adaptmesh to build a uniform mesh with a constant mesh size. To build a mesh with a
constant mesh size equal to 30
1
try:
Two operators have been introduced to remove triangles from a mesh or to divide them. Operator trunc has the
following parameters:
• boolean function to keep or remove elements
• label= sets the label number of new boundary item, one by default.
• split= sets the level 𝑛 of triangle splitting.
Each triangle is split in 𝑛 × 𝑛, one by default.
To create the mesh Th3 where all triangles of a mesh Th are split in 3×3, just write:
The following example construct all “trunced” meshes to the support of the basic function of the space Vh
(cf. abs(u)>0), split all the triangles in 5×5, and put a label number to 2 on a new boundary.
1 // Mesh
2 mesh Th = square(3, 3);
3
4 // Fespace
5 fespace Vh(Th, P1);
6 Vh u=0;
7
(a) Mesh of support the function P1 number 0, split in 5×5 (b) Mesh of support the function P1 number 6, split in 5×5
This command changes the label of elements and border elements of a mesh.
Changing the label of elements and border elements will be done using the keyword change. The parameters for this
command line are for two dimensional and three dimensional cases:
• refe= is an array of integers to change the references on edges
• reft= is an array of integers to change the references on triangles
• label= is an array of integers to change the 4 default label numbers
• region= is an array of integers to change the default region numbers
• renumv= is an array of integers, which explicitly gives the new numbering of vertices in the new mesh. By
default, this numbering is that of the original mesh
• renumt= is an array of integers, which explicitly gives the new numbering of elements in the new mesh, according
the new vertices numbering given by renumv=. By default, this numbering is that of the original mesh
• flabel= is an integer function given the new value of the label
• fregion= is an integer function given the new value of the region
• rmledges= is an integer to remove edges in the new mesh, following a label
• rmInternalEdges= is a boolean, if equal to true to remove the internal edges. By default, the internal edges
are stored
These vectors are composed of 𝑛𝑙 successive pairs of numbers 𝑂, 𝑁 where 𝑛𝑙 is the number (label or region) that we
want to change. For example, we have :
1 // Mesh
2 mesh Th1 = square(10, 10);
3 mesh Th2 = square(20, 10, [x+1, y]);
4
5 int[int] r1=[2,0];
6 plot(Th1, wait=true);
7
11 // boundary label: 1 -> 1 bottom, 2 -> 1 right, 3->1 top, 4->1 left boundary label is 1
12 int[int] re=[1,1, 2,1, 3,1, 4,1]
13 Th2=change(Th2,refe=re);
14 plot(Th2,wait=1) ;
(a) Initial mesh (b) All left mesh triangle is split conformaly in
int(1+5*(square(x-0.5)+y*y)^2 triangles
1 // Mesh
2 border a(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
3 mesh Th = buildmesh(a(20));
4 plot(Th, wait=true, ps="NotSplittedMesh.eps");
5
6 // Splitmesh
7 Th = splitmesh(Th, 1 + 5*(square(x-0.5) + y*y));
8 plot(Th, wait=true, ps="SplittedMesh.eps");
Meshing Examples
Tip: Cardioid
1 real b = 1, a = b;
2 border C(t=0, 2*pi){x=(a+b)*cos(t)-b*cos((a+b)*t/b); y=(a+b)*sin(t)-b*sin((a+b)*t/b);}
3 mesh Th = buildmesh(C(50));
4 plot(Th, ps="Cardioid.eps", bw=true);
6 real[int] p00 = [0, 1], p01 = [0, -1], q00 = [-2, 0.1], q01 = [-2, -0.5];
7 real[int] p11 = [1,-0.9], q10 = [0.1, -0.95], q11=[0.5, -1];
8 real[int] p21 = [2, 0.7], q20 = [3, -0.4], q21 = [4, 0.5];
9 real[int] q30 = [0.5, 1.1], q31 = [1.5, 1.2];
10 border G1(t=0, 1){
11 x=bzi(p00[0], p01[0], q00[0], q01[0], t);
12 y=bzi(p00[1], p01[1], q00[1], q01[1], t);
13 }
14 border G2(t=0, 1){
15 x=bzi(p01[0], p11[0], q10[0], q11[0], t);
16 y=bzi(p01[1], p11[1], q10[1], q11[1], t);
17 }
18 border G3(t=0, 1){
19 x=bzi(p11[0], p21[0], q20[0], q21[0], t);
20 y=bzi(p11[1], p21[1], q20[1], q21[1], t);
21 }
22 border G4(t=0, 1){
23 x=bzi(p21[0], p00[0], q30[0], q31[0], t);
24 y=bzi(p21[1], p00[1], q30[1], q31[1], t);
25 }
26 int m = 5;
27 mesh Th = buildmesh(G1(2*m) + G2(m) + G3(3*m) + G4(m));
28 plot(Th, ps="Bezier.eps", bw=true);
Note: Up to the version 3, FreeFEM allowed to consider a surface problem such as the PDE is treated like boundary
conditions on the boundary domain (on triangles describing the boundary domain). With the version 4, in particular
4.2.1, a completed model for surface problem is possible, with the definition of a surface mesh and a surface problem
with a variational form on domain ( with triangle elements) and application of boundary conditions on border domain
(describing by edges). The keywords to define a surface mesh is meshS.
3d mesh generation
Note: For 3D mesh tools, put load "msh3" at the top of the .edp script.
The function cube like its 2d function square is a simple way to build cubic objects, it is contained in plugin msh3
(import with load "msh3").
The following code generates a 3 × 4 × 5 grid in the unit cube [0, 1]3 .
1 load "msh3"
2
7 cout << "Volume = " << Th.measure << ", border area = " << Th.bordermeasure << endl;
8
9 int err = 0;
10 for(int i = 0; i < 100; ++i){
11 real s = int2d(Th,i)(1.);
12 real sx = int2d(Th,i)(x);
13 real sy = int2d(Th,i)(y);
14 real sz = int2d(Th,i)(z);
15
16 if(s){
17 int ix = (sx/s+1.5);
18 int iy = (sy/s+1.5);
19 int iz = (sz/s+1.5);
20 int ii = (ix + 4*(iy+1) + 16*(iz+1) );
21 //value of ix,iy,iz => face min 0, face max 2, no face 1
22 cout << "Label = " << i << ", s = " << s << " " << ix << iy << iz << " : " << ii
˓→<< endl;
23 if( i != ii ) err++;
24 }
25 }
26 real volr11 = int3d(Th,r11)(1.);
27 cout << "Volume region = " << 11 << ": " << volr11 << endl;
28 if((volr11 - Th.measure )>1e-8) err++;
29 plot(Th, fill=false);
30 cout << "Nb err = " << err << endl;
31 assert(err==0);
1 Enter: BuildCube: 3
2 kind = 3 n tet Cube = 6 / n slip 6 19
3 Cube nv=210 nt=720 nbe=296
4 Out: BuildCube
5 Volume = 8, border area = 24
6 Label = 25, s = 4 110 : 25
7 Label = 37, s = 4 101 : 37
8 Label = 40, s = 4 011 : 40
9 Label = 42, s = 4 211 : 42
10 Label = 45, s = 4 121 : 45
11 Label = 57, s = 4 112 : 57
12 Volume region = 11: 8
13 Nb err = 0
where (𝑧𝑖,𝑗 )𝑗=0,...,𝑀 are the 𝑀 + 1 equidistant points on the interval [𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 ), 𝑧𝑚𝑎𝑥(𝑉𝑖2𝑑 )]:
𝑧𝑚𝑎𝑥(𝑉𝑖2𝑑 ) − 𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 )
𝑧𝑖,𝑗 = 𝑗 𝛿𝛼 + 𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 ), 𝛿𝛼 = .
𝑀
The function 𝜃𝑖 , defined on [𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 ), 𝑧𝑚𝑎𝑥(𝑉𝑖2𝑑 )], is given by:
𝜃𝑖,0 if 𝑧 = 𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 ),
{︂
𝜃𝑖 (𝑧) =
𝜃𝑖,𝑗 if 𝑧 ∈]𝜃𝑖,𝑗−1 , 𝜃𝑖,𝑗 ],
with (𝜃𝑖,𝑗 )𝑗=0,...,𝑀𝑖 are the 𝑀𝑖 + 1 equidistant points on the interval [𝑧𝑚𝑖𝑛(𝑉𝑖2𝑑 ), 𝑧𝑚𝑎𝑥(𝑉𝑖2𝑑 )].
Set a triangle 𝐾 = (𝑉𝑖12𝑑 , 𝑉𝑖22𝑑 , 𝑉𝑖32𝑑 ) of the two dimensional mesh. 𝐾 is associated with a triangle on the upper surface
(resp. on the lower surface) of layer mesh:
3𝑑
(𝑉𝑖1,𝑀 3𝑑
, 𝑉𝑖2,𝑀 3𝑑
, 𝑉𝑖3,𝑀 ) (resp. (𝑉𝑖1,0
3𝑑 3𝑑
, 𝑉𝑖2,0 3𝑑
, 𝑉𝑖3,0 )).
Also 𝐾 is associated with 𝑀 volume prismatic elements which are defined by:
3𝑑 3𝑑 3𝑑 3𝑑 3𝑑 3𝑑
∀𝑗 = 0, . . . , 𝑀, 𝐻𝑗 = (𝑉𝑖1,𝑗 , 𝑉𝑖2,𝑗 , 𝑉𝑖3,𝑗 , 𝑉𝑖1,𝑗+1 , 𝑉𝑖2,𝑗+1 , 𝑉𝑖3,𝑗+1 ).
This vector contains successive pairs of the 2d label number at index 2𝑖 and the corresponding 3d label number
at index 2𝑖 + 1, like change.
• labelup= This vector is used to initialize the 3d label numbers of the upper/top face from the 2d region number.
This vector contains successive pairs of the 2d region number at index 2𝑖 and the corresponding 3d label number
at index 2𝑖 + 1, like change.
• labeldown= Same as the previous case but for the lower/down face label.
Moreover, we also add post processing parameters that allow to moving the mesh. These parameters correspond to
parameters transfo, facemerge and ptmerge of the command line movemesh.
The vector region, labelmid, labelup and labeldown These vectors are composed of 𝑛𝑙 successive pairs of number
𝑂𝑖 , 𝑁𝑙 where 𝑛𝑙 is the number (label or region) that we want to get.
An example of this command is given in the Build layer mesh example.
Tip: Cube
1 //Cube.idp
2 load "medit"
3 load "msh3"
4
5 func mesh3 Cube (int[int] &NN, real[int, int] &BB, int[int, int] &L){
6 real x0 = BB(0,0), x1 = BB(0,1);
7 real y0 = BB(1,0), y1 = BB(1,1);
8 real z0 = BB(2,0), z1 = BB(2,1);
9
12 // 2D mesh
13 mesh Thx = square(nx, ny, [x0+(x1-x0)*x, y0+(y1-y0)*y]);
14
15 // 3D mesh
16 int[int] rup = [0, L(2,1)], rdown=[0, L(2,0)];
17 int[int] rmid=[1, L(1,0), 2, L(0,1), 3, L(1,1), 4, L(0,0)];
18 mesh3 Th = buildlayers(Thx, nz, zbound=[z0,z1],
19 labelmid=rmid, labelup = rup, labeldown = rdown);
20
21 return Th;
22 }
1 include "Cube.idp"
2
Tip: Cone
An axisymtric mesh on a triangle with degenerateness
1 load "msh3"
2 load "medit"
3
4 // Parameters
5 real RR = 1;
6 real HH = 1;
7
8 int nn=10;
9
10 // 2D mesh
11 border Taxe(t=0, HH){x=t; y=0; label=0;}
12 border Hypo(t=1, 0){x=HH*t; y=RR*t; label=1;}
13 border Vert(t=0, RR){x=HH; y=t; label=2;}
14 mesh Th2 = buildmesh(Taxe(HH*nn) + Hypo(sqrt(HH*HH+RR*RR)*nn) + Vert(RR*nn));
15 plot(Th2, wait=true);
16
17 // 3D mesh
18 real h = 1./nn;
19 int MaxLayersT = (int(2*pi*RR/h)/4)*4;//number of layers
20 real zminT = 0;
21 real zmaxT = 2*pi; //height 2*pi
22 func fx = y*cos(z);
23 func fy = y*sin(z);
24 func fz = x;
25 int[int] r1T = [0,0], r2T = [0,0,2,2], r4T = [0,2];
(continues on next page)
1 load "msh3"
2 load "TetGen"
3 load "medit"
4
5 // Parameters
6 int C1 = 99;
7 int C2 = 98;
8
9 // 2D mesh
10 border C01(t=0, pi){x=t; y=0; label=1;}
11 border C02(t=0, 2*pi){ x=pi; y=t; label=1;}
12 border C03(t=0, pi){ x=pi-t; y=2*pi; label=1;}
13 border C04(t=0, 2*pi){ x=0; y=2*pi-t; label=1;}
14
37 func XX = x*cos(y);
38 func YY = x*sin(y);
39 func ZZ = z;
40
Remeshing
Note: if an operation on a mesh3 is performed then the same operation is applyed on its surface part (its meshS
associated)
This command changes the label of elements and border elements of a mesh. It’s the equivalent command in 2d mesh
case.
Changing the label of elements and border elements will be done using the keyword change. The parameters for this
command line are for two dimensional and three dimensional cases:
• reftet= is a vector of integer that contains successive pairs of the old label number to the new label number.
• refface= is a vector of integer that contains successive pairs of the old region number to new region number.
• flabel= is an integer function given the new value of the label.
• fregion= is an integer function given the new value of the region.
• rmInternalFaces= is a boolean, equal true to remove the internal faces.
• rmlfaces= is a vector of integer, where triangle’s label given are remove of the mesh
These vectors are composed of 𝑛𝑙 successive pairs of numbers 𝑂, 𝑁 where 𝑛𝑙 is the number (label or region) that we
want to change. For example, we have:
An example of use:
1 // Mesh
2 mesh3 Th1 = cube(10, 10);
3 mesh3 Th2 = cube(20, 10, [x+1, y,z]);
4
5 int[int] r1=[2,0];
6 plot(Th1, wait=true);
7
11 // boundary label: 1 -> 1 bottom, 2 -> 1 right, 3->1 top, 4->1 left boundary label is 1
12 int[int] re=[1,1, 2,1, 3,1, 4,1]
13 Th2=change(Th2,refe=re);
14 plot(Th2,wait=1) ;
This operator have been introduce to remove a piece of mesh or/and split all element or for a particular label element
The three named parameter - boolean function to keep or remove elements - split= sets the level n of triangle splitting.
each triangle is splitted in n × n ( one by default) - freefem:label= sets the label number of new boundary item (1 by
default)
An example of use
1 load "msh3"
2 load "medit"
3 int nn=8;
4 mesh3 Th=cube(nn,nn,nn);
5 // remove the small cube $]1/2,1[^2$
6 Th= trunc(Th,((x<0.5) |(y< 0.5)| (z<0.5)), split=3, label=3);
7 medit("cube",Th);
3D meshes can be translated, rotated, and deformed using the command line movemesh as in the 2D case (see section
movemesh). If Ω is tetrahedrized as 𝑇ℎ (Ω), and Φ(𝑥, 𝑦) = (Φ1(𝑥, 𝑦, 𝑧), Φ2(𝑥, 𝑦, 𝑧), Φ3(𝑥, 𝑦, 𝑧)) is the transformation
vector then Φ(𝑇ℎ ) is obtained by:
𝑝𝑡𝑚𝑒𝑟𝑔𝑒 = 1𝑒 − 7 𝑉 𝑜𝑙(𝐵),
where 𝐵 is the smallest axis parallel boxes containing the discretion domain of Ω and 𝑉 𝑜𝑙(𝐵) is the volume of
this box.
• orientation = An integer expression equal 1, give the oientation of the triangulation, elements must be in the
reference orientation (counter clock wise) equal -1 reverse the orientation of the tetrahedra
Note: The orientation of tetrahedra are checked by the positivity of its area and automatically corrected during the
building of the adjacency.
13 mesh3 Th=Cube(Nxyz,Bxyz,Lxyz);
14 fespace Vh(Th,[P1,P1,P1]);
15 Vh [u1,u2,u3], [v1,v2,v3];
16
20 solve Lame([u1,u2,u3],[v1,v2,v3])=
21 int3d(Th)(
22 lambda*div(u1,u2,u3)*div(v1,v2,v3)
23 +2.*mu*( epsilon(u1,u2,u3)'*epsilon(v1,v2,v3) )
24 )
25 - int3d(Th) (gravity*v3)
26 + on(1,u1=0,u2=0,u3=0);
27
34 Thm=change(Thm,label=ref2);
35 plot(Th,Thm, wait=1,cmm="coef amplification = "+coef );
movemesh doesn’t use the prefix tranfo= [.,.,.], the geometric transformation is directly given by [.,.,.] in the arguments
list
This new function allows to build the surface mesh of a volume mesh, under the condition the surface is the boundary of
the volume. By definition, a mesh3 is defined by a list of vertices, tetrahedron elements and triangle border elements.
buildSurface function create the meshS corresponding, given the list vertices which are on the border domain, the
triangle elements and build the list of edges. Remark, for a closed surface mesh, the edges list is empty.
A simple method to tranform a 2D mesh in 3D Surface mesh. The principe is to project a two dimensional domain in
a three dimensional space, 2d surface in the (x,y,z)-space to create a surface mesh 3D, meshS.
Warning: Since the release 4.2.1, the FreeFEM function movemesh23 returns a meshS type.
This corresponds to translate, rotate or deforme the domain by a displacement vector of this form Φ(x, y) =
(Φ1(𝑥, 𝑦), Φ2(𝑥, 𝑦), Φ3(𝑥, 𝑦)).
The result of moving a two dimensional mesh Th2 by this three dimensional displacement is obtained using:
𝑝𝑡𝑚𝑒𝑟𝑔𝑒 = 1𝑒 − 7 𝑉 𝑜𝑙(𝐵),
where 𝐵 is the smallest axis, parallel boxes containing the discretized domain of Ω and 𝑉 𝑜𝑙(𝐵) is the volume
of this box.
We can do a “gluing” of surface meshes using the process given in Change section. An example to obtain a three
dimensional mesh using the command line tetg and movemesh23 is given below.
1 load "msh3"
2 load "tetgen"
3
4 // Parameters
5 real x10 = 1.;
6 real x11 = 2.;
7 real y10 = 0.;
8 real y11 = 2.*pi;
9
10 func ZZ1min = 0;
11 func ZZ1max = 1.5;
12 func XX1 = x;
13 func YY1 = y;
14
20 func ZZ2 = y;
21 func XX2 = x;
22 func YY2min = 0.;
23 func YY2max = 2*pi;
24
25 real x30=0.;
26 real x31=2*pi;
27 real y30=0.;
28 real y31=1.5;
29
35 // Mesh
36 mesh Thsq1 = square(5, 35, [x10+(x11-x10)*x, y10+(y11-y10)*y]);
37 mesh Thsq2 = square(5, 8, [x20+(x21-x20)*x, y20+(y21-y20)*y]);
38 mesh Thsq3 = square(35, 8, [x30+(x31-x30)*x, y30+(y31-y30)*y]);
39
40 // Mesh 2D to 3D surface
41 meshS Th31h = movemesh23(Thsq1, transfo=[XX1, YY1, ZZ1max], orientation=1);
42 meshS Th31b = movemesh23(Thsq1, transfo=[XX1, YY1, ZZ1min], orientation=-1);
43
50 // Gluing surfaces
51 meshS Th33 = Th31h + Th31b + Th32h + Th32b + Th33h + Th33b;
52 plot(Th33, cmm="Th33");
53
(continues on next page)
59 // Build a mesh of a half cylindrical shell of interior radius 1, and exterior radius 2␣
˓→and a height of 1.5
3d Meshing examples
Tip: Lake
1 load "msh3"
2 load "medit"
3
4 // Parameters
5 int nn = 5;
6
7 // 2D mesh
8 border cc(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
9 mesh Th2 = buildmesh(cc(100));
10
11 // 3D mesh
12 int[int] rup = [0, 2], rlow = [0, 1];
13 int[int] rmid = [1, 1, 2, 1, 3, 1, 4, 1];
14 func zmin = 2-sqrt(4-(x*x+y*y));
15 func zmax = 2-sqrt(3.);
16
24 medit("Th", Th);
1 load "msh3"
2 load "TetGen"
3 load "medit"
4
8 // 3D mesh
9 //parametrization of a sphere
10 func f1 = cos(x)*cos(y);
11 func f2 = cos(x)*sin(y);
12 func f3 = sin(x);
13 //partial derivative of the parametrization
14 func f1x = sin(x)*cos(y);
15 func f1y = -cos(x)*sin(y);
16 func f2x = -sin(x)*sin(y);
17 func f2y = cos(x)*cos(y);
18 func f3x = cos(x);
19 func f3y = 0;
20 //M = DF^t DF
21 func m11 = f1x^2 + f2x^2 + f3x^2;
22 func m21 = f1x*f1y + f2x*f2y + f3x*f3y;
23 func m22 = f1y^2 + f2y^2 + f3y^2;
24
25 func perio = [[4, y], [2, y], [1, x], [3, x]];
26 real hh = 0.1;
27 real vv = 1/square(hh);
28 verbosity = 2;
29 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
30 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
31 plot(Th, wait=true);
32
48 //gluing meshes
49 meshS ThS = ThSsph + ThSsph2;
50
51 cout << " TetGen call without hole " << endl;
52 real[int] domain2 = [1.5, 0., 0., 145, 0.001, 0.5, 0., 0., 18, 0.001];
53 mesh3 Th3fin = tetg(ThS, switch="paAAQYY", nbofregions=2, regionlist=domain2);
54 medit("Sphere with two regions", Th3fin);
55
56 cout << " TetGen call with hole " << endl;
(continues on next page)
1 load "msh3"
2 load "TetGen"
3 load "medit"
4 include "MeshSurface.idp"
5
6 // Parameters
7 real hs = 0.1; //mesh size on sphere
8 int[int] N = [20, 20, 20];
9 real [int,int] B = [[-1, 1], [-1, 1], [-1, 1]];
10 int [int,int] L = [[1, 2], [3, 4], [5, 6]];
11
12 // Meshes
13 meshS ThH = SurfaceHex(N, B, L, 1);
14 meshS ThS = Sphere(0.5, hs, 7, 1);
15
Warning: Since the release 4.2.1, the surface mesh3 object (list of vertices and border elements, without tetahedra
elements) is remplaced by meshS type.
The function square3 like the function square in 2d is the simple way to a build the unit square plan in the space
R3 . To use this command, it is necessary to load the pluging msh3 (need load "msh3"). A square in 3d consists in
building a 2d square which is projected from R2 to R3 . The parameters of this command line are:
• n,m generates a n×m grid in the unit square
(a) The surface mesh of the hex with internal sphere (b) The tetrahedral mesh of the cube with internal ball
• [.,.,.] is [ Φ1, Φ2, Φ3 ] is the geometric transformation from R2 to R3 . By default, [ Φ1, Φ2, Φ3 ] = [x,y,0]
• orientation= equal 1, gives the orientation of the triangulation, elements are in the reference orientation
(counter clock wise) equal -1 reverse the orientation of the triangles it’s the global orientation of the surface
1 extern (-1 intern)
1 real R = 3, r=1;
2 real h = 0.2; //
3 int nx = R*2*pi/h;
4 int ny = r*2*pi/h;
5 func torex= (R+r*cos(y*pi*2))*cos(x*pi*2);
6 func torey= (R+r*cos(y*pi*2))*sin(x*pi*2);
7 func torez= r*sin(y*pi*2);
8
10 meshS ThS=square3(nx,ny,[torex,torey,torez],orientation=-1) ;
The following code generates a 3 × 4 × 5 grid in the unit cube [0, 1]3 with a clock wise triangulation.
Adding at the top of a FreeFEM script include "MeshSurface.idp", constructors of sphere, ellipsoid, surface mesh
of a 3d box are available.
• SurfaceHex(N, B, L, orient)
– this operator allows to build the surface mesh of a 3d box
– int[int] N=[nx,ny,nz]; // the number of seg in the 3 direction
– real [int,int] B=[[xmin,xmax],[ymin,ymax],[zmin,zmax]]; // bounding bax
– int [int,int] L=[[1,2],[3,4],[5,6]]; // the label of the 6 face left,right, front, back, down, right
⃒
𝜋 𝜋 ⃒ 𝑥=Rx 𝑐𝑜𝑠(𝑢)𝑐𝑜𝑠(𝑣)+Ox
∀𝑢 ∈ [− , [ and 𝑣 ∈ [0, 2𝜋], ⃒⃒ 𝑦=Ry 𝑐𝑜𝑠(𝑢)𝑠𝑖𝑛(𝑣)+Oy
2 2 𝑧=Rz 𝑠𝑖𝑛(𝑣)+Oz
1 func meshS SurfaceHex(int[int] & N,real[int,int] &B ,int[int,int] & L,int orientation){
2 real x0=B(0,0),x1=B(0,1);
3 real y0=B(1,0),y1=B(1,1);
4 real z0=B(2,0),z1=B(2,1);
5
6 int nx=N[0],ny=N[1],nz=N[2];
7
24 return Th;
25 }
(continues on next page)
27 func meshS Ellipsoide (real RX,real RY,real RZ,real h,int L,real Ox,real Oy,real Oz,int␣
˓→orientation) {
28 mesh Th=square(10,20,[x*pi-pi/2,2*y*pi]); // $]\frac{-pi}{2},frac{-pi}{2}[\
˓→times]0,2\pi[ $
29 // a parametrization of a sphere
30 func f1 =RX*cos(x)*cos(y);
31 func f2 =RY*cos(x)*sin(y);
32 func f3 =RZ*sin(x);
33 // partiel derivative
34 func f1x= -RX*sin(x)*cos(y);
35 func f1y= -RX*cos(x)*sin(y);
36 func f2x= -RY*sin(x)*sin(y);
37 func f2y= +RY*cos(x)*cos(y);
38 func f3x=-RZ*cos(x);
39 func f3y=0;
40 // the metric on the sphere $ M = DF^t DF $
41 func m11=f1x^2+f2x^2+f3x^2;
42 func m21=f1x*f1y+f2x*f2y+f3x*f3y;
43 func m22=f1y^2+f2y^2+f3y^2;
44 func perio=[[4,y],[2,y],[1,x],[3,x]]; // to store the periodic condition
45 real hh=h;// hh mesh size on unite sphere
46 real vv= 1/square(hh);
47 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
48 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
49 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
50 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
51 int[int] ref=[0,L];
52 meshS ThS=movemesh23(Th,transfo=[f1,f2,f3],orientation=orientation,refface=ref);
53 ThS=mmgs(ThS,hmin=h,hmax=h,hgrad=2.);
54 return ThS;
55 }
56
57 func meshS Ellipsoide (real RX,real RY,real RZ,real h,int L,int orientation) {
58 return Ellipsoide (RX,RY,RZ,h,L,0.,0.,0.,orientation);
59 }
60 func meshS Sphere(real R,real h,int L,int orientation) {
61 return Ellipsoide(R,R,R,h,L,orientation);
62 }
63 func meshS Sphere(real R,real h,int L,real Ox,real Oy,real Oz,int orientation) {
64 return Ellipsoide(R,R,R,h,L,Ox,Oy,Oz,orientation);
65 }
FreeFEM ‘s meshes can be built by the composition of the movemesh23 command from a 2d mesh generation. The
operation is a projection of a 2d plane in R3 following the geometric transformation [ Φ1, Φ2, Φ3 ].
1 load "msh3"
2 real l = 3;
3 border a(t=-l,l){x=t; y=-l;label=1;};
4 border b(t=-l,l){x=l; y=t;label=1;};
5 border c(t=l,-l){x=t; y=l;label=1;};
6 border d(t=l,-l){x=-l; y=t;label=1;};
7 int n = 100;
8 border i(t=0,2*pi){x=1.1*cos(t);y=1.1*sin(t);label=5;};
9 mesh th= buildmesh(a(n)+b(n)+c(n)+d(n)+i(-n));
10 meshS Th= movemesh23(th,transfo=[x,y,cos(x)^2+sin(y)^2]);
Remeshing
This operator allows to define a meshS by truncating another one, i.e. by removing triangles, and/or by splitting each
triangle by a given positive integer s. In a FreeFEM script, this function must be called as follows:
meshS TS2= trunc (TS1, boolean function to keep or remove elements, split = s, label = . . . )
The command has the following arguments:
• boolean function to keep or remove elements
• split= sets the level n of triangle splitting. each triangle is splitted in n × n ( one by default)
• label= sets the label number of new boundary item (1 by default)
An example of how to call the function
1 real R = 3, r=1;
2 real h = 0.2; //
3 int nx = R*2*pi/h;
4 int ny = r*2*pi/h;
5 func torex= (R+r*cos(y*pi*2))*cos(x*pi*2);
6 func torey= (R+r*cos(y*pi*2))*sin(x*pi*2);
7 func torez= r*sin(y*pi*2);
8 // build a tore
9 meshS ThS=square3(nx,ny,[torex,torey,torez]) ;
10 ThS=trunc(ThS, (x < 0.5) | (y < 0.5) | (z > 1.), split=4);
Like 2d and 3d type meshes in FreeFEM, meshS can be translated, rotated or deformated by an application [Φ1, Φ2,
Φ3]. The image 𝑇ℎ (Ω) is obtained by the command movemeshS.
The parameters of movemeshS are:
• transfo= sets the geometric transformation Φ(𝑥, 𝑦) = (Φ1(𝑥, 𝑦, 𝑧), Φ2(𝑥, 𝑦, 𝑧), Φ3(𝑥, 𝑦, 𝑧))
• region= sets the integer labels of the triangles.
0 by default.
• label= sets the labels of the border edges.
This parameter is initialized as the label for the keyword change.
• edgemerge= An integer expression.
When you transform a mesh, some triangles can be merged and fix the parameter to 1, else 0 By default,
this parameter is equal to 1.
• ptmerge = A real expression.
When you transform a mesh, some points can be merged. This parameter is the criteria to define two
merging points. By default, we use
𝑝𝑡𝑚𝑒𝑟𝑔𝑒 = 1𝑒 − 7 𝑉 𝑜𝑙(𝐵),
where 𝐵 is the smallest axis parallel boxes containing the discretion domain of Ω and 𝑉 𝑜𝑙(𝐵) is
the volume of this box.
• orientation = An integer expression
equal 1, give the oientation of the triangulation, elements must be in the reference orientation (counter
clock wise) equal -1 reverse the orientation of the triangles. It’s the global orientation of the normals at the
surface 1 extern (-1 intern)
Example of using
Equivalent for a 2d or 3d mesh, the command change changes the label of elements and border elements of a meshS.
The parameters for this command line are:
• reftri= is a vector of integer that contains successive pairs of the old label number to the new label number for
elements.
• refedge= is a vector of integer that contains successive pairs of the old region number to new region number
for boundary elements.
• flabel= is an integer function given the new value of the label.
• fregion= is an integer function given the new value of the region.
• rmledges= is a vector of integer, where edge’s label given are remove of the mesh
These vectors are composed of 𝑛𝑙 successive pairs of numbers 𝑂, 𝑁 where 𝑛𝑙 is the number (label or region) that we
want to change. For example, we have:
In topology and mathematics, the boundary of a subset S of a topological space X is the set of points which can be
approached both from S and from the outside of S. The general definitions to the boundary of a subset S of a topological
space X are:
• the closure of S without the interior of S 𝜕𝑆 = 𝑆∖𝑆.
˚
• the intersection of the closure of S with the closure of its complement 𝜕𝑆 = 𝑆 ∩ (𝑋∖𝑆).
• the set of points p of X such that every neighborhood of p contains at least one point of S and at least one point
not of S.
More concretely in FreeFEM, the gestion of a 3D mesh is as follows. Let be Ω a subset of R3 and 𝜕Ω is boundary, the
finite element discretization Ωℎ of this domain gives:
• a mesh3 type, denotes Th3, meshing the volume domain. It contains all the nodes, the tetrahedrons Ω𝑖 such as
Ωℎ = ∪𝑖 Ω𝑖 and the list of triangles describing the boundary domain
• a meshS type, denotes ThS, meshing the boundary of the volume domain. Typically, containing the nodes be-
longing to the boundary of Th3 and, if it exists the boundary triangles and the edges.
Remark: Condition of meshS existence | In FreeFEM, a meshS can be defined in 2 cases such as:
• Th3 ⊂ ThS where it exactly describes the bounder of Th3.
• a mehS is an explicite surface mesh given by a list of vertices, triangle finite elements and boundary edge elements
(can be optional follows the geometry domain)
Note: Hence, if an input mesh (.msh freefem or .mesh format) contains a list of vertices, tetrahedra, triangles and
edges, FreeFEM builds a mesh3 whitch contains explicitly a surface mesh type meshS.
The command Gamma allows to build and manipulate the border mesh independly of a volume mesh such as the surface
is described by triangle elements and edges border elements in 3d. Use this function, suppose that the mesh3 object
even contains the geometric description of its surface. That means, the input mesh explicitly contains the list of vertices,
tetrahedra, triangles and edges. In case where the surface mesh doesn’t exist, before calling Gamma, must build it by
calling the buildSurface function (see the next function description).
1 load "msh3"
2 int n= 10;
3 int nvb = (n+1)^3 - (n-1)^3;// Nb boundary vertices
4 int ntb = n*n*12; // Nb of Boundary triangle
5 mesh3 Th=cube(n,n,n);
6 Th = buildBdMesh(Th); // build the surface mesh
7 // build Th1, the surface of Th, defined by triangles elements and edges border␣
˓→elements list
Let Th3 a volume mesh (mesh3 type) ; such as the geometry description is a list of vertices, tetrahedra elements
and triangle border elements. FreeFEM can generate the surface mesh associated to Th3. The intern mechanism of
FreeFEM created directly the meshS associated to Th3 and accessible by the command meshS ThS = Th3.Gamma;.
Available for 3d meshes, the command savesurfacemesh saves the entire surface of a 3d volume mesh3 at the format
.mesh. Two possibilies about the mesh3 surface:
• the geometric surface isn’t explicite, that means the mesh3 doesn’t contain surface elements (triangles) and border
surface elements (edge). The surface is defined by the border of the volume. Hence, savesurfacemesh returns
the list of vertices and faces of the volume mesh, according to a local numbring at the border mesh.
• the geometric surface is explicite and known by the mesh3 type. This may be due to the nature of the data
mesh (list of vertices, tetrahedra, triangles, edges) or a surface building by FreeFEM with the calling of
buildSurface operator. In this case, savesurfacemesh allows to save the real geometry of the surface 3d
mesh (list of vertices, triangles, edges)
Example of use
1 load "msh3"
2 mesh3 Th3=cube(10,15,5);
3 savemesh(Th3, "surf.mesh");
4 savesurfacemesh(Th3, "surfreal.mesh");
5 mesh3 ThS3 = trunc(Th3, 1, split=3);
6 meshS ThSS = ThS3.Gamma;
7 savesurfacemesh(ThS3, "surfacesplit.mesh");
8 savemesh(ThSS,"GammaSplit.mesh" );
A surface 3d mesh can be the result of the generation of several assembled meshes, with caution of the right orientation
at the merged interfaces.
Warning: For the moment, the case of no manifold mesh are not considered in FreeFEM. To check if the meshS
contains no manifold elements, the command nbnomanifold.
1 real R = 3, r=1;
2 real h = 0.1; //
3 int nx = R*2*pi/h;
4 func torex= (R+r*cos(y*pi*2))*cos(x*pi*2);
5 func torey= (R+r*cos(y*pi*2))*sin(x*pi*2);
6 func torez= r*sin(y*pi*2);
7 meshL Th=segment(nx,[torex,torey,torez],removeduplicate=true) ;
The following code generates a 10 subsegments from the unit line with a clock wise triangulation, according to the
geometric transformation [torex,torey,torez] and removing the duplicated points/elements
This operator allows to define a curve mesh from multi-borders. The domain can be defined by a parametrized curve
(keyword border), such as Th1 in the following example or piecewise by parametrized curves, such as the construction
of the mesh Th2.
The pieces can only intersect at their endpoints, but it is possible to join more than two endpoints.
1 load "msh3"
2
3 // conical helix
(continues on next page)
Remeshing
This operator allows to define a meshL by truncating another one, i.e. by removing segments, and/or by splitting each
element by a given positive integer s. Here, an example to use this function:
meshL ThL2= trunc (ThL1, boolean function to keep or remove elements, split = s, label = . . . )
The command has the following arguments:
• boolean function to keep or remove elements
• split= sets the level n of edge splitting, each edge is splitted in n subpart( one by default)
• label= sets the label number of new boundary item (1 by default)
• new2old
• old2new
• renum
• orientation=
equal 1, gives the orientation of the triangulation, elements are in the reference orientation (counter clock
wise) equal -1 reverse the orientation of the triangles it’s the global orientation of the surface 1 extern (-1
intern)
• cleanmesh= is a boolean, allowing remove the duplicated nodes
• removeduplicate= is a boolean, allowing remove the duplicated elements and border elements
• precismesh this parameter is the criteria to define two merging points.
By default, it value is 1e-7 and define the smallest axis parallel boxes containing the discretion domain of
Ω
An example of how to call this function
1 int nx=10;
2 meshL Th=segment(nx,[5.*x,cos(pi*x),sin(pi*x)]);
3 Th=trunc(Th, (x < 0.5) | (y < 0.5) | (z > 1.), split=4);
This is the classical mesh transformation FreeFEM function, meshL can be deformed by an application [ Φ1, Φ2, Φ3
]. The image 𝑇ℎ (Ω) is obtained by the command movemeshL.
The parameters of movemesh are:
• transfo= sets the geometric transformation Φ(𝑥, 𝑦) = (Φ1(𝑥, 𝑦, 𝑧), Φ2(𝑥, 𝑦, 𝑧), Φ3(𝑥, 𝑦, 𝑧))
• refedge= sets the integer labels of the triangles.
0 by default.
• refpoint= sets the labels of the border points.
This parameter is initialized as the label for the keyword change.
• precismesh this parameter is the criteria to define two merging points.
By default, it value is 1e-7 and define the smallest axis parallel boxes containing the discretion domain of
Ω
• orientation = An integer expression
equal 1, give the oientation of the triangulation, elements must be in the reference orientation (counter
clock wise) equal -1 reverse the orientation of the triangles. It’s the global orientation of the normals at the
surface 1 extern (-1 intern)
• cleanmesh= is a boolean, allowing remove the duplicated nodes
• removeduplicate= is a boolean, allowing remove the duplicated elements and border elements
Note: The definition of the geometric transformation depends on the space dimension of the studied problem. It means
that, with curve FEM, it’s possible to treat a real 1D problem (space coordinate is x) then the transformation is given
by x: ->F(x), that means [F_x] and F_y=F_z=0 in FreeFEM function.
Example of using
1 int nx=100;
2 meshL Th=Sline(nx);
3 meshL Th31=movemesh(Th, [x]);
4 meshL Th32=movemesh(Th, [x,-x*(x-1)]);
5 meshL Th3=Th31+Th32;
The command change changes the label of elements and border elements of a meshL.
The parameters for this command line are:
• refedge= is a vector of integer that contains successive pairs of the old label number to the new label number
for elements.
• refpoint= is a vector of integer that contains successive pairs of the old region number to new region number
for boundary elements.
The command Gamma allows to extract the border mesh independly of a surface mesh. With this function, the con-
structed border mesh contains the full geometric description of th eboundary surface. In case where the border mesh
doesn’t exist, before calling Gamma, must build it by calling the buildBdMesh function (see the next function descrip-
tion).
1 load "msh3"
2 int n= 10;
3 meshS Th=square3(n,n);
4 Th = buildBdMesh(Th); // build the border mesh
5 // build Th1, the border of Th, defined by edges elements and point border elements
6 meshL Th1 = Th.Gamma;
An assembling of meshL is possible thanks to the operator +. The result returns a meshL, with caution of the right
orientation at the merged interfaces. Here, the function checkmesh can be called.
1 int n=10;
2 meshL Th1 = segment(n);
3 meshL Th2 = segment(n,[0,x,0],orientation=1);
4 meshL Th3 = segment(n,[x,0,1],orientation=1);
5 meshL Th4 = segment(n,[0,0,x],orientation=-1);
6
7 meshL Th = Th1+Th2+Th3+Th4;
8 Th=rebuildBorder(Th, ridgeangledetection=pi/2.+0.0001);
Warning: For the moment, the case of no manifold mesh are not considered in FreeFEM. To check if the meshL
contains no manifold elements, the command nbnomanifold.
This operator allows to extract a labeled piece or the entire border of a 2D mesh and project it in 3D. Optionally, a
geometic transformation can be applied.
1 mesh Th=square(10,10);
2 int[int] ll=[4];
3 meshL ThL = extract(Th,[x+2,y*5],refedge=ll);
This operator, used in the last example, allows to reconstruted the border elements following a special criteria
ridgeangledetection. By default, it value is 89 * 𝑎𝑟𝑐𝑡𝑎𝑛(1) ≈ 40, the diedral angle for a decahedron.
This function is avalaible for all 3D meshes. It checkes and validates the a given mesh, allows to remove duplicate
vertices and/or elements and border elements. The possible arguments are
• precismesh= this parameter is the criteria to define two merging points.
By default, it value is 1e-7 and define the smallest axis parallel boxes containing the discretion domain of
Ω
• removeduplicate= is a boolean, allowing remove the duplicated elements and border elements
• rebuildboundary= is a boolean, allowing rebuild the border elements (in case of incomplete list given by the
mesh)
Example:
1 mesh3 Th = checkmesh(Th);
TetGen is a software developed by Dr. Hang Si of Weierstrass Institute for Applied Analysis and Stochastics in Berlin,
Germany [HANG2006]. TetGen is free for research and non-commercial use. For any commercial license utilization,
a commercial license is available upon request to Hang Si.
This software is a tetrahedral mesh generator of a three dimensional domain defined by its boundary (a surface). The
input domain takes into account a polyhedral or a piecewise linear complex. This tetrahedralization is a constrained
Delaunay tetrahedralization.
The method used in TetGen to control the quality of the mesh is a Delaunay refinement due to Shewchuk
[SHEWCHUK1998]. The quality measure of this algorithm is the Radius-Edge Ratio (see Section 1.3.1 [HANG2006]
for more details). A theoretical bound of this ratio of the Shewchuk algorithm is obtained for a given complex of
vertices, constrained segments and facets of surface mesh, with no input angle less than 90 degrees. This theoretical
bound is 2.0.
The launch of TetGen is done with the keyword tetg. The parameters of this command line is:
• reftet= sets the label of tetrahedra.
• label= is a vector of integers that contains the old labels number at index 2𝑖 and the new labels number
at index 2𝑖 + 1 of Triangles.
This parameter is initialized as a label for the keyword change.
where Th3surf = movemesh23(Th2, transfo=[Phi(1), Phi(2), Phi(3)]) and Th2 is the input two dimen-
sional mesh of tetgtransfo.
The parameters of this command line are, on one hand, the parameters label, switch, regionlist, nboffacetcl,
facetcl of keyword tetg and on the other hand, the parameter ptmerge of keyword movemesh23.
Note: To use tetgtransfo, the result’s mesh of movemesh23 must be a closed surface and define one region only.
Therefore, the parameter regionlist is defined for one region.
An example of this keyword can be found in line 61 of the Build layer mesh example.
𝑛𝑣
𝑥1 𝑦1 𝑧1
𝑥2 𝑦2 𝑧2
.. .. ..
. . .
𝑥 𝑛𝑣 𝑦 𝑛𝑣 𝑧𝑛𝑣
The second way is to give three arrays that correspond respectively to the 𝑥−coordinates, 𝑦−coordinates and
𝑧−coordinates.
The parameters of this command line are :
• switch= A string expression.
This string corresponds to the command line switch of TetGen see Section 3.2 of [HANG2006].
• reftet= An integer expression.
Set the label of tetrahedra.
• label= An integer expression.
Set the label of triangles.
In the string switch, we can’t used the option p and q of TetGen.
Meshes in three dimension can be refined using TetGen with the command line tetgreconstruction.
The parameter of this keyword are
• region= an integer array that changes the region number of tetrahedra.
This array is defined as the parameter reftet in the keyword change.
• label= an integer array that changes the label of boundary triangles.
This array is defined as the parameter label in the keyword change.
• sizeofvolume= a reel function.
This function constraints the volume size of the tetrahedra in the domain (see Isotrope mesh adaption
section to build a 3d adapted mesh).
The parameters switch, nbofregions, regionlist, nboffacetcl and facetcl of the command line which call
TetGen (tetg) is used for tetgrefine.
In the parameter switch=, the character r should be used without the character p.
For instance, see the manual of TetGen [HANG2006] for effect of r to other character.
The parameter regionlist defines a new volume constraint in the region. The label in the regionlist will be the
previous label of region.
This parameter and nbofregions can’t be used with the parameter sizeofvolume.
**Example refinesphere.edp**
1 load "msh3"
2 load "tetgen"
3 load "medit"
4
6 // a parametrization of a sphere
7 func f1 =cos(x)*cos(y);
8 func f2 =cos(x)*sin(y);
9 func f3 = sin(x);
10 // partiel derivative of the parametrization DF
11 func f1x=sin(x)*cos(y);
12 func f1y=-cos(x)*sin(y);
13 func f2x=-sin(x)*sin(y);
14 func f2y=cos(x)*cos(y);
15 func f3x=cos(x);
16 func f3y=0;
17 // $ M = DF^t DF $
18 func m11=f1x^2+f2x^2+f3x^2;
19 func m21=f1x*f1y+f2x*f2y+f3x*f3y;
20 func m22=f1y^2+f2y^2+f3y^2;
21
22 func perio=[[4,y],[2,y],[1,x],[3,x]];
23 real hh=0.1;
24 real vv= 1/square(hh);
25 verbosity=2;
26 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
27 Th=adaptmesh(Th,m11*vv,m21*vv,m22*vv,IsMetric=1,periodic=perio);
(continues on next page)
30 verbosity=2;
31
38 meshS ThS=movemesh23(Th,transfo=[f1min,f2min,f3min]);
39
46
50
51 medit("sphere",Th3sph,wait=1);
52 medit("sphererefinedomain",wait=1,Th3sphrefine);
53 medit("sphererefinelocal",wait=1,Th3sphrefine2);
54
2d case
Users who want to read a triangulation made elsewhere should see the structure of the file generated below:
There, 𝑛𝑣 denotes the number of vertices, 𝑛𝑡 the number of triangles and 𝑛𝑠 the number of edges on boundary.
For each vertex 𝑞 𝑖 , 𝑖 = 1, · · · , 𝑛𝑣 , denoted by (𝑞𝑥𝑖 , 𝑞𝑦𝑖 ) the 𝑥-coordinate and 𝑦-coordinate.
Each triangle 𝑇𝑘 , 𝑘 = 1, · · · , 𝑛𝑡 has three vertices 𝑞 𝑘1 , 𝑞 𝑘2 , 𝑞 𝑘3 that are oriented counter-clockwise.
The boundary consists of 10 lines 𝐿𝑖 , 𝑖 = 1, · · · , 10 whose end points are 𝑞 𝑖1 , 𝑞 𝑖2 .
In FreeFEM there are many mesh file formats available for communication with other tools such as emc2, modulef,
. . . (see Mesh format chapter ).
The extension of a file implies its format. More details can be found on the file format .msh in the article by F. Hecht
“bamg : a bidimensional anisotropic mesh generator” [HECHT1998_2].
A mesh file can be read into FreeFEM except that the names of the borders are lost and only their reference numbers
are kept. So these borders have to be referenced by the number which corresponds to their order of appearance in the
program, unless this number is overwritten by the keyword label. Here are some examples:
1 // Parameters
2 int n = 10;
3
4 // Mesh
5 border floor(t=0, 1){x=t; y=0; label=1;};
6 border right(t=0, 1){x=1; y=t; label=5;};
7 border ceiling(t=1, 0){x=t; y=1; label=5;};
8 border left(t=1, 0){x=0; y=t; label=5;};
9
18 // Fespace
19 fespace femp1(th, P1);
20 femp1 f = sin(x)*cos(y);
21 femp1 g;
22
34 // Plot
35 plot(g);
36
37 // Mesh 2
38 //read the mesh for freefem format saved mesh
39 mesh th2 = readmesh("toto.msh");
40
41 // Fespace 2
42 fespace Vh2(th2, P1);
43 Vh2 u, v;
44
45 // Problem
46 //solve:
47 // $u + \Delta u = g$ in $\Omega $
48 // $u=0$ on $\Gamma_1$
49 // $\frac{\partial u }{\partial n} = g$ on $\Gamma_2$
50 solve Problem(u, v)
51 = int2d(th2)(
52 u*v
53 - dx(u)*dx(v)
54 - dy(u)*dy(v)
55 )
56 + int2d(th2)(
57 - g*v
58 )
59 + int1d(th2, 5)(
60 g*v
61 )
62 + on(1, u=0)
63 ;
64
65 // Plot
66 plot(th2, u);
1 mesh Th=readmeshS("Th.mesh");
2 mesh Thff = readmesh("Thff.msh"); // FreeFEM format
1 savemesh(Th,"Th.mesh")
2 savemesh(Thff,"Thff.msh") // FreeFEM format
3
8 savemesh(Th,"mm",[x,y,u]); // save surface mesh for medit, see for example minimal-
˓→surf.edp
1 load "iovtk"
2 mesh Th=vtkloadS("mymesh.vtk");
1 load "iovtk"
2 savevtk("Th.vtk",Th);
1 load "gmsh"
2 mesh Th=gmshload("mymesh.msh");
1 load "gmsh"
2 savegmsh(Th, "Th");
3d case
In three dimensions, the file mesh format supported for input and output files by FreeFEM are the extension .msh and
.mesh. These formats are described in the Mesh Format section.
Extension file .msh The structure of the files with extension .msh in 3D is given by:
𝑛𝑣 𝑛𝑡𝑒𝑡 𝑛𝑡𝑟𝑖
𝑞𝑥1 𝑞𝑦1 𝑞𝑧1 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
𝑞𝑥2 𝑞𝑦2 𝑞𝑧2 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
.. .. .. ..
. . . .
𝑞𝑥𝑛𝑣 𝑞𝑦𝑛𝑣 𝑞𝑧𝑛𝑣 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
11 12 13 14 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
21 22 23 24 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
.. .. .. .. ..
. . . . .
(𝑛𝑡𝑒𝑡 )1 (𝑛𝑡𝑒𝑡 )2 (𝑛𝑡𝑒𝑡 )3 (𝑛𝑡𝑒𝑡 )4 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
11 12 13 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
21 22 23 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
.. .. .. ..
. . . .
(𝑛𝑡 𝑟𝑖)1 (𝑛𝑡𝑟𝑖 )2 (𝑛𝑡𝑟𝑖 )3 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
In this structure, 𝑛𝑣 denotes the number of vertices, 𝑛𝑡𝑒𝑡 the number of tetrahedra and 𝑛𝑡𝑟𝑖 the number of triangles.
For each vertex 𝑞 𝑖 , 𝑖 = 1, · · · , 𝑛𝑣 , we denote by (𝑞𝑥𝑖 , 𝑞𝑦𝑖 , 𝑞𝑧𝑖 ) the 𝑥-coordinate, the 𝑦-coordinate and the 𝑧-coordinate.
Each tetrahedra 𝑇𝑘 , 𝑘 = 1, · · · , 𝑛𝑡𝑒𝑡 has four vertices 𝑞 𝑘1 , 𝑞 𝑘2 , 𝑞 𝑘3 , 𝑞 𝑘4 .
The boundary consists of a union of triangles. Each triangle 𝑡𝑟𝑖𝑗 , 𝑗 = 1, · · · , 𝑛𝑡𝑟𝑖 has three vertices 𝑞 𝑗1 , 𝑞 𝑗2 , 𝑞 𝑗3 .
extension file .mesh The data structure for a three dimensional mesh is composed of the data structure presented in
Mesh Format section and a data structure for the tetrahedra. The tetrahedra of a three dimensional mesh are referred
using the following field:
1 Tetrahedra
2 NbTetrahedra
3 Vertex1 Vertex2 Vertex3 Vertex4 Label
4 ...
5 Vertex1 Vertex2 Vertex3 Vertex4 Label
6 Triangles
7 NbTriangles
8 Vertex1 Vertex2 Vertex3 Label
9 ...
10 Vertex1 Vertex2 Vertex3 Label
1 mesh3 Th3=readmesh3("Th3.mesh");
2 mesh3 Th3ff = readmesh3("Th3ff.msh"); // FreeFEM format
1 savemesh(Th3,"Th3.mesh")
2 savemesh(Th3ff,"Th3ff.msh") // FreeFEM format
1 load "iovtk"
2 mesh3 Th3=vtkloadS("mymesh.vtk");
1 load "iovtk"
2 savevtk("Th3.vtk",Th3);
1 load "gmsh"
2 mesh3 Th3=gmshload3("mymesh.msh");
1 load "gmsh"
2 savegmsh(Th3, "Th3");
Surface 3d case
Like 2d and 3d, the input and output format files supported by FreeFEM are the extension .msh and .mesh. These
formats are described in the Mesh Format section.
Extension file .msh The structure of the files with extension .msh in surface 3D is given by:
𝑛𝑣 𝑛𝑡𝑟𝑖 𝑛𝑒𝑑𝑔𝑒𝑠
𝑞𝑥1 𝑞𝑦1 𝑞𝑧1 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
𝑞𝑥2 𝑞𝑦2 𝑞𝑧2 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
.. .. .. ..
. . . .
𝑞𝑥𝑛𝑣 𝑞𝑦𝑛𝑣 𝑞𝑧𝑛𝑣 𝑉 𝑒𝑟𝑡𝑒𝑥𝑙𝑎𝑏𝑒𝑙
11 12 13 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
21 22 23 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
.. .. .. ..
. . . .
(𝑛𝑡𝑟𝑖 )1 (𝑛𝑡𝑟𝑖 )2 (𝑛𝑡𝑟𝑖 )3 𝑟𝑒𝑔𝑖𝑜𝑛𝑙𝑎𝑏𝑒𝑙
11 12 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
21 22 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
.. .. ..
. . .
(𝑛𝑒 𝑑𝑔𝑒)1 (𝑛𝑒𝑑𝑔𝑒 )2 𝑏𝑜𝑢𝑛𝑑𝑎𝑟𝑦𝑙𝑎𝑏𝑒𝑙
In this structure, 𝑛𝑣 denotes the number of vertices, 𝑛𝑡𝑒𝑡 the number of tetrahedra and 𝑛𝑡𝑟𝑖 the number of triangles.
For each vertex 𝑞 𝑖 , 𝑖 = 1, · · · , 𝑛𝑣 , we denote by (𝑞𝑥𝑖 , 𝑞𝑦𝑖 , 𝑞𝑧𝑖 ) the 𝑥-coordinate, the 𝑦-coordinate and the 𝑧-coordinate.
Each tetrahedra 𝑇𝑘 , 𝑘 = 1, · · · , 𝑛𝑡𝑒𝑡 has four vertices 𝑞 𝑘1 , 𝑞 𝑘2 , 𝑞 𝑘3 , 𝑞 𝑘4 .
The boundary consists of a union of triangles. Each triangle 𝑏𝑒𝑗 , 𝑗 = 1, · · · , 𝑛𝑡𝑟𝑖 has three vertices 𝑞 𝑗1 , 𝑞 𝑗2 , 𝑞 𝑗3 .
extension file .mesh The data structure for a three dimensional mesh is composed of the data structure presented in
Mesh Format section and a data structure for the tetrahedra. The tetrahedra of a three dimensional mesh are referred
using the following field:
1 MeshVersionFormatted 2
2 Dimension 3
3
4 Vertices
5 NbVertices
6 (v0)x (v0)y (v0)z
7 ...
8 (vn)x (vn)y (vn)z
9
10 Triangles
11 NbTriangles
12 Vertex1 Vertex2 Vertex3 Label
13 ...
14 Vertex1 Vertex2 Vertex3 Label
15
16 Edges
17 NbEdges
18 Vertex1 Vertex2 Label
19 ...
20 Vertex1 Vertex2 Label
21
22 End
1 meshS ThS=readmeshS("ThS.mesh");
2 meshS Th3ff = readmeshS("ThSff.msh"); // FreeFEM format
1 savemesh(ThS,"ThS.mesh")
2 savemesh(ThSff,"ThSff.msh") // FreeFEM format
1 load "iovtk"
2 meshS ThS=vtkloadS("mymesh.vtk");
1 load "iovtk"
2 savevtk("ThS.vtk",ThS);
1 load "gmsh"
2 meshS ThS=gmshloadS("mymesh.msh");
1 load "gmsh"
2 savegmsh(ThS, "ThS");
3.2.6 Medit
The keyword medit allows to display a mesh alone or a mesh and one or several functions defined on the mesh using
the Pascal Frey’s freeware medit. medit opens its own window and uses OpenGL extensively. Naturally to use this
command medit must be installed.
A vizualisation with medit of scalar solutions 𝑓 1 and 𝑓 2 continuous, piecewise linear and known at the vertices of the
mesh Th is obtained using:
The first plot named sol1 display f1. The second plot names sol2 display f2.
The arguments of the function medit are the name of the differents scenes (separated by a space) of medit, a mesh
and solutions.
Each solution is associated with one scene. The scalar, vector and symmetric tensor solutions are specified in the format
described in the section dealing with the keyword savesol.
The parameters of this command line are :
• order= 0 if the solution is given at the center of gravity of elements.
1 is the solution is given at the vertices of elements.
• meditff= set the name of execute command of medit.
By default, this string is medit.
• save= set the name of a file .sol or .solb to save solutions.
This command line allows also to represent two differents meshes and solutions on them in the same windows. The
nature of solutions must be the same. Hence, we can vizualize in the same window the different domains in a domain
decomposition method for instance. A vizualisation with medit of scalar solutions ℎ1 and ℎ2 at vertices of the mesh
Th1 and Th2 respectively are obtained using:
Tip: Medit
1 load "medit"
2
3 // Initial Problem:
4 // Resolution of the following EDP:
5 // -Delta u_s = f on \Omega = { (x,y) | 1 <= sqrt(x^2+y^2) <= 2 }
6 // -Delta u_1 = f1 on \Omega_1 = { (x,y) | 0.5 <= sqrt(x^2+y^2) <= 1. }
7 // u = 1 on Gamma
8 // Null Neumman condition on Gamma_1 and on Gamma_2
9 // We find the solution u by solving two EDP defined on domain Omega and Omega_1
10 // This solution is visualize with medit
11
12 verbosity=3;
13
14 // Mesh
15 border Gamma(t=0, 2*pi){x=cos(t); y=sin(t); label=1;};
16 border Gamma1(t=0, 2*pi){x=2*cos(t); y=2*sin(t); label=2;};
17 border Gamma2(t=0, 2*pi){x=0.5*cos(t); y=0.5*sin(t); label=3;};
18
22 // Fespace
23 fespace Vh(Th, P2);
24 func f = sqrt(x*x + y*y);
25 Vh us, v;
26
31 // Macro
32 macro Grad2(us) [dx(us), dy(us)] // EOM
33
34 // Problem
35 problem Lap2dOmega (us, v, init=false)
36 = int2d(Th)(
37 Grad2(v)' * Grad2(us)
38 )
39 - int2d(Th)(
40 f*v
41 )
42 +on(1, us=1)
43 ;
44
55 // Solve
56 Lap2dOmega;
57 Lap2dOmega1;
58
3.2.7 Mshmet
Mshmet is a software developed by P. Frey that allows to compute an anisotropic metric based on solutions (i.e. Hessian-
based). This software can return also an isotropic metric. Moreover, mshmet can also construct a metric suitable for
levelset interface capturing. The solution can be defined on 2D or 3D structured/unstructured meshes. For example,
the solution can be an error estimate of a FE solution.
Solutions for mshmet are given as an argument. The solution can be a func, a vector func, a symmetric tensor, a
fespace function, a fespace vector function and a fespace symmetric tensor. The symmetric tensor argument is
defined as this type of data for datasol argument. This software accepts more than one solution.
For example, the metric 𝑀 computed with mshmet for the solution 𝑢 defined on the mesh 𝑇 ℎ is obtained by writing:
where 𝐻(𝑉𝑖 ) is the vector of size 6 defined by [𝑚11, 𝑚21, 𝑚22, 𝑚31, 𝑚32, 𝑚33]
Tip: mshmet
1 load "mshmet"
2 load "medit"
3 load "msh3"
4
5 // Parameters
6 real error = 0.01;
7 func zmin = 0;
8 func zmax = 1;
9 int MaxLayer = 10;
10
11 // Mesh
12 border a(t=0, 1.0){x=t; y=0; label=1;};
13 border b(t=0, 0.5){x=1; y=t; label=2;};
14 border c(t=0, 0.5){x=1-t; y=0.5; label=3;};
15 border d(t=0.5, 1){x=0.5; y=t; label=4;};
(continues on next page)
21 // Fespace
22 fespace Vh3(Th3, P2);
23 Vh3 u3, v3;
24
28 // Problem
29 problem Problem2(u3, v3, solver=sparsesolver)
30 = int3d(Th3)(
31 u3*v3*1.0e-10
32 + dx(u3)*dx(v3)
33 + dy(u3)*dy(v3)
34 + dz(u3)*dz(v3)
35 )
36 - int3d(Th3)(
37 v3
38 )
39 +on(0, 1, 2, 3, 4, 5, 6, u3=0)
40 ;
41
42 // Solve
43 Problem2;
44 cout << u3[].min << " " << u3[].max << endl;
45
48 real[int] bb = mshmet(Th3,u3);
49 cout << "Metric:" << bb << endl;
50 for (int ii = 0; ii < Th3.nv; ii++)
51 usol[][ii] = bb[ii];
52
3.2.8 FreeYams
FreeYams is a surface mesh adaptation software which is developed by P. Frey. This software is a new version of
yams. The adapted surface mesh is constructed with a geometric metric tensor field. This field is based on the intrinsic
properties of the discrete surface.
Also, this software allows to construct a simplification of a mesh. This decimation is based on the Hausdorff distance
between the initial and the current triangulation. Compared to the software yams, FreeYams can be used also to produce
anisotropic triangulations adapted to levelset simulations. A technical report on freeYams documentation is available
here.
To call FreeYams in FreeFEM, we used the keyword freeyams. The arguments of this function are the initial mesh
and/or metric. The metric with freeyams are a func, a fespace function, a symmetric tensor function, a symmetric
tensor fespace function or a vector of double (real[int]). If the metric is a vector of double, this data must be given
in metric parameter. Otherwise, the metric is given in the argument.
For example, the adapted mesh of Thinit defined by the metric 𝑢 defined as fespace function is obtained by writing:
The symmetric tensor argument for freeyams keyword is defined as this type of data for datasol argument.
• aniso= (b) aniso or iso metric (default 0, iso)
• mem= (l) memory of for freeyams in Mb (default -1, freeyams choose)
• hmin= (d)
• hmax= (d)
• gradation= (d)
• option= (l)
– 0 : mesh optimization (smoothing+swapping)
– 1 : decimation+enrichment adaptated to a metric map. (default)
– -1 : decimation adaptated to a metric map.
– 2 : decimation+enrichment with a Hausdorff-like method
– -2 : decimation with a Hausdorff-like method
– 4 : split triangles recursively.
– 9 : No-Shrinkage Vertex Smoothing
• ridgeangle= (d)
• absolute= (b)
• verbosity= (i)
• metric= vector expression.
This parameters contains the metric at the different vertices on the initial mesh. With 𝑛𝑣 is the number of
vertices, this vector is:
where 𝐻(𝑉𝑖 ) is the vector of size 6 defined by [𝑚11, 𝑚21, 𝑚22, 𝑚31, 𝑚32, 𝑚33]
• loptions= a vector of integer of size 13.
This vectors contains the integer options of FreeYams. (just for the expert)
– loptions(0): anisotropic parameter (default 0).
If you give an anisotropic metric 1 otherwise 0.
Tip: freeyams
1 load "msh3"
2 load "medit"
3 load "freeyams"
4
5 // Parameters
6 int nn = 20;
7 real zmin = 0;
8 real zmax = 1;
9
10 // Mesh
11 mesh Th2 = square(nn, nn);
12 int[int] rup = [0, 2], rdown = [0, 1];
13 int[int] rmid = [1, 1, 2, 1, 3, 1, 4, 1];
14 mesh3 Th = buildlayers(Th2, nn, zbound=[zmin, zmax], reffacemid=rmid, reffaceup=rup,␣
˓→reffacelow=rdown);
17 medit("SurfaceMesh", Th3);
3.2.9 mmg3d
Todo: mmg3d-v4.0
Note:
• If no metric is given, an isotropic metric is computed by analyzing the size of the edges in the initial mesh.
• If a displacement is given, the vertices of the surface triangles are moved without verifying the geometrical
structure of the new surface mesh.
Tip: mmg3d
1 load "msh3"
2 load "medit"
3 load "mmg3d"
4 include "Cube.idp"
5
6 // Parameters
7 int n = 6;
8 int[int] Nxyz = [12, 12, 12];
9 real [int, int] Bxyz = [[0., 1.], [0., 1.], [0., 1.]];
10 int [int, int] Lxyz = [[1, 1], [2, 2], [2, 2]];
11
12 // Mesh
13 mesh3 Th = Cube(Nxyz, Bxyz, Lxyz);
14
15 real[int] isometric(Th.nv);
(continues on next page)
21 // Plot
22 medit("Initial", Th);
23 medit("Isometric", Th3);
1 load "msh3"
2 load "TetGen"
3 load "medit"
4 load "mmg3d"
5 include "MeshSurface.idp"
6
7 // Parameters
8 real hs = 0.8;
9 int[int] N = [4/hs, 8/hs, 11.5/hs];
10 real [int, int] B = [[-2, 2], [-2, 6], [-10, 1.5]];
11 int [int, int] L = [[311, 311], [311, 311], [311, 311]];
12
18 // Meshes
19 meshS ThH = SurfaceHex(N, B, L, 1);
20 meshS ThSg = Sphere(1, hs, 300, -1);
21 meshS ThSd = Sphere(1, hs, 310, -1);
22 ThSd = movemesh(ThSd, [x, 4+y, z]);
23 meshS ThHS = ThH + ThSg + ThSd;
24 medit("ThHS", ThHS);
25
30 medit("Box-With-two-Ball", Th);
31
32 // Fespace
33 fespace Vh(Th, P1);
34 Vh uh,vh;
35
36 // Macro
37 macro Grad(u) [dx(u),dy(u),dz(u)]
38
48 // Falling loop
49 for(int it = 0; it < 29; it++){
50 cout << " ITERATION " << it << endl;
51
52 // Solve
53 Lap;
54
55 // Plot
56 plot(Th, uh);
57
58 // Sphere falling
59 Th = mmg3d(Th, options=opt, displacement=[zero, zero, uh], memory=1000);
60 }
Tip: Adaptation 3D
1 load "msh3"
2 load "TetGen"
3 load "mshmet"
4 load "medit"
5
6 // Parameters
7 int nn = 6;
8 int[int] l1111 = [1, 1, 1, 1]; //labels
9 int[int] l01 = [0, 1];
10 int[int] l11 = [1, 1];
11
14 // Mesh
15 mesh3 Th3 = buildlayers(square(nn, nn, region=0, label=l1111),
16 nn, zbound=[0, 1], labelmid=l11, labelup=l01, labeldown=l01);
17
18 Th3 = trunc(Th3, (x<0.5) | (y < 0.5) | (z < 0.5), label=1); //remove the ]0.5,1[^3 cube
19
20 // Fespace
21 fespace Vh(Th3, P1);
22 Vh u, v, usol, h;
(continues on next page)
24 // Macro
25 macro Grad(u) [dx(u), dy(u), dz(u)] // EOM
26
27 // Problem
28 problem Poisson (u, v, solver=CG)
29 = int3d(Th3)(
30 Grad(u)' * Grad(v)
31 )
32 - int3d(Th3)(
33 1*v
34 )
35 + on(1, u=0)
36 ;
37
38 // Loop
39 for (int ii = 0; ii < 5; ii++){
40 // Solve
41 Poisson;
42 cout << "u min, max = " << u[].min << " "<< u[].max << endl;
43
46 cout << "h min, max = " << h[].min << " "<< h[].max << " " << h[].n << " " << Th3.nv
˓→<< endl;
47 plot(u, wait=true);
48
The idea is to get the discretization of an isoline of fluid meshes, this tool can
√︀be useful to construct meshes from image.
First, we give an example of the isovalue meshes 0.2 of analytical function (𝑥 − 1/2)2 + (𝑦 − 1/2)2 , on unit square.
1 load "isoline"
2
The isoline parameters are Th the mesh, the expression 𝑢, the bidimentionnal array xy to store the list coordinate of
the points. The list of named parameter are :
• iso= value of the isoline to compute (0 is the default value)
• close= close the isoline with the border (default true), we add the part of the mesh border such the value is
less than the isovalue
• smoothing= number of smoothing process is the 𝑙𝑟 𝑠 where 𝑙 is the length of the current line component, 𝑟 the
ratio, 𝑠 is smoothing value. The smoothing default value is 0.
• ratio= the ratio (1 by default).
• eps= relative 𝜀 (default 1e-10)
• beginend= array to get begin, end couple of each of sub line (resize automatically)
• file= to save the data curve in data file for gnuplot
In the array xy you get the list of vertices of the isoline, each connex line go from 𝑖 = 𝑖𝑐0 , . . . , 𝑖𝑐1 − 1 with 𝑖𝑐0 = 𝑏𝑒(2 * 𝑐)
𝑖𝑐1 = 𝑏𝑒(2 * 𝑐 + 1), and where 𝑥𝑖 = 𝑥𝑦(0, 𝑖), 𝑦𝑖 = 𝑦𝑥(1, 𝑖), 𝑙𝑖 = 𝑥𝑦(2, 𝑖).
Here 𝑙𝑖 is the length of the line (the origin of the line is point 𝑖𝑐0 ).
The sense of the isoline is such that the upper part is at the left size of the isoline. So here : the minimum is a point
0.5, 05 so the curve 1 turn in the clockwise sense, the order of each component are sort such that the number of point
by component is decreasing.
1 cout << "Number of the line component = " << nbc << endl;
2 cout << "Number of points = " << xy.m << endl;
3 cout << "be = " << be << endl;
4
15 cout << "length of last curve = " << xy(2, xy.m-1) << endl;
We also have a new function to easily parametrize a discrete curve defined by the couple 𝑏𝑒, 𝑥𝑦.
Secondly, we use this idea to build meshes from an image, we use the plugins ppm2rnm to read pgm a gray scale image
and then we extract the gray contour at level 0.25.
4 // Parameters
5 string leman = "LemanLake.pgm";
6 real AreaLac = 580.03; //in km^2
7 real hsize = 5;
8 real[int, int] Curves(3, 1);
9 int[int] be(1);
10 int nc; //nb of curve
11 {
12 real[int, int] ff1(leman); //read image
13 //and set it in a rect. array
14 int nx = ff1.n, ny = ff1.m;
15 //build a Cartesian mesh such that the origin is in the right place.
16 mesh Th = square(nx-1, ny-1, [(nx-1)*(x), (ny-1)*(1-y)]);
17 //warning the numbering of the vertices (x,y) is
18 //given by $i = x/nx + nx* y/ny $
19 fespace Vh(Th, P1);
20 Vh f1;
21 f1[] = ff1; //transform array in finite element functions.
22 nc = isoline(Th, f1, iso=0.25, close=1, Curves, beginend=be, smoothing=.1, ratio=0.
˓→5);
23 }
24
35 mesh Th = buildmesh(G(-NC));
36 plot(Th, wait=true);
(continues on next page)
with finite element basis functions 𝜑𝑘 (𝑥, 𝑦) and numbers 𝑤𝑘 (𝑘 = 0, · · · , 𝑀 − 1). The functions 𝜑𝑘 (𝑥, 𝑦) are con-
structed from the triangle 𝑇𝑖𝑘 , and called shape functions.
In FreeFEM, the finite element space:
𝑉ℎ = {𝑤 | 𝑤0 𝜑0 + 𝑤1 𝜑1 + · · · + 𝑤𝑀 −1 𝜑𝑀 −1 , 𝑤𝑖 ∈ R }
is easily created by
• in 2d
1 fespace IDspace(IDmesh,<IDFE>);
1 fespace IDspace(IDmesh,<IDFE>,
2 periodic=[[la1, sa1], [lb1, sb1],
3 ...
4 [lak, sak], [lbk, sbl]]);
• in 3D:
1 fespace IDspace(IDmesh3,<IDFE>,
2 periodic=[[la1, sa1, ta1], [lb1, sb1, tb1],
3 ...
4 [lak, sak, tak], [lbk, sbl, tbl]]);
• in surface 3D:
1 fespace IDspace(IDmeshS,<IDFE>,
2 periodic=[[la1, sa1, ta1], [lb1, sb1, tb1],
3 ...
4 [lak, sak, tak], [lbk, sbl, tbl]]);
where IDspace is the name of the space (e.g. Vh), IDmesh IDmesh3 IDmeshS `is respectly the name of the
associated :freefem:`mesh, mesh3, meshS and <IDFE> is an identifier of finite element type.
In 2D we have a pair of periodic boundary conditions, if [𝑙𝑎𝑖 , 𝑠𝑎𝑖 ], [𝑙𝑏𝑖 , 𝑠𝑏𝑖 ] is a pair of int, and the 2 labels 𝑙𝑎𝑖 and
𝑙𝑏𝑖 refer to 2 pieces of boundary to be in equivalence.
If [𝑙𝑎𝑖 , 𝑠𝑎𝑖 ], [𝑙𝑏𝑖 , 𝑠𝑏𝑖 ] is a pair of real, then 𝑠𝑎𝑖 and 𝑠𝑏𝑖 give two common abscissa on the two boundary curves, and
two points are identified as one if the two abscissa are equal.
In 2D, we have a pair of periodic boundary conditions, if [𝑙𝑎𝑖 , 𝑠𝑎𝑖 , 𝑡𝑎𝑖 ], [𝑙𝑏𝑖 , 𝑠𝑏𝑖 , 𝑡𝑏𝑖 ] is a pair of int, the 2 labels 𝑙𝑎𝑖
and 𝑙𝑏𝑖 define the 2 pieces of boundary to be in equivalence.
If [𝑙𝑎𝑖 , 𝑠𝑎𝑖 , 𝑡𝑎𝑖 ], [𝑙𝑏𝑖 , 𝑠𝑏𝑖 , 𝑡𝑏𝑖 ] is a pair of real, then 𝑠𝑎𝑖 , 𝑡𝑎𝑖 and 𝑠𝑏𝑖 , 𝑡𝑏𝑖 give two common parameters on the two
boundary surfaces, and two points are identified as one if the two parameters are equal.
Note: The 2D mesh of the two identified borders must be the same, so to be sure, use the parameter
fixedborder=true in buildmesh command (see fixedborder).
• [P1] piecewise linear continuous finite element (2d, 3d, surface 3d), the degrees of freedom are the vertices
values.
Warning: Due to an interpolation problem, the degree of freedom is not the vertices but three vertices which
move inside 𝑇 (𝑋) = 𝐺 + .99(𝑋 − 𝐺) where 𝐺 is the barycenter.
• [P1b] piecewise linear continuous finite element plus bubble (2d, 3d)
The 2D Case:
The 3D Case:
where 𝜆𝐾𝑖 , 𝑖 = 0, .., 𝑑 are the 𝑑 + 1 barycentric coordinate functions of the element 𝐾 (triangle or
tetrahedron).
• P1bl,P1bl3d piecewise linear continuous finite element plus linear bubble (with load”Element_P1bl” 2d, 3d).
The bubble is built by splitting the 𝐾, a barycenter in 𝑑 + 1 sub element. (need load "Element_P1bl")
• [P2, P2] piecewise 𝑃2 continuous finite element (2d, 3d, surface 3d)
The 3D Case:
Warning: Due to an interpolation problem, the degree of freedom is not the six P2 nodes but six nodes
which move inside 𝑇 (𝑋) = 𝐺 + .99(𝑋 − 𝐺) where 𝐺 is the barycenter.
𝑣 continuous at vertices,
{︂ ⃒ {︂ }︂
P2ℎ 2
⃒
= 𝑣 ∈ 𝐿 (Ω) ⃒ ∀𝐾 ∈ 𝒯ℎ , 𝑣|𝐾 ∈ 𝑃3 ,
𝜕𝑛 𝑣 continuous at middle of edge,
⃒
Warning: To build the interplant of a function 𝑢 (scalar) for this finite element, we need the func-
tion and 2 partial derivatives (𝑢, 𝑢𝑥 , 𝑢𝑦 ), creating this vectorial finite element with 3 components
(𝑢, 𝑢𝑥 , 𝑢𝑦 ).
1 load "Morley"
2
3 // Parameters
4 int nn = 10;
5 real h = 0.01;
6
7 real f = 1;
8
9 // Mesh
10 mesh Th = square(nn, nn);
11 Th = adaptmesh(Th, h, IsMetric=1);
12
13 // Fespace
14 fespace Vh(Th, P2Morley); //The Morley finite element space
15 Vh [u, ux, uy], [v, vx, vy];
16
17 // Macro
18 macro bilaplacien(u, v) (dxx(u)*dxx(v) + dyy(u)*dyy(v) + 2.*dxy(u)*dxy(v)) /
˓→/
19
20 // Problem
21 solve bilap ([u, ux, uy], [v, vx, vy])
22 = int2d(Th)(
23 bilaplacien(u, v)
24 )
25 - int2d(Th)(
26 f*v
27 )
28 + on(1, 2, 3, 4, u=0, ux=0, uy=0)
29 ;
30
31 // Plot
32 plot(u, cmm="u");
• [HCT] 𝑃3 𝐶 1 conforms finite element (2d) (needs load "Element_HCT") one 3 sub triangles.
Lets call 𝒯ℎ△ the sub mesh of 𝒯ℎ where all triangles are split in 3 at the barycenter.
{︁ ⃒ }︁
P𝐻𝐶𝑇 = 𝑣 ∈ 𝐶 1 (Ω) ⃒ ∀𝐾 ∈ 𝒯ℎ△ , 𝑣|𝐾 ∈ 𝑃3
⃒
ℎ
Warning: To build the interplant of a function 𝑢 (scalar) for this finite element, we need the func-
tion and 2 partial derivatives (𝑢, 𝑢𝑥 , 𝑢𝑦 ), creating this vectorial finite element with 3 components
(𝑢, 𝑢𝑥 , 𝑢𝑦 ) like in previous finite element.
• [P2BR] (needs load "BernadiRaugel") the Bernadi Raugel Finite Element is a Vectorial element (2d) with
2 components, see [BERNARDI1985].
It is a 2D coupled Finite Element, where the Polynomial space is 𝑃12 with 3 normal bubble edge
functions (𝑃2 ). There are 9 degrees of freedom:
The 3D Case:
{︃ ⃒ ⃒ 1 }︃
⃒ ⃒ 𝛼𝐾 ⃒𝑥
𝑅𝑇 0ℎ = v ∈ 𝐻(div) ⃒ ∀𝐾 ∈ 𝒯ℎ , v|𝐾 (𝑥, 𝑦, 𝑧) = ⃒ 𝛼𝐾 + 𝛽𝐾 ⃒ (3.8)
⃒ ⃒ 2 ⃒𝑦
⃒ ⃒ 𝛼3𝐾 𝑧
∑︀𝑑
where by writing div w = 𝑖=1 𝜕𝑤𝑖 /𝜕𝑥𝑖 with w = (𝑤𝑖 )𝑑𝑖=1 :
and where 𝛼𝐾
1
, 𝛼𝐾
2
, 𝛼𝐾
3
, 𝛽𝐾 are real numbers.
• [RT0Ortho] Raviart-Thomas Orthogonal, or Nedelec finite element type I of degree 0 in dimension 2
{︁ ⃒ ⃒ 1 }︁
⃒𝛼
𝑅𝑇 0𝑂𝑟𝑡ℎ𝑜ℎ = v ∈ 𝐻(curl) ⃒ ∀𝐾 ∈ 𝒯ℎ , v|𝐾 (𝑥, 𝑦) = ⃒ 𝛼𝐾 + 𝛽𝐾 | −𝑦 (3.9)
⃒
2 𝑥
𝐾
{︃ ⃒ ⃒ 1 ⃒ 1 }︃
⃒ ⃒ 𝛼𝐾 ⃒ 𝛽𝐾 ⃒𝑥
𝐸𝑑𝑔𝑒0ℎ = v ∈ 𝐻(Curl) ⃒ ∀𝐾 ∈ 𝒯ℎ , v|𝐾 (𝑥, 𝑦, 𝑧) = ⃒ 𝛼𝐾 + ⃒ 𝛽𝐾 × ⃒ : 𝑙𝑎𝑏𝑒𝑙 : 𝑒𝑞 : 𝐸𝑑𝑔𝑒03𝑑
⃒ ⃒ 2 ⃒ 2 ⃒𝑦
⃒ ⃒ 𝛼3𝐾 3
⃒ 𝛽𝐾 𝑧
⃒
⃒ 𝜕𝑤2 /𝜕𝑥3 −𝜕𝑤3 /𝜕𝑥2
where by writing curlw = ⃒⃒ 𝜕𝑤3 /𝜕𝑥1 −𝜕𝑤1 /𝜕𝑥3 with w = (𝑤𝑖 )𝑑𝑖=1 :
𝜕𝑤1 /𝜕𝑥2 −𝜕𝑤2 /𝜕𝑥1
and 𝛼𝐾
1 2
, 𝛼𝐾 3
, 𝛼𝐾 1
, 𝛽𝐾 2
, 𝛽𝐾 3
, 𝛽𝐾 are real numbers.
• [Edge13d] (needs load "Element_Mixte3d") 3d Nedelec finite element or Edge Element of degree 1.
• [Edge23d] (needs load "Element_Mixte3d") 3d Nedelec finite element or Edge Element of degree 2.
• [P1nc] piecewise linear element continuous at the mid-point of the edge only in 2D (Crouzeix-Raviart Finite
Element 2D).
• [P2pnc] piecewise quadratic plus a P3 bubble element with the continuity of the 2 moments on each edge (needs
load "Element_P2pnc")
• [RT1] (needs load "Element_Mixte")
{︁ ⃒ ⃒ 1 }︁
⃒𝛼
1
𝑅𝑇 1ℎ = v ∈ 𝐻(div) ⃒ ∀𝐾 ∈ 𝒯ℎ , 𝛼𝐾 2
, 𝛼𝐾 , 𝛽𝐾 ∈ 𝑃12 , 𝑃0 , v|𝐾 (𝑥, 𝑦) = ⃒ 𝛼𝐾 + 𝛽𝐾 | 𝑥𝑦 (3.10)
⃒
2
𝐾
{︁ ⃒ ⃒ 1 }︁
1 2 ⃒𝛼
𝑅𝑇 1ℎ = v ∈ 𝐻(curl) ⃒ ∀𝐾 ∈ 𝒯ℎ , 𝛼𝐾 , 𝛼𝐾 , 𝛽𝐾 ∈ 𝑃12 , 𝑃0 , v|𝐾 (𝑥, 𝑦) = ⃒ 𝛼𝐾 + 𝛽𝐾 | −𝑦
⃒
2 𝑥
𝐾
(3.11)
{︁ ⃒ ⃒ 1 }︁
⃒𝛼
1
𝑅𝑇 2ℎ = v ∈ 𝐻(div) ⃒ ∀𝐾 ∈ 𝒯ℎ , 𝛼𝐾 2
, 𝛼𝐾 , 𝛽𝐾 ∈ 𝑃22 , 𝑃1 , v|𝐾 (𝑥, 𝑦) = ⃒ 𝛼𝐾 + 𝛽𝐾 | 𝑥𝑦 (3.12)
⃒
2
𝐾
{︁ ⃒ ⃒ 1 }︁
1 2 ⃒𝛼
𝑅𝑇 2ℎ = v ∈ 𝐻(curl) ⃒ ∀𝐾 ∈ 𝒯ℎ , 𝛼𝐾 , 𝛼𝐾 , 𝛽𝐾 ∈ 𝑃22 , 𝑃1 , v|𝐾 (𝑥, 𝑦) = ⃒ 𝛼𝐾 + 𝛽𝐾 | −𝑦
⃒
2 𝑥
𝐾
(3.13)
• [BDM1Ortho] (needs load "Element_Mixte") the Brezzi-Douglas-Marini Orthogonal also call Nedelec of
type II , finite element
• [FEQF] (needs load "Element_QF") the finite element to store functions at default quadrature points (so the
quadrature is qf5pT in 2D and is qfV5 in 3d).
For over quadrature you have the following corresponding finite element’s quadrature formula.
– FEQF1 ↦→ qf1pT,
– FEQF2 ↦→ qf2pT,
– FEQF5 ↦→ qf5pT,
– FEQF7 ↦→ qf7pT,
– FEQF9 ↦→ qf9pT,
– FEQF13d ↦→ qfV1,
– FEQF23d ↦→ qfV2,
– FEQF53d ↦→ qfV5
You can use this element to optimize the storage and reuse of functions with a long formula inside an integral for non
linear processes.
𝑋ℎ = 𝑣 ∈ 𝐻 1 (]0, 1[2 )| ∀𝐾 ∈ 𝒯ℎ
{︀ }︀
𝑣|𝐾 ∈ 𝑃1
. .
𝑋𝑝ℎ = {𝑣 ∈ 𝑋ℎ | 𝑣 (| 0. ) = 𝑣 (| 1. ) , 𝑣 (| 0 ) = 𝑣 (| 1 )}
⃒ 𝛼𝐾
𝑅ℎ = v ∈ 𝐻 1 (]0, 1[2 )2 | ∀𝐾 ∈ 𝒯ℎ v|𝐾 (𝑥, 𝑦) = ⃒ 𝛽𝐾 + 𝛾𝐾 | 𝑥𝑦
{︀ }︀
when 𝒯ℎ is a mesh 10 × 10 of the unit square ]0, 1[2 , we only write in FreeFEM:
where Xh, Mh, Rh expresses finite element spaces (called FE spaces) 𝑋ℎ , 𝑀ℎ , 𝑅ℎ , respectively.
To use FE-functions 𝑢ℎ , 𝑣ℎ ∈ 𝑋ℎ , 𝑝ℎ , 𝑞ℎ ∈ 𝑀ℎ and 𝑈ℎ , 𝑉ℎ ∈ 𝑅ℎ , we write:
1 Xh uh, vh;
2 Xph uph, vph;
3 Mh ph, qh;
4 Rh [Uxh, Uyh], [Vxh, Vyh];
5 Xh[int] Uh(10); //array of 10 functions in Xh
6 Rh[int] [Wxh, Wyh](10); //array of 10 functions in Rh
7 Wxh[5](0.5,0.5); //the 6th function at point (0.5, 0.5)
8 Wxh[5][]; //the array of the degree of freedom of the 6th function
and 𝑉ℎ = ⃒ 𝑉𝑉 𝑥ℎ
⃒ 𝑥ℎ ⃒
𝑈ℎ = ⃒ 𝑈
𝑈 𝑦ℎ 𝑦ℎ
𝑀ℎ = {𝑣 ∈ 𝐻 (]0, 1[3 )| ∀𝐾 ∈ 𝒯ℎ
1
𝑣|𝐾 ∈ 𝑃2 }
{︁ ⃒ 𝛼𝐾 ⃒ 𝑥 }︁
𝑅ℎ = v ∈ 𝐻 1 (]0, 1[3 )2 | ∀𝐾 ∈ 𝒯ℎ v|𝐾 (𝑥, 𝑦, 𝑧) = ⃒ 𝛽𝐾 + 𝛿𝐾 ⃒ 𝑦
⃒ ⃒
𝛾
𝐾 𝑧
where Xh, Mh, Rh expresses finite element spaces (called FE spaces) 𝑋ℎ , 𝑀ℎ , 𝑅ℎ , respectively.
The functions 𝑈ℎ , 𝑉ℎ have two components so we have
1 Xh uh, vh;
2 Xh[int] Uh(10); //array of 10 functions in Xh
1 Xh uh, vh;
2 Xph uph, vph;
3 Mh ph, qh;
4 Rh [Uxh, Uyh, Uyzh], [Vxh, Vyh, Vyzh];
5 Xh[int] Uh(10); //array of 10 functions in Xh
6 Rh[int] [Wxh,Wyh,Wzh](10); // array of 10 functions in Rh
7 Wxh[5](0.5,0.5,0.5); //the 6th function at point (0.5, 0.5, 0.5)
8 Wxh[5][]; //the array of the degree of freedom of the 6th function
Note: One challenge of the periodic boundary condition is that the mesh must have equivalent faces.
The buildlayers mesh generator splits each quadrilateral face with the diagonal passing through the vertex with
maximum number, so to be sure to have the same mesh one both face periodic the 2D numbering in corresponding
edges must be compatible (for example the same variation).
By Default, the numbering of square vertex is correct.
To change the mesh numbering you can use the change function like:
1 {
2 int[int] old2new(0:Th.nv-1); //array set on 0, 1, .., nv-1
3 fespace Vh2(Th, P1);
4 Vh2 sorder = x+y; //choose an order increasing on 4 square borders with x or y
5 sort(sorder[], old2new); //build the inverse permutation
6 int[int] new2old = old2new^-1; //inverse the permutation
7 Th = change(Th, renumv=new2old);
8 }
P0-element
For each triangle (d=2) or tetrahedron (d=3) 𝑇𝑘 , the basis function 𝜑𝑘 in Vh(Th, P0) is given by:
1 if (x) ∈ 𝑇𝑘
{︂
𝜑𝑘 (x) =
0 if (x) ̸∈ 𝑇𝑘
If we write:
1 Vh(Th, P0);
2 Vh fh = f(x,y);
𝑞 𝑘𝑖
∑︁ ∑︀
then for vertices 𝑞 𝑘𝑖 , 𝑖 = 1, 2, ..𝑑 + 1 in Fig. 3.30, 𝑓ℎ is built as fh= 𝑓ℎ (𝑥, 𝑦) = 𝑓( 𝑖
)𝜑𝑘
𝑑+1
𝑘
See Fig. 3.31b for the projection of 𝑓 (𝑥, 𝑦) = sin(𝜋𝑥) cos(𝜋𝑦) on Vh(Th, P0) when the mesh Th is a 4 × 4-grid of
[−1, 1]2 as in Fig. 3.31a.
P1-element
For each vertex 𝑞 𝑖 , the basis function 𝜑𝑖 in Vh(Th, P1) is given by:
The basis function 𝜑𝑘1 (𝑥, 𝑦) with the vertex 𝑞 𝑘1 in Fig. 3.30 at point 𝑝 = (𝑥, 𝑦) in triangle 𝑇𝑘 simply coincide with the
barycentric coordinates 𝜆𝑘1 (area coordinates):
area of triangle(𝑝, 𝑞 𝑘2 , 𝑞 𝑘3 )
𝜑𝑘1 (𝑥, 𝑦) = 𝜆𝑘1 (𝑥, 𝑦) =
area of triangle(𝑞 𝑘1 , 𝑞 𝑘2 , 𝑞 𝑘3 )
If we write:
1 Vh(Th, P1);
2 Vh fh = g(x.y);
then:
𝑛𝑣
∑︁
fh = 𝑓ℎ (𝑥, 𝑦) = 𝑓 (𝑞 𝑖 )𝜑𝑖 (𝑥, 𝑦)
𝑖=1
See Fig. 3.32a for the projection of 𝑓 (𝑥, 𝑦) = sin(𝜋𝑥) cos(𝜋𝑦) into Vh(Th, P1).
P2-element
For each vertex or mid-point 𝑞 𝑖 . The basis function 𝜑𝑖 in Vh(Th, P2) is given by:
The basis function 𝜑𝑘1 (𝑥, 𝑦) with the vertex 𝑞 𝑘1 in Fig. 3.30 is defined by the barycentric coordinates:
If we write:
1 Vh(Th, P2);
2 Vh fh = f(x.y);
then:
𝑀
∑︁
fh = 𝑓ℎ (𝑥, 𝑦) = 𝑓 (𝑞 𝑖 )𝜑𝑖 (𝑥, 𝑦) (summation over all vertex or mid-point)
𝑖=1
See Projection to Vh(Th, P2) for the projection of 𝑓 (𝑥, 𝑦) = sin(𝜋𝑥) cos(𝜋𝑦) into Vh(Th, P2).
To build the surface Pk-Lagrange, the main idea is to consider the usual 2d Lagrangian Finite Elements ; and its writing
in barycentric coordinates ; apply a space transformation and barycentric properties. The FreeFEM finite elements for
surface problem are: P0 P1 P2 P1b.
0) Notation
• Let 𝐾
ˆ be the shape triangle in the space R2 of vertice (𝑖0 , 𝑖1 , 𝑖2 )
𝐹˜ : R2 → R3
ˆ→𝑋
𝑥
⎛ ⎞ ⎛ −−−→ ⎞
𝑥 𝐴0 𝐴1
⎝𝑦 ⎠ → ⎜ −−−→
𝐴0 𝐴2 𝑥 − 𝐴0 )
⎠ (ˆ
⎟
−−−→ −−−→
⎝
0 𝐴0 𝐴1 ∧ 𝐴0 𝐴2
The affine transformation 𝐹˜ allows you to pass from the 2d reference triangle, which we project in R3 to the 3d current
triangle, discretizing the surface we note Γ.
Then 𝐹˜ −1 is well defined and allows to return to the reference triangle 𝐾,
ˆ to the usual coordinates of R2 completed by
the coordinate 𝑧 = 0.
2) Interpolation element fini
Remember that the reference geometric element for the finite element 𝑃 1𝑠 that we are building is the reference triangle
ˆ in the vertex plane (𝑖0 , 𝑖1 , 𝑖2 ), which we project into space by posing 𝑧 = 0 by the membrane hypothesis.
𝐾
⎛ ⎞ ⎛ ⎞ ⎛ ⎞
0 1 0
Hence 𝑖0 = ⎝0⎠, 𝑖1 = ⎝0⎠, 𝑖1 = ⎝1⎠.
0 0 0
Let X be a point of the current triangle K, we have X= 𝐹˜ (ˆ
𝑥). The barycentric coordinates of X in K are given by:
∑︀2 ˆ
𝑋 = 𝑖=0 𝐴𝑖 𝜆(ˆ 𝑥) où
• 𝐴𝑖 the points of the current triangle K
ˆ 𝑖 basic functions 𝑃 12𝑑
• 𝜆
ˆ 0 (𝑥, 𝑦) = 1 − 𝑥 − 𝑦
• 𝜆
ˆ 1 (𝑥, 𝑦) = 𝑥
• 𝜆
ˆ 2 (𝑥, 𝑦) = 𝑦
• 𝜆
We need to define a quadrature formula for the finite element⎛approximation.
⎞ The usual formulation for a 2d triangle
𝑥
ˆ𝑞
will be used by redefining the quadrature points 𝑋𝑞 = 𝑥𝑞 = ⎝ 𝑦ˆ𝑞 ⎠.
0
3) The Lagragian P1 functions and theirs 1st order derivatives
The finite element interpolation gives us the following relationship: 𝜓𝑖 (𝑋) = 𝐹 −1 (𝜓𝑖 )(𝐹 −1 (𝑋)). To find the ex-
pression of the basic functions 𝜓 on the current triangle K, it is sufficient to use the inverse of the transformation 𝐹˜ to
get back to the reference triangle 𝐾.
ˆ However in FreeFEM, the definition of the reference finite element, the current
geometry is based on barycentric coordinates in order not to use geometric transformation. 𝐹˜ . The method used here
is geometric and based on the properties of the vector product and the area of the current triangle K.
i) The shape functions
Let be the triangle K of vertices 𝑖0 , 𝑖1 , 𝑖2 ⊂ R3 and (𝜆𝑖 )2𝑖=0 the local barycentric coordinates at K. The normal is
−−−→ −−−→ −−−→ −−−→
defined as the tangent plane generated by (𝐴0 , 𝐴0 𝐴1 , 𝐴0 𝐴2 ), \ ⃗𝑛 = 𝐴0 𝐴1 ∧ 𝐴0 𝐴2 avec || ⃗𝑛 ||= 2 mes (𝐾).
ˆ
Le denotes the operator V, defines the usual vector product of R3 such as 𝑉 (𝐴, 𝐵, 𝐶) = (𝐵 − 𝐴) ∧ (𝐶 − 𝐴)
The mixed product of three vectors u, v, w, noté [𝑢, 𝑣, 𝑤], is the determinant of these three vectors in any direct or-
thonormal basis, thus (𝐴 ∧ 𝑉, 𝐶) = det (𝐴, 𝐵, 𝐶)
with (., .) is the usual scalar product of R3 . \ Let Ph :math:` in mathbb{R}^3` and P his projected in the triangle K
such as:
Let’s lay the sub-triangles as follows :
Note:
Properties in R3
−−−→ −−−→
• Let ⃗𝑛 be the normal to the tangent plane generated by (𝐴0 , 𝐴0 𝐴1 , 𝐴0 𝐴2 )
−−−→ −−−→
• ⃗𝑛 = 𝐴0 𝐴1 ∧ 𝐴0 𝐴2
• By definition, 𝒜 = 12 |< ⃗𝑛, ⃗𝑛 >| and the vectorial area by 𝒜𝒮 = 12 < ⃗𝑛, ⃗𝑛 > hence
𝒜𝒮 (𝑃 𝐵𝐶) = 21 < ⃗𝑛0 , ⃗𝑛 >, with ⃗𝑛0 the normal vector to the plane (PBC)
• 𝑁
⃗ 1 (𝑃 ) = 𝑉 (𝐴0, 𝑃, 𝐴2) the vectorial area of K1
• 𝑁
⃗ 2 (𝑃 ) = 𝑉 (𝐴0, 𝐴1, 𝑃 ) the vectorial area of K2
By definition, in 3d, the barycentric coordinates are given by algebraic area ratio: :math:` lambda_i(P) = frac {(vec
N_i(P),vec N)}{(vec N,vec N)}label{basisfunc}`
Note that (𝑁 ⃗ ) = 2 sign mes (𝐾𝑖 ) || 𝑁
⃗ 𝑖 (𝑃 ), 𝑁 ⃗ || and (𝑁 ⃗ ) = 2 sign mes (𝐾) || 𝑁
⃗,𝑁 ⃗ ||, avec 𝑠𝑖𝑔𝑛 the orientation of
the current triangle compared to the reference triangle.
∑︀2
We find the finite element interpolation, 𝑃 = 𝑖=0 𝜆𝑖 (𝑃 )𝐴𝑖 .
ii) 1st order derivatives of Lagrangian P1 FE
Let 𝑌
⃗ be any vector of ∈ R3 .
⃗ 2 (𝑃 ), 𝑌
(𝑁 ⃗ ) = ((𝐴1 − 𝐴0 ) ∧ (𝑃 − 𝐴0 ), 𝑌 )
= det(𝐴1 − 𝐴0 , 𝑃 − 𝐴0 , 𝑌 )
= det(𝐴1 − 𝐴0 , 𝑃, 𝑌 ) − det(𝐴1 − 𝐴0 , 𝐴0 , 𝑌 )
⃗ ) = det(𝐴1 − 𝐴0 , 𝑃 ′ , 𝑌 )𝑑𝑃
⃗ 2 (𝑃 ), 𝑌
𝐷𝑃 (𝑁
∇𝑃 (𝑁 ⃗ ) = det (𝐴1 − 𝐴0 , 𝑃 ′ , 𝑌
⃗ 2 (𝑃 ), 𝑌 ⃗)
⃗ , 𝑃 ′)
= −𝑑𝑒𝑡(𝐴1 − 𝐴0 , 𝑌
⃗ .𝑃 ′
= −(𝐴1 − 𝐴0 ) ∧ 𝑌
⃗ ∧ (𝐴1 − 𝐴0 )
=𝑌
⃗ 2 (𝑃 ), 𝑁
∇ 𝑃 (𝑁 ⃗) = 𝑁
⃗ ∧ (𝐴1 − 𝐴0 )
=𝑁⃗ ∧ 𝐸2 ⃗ , 𝑃 ′)
= −det(𝐴1 − 𝐴0 , 𝑌
This leads to :math:` nabla_P lambda_2(P) = frac {(vec N wedge E_2)}{(vec N,vec N)} `
By similar calculations for 𝑁
⃗ 0 (𝑃 ) et 𝑁
⃗ 1 (𝑃 )
(𝑁⃗ ∧𝐸𝑖 )
∇𝑃 𝜆𝑖 (𝑃 ) = (𝑁⃗ ,𝑁
⃗)
Note: With the definition of the surface gradient and the 2d Pk-Lagrange FE used barycentric coordinates, surface
Pk-Langrange FE are trivial.
Refer to [THOMASSET2012] for details; briefly, we now consider non-continuous approximations so we will lose the
property:
𝑤ℎ ∈ 𝑉ℎ ⊂ 𝐻 1 (Ω)
If we write:
1 Vh(Th, P1nc);
2 Vh fh = f(x.y);
then:
𝑛𝑣
∑︁
fh = 𝑓ℎ (𝑥, 𝑦) = 𝑓 (𝑚𝑖 )𝜑𝑖 (𝑥, 𝑦) (summation over all midpoint)
𝑖=1
Here the basis function 𝜑𝑖 associated with the mid-point 𝑚𝑖 = (𝑞 𝑘𝑖 + 𝑞 𝑘𝑖+1 )/2 where 𝑞 𝑘𝑖 is the 𝑖-th point in 𝑇𝑘 , and
we assume that 𝑗 + 1 = 0 if 𝑗 = 3:
Strictly speaking 𝜕𝜑𝑖 /𝜕𝑥, 𝜕𝜑𝑖 /𝜕𝑦 contain Dirac distribution 𝜌𝛿𝜕𝑇𝑘 .
The numerical calculations will automatically ignore them. In [THOMASSET2012], there is a proof of the estimation
(︃ 𝑛 ∫︁ )︃1/2
∑︁𝑣
2. 𝑓 ≥ 0 ⇒ 𝑢ℎ ≥ 0
3. If 𝑖 ̸= 𝑗, the basis function 𝜑𝑖 and 𝜑𝑗 are 𝐿2 -orthogonal:
∫︁
𝜑𝑖 𝜑𝑗 d𝑥d𝑦 = 0 if 𝑖 ̸= 𝑗
Ω
For each triangle 𝑇𝑘 ∈ 𝒯ℎ , let 𝜆𝑘1 (𝑥, 𝑦), 𝜆𝑘2 (𝑥, 𝑦), 𝜆𝑘3 (𝑥, 𝑦) be the area cordinate of the triangle (see Fig. 3.30), and
put:
called bubble function on 𝑇𝑘 . The bubble function has the feature: 1. 𝛽𝑘 (𝑥, 𝑦) = 0 if (𝑥, 𝑦) ∈ 𝜕𝑇𝑘 .
𝑞 𝑘1 +𝑞 𝑘2 +𝑞 𝑘3
2. 𝛽𝑘 (𝑞 𝑘𝑏 ) = 1 where 𝑞 𝑘𝑏 is the barycenter 3 .
If we write:
1 Vh(Th, P1b);
2 Vh fh = f(x.y);
then:
𝑛𝑣
∑︁ 𝑛𝑡
∑︁
𝑖
fh = 𝑓ℎ (𝑥, 𝑦) = 𝑓 (𝑞 )𝜑𝑖 (𝑥, 𝑦) + 𝑓 (𝑞 𝑘𝑏 )𝛽𝑘 (𝑥, 𝑦)
𝑖=1 𝑘=1
See Fig. 3.34b for the projection of 𝑓 (𝑥, 𝑦) = sin(𝜋𝑥) cos(𝜋𝑦) into Vh(Th, P1b).
Functions from R2 to R𝑁 with 𝑁 = 1 are called scalar functions and called vector valued when 𝑁 > 1. When 𝑁 = 2
Raviart-Thomas Element
In the Raviart-Thomas finite element 𝑅𝑇∫︀0ℎ , the degrees of freedom are the fluxes across edges 𝑒 of the mesh, where
the flux of the function f : R2 −→ R2 is 𝑒 f .𝑛𝑒 , 𝑛𝑒 is the unit normal of edge 𝑒.
This implies an orientation of all the edges of the mesh, for example we can use the global numbering of the edge
vertices and we just go from small to large numbers.
To compute the flux, we use a quadrature with one Gauss point, the mid-point of the edge.
Consider a triangle 𝑇𝑘 with three vertices (a, b, c).
Lets denote the vertices numbers by 𝑖𝑎 , 𝑖𝑏 , 𝑖𝑐 , and define the three edge vectors e1 , e2 , e3 by 𝑠𝑔𝑛(𝑖𝑏 − 𝑖𝑐 )(b − c),
𝑠𝑔𝑛(𝑖𝑐 − 𝑖𝑎 )(c − a), 𝑠𝑔𝑛(𝑖𝑎 − 𝑖𝑏 )(a − b).
We get three basis functions:
1 Vh(Th, RT0);
2 Vh [f1h, f2h] = [f1(x, y), f2(x, y)];
then:
6
𝑛𝑡 ∑︁
∑︁
fh = fℎ (𝑥, 𝑦) = 𝑛𝑖𝑙 𝑗𝑙 |eil |𝑓𝑗𝑙 (𝑚𝑖𝑙 )𝜑𝑖𝑙 𝑗𝑙
𝑘=1 𝑙=1
1 // Mesh
2 mesh Th = square(2, 2);
3
4 // Fespace
5 fespace Xh(Th, P1);
6 Xh uh = x^2 + y^2, vh;
7
14 //Xh is unchanged
15 //Uxh = x; //error: impossible to set only 1 component
16 //of a vector FE function
17 vh = Uxh;//ok
18 //and now vh use the 5x5 mesh
19 //but the fespace of vh is always the 2x2 mesh
20
21 // Plot
22 plot(uh);
23 uh = uh; //do a interpolation of uh (old) of 5x5 mesh
24 //to get the new uh on 10x10 mesh
25 plot(uh);
26
To get the value at a point 𝑥 = 1, 𝑦 = 2 of the FE function uh, or [Uxh, Uyh], one writes:
1 real value;
2 value = uh(2,4); //get value = uh(2, 4)
3 value = Uxh(2, 4); //get value = Uxh(2, 4)
4 //OR
5 x = 1; y = 2;
6 value = uh; //get value = uh(1, 2)
7 value = Uxh; //get value = Uxh(1, 2)
8 value = Uyh; //get value = Uyh(1, 2)
To get the value of the array associated to the FE function uh, one writes
Warning: For a non-scalar finite element function [Uxh, Uyh] the two arrays Uxh[] and Uyh[] are the same
array, because the degree of freedom can touch more than one component.
In practice, one may discretize the variational equations by the Finite Element method. Then there will be one mesh
for Ω1 and another one for Ω2 . The computation of integrals of products of functions defined on different meshes is
difficult.
Quadrature formula and interpolations from one mesh to another at quadrature points are needed. We present below
the interpolation operator which we have used and which is new, to the best of our knowledge.
Let 𝒯ℎ0 = ∪𝑘 𝑇𝑘0 , 𝒯ℎ1 = ∪𝑘 𝑇𝑘1 be two triangulations of a domain Ω. Let:
Although this is a seemingly simple problem, it is difficult to find an efficient algorithm in practice.
⟩
We propose an algorithm which is of complexity 𝑁 1 log 𝑁 0 , where 𝑁 𝑖 is the number of vertices of 𝒯⟨ , and which is
very fast for most practical 2D applications.
Algorithm
The method has 5 steps.
First a quadtree is built containing all the vertices of the mesh 𝒯ℎ0 such that in each terminal cell there are at least one,
and at most 4, vertices of 𝒯ℎ0 .
For each 𝑞 1 , vertex of 𝒯ℎ1 do:
1. Find the terminal cell of the quadtree containing 𝑞 1 .
2. Find the the nearest vertex 𝑞𝑗0 to 𝑞 1 in that cell.
3. Choose one triangle 𝑇𝑘0 ∈ 𝒯ℎ0 which has 𝑞𝑗0 for vertex.
4. Compute the barycentric coordinates {𝜆𝑗 }𝑗=1,2,3 of 𝑞 1 in 𝑇𝑘0 .
• if all barycentric coordinates are positive, go to Step 5
• otherwise, if one barycentric coordinate 𝜆𝑖 is negative, replace 𝑇𝑘0 by the adjacent triangle opposite 𝑞𝑖0 and
go to Step 4.
• otherwise, if two barycentric coordinates are negative, take one of the two randomly and replace 𝑇𝑘0 by the
adjacent triangle as above.
5. Calculate 𝑔(𝑞 1 ) on 𝑇𝑘0 by linear interpolation of 𝑓 :
∑︁
𝑔(𝑞 1 ) = 𝜆𝑗 𝑓 (𝑞𝑗0 )
𝑗=1,2,3
Note: Sometimes, in rare cases, the interpolation process misses some points, we can change the search algorithm
through a global variable searchMethod
Fig. 3.37: To interpolate a function at 𝑞 0 , the knowledge of the triangle which contains 𝑞 0 is needed. The algorithm
may start at 𝑞 1 ∈ 𝑇𝑘0 and stall on the boundary (thick line) because the line 𝑞 0 𝑞 1 is not inside Ω. But if the holes are
triangulated too (doted line) then the problem does not arise.
Note: Step 3 requires an array of pointers such that each vertex points to one triangle of the triangulation.
Note: The operator = is the interpolation operator of FreeFEM, the continuous finite functions are extended by
continuity to the outside of the domain.
Try the following example :
1 // Mesh
2 mesh Ths = square(10, 10);
3 mesh Thg = square(30, 30, [x*3-1, y*3-1]);
4 plot(Ths, Thg, wait=true);
5
6 // Fespace
7 fespace Ch(Ths, P2);
8 Ch us = (x-0.5)*(y-0.5);
9
16 // Plot
17 plot(us, ug, wait=true);
(continues on next page)
For FreeFEM, a problem must be given in variational form, so we need a bilinear form 𝑎(𝑢, 𝑣), a linear form ℓ(𝑓, 𝑣),
and possibly a boundary condition form must be added.
1 problem P (u, v)
2 = a(u,v) - l(f,v)
3 + (boundary condition)
4 ;
Note: When you want to formulate the problem and solve it in the same time, you can use the keyword solve.
To present the principles of Variational Formulations, also called weak form, for the Partial Differential Equations, let’s
take a model problem: a Poisson equation with Dirichlet and Robin Boundary condition.
The problem: Find 𝑢 a real function defined on a domain Ω of R𝑑 (𝑑 = 2, 3) such that:
−∇ · (𝜅∇𝑢) = 𝑓 in Ω
𝜕𝑢
𝑎𝑢 + 𝜅 𝜕𝑛 = 𝑏 on Γ𝑟
𝑢 = 𝑔 on Γ𝑑
where:
• if 𝑑 = 2 then ∇.(𝜅∇𝑢) = 𝜕𝑥 (𝜅𝜕𝑥 𝑢) + 𝜕𝑦 (𝜅𝜕𝑦 𝑢) with 𝜕𝑥 𝑢 = 𝜕𝑢
𝜕𝑥 and 𝜕𝑦 𝑢 = 𝜕𝑢
𝜕𝑦
Note: This is the well known Neumann boundary condition if 𝑎 = 0, and if Γ𝑑 is empty.
In this case the function appears in the problem just by its derivatives, so it is defined only up to a constant (if 𝑢 is a
solution then 𝑢 + 𝑐 is also a solution).
where 𝑉0 = {𝑣 ∈ 𝐻 1 (Ω)/𝑣 = 0 on Γ𝑑 }
Except in the case of Neumann conditions everywhere, the problem (3.16) is well posed when 𝜅 ≥ 𝜅0 > 0.
Note: If we have only the Neumann boundary condition, linear algebra tells us that the right hand side must be
orthogonal to the kernel of the operator for the solution to exist.
One way of writing the compatibility condition is:
∫︁ ∫︁
𝑓 𝑑𝜔 + 𝑏 𝑑𝛾 = 0
Ω Γ
and a way to fix the constant is to solve for 𝑢 ∈ 𝐻 1 (Ω) such that:
∫︁ ∫︁ ∫︁
(𝜀𝑢𝑣 + 𝜅∇𝑣.∇𝑢) 𝑑𝜔 = 𝑓 𝑣 𝑑𝜔 + 𝑏𝑣 𝑑𝛾, ∀𝑣 ∈ 𝐻 1 (Ω)
Ω Ω Γ𝑟
2
where 𝜀 is a small parameter (∼ 𝜅 10−10 |Ω| ). 𝑑
Remark that
∫︀ if the solution is of order 𝜀 then the compatibility condition is unsatisfied, otherwise we get the solution
1
such that Ω 𝑢 = 0, you can also add a Lagrange multiplier to solve the real mathematical problem like in the Lagrange
multipliers example.
1 problem Pw (u, v)
2 = int2d(Th)( //int_{Omega} kappa nabla v . nabla u
3 kappa*(dx(u)*dx(v) + dy(u)*dy(v))
4 )
5 + int1d(Th, gn)( //int_{Gamma_r} a u v
6 a * u*v
7 )
8 - int2d(Th)( //int_{Omega} f v
9 f*v
10 )
11 - int1d(Th, gn)( //int_{Gamma_r} b v
12 b * v
13 )
14 + on(gd, u=g) //u = g on Gamma_d
15 ;
where Th is a mesh of the bi-dimensional domain Ω, and gd and gn are respectively the boundary labels of boundary
Γ𝑑 and Γ𝑛 .
And the three dimensional problem (3.16) becomes
where Th is a mesh of the three dimensional domain Ω, and gd and gn are respectively the boundary labels of boundary
Γ𝑑 and Γ𝑛 .
The parameters are FE functions real or complex, the number 𝑛 of parameters is even (𝑛 = 2 * 𝑘), the 𝑘 first function
parameters are unknown, and the 𝑘 last are test functions.
Note: If the functions are a part of vectorial FE then you must give all the functions of the vectorial FE in the same
order (see Poisson problem with mixed finite element for example).
Warning: Bug:
The mixing of multiple fespace with different periodic boundary conditions are not implemented.
So all the finite element spaces used for tests or unknown functions in a problem, must have the same type of
periodic boundary conditions or no periodic boundary conditions.
No clean message is given and the result is unpredictable.
|𝜀|
||𝐴𝑥 − 𝑏|| <
||𝐴𝑥0 − 𝑏||
∑︁ ∫︁
– int3d(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝑇
∑︁ ∫︁
– int3d(Th, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝑇,𝜑<0
∑︁ ∫︁
– int3d(Th, l, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑<0
∑︁ ∫︁
– int2d(Th, 2, 5)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int2d(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝑇
∑︁ ∫︁
– int2d(Th, 2, 5)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int2d(Th, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝑇,𝜑=0
∑︁ ∫︁
– int2d(Th, l, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑=0
∑︁ ∫︁
– intallfaces(Th)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝜕𝑇
∑︁ ∫︁
– intallfaces(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝜕𝑇
– They contribute to the sparse matrix of type matrix which, whether declared explicitly or not, is con-
structed by FreeFEM.
• Bilinear part for 2D meshes Th
∑︁ ∫︁
– int2d(Th)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝑇
∑︁ ∫︁
– int2d(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝑇
∑︁ ∫︁
– int2d(Th, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝑇,𝜑<0
∑︁ ∫︁
– int2d(Th, l, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑<0
∑︁ ∫︁
– int1d(Th, 2, 5)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int1d(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝑇
∑︁ ∫︁
– int1d(Th, 2, 5)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int1d(Th, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝑇,𝜑=0
∑︁ ∫︁
– int1d(Th, l, levelset=phi)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑=0
∑︁ ∫︁
– intalledges(Th)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th 𝜕𝑇
∑︁ ∫︁
– intalledges(Th, 1)(K*v*w) = 𝐾𝑣𝑤
𝑇 ∈Th,𝑇 ⊂Ω1 𝜕𝑇
– They contribute to the sparse matrix of type matrix which, whether declared explicitly or not, is con-
structed by FreeFEM.
– The right hand-side of the Partial Differential Equation in 3D, the terms of the linear form: for given
functions 𝐾, 𝑓 :
∑︁ ∫︁
– int3d(Th)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇
∑︁ ∫︁
– int3d(Th, l)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ∈Ω𝑙 𝑇
∑︁ ∫︁
– int3d(Th, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇,𝜑<0
∑︁ ∫︁
– int3d(Th, l, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑<0
∑︁ ∫︁
– int2d(Th, 2, 5)(K*w) = 𝐾𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int2d(Th, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇,𝜑=0
∑︁ ∫︁
– int2d(Th, l, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑=0
∑︁ ∫︁
– intallfaces(Th)(f*w) = 𝑓𝑤
𝑇 ∈Th 𝜕𝑇
• The right hand-side of the Partial Differential Equation in 2D, the terms of the linear form: for given functions
𝐾, 𝑓 :
∑︁ ∫︁
– int2d(Th)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇
∑︁ ∫︁
– int2d(Th, l)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ∈Ω𝑙 𝑇
∑︁ ∫︁
– int2d(Th, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇,𝜑<0
∑︁ ∫︁
– int2d(Th, l, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑<0
∑︁ ∫︁
– int1d(Th, 2, 5)(K*w) = 𝐾𝑤
𝑇 ∈Th (𝜕𝑇 ∪Γ)∩(Γ2 ∪Γ5 )
∑︁ ∫︁
– int1d(Th, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th 𝑇,𝜑=0
∑︁ ∫︁
– int1d(Th, l, levelset=phi)(K*w) = 𝐾𝑤
𝑇 ∈Th,𝑇 ⊂Ω𝑙 𝑇,𝜑=0
∑︁ ∫︁
– intalledges(Th)(f*w) = 𝑓𝑤
𝑇 ∈Th 𝜕𝑇
Note:
• An “on” vectorial form (for Dirichlet): on(1, u1=g1, u2=g2)
If you have vectorial finite element like RT0, the 2 components are coupled, and so you have : 𝑏[𝑖] = ”(Πℎ (𝑔1, 𝑔2))[𝑖]”×
𝑡𝑔𝑣, where Πℎ is the vectorial finite element interpolant.
• An “on” vectorial form (for Dirichlet): on(u=g, tgv= none positive value ) ,
if the value is equal to -2 (i.e tgv == -2 `) then we put to :math:`0 all term of the line and
colomn 𝑖 in the matrix, except diagonal term 𝑎𝑖𝑖 = 1, and 𝑏[𝑖] = ”(Πℎ 𝑔)[𝑖]” else if the value is equal
to -20 (i.e tgv == -20 `) then we put to :math:`0 all term of the line and colomn 𝑖 in the matrix,
and 𝑏[𝑖] = ”(Πℎ 𝑔)[𝑖]”
else if the value is equal to -10 (i.e tgv == -10 `) then we put to :math:`0 all term of the line the
matrix, and 𝑏[𝑖] = ”(Πℎ 𝑔)[𝑖]”
else (i.e tgv == -1 `) we put to :math:`0 all term of the line 𝑖 in the matrix, except diagonal term
𝑎𝑖𝑖 = 1, and 𝑏[𝑖] = ”(Πℎ 𝑔)[𝑖]”.
• If needed, the different kind of terms in the sum can appear more than once.
• The integral mesh and the mesh associated to test functions or unknown functions can be different in the case of
varf form.
• N.x, N.y and N.z are the normal’s components.
• Ns.x, Ns.y and Ns.z are the normal’s components of the suface in case of meshS integral
• Tl.x, Tl.y and Tl.z are the tangent’s components of the line in case of meshL integral
Warning: It is not possible to write in the same integral the linear part and the bilinear part such as in
int1d(Th)(K*v*w - f*w).
then we have an error estimate (see [CROUZEIX1984]), and then there exists a constant 𝐶 > 0 such that
⃒∫︁ 𝐿
⃒
⃒ ∑︁ ⃒
⃒ 𝑓 (x) − 𝜔ℓ 𝑓 (𝜉ℓ )⃒ ≤ 𝐶|𝐷|ℎ𝑟+1
⃒ ⃒
⃒ 𝐷 ⃒
ℓ=1
for any function 𝑟 + 1 times continuously differentiable 𝑓 in 𝐷, where ℎ is the diameter of 𝐷 and |𝐷| its measure (a
point in the segment [𝑞 𝑖 𝑞 𝑗 ] is given as
where * stands for the name of the quadrature formula or the precision (order) of the Gauss formula.
5 qf5pE 10 (1 ± 245+14
21
70
)/2 322−13 70
1800 ||𝑞 𝑖 𝑞 𝑗 || 9
64 𝑖 𝑗
1/2 √ 225 ||𝑞 √ 𝑞 ||
(1 ± 245−14
21
70
)/2 322+13 70
1800 ||𝑞 𝑖 𝑞 𝑗 ||
𝑖 𝑗
2 qf1pElump2 0 ||𝑞 𝑞 ||/2 1
1 ||𝑞 𝑖 𝑞 𝑗 ||/2
For each triangle 𝑇𝑘 = [𝑞 𝑘1 𝑞 𝑘2 𝑞 𝑘3 ], the point 𝑃 (𝑥, 𝑦) in 𝑇𝑘 is expressed by the area coordinate as 𝑃 (𝜉, 𝜂):
⃒ 1 𝑞𝑥𝑘1 𝑞𝑦𝑘1 ⃒
⃒ ⃒ ⃒ ⃒ ⃒ ⃒ ⃒ ⃒
⃒ 1 𝑥 𝑦 ⃒⃒ ⃒ 1 𝑞𝑥𝑘1 𝑞𝑦𝑘1 ⃒ ⃒ 1 𝑞𝑥𝑘1 𝑞𝑦𝑘1 ⃒
1 ⃒⃒ ⃒ ⃒ ⃒ ⃒ ⃒ ⃒
|𝑇𝑘 | = ⃒ 1 𝑞𝑥𝑘2 𝑞𝑦𝑘2 ⃒⃒ 𝐷1 = ⃒⃒ 1 𝑞𝑥𝑘2 𝑞𝑦𝑘2 ⃒⃒ 𝐷2 = ⃒⃒ 1 𝑥 𝑦 ⃒⃒ 𝐷3 = ⃒⃒ 1 𝑞𝑥𝑘2 𝑞𝑦𝑘2 ⃒⃒
2⃒ ⃒ 1 𝑞𝑥𝑘3 𝑞𝑦𝑘3 ⃒ ⃒ 1 𝑞𝑥𝑘3 𝑞𝑦𝑘3 ⃒
1 𝑞𝑥𝑘3 𝑞𝑦𝑘3 ⃒ ⃒ 1 𝑥 𝑦 ⃒
1 1 1
𝜉 = 𝐷1 /|𝑇𝑘 | 𝜂 = 𝐷2 /|𝑇𝑘 | then 1 − 𝜉 − 𝜂 = 𝐷3 /|𝑇𝑘 |
2 2 2
∑︀𝑛𝑡
For a two dimensional domain or a border of three dimensional domain Ωℎ = 𝑘=1 𝑇𝑘 , 𝒯ℎ = {𝑇𝑘 }, we can calculate
the integral over Ωℎ by:
𝑓 (𝑥, 𝑦) =int2d(Th)(f) =int2d(Th, qft=*)(f) =int2d(Th, qforder=*)(f)
∫︀
Ωℎ
where * stands for the name of quadrature formula or the order of the Gauss formula.
∑︀𝑛𝑡
For a three dimensional domain Ωℎ = 𝑘=1 𝑇𝑘 , 𝒯ℎ = {𝑇𝑘 }, we can calculate the integral over Ωℎ by:
𝑓 (𝑥, 𝑦) =int3d(Th)(f) =int3d(Th,qfV=*)(f) =int3D(Th,qforder=*)(f)
∫︀
Ωℎ
where * stands for the name of quadrature formula or the order of the Gauss formula.
Where 𝐺4(𝑎, 𝑏, 𝑏) such that 𝑎 + 3𝑏 = 1 is the set of the four point in barycentric coordinate:
and where 𝐺6(𝑎, 𝑏, 𝑏) such that 2𝑎 + 2𝑏 = 1 is the set of the six points in barycentric coordinate:
{(𝑎, 𝑎, 𝑏, 𝑏), (𝑎, 𝑏, 𝑎, 𝑏), (𝑎, 𝑏, 𝑏, 𝑎), (𝑏, 𝑏, 𝑎, 𝑎), (𝑏, 𝑎, 𝑏, 𝑎), (𝑏, 𝑎, 𝑎, 𝑏)}
Note: By default, we use the formula which is exact for polynomials of degree 5 on triangles or edges (in bold in three
tables).
It is possible to add an own quadrature formulae with using plugin qf11to25 on segment, triangle or Tetrahedron.
The quadrature formulae in 𝐷 dimension is a bidimentional array of size 𝑁𝑞 × (𝐷 + 1) such that the 𝐷 + 1 value of on
∑︀𝐷
row 𝑖 = 0, ..., 𝑁𝑝 − 1 are 𝑤𝑖 , 𝑥 ˆ𝑖𝐷 where 𝑤𝑖 is the weight of the quadrature point, and 1 − 𝑘=1 𝑥
ˆ𝑖1 , ..., 𝑥 ˆ𝑖𝑘 , 𝑥
ˆ𝑖1 , ..., 𝑥
ˆ𝑖𝐷
is the barycentric coordinate the quadrature point.
1 load "qf11to25"
2
3 // Quadrature on segment
4 real[int, int] qq1 = [
5 [0.5, 0],
6 [0.5, 1]
7 ];
8
13 //Quadrature on triangle
14 real[int, int] qq2 = [
15 [1./3., 0, 0],
16 [1./3., 1, 0],
17 [1./3., 0, 1]
18 ];
19
25 // Quadrature on tetrahedron
26 real[int, int] qq3 = [
27 [1./4., 0, 0, 0],
28 [1./4., 1, 0, 0],
29 [1./4., 0, 1, 0],
30 [1./4., 0, 0, 1]
31 ];
32
The output is
1 1.67 == 1.67
2 0.335 == 0.335
In FreeFEM it is possible to define variational forms, and use them to build matrices and vectors, and store them to
speed-up the script (4 times faster here).
For example let us solve the Thermal Conduction problem.
The variational formulation is in 𝐿2 (0, 𝑇 ; 𝐻 1 (Ω)); we shall seek 𝑢𝑛 satisfying:
∫︁ 𝑛
𝑢 − 𝑢𝑛−1
∫︁
∀𝑤 ∈ 𝑉0 ; 𝑤 + 𝜅∇𝑢 ∇𝑤) + 𝛼(𝑢𝑛 − 𝑢𝑢𝑒 )𝑤 = 0
𝑛
Ω 𝛿𝑡 Γ
𝑏”𝑖 if 𝑖 ∈ Γ24
{︂
𝑛 −1 𝑛 ′ 𝑛−1 1 𝑛
𝑢 =𝐴 𝑏 , 𝑏 = 𝑏0 + 𝑀 𝑢 , 𝑏” = 𝑏𝑐𝑙 , 𝑏𝑖 =
𝜀 𝑏′𝑖 else if ̸∈ Γ24
Where with 1
𝜀 = tgv = 1030 :
1
if 𝑖 ∈ Γ24 , and𝑗 = 𝑖
⎧
⎨ ∫︁ 𝜀 ∫︁
𝐴𝑖𝑗 =
⎩ 𝑤𝑗 𝑤𝑖 /𝑑𝑡 + 𝑘(∇𝑤𝑗 .∇𝑤𝑖 ) + 𝛼𝑤𝑗 𝑤𝑖 else if 𝑖 ̸∈ Γ24 , or𝑗 ̸= 𝑖
⎧ Ω Γ13
⎨ ∫︁ 1
𝜀 if 𝑖 ∈ Γ24 , and𝑗 = 𝑖
𝑀𝑖𝑗 =
⎩ 𝑤𝑗 𝑤𝑖 /𝑑𝑡 else if 𝑖 ̸∈ Γ24 , or𝑗 ̸= 𝑖
∫︀ Ω
𝑏0,𝑖 = Γ13 𝛼𝑢𝑢𝑒 𝑤𝑖
𝑏𝑐𝑙 = 𝑢0 the initial data
1 // Parameters
2 func fu0 = 10 + 90*x/6;
3 func k = 1.8*(y<0.5) + 0.2;
(continues on next page)
9 // Mesh
10 mesh Th = square(30, 5, [6*x, y]);
11
12 // Fespace
13 fespace Vh(Th, P1);
14 Vh u0 = fu0, u = u0;
1 // Problem
2 varf vthermic (u, v)
3 = int2d(Th)(
4 u*v/dt
5 + k*(dx(u)*dx(v) + dy(u)*dy(v))
6 )
7 + int1d(Th, 1, 3)(
8 alpha*u*v
9 )
10 + on(2, 4, u=1)
11 ;
12
Note: The boundary condition is implemented by penalization and vector bcn contains the contribution of the bound-
ary condition 𝑢 = 1, so to change the boundary condition, we have just to multiply the vector bcn[] by the current
value f of the new boundary condition term by term with the operator .*.
Uzawa model gives a real example of using all this features.
1 // Time loop
2 ofstream ff("thermic.dat");
3 for(real t = 0; t < T; t += dt){
4 // Update
5 real[int] b = b0; //for the RHS
6 b += M*u[]; //add the the time dependent part
7 //lock boundary part:
8 b = bcn ? bcl : b; //do forall i: b[i] = bcn[i] ? bcl[i] : b[i]
9
10 // Solve
11 u[] = A^-1*b;
12
13 // Save
14 ff << t << " " << u(3, 0.5) << endl;
15
16 // Plot
17 plot(u);
18 }
19
20 // Display
21 for(int i = 0; i < 20; i++)
22 cout << dy(u)(6.0*i/20.0, 0.9) << endl;
23
24 // Plot
25 plot(u, fill=true, wait=true);
Note: The functions appearing in the variational form are formal and local to the varf definition, the only important
thing is the order in the parameter list, like in:
To build matrix 𝐴 from the bilinear part the variational form 𝑎 of type varf simply write:
1 A = a(Vh, Wh , []...]);
2 // where
3 //Vh is "fespace" for the unknown fields with a correct number of component
4 //Wh is "fespace" for the test fields with a correct number of component
the matrix is sky-line symmetric positive definite, for CG the matrix is sparse symmetric positive, and
for GMRES, sparsesolver or UMFPACK the matrix is just sparse.
• factorize = If true then do the matrix factorization for LU, Cholesky or Crout, the default value is false.
• eps= A real expression.
𝜀 sets the stopping test for the iterative methods like CG.
Note that if 𝜀 is negative then the stopping test is:
|𝜀|
||𝐴𝑥 − 𝑏|| <
||𝐴𝑥0 − 𝑏||
Note: The line of the matrix corresponding to the space Wh and the column of the matrix corresponding to the space
Vh.
To build the dual vector b (of type real[int]) from the linear part of the variational form a do simply:
1 real b(Vh.ndof);
2 b = a(0, Vh);
A first example to compute the area of each triangle 𝐾 of mesh 𝑇 ℎ, just do:
Effectively, the basic functions of space 𝑁 ℎ, are the characteristic function of the element of Th, and the numbering is
the numeration of the element, so by construction:
∫︁ ∫︁
etaK[𝑖] = 1|𝐾𝑖 = 1;
𝐾𝑖
Now, we can use this to compute error indicators like in example Adaptation using residual error indicator.
First to compute a continuous approximation to the function ℎ “density mesh size” of the mesh 𝑇 ℎ.
where ℎ𝐾 is size of the longest edge (hTriangle), ℎ𝑒 is the size of the current edge (lenEdge), 𝑛 the normal.
We add automatic expression optimization by default, if this optimization creates problems, it can be removed with the
keyword optimize as in the following example:
or you can also do optimization and remove the check by setting optimize=2.
Remark, it is all possible to build interpolation matrix, like in the following example:
and after some operations on sparse matrices are available for example:
1 int N = 10;
2 real [int, int] A(N, N); //a full matrix
3 real [int] a(N), b(N);
4 A = 0;
5 for (int i = 0; i < N; i++){
6 A(i, i) = 1+i;
7 if (i+1 < N) A(i, i+1) = -i;
8 a[i] = i;
9 }
10 b = A*b;
11 matrix sparseA = A;
12 cout << sparseA << endl;
13 sparseA = 2*sparseA + sparseA';
14 sparseA = 4*sparseA + sparseA*5;
15 matrix sparseB = sparseA + sparseA + sparseA; ;
16 cout << "sparseB = " << sparseB(0,0) << endl;
It is also possible to store the matrix of a linear interpolation operator from a finite element space 𝑉ℎ to another 𝑊ℎ to
interpolate(𝑊ℎ ,𝑉ℎ ,. . . ) a function.
Note that the continuous finite functions are extended by continuity outside of the domain.
The named parameters of function interpolate are:
• inside= set true to create zero-extension.
• t= set true to get the transposed matrix
• op= set an integer written below
– 0 the default value and interpolate of the function
– 1 interpolate the 𝜕𝑥
– 2 interpolate the 𝜕𝑦
– 3 interpolate the 𝜕𝑧
• U2Vc= set the which is the component of 𝑊ℎ come in 𝑉ℎ in interpolate process in a int array so the size of the
array is number of component of 𝑊ℎ , if the put −1 then component is set to 0, like in the following example:
(by default the component number is unchanged).
1 // Mesh
2 mesh Th = square(4, 4);
3 mesh Th4 = square(2, 2, [x*0.5, y*0.5]);
4 plot(Th, Th4, wait=true);
5
6 // Fespace
7 fespace Vh(Th, P1);
8 Vh v, vv;
9 fespace Vh4(Th4, P1);
10 Vh4 v4=x*y;
11
15 // Interpolation
16 matrix IV = interpolate(Vh, Vh4); //here the function is exended by continuity
17 cout << "IV Vh<-Vh4 " << IV << endl;
18
19 v=v4;
20 vv[]= IV*v4[]; //here v == vv
21
26 matrix IV0 = interpolate(Vh, Vh4, inside=1); //here the function is exended by zero
27 cout << "IV Vh<-Vh4 (inside=1) " << IV0 << endl;
28
where 𝑤𝑖 is the basic finite element function, 𝑐 the component number, 𝑑𝑜𝑝 the type of diff operator like in op def.
Tip: Schwarz
The following shows how to implement with an interpolation matrix a domain decomposition algorithm based on
Schwarz method with Robin conditions.
Given a non-overlapping partition Ω̄ = Ω̄0 ∪ Ω̄1 with Ω0 ∩ Ω1 = ∅, Σ := Ω̄0 ∩ Ω̄1 the algorithm is:
−∆𝑢𝑖 = 𝑓 in Ω𝑖 , 𝑖 = 0, 1,
𝜕(𝑢1 − 𝑢0 )
+ 𝛼(𝑢1 − 𝑢0 ) = 0 on Σ.
𝜕𝑛
The same in variational form is:
∫︀ ∫︀ ∫︀
Ω𝑖
∇𝑢𝑖 · ∇𝑣 + Σ
𝛼𝑢𝑖 𝑣 = Ω𝑖
𝑓𝑣
𝛼𝑢𝑗 𝑣, ∀𝑣 ∈ 𝐻01 (Ω), 𝑖, 𝑗 = [0, 1] ∪ [1, 0]
∫︀ ∫︀
− Ω𝑗
(∇𝑢𝑗 · ∇𝑣 − 𝑓 𝑣) + Σ
To discretized with the 𝑃 1 triangular Lagrangian finite element space 𝑉ℎ simply replace 𝐻01 (Ω) by 𝑉ℎ (Ω0 ) ∪ 𝑉ℎ (Ω1 ).
Then difficulty is to compute Ω𝑗 ∇𝑢𝑗 · ∇𝑣 when 𝑣 is a basis function of 𝑉ℎ (Ω𝑖 ), 𝑖 ̸= 𝑗.
∫︀
1 // Parameters
2 int n = 30;
3 int Gamma = 1;
4 int Sigma = 2;
5
6 func f = 1.;
7 real alpha = 1.;
8
11 // Mesh
12 mesh[int] Th(2);
13 int[int] reg(2);
14
30 // Fespace
31 fespace Vh0(Th[0], P1);
32 Vh0 u0 = 0;
33
37 // Macro
38 macro grad(u) [dx(u), dy(u)] //
39
40 // Problem
41 int i;
42 varf a (u, v)
43 = int2d(Th[i])(
44 grad(u)'*grad(v)
45 )
46 + int1d(Th[i], Sigma)(
47 alpha*u*v
48 )
49 + on(Gamma, u=0)
50 ;
51
52 varf b (u, v)
53 = int2d(Th[i])(
54 f*v
55 )
56 + on(Gamma, u=0)
57 ;
58
84 matrix[int] A(2);
85 i = 0; A[i] = a(Vh0, Vh0);
86 i = 1; A[i] = a(Vh1, Vh1);
87
88 // Solving loop
89 for(int iter = 0; iter < Niter; iter++){
90 // Solve on Th[0]
91 {
92 i = 0;
93 real[int] b0 = b(0, Vh0);
94 real[int] Du1dn = du1dn(0, Vh1);
95 real[int] Tdu1dn(Vh0.ndof); Tdu1dn = I01'*Du1dn;
96 b0 += Tdu1dn;
97 u0[] = A[0]^-1*b0;
98 }
99 // Solve on Th[1]
100 {
101 i = 1;
102 real[int] b1 = b(0, Vh1);
103 real[int] Du0dn = du0dn(0, Vh0);
104 real[int] Tdu0dn(Vh1.ndof); Tdu0dn = I10'*Du0dn;
105 b1 += Tdu0dn;
106 u1[] = A[1]^-1*b1;
107 }
108 plot(u0, u1, cmm="iter="+iter);
109 }
Here, we show how to get informations on a finite element space 𝑊ℎ (𝒯𝑛 , *), where “*” may be P1, P2, P1nc, etc.
• Wh.nt gives the number of element of 𝑊ℎ
• Wh.ndof gives the number of degrees of freedom or unknown
• Wh.ndofK gives the number of degrees of freedom on one element
• Wh(k,i) gives the number of 𝑖th degrees of freedom of element 𝑘.
See the following example:
1 // Mesh
2 mesh Th = square(5, 5);
(continues on next page)
4 // Fespace
5 fespace Wh(Th, P2);
6
7 cout << "Number of degree of freedom = " << Wh.ndof << endl;
8 cout << "Number of degree of freedom / ELEMENT = " << Wh.ndofK << endl;
9
3.4 Visualization
Results created by the finite element method can be a huge set of data, so it is very important to render them easy to
grasp.
There are two ways of visualization in FreeFEM:
• One, the default view, which supports the drawing of meshes, isovalues of real FE-functions, and of vector fields,
all by the command plot (see Plot section below). For publishing purpose, FreeFEM can store these plots as
postscript files.
• Another method is to use external tools, for example, gnuplot (see Gnuplot section, medit section, Paraview
section, Matlab/Octave section) using the command system to launch them and/or to save the data in text files.
3.4.1 Plot
With the command plot, meshes, isovalues of scalar functions, and vector fields can be displayed.
The parameters of the plot command can be meshes, real FE functions, arrays of 2 real FE functions, arrays of two
double arrays, to plot respectively a mesh, a function, a vector field, or a curve defined by the two double arrays.
Note: The length of an arrow is always bound to be in [5‰, 5%] of the screen size in order to see something.
3 mesh Th = square(5,5);
4
11 //zoom on box defined by the two corner points [0.1,0.2] and [0.5,0.6]
12 plot(uh, [uh, vh], bb=[[0.1, 0.2], [0.5, 0.6]],
(continues on next page)
15 //compute a cut
16 for (int i = 0; i < 10; i++){
17 x = i/10.;
18 y = i/10.;
19 xx[i] = i;
20 yy[i] = uh; //value of uh at point (i/10., i/10.)
21 }
22 plot([xx, yy], ps="likegnu.eps", wait=true);
To change the color table and to choose the value of iso line you can do:
1 // from: \url{http://en.wikipedia.org/wiki/HSV_color_space}
2 // The HSV (Hue, Saturation, Value) model defines a color space
3 // in terms of three constituent components:
4 // HSV color space as a color wheel
5 // Hue, the color type (such as red, blue, or yellow):
6 // Ranges from 0-360 (but normalized to 0-100% in some applications, like here)
7 // Saturation, the "vibrancy" of the color: Ranges from 0-100%
8 // The lower the saturation of a color, the more "grayness" is present
9 // and the more faded the color will appear.
10 // Value, the brightness of the color: Ranges from 0-100%
11
(a) Mesh, isovalue and vector (b) Enlargement in grey of isovalue and vector
(c) Plots a cut of uh. Note that a refinement of the same can
be obtained in combination with gnuplot
Example Membrane shows how to generate a gnuplot from a FreeFEM file. Here is another technique which has the
advantage of being online, i.e. one doesn’t need to quit FreeFEM to generate a gnuplot.
However, this works only if gnuplot is installed, and only on an Unix-like computer.
Add to the previous example:
7 // to call gnuplot command and wait 5 second (due to the Unix command)
8 // and make postscript plot
9 exec("echo 'plot \"plot.gp\" w l \n pause 5 \n set term postscript \n set output \
˓→"gnuplot.eps\" \n replot \n quit' | gnuplot");
As said above, medit is a freeware display package by Pascal Frey using OpenGL. Then you may run the following
example.
Now medit software is included in FreeFEM under ffmedit name.
The medit command parameters are listed in the Reference part.
With version 3.2 or later
1 load "medit"
2
Before:
1 mesh Th = square(10, 10, [2*x-1, 2*y-1]);
2
One can also export mesh or results in the .vtk format in order to post-process data using Paraview.
1 load "iovtk"
2
In order to create a plot from a FreeFEM simulation in Octave and Matlab the mesh, the finite element space connec-
tivity and the simulation data must be written to files:
1 include "ffmatlib.idp"
2
7 savemesh(Th,"export_mesh.msh");
8 ffSaveVh(Th,Vh,"export_vh.txt");
9 ffSaveData(u,"export_data.txt");
Within Matlab or Octave the files can be plot with the ffmatlib library:
1 addpath('path to ffmatlib');
2 [p,b,t]=ffreadmesh('export_mesh.msh');
3 vh=ffreaddata('export_vh.txt');
4 u=ffreaddata('export_data.txt');
5 ffpdeplot(p,b,t,'VhSeq',vh,'XYData',u,'ZStyle','continuous','Mesh','on');
6 grid;
Note: For more Matlab / Octave plot examples have a look at the tutorial section Matlab / Octave Examples or visit
the ffmatlib library on github.
Suppose we want to solve the Euler problem (here 𝑥 has nothing to do with the reserved variable for the first coordinate
in FreeFEM):
find 𝑥 ∈ R𝑛 such that
(︂ )︂
𝜕𝐽
∇𝐽(𝑥) = (x) = 0 (3.17)
𝜕𝑥𝑖
where 𝐽 is a function (to minimize for example) from R𝑛 to R.
If the function is convex we can use the conjugate gradient algorithm to solve the problem, and we just need the function
(named dJ for example) which computes ∇𝐽, so the parameters are the name of that function with prototype func
real[int] dJ(real[int] &xx); which computes ∇𝐽, and a vector x of type (of course the number 20 can be
changed) real[int] x(20); to initialize the process and get the result.
Given an initial value x(0) , a maximum number 𝑖max of iterations, and an error tolerance 0 < 𝜖 < 1:
Put x = x(0) and write
will give the solution of x of ∇𝐽(x) = 0. We can omit parameters precon, nbiter, eps, stop. Here 𝑀 is the
preconditioner whose default is the identity matrix.
The stopping test is
‖∇𝐽(x)‖2𝑃 ≤ 𝜖
where u is the current solution, and g, the current gradient, is not preconditioned.
Tip: Algorithms.edp
For a given function 𝑏, let us find the minimizer 𝑢 of the function
𝐽(𝑢) = 21 Ω 𝑓 (|∇𝑢|2 ) − Ω 𝑢𝑏
∫︀ ∫︀
1
𝑓 (𝑥) = 𝑎𝑥 + 𝑥 − ln(1 + 𝑥), 𝑓 ′ (𝑥) = 𝑎 + 𝑥
1+𝑥 , 𝑓 ′′ (𝑥) = (1+𝑥)2
4 // The function J
5 //J(u) = 1/2 int_Omega f(|nabla u|^2) - int_Omega u b
6 func real J (real[int] & u){
7 Vh w;
8 w[] = u;
9 real r = int2d(Th)(0.5*f(dx(w)*dx(w) + dy(w)*dy(w)) - b*w);
10 cout << "J(u) = " << r << " " << u.min << " " << u.max << endl;
11 return r;
12 }
13
14 // The gradient of J
15 func real[int] dJ (real[int] & u){
16 Vh w;
17 w[] = u;
18 alpha = df(dx(w)*dx(w) + dy(w)*dy(w));
19 varf au (uh, vh)
20 = int2d(Th)(
21 alpha*(dx(w)*dx(vh) + dy(w)*dy(vh))
22 - b*vh
23 )
24 + on(1, 2, 3, 4, uh=0)
25 ;
26
27 u = au(0, Vh);
(continues on next page)
where 𝛼 = 𝑓 ′ (|∇𝑢|2 ).
19 // Preconditionner
20 func real[int] C(real[int] & u){
21 real[int] w = u;
22 u = Alap^-1*w;
23 return u; //warning: no return of local array variable
24 }
To solve the problem, we make 10 iterations of the conjugate gradient, recompute the preconditioner and restart the
conjugate gradient:
1 int conv=0;
2 for(int i = 0; i < 20; i++){
3 conv = NLCG(dJ, u[], nbiter=10, precon=C, veps=eps, verbosity=5);
4 if (conv) break;
5
11 // Plot
12 plot (u, wait=true, cmm="solution with NLCG");
Also, we can use the non-linear version of GMRES algorithm (the function 𝐽 is just convex)
For the details of these algorithms, refer to [PIRONNEAU1998], Chapter IV, 1.3.
Two algorithms of COOOL package are interfaced with the Newton Raphson method (called Newton) and the BFGS
method. These two are directly available in FreeFEM (no dynamical link to load). Be careful with these algorithms,
because their implementation uses full matrices. We also provide several optimization algorithms from the NLopt
library as well as an interface for Hansen’s implementation of CMAES (a MPI version of this one is also available).
Tip: BFGS
3 //J
4 func real J (real[int] & u){
5 real s = 0;
6 for (int i = 0; i < u.n; i++)
7 s += (i+1)*u[i]*u[i]*0.5 - b[i]*u[i];
8 if (debugJ)
9 cout << "J = " << s << ", u = " << u[0] << " " << u[1] << endl;
10 return s;
11 }
12
25 b=1;
26 u=2;
27 BFGS(J, DJ, u, eps=1.e-6, nbiter=20, nbiterline=20);
28 cout << "BFGS: J(u) = " << J(u) << ", err = " << error(u, b) << endl;
It is almost the same a using the CMA evolution strategy except, that since it is a derivative free optimizer, the dJ
argument is omitted and there are some other named parameters to control the behavior of the algorithm. With the
same objective function as above, an example of utilization would be (see CMAES Variational inequality for a complete
example):
1 load "ff-cmaes"
2 //define J, u, ...
3 real min = cmaes(J, u, stopTolFun=1e-6, stopMaxIter=3000);
4 cout << "minimum value is " << min << " for u = " << u << endl;
This algorithm works with a normal multivariate distribution in the parameters space and tries to adapt its covariance
matrix using the information provided by the successive function evaluations (see NLopt documentation for more
details). Therefore, some specific parameters can be passed to control the starting distribution, size of the sample
generations, etc. . . Named parameters for this are the following:
• seed= Seed for random number generator (val is an integer). No specified value will lead to a clock based seed
initialization.
• initialStdDev= Value for the standard deviations of the initial covariance matrix ( val is a real). If the value
𝜎 is passed, the initial covariance matrix will be set to 𝜎𝐼. The expected initial distance between initial 𝑋 and
the 𝑎𝑟𝑔𝑚𝑖𝑛 should be roughly initialStdDev. Default is 0.3.
• initialStdDevs= Same as above except that the argument is an array allowing to set a value of the initial
standard deviation for each parameter. Entries differing by several orders of magnitude should be avoided (if it
can’t be, try rescaling the problem).
• stopTolFun= Stops the algorithm if function value differences are smaller than the passed one, default is 10−12 .
• stopTolFunHist= Stops the algorithm if function value differences from the best values are smaller than the
passed one, default is 0 (unused).
• stopTolX= Stopping criteria is triggered if step sizes in the parameters space are smaller than this real value,
default is 0.
• stopTolXFactor= Stopping criteria is triggered when the standard deviation increases more than this value.
The default value is 103 .
• stopMaxFunEval= Stops the algorithm when stopMaxFunEval function evaluations have been done. Set to
900(𝑛 + 3)2 by default, where 𝑛 is the parameters space dimension.
• stopMaxIter= Integer stopping the search when stopMaxIter generations have been sampled. Unused by
default.
• popsize= Integer value used to change the sample size. The default value is 4 + ⌊3 ln(𝑛)⌋. Increasing the
population size usually improves the global search capabilities at the cost of, at most, a linear reduction of the
convergence speed with respect to popsize.
• paramFile= This string type parameter allows the user to pass all the parameters using an extern file, as in
Hansen’s original code. More parameters related to the CMA-ES algorithm can be changed with this file. Note
that the parameters passed to the CMAES function in the FreeFEM script will be ignored if an input parameters
file is given.
3.5.3 IPOPT
The ff-Ipopt package is an interface for the IPOPT [WÄCHTER2006] optimizer. IPOPT is a software library for
large scale, non-linear, constrained optimization. It implements a primal-dual interior point method along with filter
method based line searches.
IPOPT needs a direct sparse symmetric linear solver. If your version of FreeFEM has been compiled with the
--enable-downlad tag, it will automatically be linked with a sequential version of MUMPS. An alternative to
MUMPS would be to download the HSL subroutines (see Compiling and Installing the Java Interface JIPOPT) and
place them in the /ipopt/Ipopt-3.10.2/ThirdParty/HSL directory of the FreeFEM downloads folder before
compiling.
In this section, we give a very brief glimpse at the underlying mathematics of IPOPT. For a deeper introduction on
interior methods for nonlinear smooth optimization, one may consult [FORSGREN2002], or [WÄCHTER2006] for
more IPOPT specific elements. IPOPT is designed to perform optimization for both equality and inequality constrained
problems. However, nonlinear inequalities are rearranged before the beginning of the optimization process in order to
restrict the panel of nonlinear constraints to those of the equality kind. Each nonlinear inequality is transformed into a
pair of simple bound inequalities and nonlinear equality constraints by the introduction of as many slack variables as
is needed : 𝑐𝑖 (𝑥) ≤ 0 becomes 𝑐𝑖 (𝑥) + 𝑠𝑖 = 0 and 𝑠𝑖 ≤ 0, where 𝑠𝑖 is added to the initial variables of the problems
𝑥𝑖 . Thus, for convenience, we will assume that the minimization problem does not contain any nonlinear inequality
constraint. It means that, given a function 𝑓 : R𝑛 ↦→ R, we want to find:
𝑥0 = argmin𝑓 (𝑥)
𝑥∈𝑉 (3.18)
with 𝑉 = {𝑥 ∈ R𝑛 | 𝑐(𝑥) = 0 and 𝑥𝑙 ≤ 𝑥 ≤ 𝑥𝑢 }
Where 𝑐 : R𝑛 → R𝑚 and 𝑥𝑙 , 𝑥𝑢 ∈ R𝑛 and inequalities hold componentwise. The 𝑓 function as well as the constraints
𝑐 should be twice-continuously differentiable.
As a barrier method, interior points algorithms try to find a Karush-Kuhn-Tucker point for (3.18) by solving a sequence
of problems, unconstrained with respect to the inequality constraints, of the form:
The remaining equality constraints are handled with the usual Lagrange multipliers method. If the sequence of barrier
parameters 𝜇 converge to 0, intuition suggests that the sequence of minimizers of (3.19) converge to a local constrained
minimizer of (3.18). For a given 𝜇, (3.19) is solved by finding (𝑥𝜇 , 𝜆𝜇 ) ∈ R𝑛 × R𝑚 such that:
𝑚
∑︁
∇𝐵(𝑥𝜇 , 𝜇) + 𝜆𝜇,𝑖 ∇𝑐𝑖 (𝑥𝜇 ) = ∇𝐵(𝑥𝜇 , 𝜇) + 𝐽𝑐 (𝑥𝜇 )𝑇 𝜆𝜇 = 0
𝑖=1 (3.20)
𝑐(𝑥𝜇 ) = 0
If we respectively call 𝑧𝑢 (𝑥, 𝜇) = (𝜇/(𝑥𝑢,1 − 𝑥1 ), . . . , 𝜇/(𝑥𝑢,𝑛 − 𝑥𝑛 )) and 𝑧𝑙 (𝑥, 𝜇) the other vector appearing in the
above equation, then the optimum (𝑥𝜇 , 𝜆𝜇 ) satisfies:
In this equation, the 𝑧𝑙 and 𝑧𝑢 vectors seem to play the role of Lagrange multipliers for the simple bound inequalities,
and indeed, when 𝜇 → 0, they converge toward some suitable Lagrange multipliers for the KKT conditions, provided
some technical assumptions are fulfilled (see [FORSGREN2002]).
Equation (3.21) is solved by performing a Newton method in order to find a solution of (3.20) for each of the decreas-
ing values of 𝜇. Some order 2 conditions are also taken into account to avoid convergence to local maximizers, see
[FORSGREN2002] for details about them. In the most classic IP algorithms, the Newton method is directly applied to
(3.20). This is in most case inefficient due to frequent computation of infeasible points. These difficulties are avoided
in Primal-Dual interior point methods where (3.20) is transformed into an extended system where 𝑧𝑢 and 𝑧𝑙 are treated
as unknowns and the barrier problems are finding (𝑥, 𝜆, 𝑧𝑢 , 𝑧𝑙 ) ∈ R𝑛 × R𝑚 × R𝑛 × R𝑛 such that:
⎧ 𝑇
⎪ ∇𝑓 (𝑥) + 𝐽𝑐 (𝑥) 𝜆 + 𝑧𝑢 − 𝑧𝑙 = 0
⎪
𝑐(𝑥) = 0
⎨
(3.22)
⎪
⎪ (𝑋 𝑢 − 𝑋)𝑧 𝑢 − 𝜇𝑒 = 0
(𝑋 − 𝑋𝑙 )𝑧𝑙 − 𝜇𝑒 = 0
⎩
Where if 𝑎 is a vector of R𝑛 , 𝐴 denotes the diagonal matrix 𝐴 = (𝑎𝑖 𝛿𝑖𝑗 )1≤𝑖,𝑗≤𝑛 and 𝑒 ∈ R𝑛 = (1, 1, . . . , 1). Solving
this nonlinear system by the Newton method is known as being the primal-dual interior point method. Here again,
more details are available in [FORSGREN2002]. Most actual implementations introduce features in order to globalize
the convergence capability of the method, essentially by adding some line-search steps to the Newton algorithm, or by
using trust regions. For the purpose of IPOPT, this is achieved by a filter line search methods, the details of which can
be found in [WÄCHTER2006].
More IPOPT specific features or implementation details can be found in [WÄCHTER2006]. We will just retain that
IPOPT is a smart Newton method for solving constrained optimization problems, with global convergence capabilities
due to a robust line search method (in the sense that the algorithm will converge no matter the initializer). Due to the
underlying Newton method, the optimization process requires expressions of all derivatives up to the order 2 of the
fitness function as well as those of the constraints. For problems whose Hessian matrices are difficult to compute or
lead to high dimensional dense matrices, it is possible to use a BFGS approximation of these objects at the cost of a
much slower convergence rate.
IPOPT in FreeFEM
Calling the IPOPT optimizer in a FreeFEM script is done with the IPOPT function included in the ff-Ipopt dynamic
library. IPOPT is designed to solve constrained minimization problems in the form:
find𝑥0 = argmin𝑓 (𝑥)
𝑥∈R𝑛
∀𝑖 ≤ 𝑛, 𝑥lb ub
{︂
s.t. 𝑖 ≤ 𝑥𝑖 ≤ 𝑥𝑖 (simple bounds)
∀𝑖 ≤ 𝑚, 𝑐𝑖 ≤ 𝑐𝑖 (𝑥) ≤ 𝑐ub
lb
𝑖 (constraints functions)
Where ub and lb stand for “upper bound” and “lower bound”. If for some 𝑖, 1 ≤ 𝑖 ≤ 𝑚 we have 𝑐lb
𝑖 = 𝑐𝑖 , it means
ub
There are different ways to pass the fitness function and constraints. The more general one is to define the functions
using the keyword func. Any returned matrix must be a sparse one (type matrix, not a real[int,int]):
Warning: In the current version of FreeFEM, returning a matrix object that is local to a function block leads to
undefined results. For each sparse matrix returning function you define, an extern matrix object has to be declared,
whose associated function will overwrite and return on each call. Here is an example for jacC:
1 matrix jacCBuffer; //just declare, no need to define yet
2 func matrix jacC (real[int] &X){
3 ...//fill jacCBuffer
4 return jacCBuffer;
5 }
Warning: IPOPT requires the structure of each matrix at the initialization of the algorithm. Some errors may occur
if the matrices are not constant and are built with the matrix A = [I,J,C] syntax, or with an intermediary full
matrix (real[int,int]), because any null coefficient is discarded during the construction of the sparse matrix.
It is also the case when making matrices linear combinations, for which any zero coefficient will result in the
suppression of the matrix from the combination. Some controls are available to avoid such problems. Check the
named parameter descriptions (checkindex, structhess and structjac can help). We strongly advice to use
varf as much as possible for the matrix forging.
The Hessian returning function is somewhat different because it has to be the Hessian of the Lagrangian function:
𝑚
∑︁
2
(𝑥, 𝜎𝑓 , 𝜆) ↦→ 𝜎𝑓 ∇ 𝑓 (𝑥) + 𝜆𝑖 ∇2 𝑐𝑖 (𝑥) where 𝜆 ∈ R𝑚 and 𝜎 ∈ R
𝑖=1
If the constraints functions are all affine, or if there are only simple bound constraints, or no constraint at all, the
Lagrangian Hessian is equal to the fitness function Hessian, one can then omit the sigma and lambda parameters:
1 matrix hessianJBuffer;
2 func matrix hessianJ (real[int] &X){...} //Hessian prototype when constraints are affine
If the Hessian is omitted, the interface will tell IPOPT to use the (L)BFGS approximation (it can also be enabled with
a named parameter, see further). Simple bound or unconstrained problems do not require the constraints part, so the
following expressions are valid:
Simple bounds are passed using the lb and ub named parameters, while constraint bounds are passed with the clb
and cub ones. Unboundedness in some directions can be achieved by using the 1𝑒19 and −1𝑒19 values that IPOPT
recognizes as +∞ and −∞:
P2 fitness function and affine constraints function : In the case where the fitness function or constraints function
can be expressed respectively in the following forms:
where 𝐴 and 𝑏 are constant, it is possible to directly pass the (𝐴, 𝑏) pair instead of defining 3 (or 2) functions. It also
indicates to IPOPT that some objects are constant and that they have to be evaluated only once, thus avoiding multiple
copies of the same matrix. The syntax is:
Note that if you define the constraints in this way, they don’t contribute to the Hessian, so the Hessian should only take
one real[int] as an argument.
If both objective and constraint functions are given this way, it automatically activates the IPOPT
mehrotra_algorithm option (better for linear and quadratic programming according to the documentation).
Otherwise, this option can only be set through the option file (see the named parameters section).
A false case is the one of defining 𝑓 in this manner while using standard functions for the constraints:
Indeed, when passing [A, b] in order to define 𝑓 , the Lagrangian Hessian is automatically built and has the constant
𝑥 ↦→ 𝐴 function, with no way to add possible constraint contributions, leading to incorrect second order derivatives.
So, a problem should be defined like that in only two cases:
1. constraints are nonlinear but you want to use the BFGS mode (then add bfgs=1 to the named parameter),
2. constraints are affine, but in this case, compatible to pass in the same way
Here are some other valid definitions of the problem (cases when 𝑓 is a pure quadratic or linear form, or 𝐶 a pure linear
function, etc. . . ):
Returned Value : The IPOPT function returns an error code of type int. A zero value is obtained when the algorithm
succeeds and positive values reflect the fact that IPOPT encounters minor troubles. Negative values reveal more prob-
lematic cases. The associated IPOPT return tags are listed in the table below. The IPOPT pdf documentation provides
a more accurate description of these return statuses:
Success Failures
0 Solve_Succeeded
1 Solved_To_Acceptable_Level -1 Maximum_Iterations_Exceeded
2 Infeasible_Problem_Detected -2 Restoration_Failed
3 Search_Direction_Becomes_Too_Small -3 Error_In_Step_Computation
4 Diverging_Iterates -4 Maximum_CpuTime_Exceeded
5 User_Requested_Stop
6 Feasible_Point_Found
Named Parameters : The available named parameters in this interface are those we thought to be the most subject
to variations from one optimization to another, plus a few that are interface specific. Though, as one could see at
IPOPT Linear solver, there are many parameters that can be changed within IPOPT, affecting the algorithm behavior.
These parameters can still be controlled by placing an option file in the execution directory. Note that IPOPT’s pdf
documentation may provides more information than the previously mentioned online version for certain parameters.
The in-script available parameters are:
• lb, ub : real[int] for lower and upper simple bounds upon the search variables must be of size 𝑛 (search
space dimension). If two components of the same index in these arrays are equal then the corresponding search
variable is fixed. By default IPOPT will remove any fixed variable from the optimization process and always use
the fixed value when calling functions. It can be changed using the fixedvar parameter.
• clb, cub : real[int] of size 𝑚 (number of constraints) for lower and upper constraints bounds. Equality
between two components of the same index 𝑖 in clb and cub reflect an equality constraint.
• structjacc : To pass the greatest possible structure (indexes of non null coefficients) of the constraint Jacobians
under the form [I,J] where I and J are two integer arrays. If not defined, the structure of the constraint
Jacobians, evaluated in Xi, is used (no issue if the Jacobian is constant or always defined with the same varf,
hazardous if it is with a triplet array or if a full matrix is involved).
• structhess : Same as above but for the Hessian function (unused if 𝑓 is P2 or less and constraints are affine).
Here again, keep in mind that it is the Hessian of the Lagrangian function (which is equal to the Hessian of 𝑓
only if constraints are affine). If no structure is given with this parameter, the Lagrangian Hessian is evaluated on
the starting point, with 𝜎 = 1 and 𝜆 = (1, 1, . . . , 1) (it is safe if all the constraints and fitness function Hessians
are constant or build with varf, and here again it is less reliable if built with a triplet array or a full matrix).
• checkindex : A bool that triggers a dichotomic index search when matrices are copied from FreeFEM func-
tions to IPOPT arrays. It is used to avoid wrong index matching when some null coefficients are removed from
the matrices by FreeFEM. It will not solve the problems arising when a too small structure has been given at the
initialization of the algorithm. Enabled by default (except in cases where all matrices are obviously constant).
• warmstart : If set to true, the constraints dual variables 𝜆, and simple bound dual variables are initialized with
the values of the arrays passed to lm, lz and uz named parameters (see below).
• lm : real[int] of size 𝑚, which is used to get the final values of the constraints dual variables 𝜆 and/or initialize
them in case of a warm start (the passed array is also updated to the last dual variables values at the end of the
algorithm).
• lz, uz : real[int] of size 𝑛 to get the final values and/or initialize (in case of a warm start) the dual variables
associated to simple bounds.
• tol : real, convergence tolerance for the algorithm, the default value is 10−8 .
• maxiter : int, maximum number of iterations with 3000 as default value.
• maxcputime : real value, maximum runtime duration. Default is 106 (almost 11 and a halfdays).
• bfgs : bool enabling or not the (low-storage) BFGS approximation of the Lagrangian Hessian. It is set to false
by default, unless there is no way to compute the Hessian with the functions that have been passed to IPOPT.
• derivativetest : Used to perform a comparison of the derivatives given to IPOPT with finite differences
computation. The possible string values are : "none" (default), "first-order", "second-order" and
"only-second-order". The associated derivative error tolerance can be changed via the option file. One
should not care about any error given by it before having tried, and failed, to perform a first optimization.
• dth : Perturbation parameter for the derivative test computations with finite differences. Set by default to 10−8 .
• dttol : Tolerance value for the derivative test error detection (default value unknown yet, maybe 10−5 ).
• optfile : string parameter to specify the IPOPT option file name. IPOPT will look for a ipopt.opt file by
default. Options set in the file will overwrite those defined in the FreeFEM script.
• printlevel : An int to control IPOPT output print level, set to 5 by default, the possible values are from 0 to
12. A description of the output information is available in the PDF documentation of IPOPT.
• fixedvar : string for the definition of simple bound equality constraints treatment : use "make_parameter"
(default value) to simply remove them from the optimization process (the functions will always be evalu-
ated with the fixed value for those variables), "make_constraint" to treat them as any other constraint or
"relax_bounds" to relax fixing bound constraints.
• mustrategy : a string to choose the update strategy for the barrier parameter 𝜇. The two possible tags are
"monotone", to use the monotone (Fiacco-McCormick) strategy, or "adaptive" (default setting).
• muinit : real positive value for the barrier parameter initialization. It is only relevant when mustrategy has
been set to monotone.
• pivtol : real value to set the pivot tolerance for the linear solver. A smaller number pivots for sparsity, a larger
number pivots for stability. The value has to be in the [0, 1] interval and is set to 10−6 by default.
• brf : Bound relax factor: before starting the optimization, the bounds given by the user are relaxed. This option
sets the factor for this relaxation. If it is set to zero, then the bound relaxation is disabled. This real has to be
positive and its default value is 10−8 .
• objvalue : An identifier to a real type variable to get the last value of the objective function (best value in case
of success).
• mumin : minimum value for the barrier parameter 𝜇, a real with 10−11 as default value.
• linesearch : A boolean which disables the line search when set to false. The line search is activated by
default. When disabled, the method becomes a standard Newton algorithm instead of a primal-dual system. The
global convergence is then no longer assured, meaning that many initializers could lead to diverging iterates. But
on the other hand, it can be useful when trying to catch a precise local minimum without having some out of
control process making the iterate caught by some other near optimum.
1 // Solve
2 //- Delta u = f
3 //u < g
4 //u = 0 on Gamma
5 load "ff-Ipopt";
6
7 // Parameters
8 int nn = 20;
9 func f = 1.; //rhs function
10 real r = 0.03, s = 0.1;
11 func g = r - r/2*exp(-0.5*(square(x-0.5) + square(y-0.5))/square(s));
12
13 // Mesh
14 mesh Th = square(nn, nn);
15
16 // Fespace
17 fespace Vh(Th, P2);
18 Vh u = 0;
19 Vh lb = -1.e19;
20 Vh ub = g;
21
22 // Macro
23 macro Grad(u) [dx(u), dy(u)] //
24
25 // Problem
26 varf vP (u, v)
27 = int2d(Th)(
28 Grad(u)'*Grad(v)
29 )
30 - int2d(Th)(
31 f*v
32 )
33 ;
Here we build the matrix and second member associated to the function to fully and finally minimize it. The [A,b]
syntax for the fitness function is then used to pass it to IPOPT.
We use simple bounds to impose the boundary condition 𝑢 = 0 on 𝜕Ω, as well as the 𝑢 ≤ 𝑔 condition.
8 // Solve
9 IPOPT([A, b], u[], lb=lb[], ub=ub[]);
10
11 // Plot
12 plot(u);
The problem entails finding (numerically) two functions (𝑢1 , 𝑢2 ) = argmin 𝐽(𝑣1 , 𝑣2 ).
(𝑣1 ,𝑣2 )∈𝑉
1 load "ff-Ipopt";
2
3 // Parameters
4 int nn = 10;
5 func f1 = 10;//right hand side
6 func f2 = -15;
7 func g1 = -0.1;//Boundary condition functions
8 func g2 = 0.1;
9
10 // Mesh
11 mesh Th = square(nn, nn);
12
13 // Fespace
14 fespace Vh(Th, [P1, P1]);
15 Vh [uz, uz2] = [1, 1];
16 Vh [lz, lz2] = [1, 1];
17 Vh [u1, u2] = [0, 0]; //starting point
18
22 // Macro
23 macro Grad(u) [dx(u), dy(u)] //
24
25 // Loop
(continues on next page)
49 //Boundary conditions
50 varf vGamma ([u1, u2], [v1, v2]) = on(1, 2, 3, 4, u1=1, u2=1);
51 real[int] onGamma = vGamma(0, Vh);
52 Vh [ub1, ub2] = [g1, g2];
53 Vh [lb1, lb2] = [g1, g2];
54 ub1[] = onGamma ? ub1[] : 1e19; //Unbounded in interior
55 lb1[] = onGamma ? lb1[] : -1e19;
56
61 // Solve
62 IPOPT([b, A], CC, ui1[], lb=lb1[], clb=cl[], ub=ub1[], warmstart=iter>1, uz=uzi[],␣
˓→lz=lzi[], lm=lmi[]);
63
64 // Plot
65 plot(ui1, ui2, wait=true, nbiso=60, dim=3);
66
69 // Mesh adpatation
70 Th = adaptmesh(Th, [ui1, ui2], err=0.004, nbvx=100000);
71 [uz, uz2] = [uzi, uzi2];
72 [lz, lz2] = [lzi, lzi2];
73 [u1, u2] = [ui1, ui2];
74 lm = lmi;
75 }
This example is aimed at numerically solving some constrained minimum surface problems with the IPOPT algorithm.
We restrain to 𝐶 𝑘 (𝑘 ≥ 1), closed, spherically parametrizable surfaces, i.e. surfaces 𝑆 such that:
⎧ ⎛ ⎞ ⎫
⎨ 𝜌(𝜃, 𝜑) ⎬
∃𝜌 ∈ 𝐶 𝑘 ([0, 2𝜋] × [0, 𝜋])|𝑆 = 𝑋 = ⎝ 0 ⎠ , (𝜃, 𝜑) ∈ [0, 2𝜋] × [0, 𝜋]
0
⎩ ⎭
Where the components are expressed in the spherical coordinate system. Let’s call Ω the [0, 2𝜋] × [0, 𝜋] angular
parameters set. In order to exclude self crossing and opened shapes, the following assumptions upon 𝜌 are made:
For a given function 𝜌 the first fundamental form (the metric) of the defined surface has the following matrix represen-
tation:
(︂ 2 2
𝜌 sin (𝜑) + (𝜕𝜃 𝜌)2
)︂
𝜕𝜃 𝜌𝜕𝜑 𝜌
𝐺= (3.23)
𝜕𝜃 𝜌𝜕𝜑 𝜌 𝜌2 + (𝜕𝜑 𝜌)2
This metric is used to express the area of the surface. Let 𝑔 = det(𝐺), then we have:
∫︀ ∫︀ √
𝒜(𝜌) = Ω‖𝜕 𝜃 𝑋 ∧ 𝜕𝜑 𝑋‖ = Ω 𝑔
√︁ (3.24)
= Ω 𝜌2 (𝜕𝜃 𝜌)2 + 𝜌4 sin2 (𝜑) + 𝜌2 (𝜕𝜑 𝜌)2 sin2 (𝜑)𝑑𝜃𝑑𝜑
∫︀
The volume of the space enclosed within the shape is easier to express:
∫︁ ∫︁ 𝜌(𝜃,𝜑) ∫︁
1
𝒱(𝜌) = Ω 𝑟2 sin(𝜑)𝑑𝑟𝑑𝜃𝑑𝜑 = Ω𝜌3 sin(𝜑)𝑑𝜃𝑑𝜑 (3.25)
0 3
Derivatives
In order to use a Newton based interior point optimization algorithm, one must be able to evaluate the derivatives of 𝒜
and 𝒱 with respect to 𝑟ℎ𝑜. Concerning the area, we have the following result:
∫︁
1 𝑑¯
𝑔 (𝜌)(𝑣)
∀𝑣 ∈ 𝐶 1 (Ω) , ⟨𝑑𝒜(𝜌), 𝑣⟩ = Ω √ 𝑑𝜃𝑑𝜑
2 𝑔
Where 𝑔¯ is the application mapping the (𝜃, 𝜑) ↦→ 𝑔(𝜃, 𝜑) scalar field to 𝜌. This leads to the following expression, easy
to transpose in a freefem script using:
∀𝑣 ∈ 𝐶 1 (Ω) ∫︀ (︀ 3 2 2
2 2
(3.26)
)︀
⟨𝑑𝒜(𝜌), 𝑣⟩ = Ω 2𝜌 sin (𝜑) + 𝜌(𝜕 𝜃 𝜌) + 𝜌(𝜕𝜑 𝜌) sin (𝜑) 𝑣
2 2 2
∫︀
+ Ω 𝜌 𝜕𝜃 𝜌𝜕𝜃 𝑣 + 𝜌 𝜕𝜑 𝜌 sin (𝜑)𝜕𝜑 𝑣
With a similar approach, one can derive an expression for second order derivatives. However, comporting no specific
difficulties, the detailed calculus are tedious, the result is that these derivatives can be written using a 3 × 3 matrix B
whose coefficients are expressed in term of 𝜌 and its derivatives with respect to 𝜃 and 𝜑, such that:
⎛ ⎞
∫︁ 𝑣
∀(𝑤, 𝑣) ∈ 𝐶 1 (Ω) , 𝑑2 𝒜(𝜌)(𝑤, 𝑣) = Ω 𝑤 𝜕𝜃 𝑤 𝜕𝜑 𝑤 B ⎝ 𝜕𝜃 𝑣 ⎠ 𝑑𝜃𝑑𝜑 (3.27)
(︀ )︀
𝜕𝜑 𝑣
Deriving the volume function derivatives is again an easier task. We immediately get the following expressions:
The whole code is available in IPOPT minimal surface & volume example. We propose to solve the following problem:
Tip: Given a positive function 𝜌object piecewise continuous, and a scalar 𝒱max > 𝒱(𝜌object ), find 𝜌0 such that:
If 𝜌object is the spherical parametrization of the surface of a 3-dimensional object (domain) 𝒪, it can be interpreted
as finding the surface with minimum area enclosing the object with a given maximum volume. If 𝒱max is close to
𝒱(𝜌object ), so should be 𝜌0 and 𝜌object . With higher values of 𝒱max , 𝜌 should be closer to the unconstrained minimum
surface surrounding 𝒪 which is obtained as soon as 𝒱max ≥ 43 𝜋‖𝜌object ‖3∞ (sufficient but not necessary).
It also could be interesting to solve the same problem with the constraint 𝒱(𝜌0 ) ≥ 𝒱min which leads to a sphere when
𝒱min ≥ 16 𝜋diam(𝒪)3 and moves toward the solution of the unconstrained problem as 𝒱min decreases.
We start by meshing the domain [0, 2𝜋] × [0, 𝜋], then a periodic P1 finite elements space is defined.
1 load "msh3";
2 load "medit";
3 load "ff-Ipopt";
4
5 // Parameters
6 int nadapt = 3;
7 real alpha = 0.9;
8 int np = 30;
(continues on next page)
19 // Mesh
20 mesh Th = square(2*np, np, [2*pi*x, pi*y]);
21
22 // Fespace
23 fespace Vh(Th, P1, periodic=[[2, y], [4, y]]);
24 //Initial shape definition
25 //outside of the mesh adaptation loop to initialize with the previous optimial shape␣
˓→found on further iterations
26 Vh startshape = 5;
We create some finite element functions whose underlying arrays will be used to store the values of dual variables
associated to all the constraints in order to reinitialize the algorithm with it in the case where we use mesh adaptation.
Doing so, the algorithm will almost restart at the accuracy level it reached before mesh adaptation, thus saving many
iterations.
1 Vh uz = 1., lz = 1.;
2 rreal[int] lm = [1];
Then, follows the mesh adaptation loop, and a rendering function, Plot3D, using 3D mesh to display the shape it is
passed with medit (the movemesh23 procedure often crashes when called with ragged shapes).
16 if(ffplot)
17 plot(Sphere);
18 else
19 medit(cmm, Sphere);
(continues on next page)
Here are the functions related to the area computation and its shape derivative, according to equations (3.24) and (3.26):
1 // Surface computation
2 //Maybe is it possible to use movemesh23 to have the surface function less complicated
3 //However, it would not simplify the gradient and the hessian
4 func real Area (real[int] &X){
5 Vh rho;
6 rho[] = X;
7 Vh rho2 = square(rho);
8 Vh rho4 = square(rho2);
9 real res = int2d(Th)(sqrt(rho4*sin2 + rho2*square(dx(rho)) +␣
˓→rho2*sin2*square(dy(rho))));
10 ++iter;
11 if(1)
12 plot(rho, value=true, fill=true, cmm="rho(theta,phi) on [0,2pi]x[0,pi] - S="+res,␣
˓→dim=3);
13 else
14 Plot3D(rho[], "shape_evolution", 1);
15 return res;
16 }
17
The function returning the hessian of the area for a given shape is a bit blurry, thus we won’t show here all of equation
(3.27) coefficients definition, they can be found in the edp file.
1 matrix hessianA;
2 func matrix HessianArea (real[int] &X){
3 Vh rho, rho2;
4 rho[] = X;
5 rho2 = square(rho);
6 Vh sqrtPsi, sqrtPsi3, C00, C01, C02, C11, C12, C22, A;
7 {
8 Vh C0, C1, C2;
9 Vh dxrho2 = dx(rho)*dx(rho), dyrho2 = dy(rho)*dy(rho);
10 sqrtPsi = sqrt( rho2*rho2*sin2 + rho2*dxrho2 + rho2*dyrho2*sin2);
11 sqrtPsi3 = (rho2*rho2*sin2 + rho2*dxrho2 + rho2*dyrho2*sin2)*sqrtPsi;
12 C0 = 2*rho2*rho*sin2 + rho*dxrho2 + rho*dyrho2*sin2;
13 C1 = rho2*dx(rho);
14 C2 = rho2*sin2*dy(rho);
15 C00 = square(C0);
16 C01 = C0*C1;
17 C02 = C0*C2;
18 C11 = square(C1);
19 C12 = C1*C2;
20 C22 = square(C2);
21 A = 6.*rho2*sin2 + dxrho2 + dyrho2*sin2;
22 }
23 varf d2Area (w, v)
24 =int2d(Th)(
25 1./sqrtPsi * (
26 A*w*v
27 + 2*rho*dx(rho)*dx(w)*v
28 + 2*rho*dx(rho)*w*dx(v)
29 + 2*rho*dy(rho)*sin2*dy(w)*v
30 + 2*rho*dy(rho)*sin2*w*dy(v)
31 + rho2*dx(w)*dx(v)
32 + rho2*sin2*dy(w)*dy(v)
33 )
34 + 1./sqrtPsi3 * (
35 C00*w*v
36 + C01*dx(w)*v
37 + C01*w*dx(v)
38 + C02*dy(w)*v
39 + C02*w*dy(v)
40 + C11*dx(w)*dx(v)
41 + C12*dx(w)*dy(v)
42 + C12*dy(w)*dx(v)
43 + C22*dy(w)*dy(v)
44 )
45 )
46 ;
47 hessianA = d2Area(Vh, Vh);
48 return hessianA;
49 }
18 matrix hessianV;
19 func matrix HessianVolume(real[int] &X){
20 Vh rho;
21 rho[] = X;
22 varf d2Volume(w, v) = int2d(Th)(2*rho*sin(y)*v*w);
23 hessianV = d2Volume(Vh, Vh);
24 return hessianV;
25 }
If we want to use the volume as a constraint function we must wrap it and its derivatives in some FreeFEM functions
returning the appropriate types. It is not done in the above functions in cases where one wants to use it as a fitness
function. The lagrangian hessian also has to be wrapped since the Volume is not linear with respect to 𝜌, it has some
non-null second order derivatives.
1 func real[int] ipVolume (real[int] &X){ real[int] vol = [Volume(X)]; return vol; }
2 matrix mdV;
3 func matrix ipGradVolume (real[int] &X) { real[int,int] dvol(1,Vh.ndof); dvol(0,:) =␣
˓→GradVolume(X); mdV = dvol; return mdV; }
4 matrix HLagrangian;
5 func matrix ipHessianLag (real[int] &X, real objfact, real[int] &lambda){
6 HLagrangian = objfact*HessianArea(X) + lambda[0]*HessianVolume(X);
7 return HLagrangian;
8 }
The ipGradVolume function could pose some troubles during the optimization process because the gradient vector is
transformed in a sparse matrix, so any null coefficient will be discarded. Here we create the IPOPT structure manually
and use the checkindex named-parameter to avoid bad indexing during copies. This gradient is actually dense, there
is no reason for some components to be constantly zero:
These two arrays will be passed to IPOPT with structjacc=[gvi,gvj]. The last remaining things are the bound
definitions. The simple lower bound must be equal to the components of the P1 projection of 𝜌𝑜𝑏𝑗𝑒𝑐𝑡 . And we choose
𝛼 ∈ [0, 1] to set 𝒱max to (1 − 𝛼)𝒱(𝜌𝑜𝑏𝑗𝑒𝑐𝑡 ) + 𝛼 43 𝜋‖𝜌object ‖3∞ :
4 if(1){
5 lb = r0;
6 for (int q = 0; q < 5; ++q){
7 func f = rr*Gaussian(x, y, 2*q*pi/5., pi/3.);
8 func g = rr*Gaussian(x, y, 2*q*pi/5.+pi/5., 2.*pi/3.);
9 lb = max(max(lb, f), g);
10 }
11 lb = max(lb, rr*Gaussian(x, y, 2*pi, pi/3));
12 }
13 lb = max(lb, max(disc1, disc2));
14 real Vobj = Volume(lb[]);
15 real Vnvc = 4./3.*pi*pow(lb[].linfty,3);
16
17 if(1)
18 Plot3D(lb[], "object_inside", 1);
19 real[int] clb = 0., cub = [(1-alpha)*Vobj + alpha*Vnvc];
Calling IPOPT:
6 // Plot
7 Plot3D(rc[], "Shape_at_"+kkk, 1);
8 Plot3D(GradArea(rc[]), "ShapeGradient", 1);
Finally, before closing the mesh adaptation loop, we have to perform the said adaptation. The mesh is adaptated with
respect to the 𝑋 = (𝜌, 0, 0) (in spherical coordinates) vector field, not directly with respect to 𝜌, otherwise the true
curvature of the 3D-shape would not be well taken into account.
Here are some pictures of the resulting surfaces obtained for decreasing values of 𝛼 (and a slightly more complicated
object than two orthogonal discs). We return to the enclosed object when 𝛼 = 0:
The ff-NLopt package provides a FreeFEM interface to the free/open-source library for nonlinear optimization, eas-
ing the use of several different free optimization (constrained or not) routines available online along with the PDE
solver. All the algorithms are well documented in NLopt documentation, therefore no exhaustive information concern-
ing their mathematical specificities will be found here and we will focus on the way they are used in a FreeFEM script.
If needing detailed information about these algorithms, visit the website where a description of each of them is given,
as well as many bibliographical links.
Most of the gradient based algorithms of NLopt uses a full matrix approximation of the Hessian, so if you’re planning
to solve a large scale problem, use the IPOPT optimizer which definitely surpass them.
All the NLopt features are identified that way:
1 load "ff-NLopt"
2 //define J, u, and maybe grad(J), some constraints etc...
3 real min = nloptXXXXXX(J, u, //Unavoidable part
4 grad=<name of grad(J)>, //if needed
5 lb= //Lower bounds array
6 ub= //Upper bounds array
7 ... //Some optional arguments:
(continues on next page)
XXXXXX refers to the algorithm tag (not necessarily 6 characters long). u is the starting position (a real[int] type
array) which will be overwritten by the algorithm, the value at the end being the found 𝑎𝑟𝑔𝑚𝑖𝑛. And as usual, J is
a function taking a real[int] type array as argument and returning a real. grad, lb and ub are “half-optional”
arguments, in the sense that they are obligatory for some routines but not all.
The possible optionally named parameters are the following, note that they are not used by all algorithms (some do
not support constraints, or a type of constraints, some are gradient-based and others are derivative free, etc. . . ). One
can refer to the table after the parameters description to check which are the named parameters supported by a specific
algorithm. Using an unsupported parameter will not stop the compiler work, seldom breaks runtime, and will just
be ignored. When it is obvious you are missing a routine, you will get a warning message at runtime (for example if
you pass a gradient to a derivative free algorithm, or set the population of a non-genetic one, etc. . . ). In the following
description, 𝑛 stands for the dimension of the search space.
Half-optional parameters :
• grad= The name of the function which computes the gradient of the cost function (prototype should be
real[int] → real[int], both argument and result should have the size 𝑛). This is needed as soon as a
gradient-based method is involved, which is ignored if defined in a derivative free context.
• lb/ub = Lower and upper bounds arrays ( real[int] type) of size 𝑛. Used to define the bounds within which
the search variable is allowed to move. Needed for some algorithms, optional, or unsupported for others.
• subOpt : Only enabled for the Augmented Lagrangian and MLSL methods who need a sub-optimizer in order
to work. Just pass the tag of the desired local algorithm with a string.
Constraints related parameters (optional - unused if not specified):
• IConst/EConst : Allows to pass the name of a function implementing some inequality (resp. equality) con-
straints on the search space. The function type must be real[int] → real[int] where the size of the returned
array is equal to the number of constraints (of the same type - it means that all of the constraints are computed
in one vectorial function). In order to mix inequality and equality constraints in a same minimization attempt,
two vectorial functions have to be defined and passed. See example (3.29) for more details about how these
constraints have to be implemented.
• gradIConst/gradEConst : Use to provide the inequality (resp. equality) constraints gradient. These are
real[int] → real[int,int] type functions. Assuming we have defined a constraint function (either in-
equality or equality) with 𝑝 constraints, the size of the matrix returned by its associated gradient must be 𝑝 × 𝑛
(the 𝑖-th line of the matrix is the gradient of the 𝑖-th constraint). It is needed in a gradient-based context as soon
as an inequality or equality constraint function is passed to the optimizer and ignored in all other cases.
• tolIConst/tolEConst : Tolerance values for each constraint. This is an array of size equal to the number of
inequality (resp. equality) constraints. Default value is set to 10−12 for each constraint of any type.
Stopping criteria :
• stopFuncValue : Makes the algorithm end when the objective function reaches this real value.
• stopRelXTol : Stops the algorithm when the relative moves in each direction of the search space is smaller than
this real value.
• stopAbsXTol : Stops the algorithm when the moves in each direction of the search space is smaller than the
corresponding value in this real[int] array.
• stopRelFTol : Stops the algorithm when the relative variation of the objective function is smaller than this
real value.
• stopAbsFTol : Stops the algorithm when the variation of the objective function is smaller than this real value.
• stopMaxFEval : Stops the algorithm when the number of fitness evaluations reaches this integer value.
• stopTime : Stops the algorithm when the optimization time in seconds exceeds this real value. This is not a
strict maximum: the time may exceed it slightly, depending upon the algorithm and on how slow your function
evaluation is.
Note that when an AUGLAG or MLSL method is used, the meta-algorithm and the sub-algorithm may have
different termination criteria. Thus, for algorithms of this kind, the following named parameters has been defined
(just adding the SO prefix - for Sub-Optimizer) to set the ending condition of the sub-algorithm (the meta one
uses the ones above): SOStopFuncValue, SOStopRelXTol, and so on. . . If these are not used, the sub-optimizer
will use those of the master routine.
Other named parameters :
• popSize : integer used to change the size of the sample for stochastic search methods. Default value is a
peculiar heuristic to the chosen algorithm.
• SOPopSize : Same as above, but when the stochastic search is passed to a meta-algorithm.
• nGradStored : The number (integer type) of gradients to “remember” from previous optimization steps:
increasing this increases the memory requirements but may speed convergence. It is set to a heuristic value by
default. If used with AUGLAG or MLSL, it will only affect the given subsidiary algorithm.
The following table sums up the main characteristics of each algorithm, providing the more important information about
which features are supported by which algorithm and what are the unavoidable arguments they need. More details can
be found in NLopt documentation.
The problem consists in finding (numerically) two functions (𝑢1 , 𝑢2 ) = argmin 𝐽(𝑣1 , 𝑣2 ).
(𝑣1 ,𝑣2 )∈𝑉
This can be interpreted as finding 𝑢1 , 𝑢2 as close as possible (in a certain sense) to the solutions of the Laplace equation
with respectively 𝑓1 , 𝑓2 second members and 𝑔1 , 𝑔2 Dirichlet boundary conditions with the 𝑢1 ≤ 𝑢2 almost everywhere
constraint.
Here is the corresponding script to treat this variational inequality problem with one of the NLOpt algorithms.
1 //A brief script to demonstrate how to use the freefemm interfaced nlopt routines
2 //The problem consist in solving a simple variational inequality using one of the
3 //optimization algorithm of nlopt. We restart the algorithlm a few times after
4 //performing some mesh adaptation to get a more precise output
5
6 load "ff-NLopt"
7
20 // Mesh
21 mesh Th = square(NN, NN);
22
23 // Fespace
24 fespace Vh(Th, P1);
25 Vh oldu1, oldu2;
26
27 // Adaptation loop
28 for (int al = 0; al < nadapt; ++al){
29 varf BVF (v, w) = int2d(Th)(0.5*dx(v)*dx(w) + 0.5*dy(v)*dy(w));
30 varf LVF1 (v, w) = int2d(Th)(f1*w);
31 varf LVF2 (v, w) = int2d(Th)(f2*w);
32 matrix A = BVF(Vh, Vh);
33 real[int] b1 = LVF1(0, Vh), b2 = LVF2(0, Vh);
34
37 Vh In, Bord;
38 Bord[] = Vbord(0, Vh, tgv=1);
39 In[] = Bord[] ? 0:1;
40 Vh gh1 = Bord*g1, gh2 = Bord*g2;
41
53 return val;
54 }
55
126 up = 1000000;
127 lo = -1000000;
128 for (int i = 0; i < Vh.ndof; ++i){
129 if (Bord[][i]){
130 up[i] = gh1[][i] + bctol;
131 lo[i] = gh1[][i] - bctol;
132 up[i+Vh.ndof] = gh2[][i] + bctol;
133 lo[i+Vh.ndof] = gh2[][i] - bctol;
134 }
135 }
136
The only quick way to use the previously presented algorithms on a parallel architecture lies in parallelizing the used
cost function (which is in most real life cases, the expensive part of the algorithm). Somehow, we provide a parallel
version of the CMA-ES algorithm. The parallelization principle is the trivial one of evolving/genetic algorithms: at
each iteration the cost function has to be evaluated 𝑁 times without any dependence at all, these 𝑁 calculus are then
equally distributed to each process. Calling the MPI version of CMA-ES is nearly the same as calling its sequential
version (a complete example of use can be found in the CMAES MPI variational inequality example):
1 load "mpi-cmaes"
2 ... // Define J, u and all here
3 real min = cmaesMPI(J, u, stopTolFun=1e-6, stopMaxIter=3000);
4 cout << "minimum value is " << min << " for u = " << u << endl;
If the population size is not changed using the popsize parameter, it will use the heuristic value slightly changed
to be equal to the closest greatest multiple of the size of the communicator used by the optimizer. The FreeFEM
mpicommworld is used by default. The user can specify his own MPI communicator with the named parameter comm=,
see the MPI section of this manual for more information about communicators in FreeFEM.
3.6 Parallelization
A first attempt of parallelization of FreeFEM is made here with MPI. An extended interface with MPI has been added
to FreeFEM version 3.5, (see the MPI documentation for the functionality of the language).
3.6.1 MPI
MPI Keywords
MPI Constants
MPI Constructor
1 // Parameters
2 int[int] proc1 = [1, 2], proc2 = [0, 3];
3 int color = 1;
4 int key = 1;
5
6 // MPI ranks
7 cout << "MPI rank = " << mpirank << endl;
8
9 // MPI
10 mpiComm comm(mpiCommWorld, 0, 0); //set a MPI_Comm to MPI_COMM_WORLD
11
17 mpiComm ncomm2(comm, color, key); //MPI_Comm_split(MPI_Comm comm, int color, int key,␣
˓→MPI_Comm *ncomm)
18
MPI Functions
6 if (MPIRank == 0) cout << "MPI Comm size = " << MPICommSize << endl;
7 cout << "MPI rank in Comm = " << mpiRank(Comm) << endl;
8
9 mpiRequest Req;
10 mpiRequest[int] ReqArray(10);
11
28 processorblock(mpiAnySource);
29 //return processor i in in Comm in block mode
30 processorblock(i, Comm);
31 }
32
where a processor is just a integer rank, pointer to a MPI_comm and pointer to a MPI_Request, and processorblock
with a special MPI_Request.
where the data type of a can be of type of int, real, complex, int[int], real[int], complex[int],
int[int,int], double[int,int], complex[int,int], mesh, mesh3, mesh[int], mesh3[int], matrix,
matrix<complex>
If a, b are arrays or full matrices of int, real, or complex, we can use the following MPI functions:
1 mpiAlltoall(a, b, [comm]);
2 mpiAllgather(a, b, [comm]);
3 mpiGather(a, b, processor(..) );
4 mpiScatter(a, b, processor(..));
5 mpiReduce(a, b, processor(..), mpiMAX);
6 mpiAllReduce(a, b, comm, mpiMAX);
Thank you to Guy-Antoine Atenekeng Kahou for his help to code this interface.
1 if (mpisize != 2){
2 cout << " sorry, number of processors !=2 " << endl;
3 exit(1);
4 }
5
6 // Parameters
7 verbosity = 0;
8 int interior = 2;
9 int exterior = 1;
10 int n = 4;
11
12 // Mesh
13 border a(t=1, 2){x=t; y=0; label=exterior;}
14 border b(t=0, 1){x=2; y=t; label=exterior;}
15 border c(t=2, 0){x=t; y=1; label=exterior;}
16 border d(t=1, 0){x=1-t; y=t; label=interior;}
17 border e(t=0, pi/2){x=cos(t); y=sin(t); label=interior;}
18 border e1(t=pi/2, 2*pi){x=cos(t); y=sin(t); label=exterior;}
19 mesh[int] Th(mpisize);
20 if (mpirank == 0)
21 Th[0] = buildmesh(a(5*n) + b(5*n) + c(10*n) + d(5*n));
22 else
23 Th[1] = buildmesh(e(5*n) + e1(25*n));
24
25 broadcast(processor(0), Th[0]);
26 broadcast(processor(1), Th[1]);
27
28 // Fespace
(continues on next page)
35 //Problem
36 int i = 0;
37 problem pb (u, v, init=i, solver=Cholesky)
38 = int2d(Th[mpirank])(
39 dx(u)*dx(v)
40 + dy(u)*dy(v)
41 )
42 - int2d(Th[mpirank])(
43 v
44 )
45 + on(interior, u=U)
46 + on(exterior, u= 0 )
47 ;
48
49 // Loop
50 for (i = 0; i < 20; i++){
51 cout << mpirank << " - Loop " << i << endl;
52
53 // Solve
54 pb;
55 //send u to the other proc, receive in U
56 processor(1-mpirank) << u[];
57 processor(1-mpirank) >> U[];
58
59 // Error
60 real err0, err1;
61 err0 = int1d(Th[mpirank],interior)(square(U - u));
62 // send err0 to the other proc, receive in err1
63 processor(1-mpirank) << err0;
64 processor(1-mpirank) >> err1;
65 real err = sqrt(err0 + err1);
66 cout << " err = " << err << " - err0 = " << err0 << " - err1 = " << err1 << endl;
67 if (err < 1e-3) break;
68 }
69 if (mpirank == 0)
70 plot(u, U);
−∆𝑢 = 𝑓 in Ω
𝑢 = 𝑔 on Γ
1
where 𝑓 and 𝑔 are two given functions of 𝐿2 (Ω) and of 𝐻 2 (Γ),
Lets introduce (𝜋𝑖 )𝑖=1,..,𝑁𝑝 a regular partition of the unity of Ω, q-e-d:
𝑁𝑝
∑︁
𝜋𝑖 ∈ 𝒞 0 (Ω) : 𝜋𝑖 ≥ 0 and 𝜋𝑖 = 1.
𝑖=1
Denote Ω𝑖 the sub domain which is the support of 𝜋𝑖 function and also denote Γ𝑖 the boundary of Ω𝑖 .
The parallel Schwarz method is:
Let ℓ = 0 the iterator and a initial guest 𝑢0 respecting the boundary condition (i.e. 𝑢0|Γ = 𝑔).
∀𝑖 = 1.., 𝑁𝑝 :
−∆𝑢ℓ𝑖 = 𝑓 in Ω𝑖
𝑢ℓ𝑖 = 𝑢ℓ on Γ𝑖 ∖ Γ
𝑢ℓ𝑖 = 𝑔 on Γ𝑖 ∩ Γ
𝑁𝑝
(3.30)
∑︁
ℓ+1
𝑢 = 𝜋𝑖 𝑢ℓ𝑖
𝑖=1
After discretization with the Lagrange finite element method, with a compatible mesh 𝒯ℎ𝑖 of Ω𝑖 , i. e., the exist a global
mesh 𝒯ℎ such that 𝒯ℎ𝑖 is include in 𝒯ℎ .
Let us denote:
• 𝑉ℎ𝑖 the finite element space corresponding to domain Ω𝑖 ,
• 𝒩ℎ𝑖 is the set of the degree of freedom 𝜎𝑖𝑘 ,
Γ𝑖
• 𝒩ℎ𝑖 is the set of the degree of freedom of 𝑉ℎ𝑖 on the boundary Γ𝑖 of Ω𝑖 ,
• 𝜎𝑖𝑘 (𝑣ℎ ) is the value the degree of freedom 𝑘,
Γ𝑖
• 𝑉0ℎ𝑖 = {𝑣ℎ ∈ 𝑉ℎ𝑖 : ∀𝑘 ∈ 𝒩ℎ𝑖 , 𝜎𝑖𝑘 (𝑣ℎ ) = 0},
• The conditional expression 𝑎 ? 𝑏 : 𝑐 is defined like in :c`C` of C++ language by
Note: We never use finite element space associated to the full domain Ω because it is too expensive.
We denote 𝑢ℓℎ|𝑖 the restriction of 𝑢ℓℎ on 𝑉ℎ𝑖 , so the discrete problem on Ω𝑖 of problem (3.30) is find 𝑢ℓℎ𝑖 ∈ 𝑉ℎ𝑖 such
that:
∫︁ ∫︁
Γ𝑖
∀𝑣ℎ𝑖 ∈ 𝑉0𝑖 : ∇𝑣ℎ𝑖 · ∇𝑢ℎ ℓ𝑖 = 𝑓 𝑣ℎ𝑖 , ∀𝑘 ∈ 𝒩ℎ𝑖 : 𝜎𝑖𝑘 (𝑢ℎ ℓ𝑖 ) = (𝑘 ∈ Γ) ? 𝑔𝑖𝑘 : 𝜎𝑖𝑘 (𝑢ℎ ℓ|𝑖 )
Ω𝑖 Ω𝑖
Γ𝑖
where 𝑔𝑖𝑘 is the value of 𝑔 associated to the degree of freedom 𝑘 ∈ 𝒩ℎ𝑖 .
In FreeFEM, it can be written has with U is the vector corresponding to 𝑢ℓℎ|𝑖 and the vector U1 is the vector corre-
sponding to 𝑢ℓℎ𝑖 is the solution of:
1 real[int] U1(Ui.n);
2 real[int] b = onG .* U;
3 b = onG ? b : Bi ;
4 U1 = Ai^-1*b;
where onG[𝑖] = (𝑖 ∈ Γ𝑖 ∖ Γ)?1 : 0, and Bi the right of side of the problem, are defined by
1 // Fespace
2 fespace Whi(Thi, P2);
3
4 // Problem
5 varf vPb (U, V)
6 = int3d(Thi)(
7 grad(U)'*grad(V)
8 )
9 + int3d(Thi)(
10 F*V
11 )
12 + on(1, U=g)
13 + on(10, U=G)
14 ;
15
1 macro InitU(n, Vh, Th, aTh, U) Vh[int] U(n); for (int j = 0; j < n; ++j){Th = aTh[j];␣
˓→U[j] = 0;}
First GMRES algorithm: you can easily accelerate the fixed point algorithm by using a parallel GMRES algorithm
after the introduction the following affine 𝒜𝑖 operator sub domain Ω𝑖 .
Where the parallel MPIGMRES or MPICG algorithm is just a simple way to solve in parallel the following 𝐴𝑖 𝑥𝑖 = 𝑏𝑖 , 𝑖 =
1, .., 𝑁𝑝 by just changing the dot product by reduce the local dot product of all process with the following MPI code:
We have all ingredient to solve in parallel if we have et the partitions of the unity. To build this partition we do:
The initial step on process 1 to build a coarse mesh, 𝒯ℎ * of the full domain, and build the partition 𝜋 function constant
equal to 𝑖 on each sub domain 𝒪𝑖 , 𝑖 = 1, .., 𝑁𝑝 , of the grid with the metis graph partitioner [KARYPIS1995] and on
each process 𝑖 in 1.., 𝑁𝑝 do
1. Broadcast from process 1, the mesh 𝒯ℎ * (call Thii in FreeFEM script), and 𝜋 function,
2. remark that the characteristic function 1I𝒪𝑖 of domain 𝒪𝑖 , is defined by (𝜋 = 𝑖)?1 : 0,
3. Let us call Π2𝑃 (resp. Π2𝑉 ) the 𝐿2 on 𝑃ℎ* the space of the constant finite element function per element on 𝒯ℎ *
(resp. 𝑉ℎ* the space of the affine continuous finite element per element on 𝒯ℎ * ) and build in parallel the 𝜋𝑖
and Ω𝑖 , such that 𝒪𝑖 ⊂ Ω𝑖 where 𝒪𝑖 = 𝑠𝑢𝑝𝑝((Π2𝑉 Π2𝐶 )𝑚 1I𝑂𝑖 ), and 𝑚 is a the overlaps size on the coarse
mesh (generally one), (this is done in function AddLayers(Thii,suppii[],nlayer,phii[]); We choose a
function 𝜋𝑖* = (Π21 Π20 )𝑚 1I𝒪𝑖 so the partition of the unity is simply defined by
𝜋*
𝜋𝑖 = ∑︀𝑁𝑝𝑖
𝑗=1 𝜋𝑗*
The set 𝐽𝑖 of neighborhood of the domain Ω𝑖 , and the local version on 𝑉ℎ𝑖 can be defined the array
jpart and njpart with:
4. We call 𝒯ℎ *𝑖𝑗 the sub mesh part of 𝒯ℎ𝑖 where 𝜋𝑗 are none zero. And thanks to the function trunc to build this
array,
5. At this step we have all on the coarse mesh, so we can build the fine final mesh by splitting all meshes: Thi,
Thij[j], Thij[j] with FreeFEM trunc mesh function which do restriction and slipping.
6. The construction of the send/recv matrices sMj and freefem:`rMj: can done with this code:
To buil a not too bad application, all variables come from parameters value with the following code
1 include "getARGV.idp"
2 verbosity = getARGV("-vv", 0);
3 int vdebug = getARGV("-d", 1);
4 int ksplit = getARGV("-k", 10);
5 int nloc = getARGV("-n", 25);
6 string sff = getARGV("-p, ", "");
7 int gmres = getARGV("-gmres", 3);
8 bool dplot = getARGV("-dp", 0);
9 int nC = getARGV("-N", max(nloc/10, 4));
And small include to make graphic in parallel of distribute solution of vector 𝑢 on mesh 𝑇ℎ with the following interface:
1 include "MPIplot.idp"
2 func bool plotMPIall(mesh &Th, real[int] &u, string cm){
3 PLOTMPIALL(mesh, Pk, Th, u, {cmm=cm, nbiso=20, fill=1, dim=3, value=1});
4 return 1;
5 }
Note: The cmm=cm, ... in the macro argument is a way to quote macro argument so the argument is cmm=cm, ....
Parallel sparse solvers use several processors to solve linear systems of equation. Like sequential, parallel linear solvers
can be direct or iterative. In FreeFEM both are available.
We recall that the solver parameters are defined in the following commands: solve, problem, set (setting parameter
of a matrix) and in the construction of the matrix corresponding to a bilinear form. In these commands, the parameter
solver must be set to sparsesolver for parallel sparse solver. We have added specify parameters to these command
lines for parallel sparse solvers. These are:
• lparams : vector of integer parameters (l is for the C++ type long)
• dparams : vector of real parameters
• sparams : string parameters
• datafilename : name of the file which contains solver parameters
The following four parameters are only for direct solvers and are vectors. These parameters allow the user to preprocess
the matrix (see the section on sparse direct solver for more information).
• permr : row permutation (integer vector)
• permc : column permutation or inverse row permutation (integer vector)
• scaler : row scaling (real vector)
• scalec : column scaling (real vector)
There are two possibilities to control solver parameters. The first method defines parameters with lparams, dparams
and sparams in .edp file.
The second one reads the solver parameters from a data file. The name of this file is specified by datafilename. If
lparams, dparams, sparams or datafilename is not provided by the user, the solver’s default values are used.
To use parallel solver in FreeFEM, we need to load the dynamic library corresponding to this solver. For example to
use MUMPS solver as parallel solver in FreeFEM, write in the .edp file load "MUMPS_FreeFem".
If the libraries are not loaded, the default sparse solver will be loaded (default sparse solver is UMFPACK). The Table 3.2
gives this new value for the different libraries.
Table 3.2: Default sparse solver for real and complex arithmetics when we load a parallel sparse solver library
Libraries Default sparse solver
real complex
MUMPS_FreeFem mumps mumps
real_SuperLU_DIST_FreeFem SuperLU_DIST previous solver
complex_SuperLU_DIST_FreeFem previous solver SuperLU_DIST
real_pastix_FreeFem PaStiX previous solver
complex_pastix_FreeFem previous solver PaStiX
hips_FreeFem hips previous solver
hypre_FreeFem hypre previous solver
parms_FreeFem parms previous solver
We also add functions (see Table 3.3) with no parameter to change the default sparse solver in the .edp file. To use
these functions, we need to load the library corresponding to the solver. An example of using different parallel sparse
solvers for the same problem is given in Direct solvers example.
Table 3.3: Functions that allow to change the default sparse solver for real and complex arithmetics and the result of
these functions
Function default sparse solver
real complex
defaulttoMUMPS() mumps mumps
realdefaulttoSuperLUdist() SuperLU_DIST previous solver
complexdefaulttoSuperLUdist() previous solver SuperLU_DIST
realdefaultopastix() pastix previous solver
complexdefaulttopastix() previous solver pastix
defaulttohips() hips previous solver
defaulttohypre() hypre previous solver
defaulttoparms() parms previous solver
1 load "MUMPS_FreeFem"
2 //default solver: real-> MUMPS, complex -> MUMPS
3 load "real_SuperLU_DIST_FreeFem"
4 //default solver: real-> SuperLU_DIST,
5 complex -> MUMPS load "real_pastix_FreeFem"
6 //default solver: real-> pastix, complex -> MUMPS
7
In this section, we present the sparse direct solvers interfaced with FreeFEM.
MUMPS solver
Warning: MUMPS does not solve linear system with a rectangular matrix.
MUMPS parameters:
There are four input parameters in MUMPS. Two integers SYM and PAR, a vector of integer of size 40 INCTL and a
vector of real of size 15 CNTL.
The first parameter gives the type of the matrix: 0 for unsymmetric matrix, 1 for symmetric positive matrix and 2 for
general symmetric.
The second parameter defined if the host processor work during the factorization and solves steps : PAR=1 host processor
working and PAR=0 host processor not working.
The parameter INCTL and CNTL is the control parameter of MUMPS. The vectors ICNTL and CNTL in MUMPS becomes
with index 1 like vector in Fortran. For more details see the MUMPS user’s guide.
We describe now some elements of the main parameters of ICNTL for MUMPS.
• Input matrix parameter The input matrix is controlled by parameters ICNTL(5) and ICNTL(18).
The matrix format (resp. matrix pattern and matrix entries) are controlled by INCTL(5) (resp. INCTL(18)).
The different values of ICNTL(5) are 0 for assembled format and 1 for element format. In the current
release of FreeFEM, we consider that FE matrix or matrix is storage in assembled format. Therefore,
INCTL(5) is treated as 0 value.
The main option for ICNTL(18): INCLTL(18)=0 centrally on the host processor, ICNTL(18)=3 dis-
tributed the input matrix pattern and the entries (recommended option for distributed matrix by developer
of MUMPS). For other values of ICNTL(18) see the MUMPS user’s guide. These values can be used also
in FreeFEM.
The default option implemented in FreeFEM are ICNTL(5)=0 and ICNTL(18)=0.
• Preprocessing parameter The preprocessed matrix 𝐴𝑝 that will be effectively factored is defined by
𝐴𝑝 = 𝑃 𝐷𝑟 𝐴 𝑄𝑐 𝐷𝑐 𝑃 𝑡
where 𝑃 is the permutation matrix, 𝑄𝑐 is the column permutation, 𝐷𝑟 and 𝐷𝑐 are diagonal matrix
for respectively row and column scaling.
The ordering strategy to obtain 𝑃 is controlled by parameter ICNTL(7). The permutation of zero
free diagonal 𝑄𝑐 is controlled by parameter ICNTL(6). The row and column scaling is controlled by
parameter ICNTL(18). These option are connected and also strongly related with ICNTL(12) (see
the MUMPS user’s guide for more details).
The parameters permr, scaler, and scalec in FreeFEM allow to give permutation matrix(𝑃 ), row
scaling (𝐷𝑟 ) and column scaling (𝐷𝑐 ) of the user respectively.
Calling MUMPS in FreeFEM
To call MUMPS in FreeFEM, we need to load the dynamic library MUMPS_freefem.dylib (MacOSX),
MUMPS_freefem.so (Unix) or MUMPS_freefem.dll (Windows).
This is done in typing load "MUMPS_FreeFem" in the .edp file. We give now the two methods to give the option of
MUMPS solver in FreeFEM.
• Solver parameters is defined in .edp file: In this method, we need to give the parameters lparams and
dparams.
These parameters are defined for MUMPS by :
– lparams[0] = SYM, lparams[1] = PAR,
– ∀𝑖 = 1,. . . ,40, lparams[i+1] = ICNTL(i)
– ∀𝑖 = 1,. . . ,15, dparams[i-1] = CNTL(i)
• Reading solver parameters on a file:
The structure of data file for MUMPS in FreeFEM is : first line parameter SYM and second line
parameter PAR and in the following line the different value of vectors ICNTL and CNTL. An example
of this parameter file is given in ffmumpsfileparam.txt.
˓→release of FreeFEM */
˓→release of FreeFEM */
The package SuperLU_DIST solves linear systems using LU factorization. It is a free scientific library
This library provides functions to handle square or rectangular matrix in real and complex arithmetics. The method
implemented in SuperLU_DIST is a supernodal method. New release of this package includes a parallel symbolic
factorization. This scientific library is written in C and MPI for communications.
SuperLU_DIST parameters:
We describe now some parameters of SuperLU_DIST. The SuperLU_DIST library use a 2D-logical process group.
This process grid is specified by 𝑛𝑝𝑟𝑜𝑤 (process row) and 𝑛𝑝𝑐𝑜𝑙 (process column) such that 𝑁𝑝 = 𝑛𝑝𝑟𝑜𝑤 𝑛𝑝𝑐𝑜𝑙
where 𝑁𝑝 is the number of all process allocated for SuperLU_DIST.
The input matrix parameters is controlled by “matrix=” in sparams for internal parameter or in the third line of pa-
rameters file. The different value are
• matrix=assembled global matrix are available on all process
• matrix=distributedglobal The global matrix is distributed among all the process
• matrix=distributed The input matrix is distributed (not yet implemented)
The option arguments of SuperLU_DIST are described in the section Users-callable routine of the SuperLU users’
guide.
The parameter Fact and TRANS are specified in FreeFEM interfaces to SuperLU_DIST during the different steps. For
this reason, the value given by the user for this option is not considered.
The factorization LU is calculated in SuperLU_DIST on the matrix 𝐴𝑝 .
𝐴𝑝 = 𝑃𝑐 𝑃𝑟 𝐷𝑟 𝐴 𝐷𝑐 𝑃𝑐𝑡
where 𝑃𝑐 and 𝑃𝑟 is the row and column permutation matrix respectively, 𝐷𝑟 and 𝐷𝑐 are diagonal matrix for respectively
row and column scaling.
The option argument RowPerm (resp. ColPerm) control the row (resp. column) permutation matrix. 𝐷𝑟 and 𝐷𝑐 is
controlled by the parameter DiagScale.
The parameter permr, permc, scaler, and scalec in FreeFEM is provided to give row permutation, column permu-
tation, row scaling and column scaling of the user respectively.
The other parameters for LU factorization are ParSymFact and ReplaceTinyPivot. The parallel symbolic factor-
ization works only on a power of two processes and need the ParMetis ordering. The default option argument of
SuperLU_DIST are given in the file ffsuperlu_dist_fileparam.txt.
Calling SuperLU_DIST in FreeFEM
To call SuperLU_DIST in FreeFEM, we need to load the library dynamic correspond to interface. This done by the
following line load "real_superlu _DIST_FreeFem" (resp. load "complex_superlu_DIST_FreeFem") for
real (resp. complex) arithmetics in the file .edp.
Solver parameters is defined in .edp file:
To call SuperLU_DIST with internal parameter, we used the parameters sparams. The value of parameters of Su-
perLU_DIST in sparams are defined by :
• nprow=1,
• npcol=1,
• matrix= distributedgloba,
• Fact= DOFACT,
• Equil=NO,
• ParSymbFact=NO,
• ColPerm= MMD_AT_PLUS_A,
• RowPerm= LargeDiag,
• DiagPivotThresh=1.0,
• IterRefine=DOUBLE,
• Trans=NOTRANS,
• ReplaceTinyPivot=NO,
• SolveInitialized=NO,
• PrintStat=NO,
• DiagScale=NOEQUIL
This value correspond to the parameter in the file ffsuperlu_dist_fileparam.txt. If one parameter is not specified
by the user, we take the default value of SuperLU_DIST.
Reading solver parameters on a file: The structure of data file for SuperLU_DIST in FreeFEM is given in the file
ffsuperlu_dist_fileparam.txt (default value of the FreeFEM interface).
Tip: A simple example of calling SuperLU_DIST in FreeFEM with this two methods is given in the Solver su-
perLU_DIST example.
PaStiX solver
PaStiX (Parallel Sparse matrix package) is a free scientific library under CECILL-C license. This package solves sparse
linear system with a direct and block ILU(k) iterative methods. his solver can be applied to a real or complex matrix
with a symmetric pattern.
PaStiX parameters:
The input matrix parameter of FreeFEM depend on PaStiX interface. matrix = assembled for non distributed
matrix. It is the same parameter for SuperLU_DIST.
There are four parameters in PaStiX : iparm, dparm, perm and invp. These parameters are respectively the integer
parameters (vector of size 64), real parameters (vector of size 64), permutation matrix and inverse permutation matrix
respectively. iparm and dparm vectors are described in PaStiX RefCard.
The parameters permr and permc in FreeFEM are provided to give permutation matrix and inverse permutation matrix
of the user respectively.
Solver parameters defined in .edp file:
To call PaStiX in FreeFEM in this case, we need to specify the parameters lparams and dparams. These parameters
are defined by :
∀𝑖 = 0,. . . ,63, lparams[i] = iparm[i].
∀𝑖 = 0,. . . ,63, dparams[i] = dparm[i].
Reading solver parameters on a file:
The structure of data file for PaStiX parameters in FreeFEM is: first line structure parameters of the matrix and in the
following line the value of vectors iparm and dparm in this order.
An example of this file parameter is given in ffpastix_iparm_dparm.txt with a description of these parameters.
This file is obtained with the example file iparm.txt and dparm.txt including in the PaStiX package.
If no solver parameter is given, we use the default option of PaStiX solver.
Tip: A simple example of calling PaStiX in FreeFEM with this two methods is given in the Solver PaStiX example.
In Table 3.4, we recall the different matrix considering in the different direct solvers.
Table 3.4: Type of matrix used by the different direct sparse solver
direct solver square matrix rectangular matrix
sym sym pattern unsym sym sym pattern unsym
SuperLU_DIST yes yes yes yes yes yes
MUMPS yes yes yes no no no
Pastix yes yes no no no no
pARMS solver
pARMS (parallel Algebraic Multilevel Solver) is a software developed by Youssef Saad and al at University of Min-
nesota.
This software is specialized in the resolution of large sparse non symmetric linear systems of equation. Solvers devel-
oped in pARMS are of type “Krylov’s subspace”.
It consists of variants of GMRES like FGMRES (Flexible GMRES), DGMRES (Deflated GMRES) [SAAD2003] and
BICGSTAB. pARMS also implements parallel preconditioner like RAS (Restricted Additive Schwarz) [CAI1989] and
Schur Complement type preconditioner.
All these parallel preconditioners are based on the principle of domain decomposition. Thus, the matrix 𝐴 is partitioned
into sub matrices 𝐴𝑖 (𝑖 = 1, ..., 𝑝) where p represents the number of partitions one needs. The union of 𝐴𝑖 forms the
original matrix. The solution of the overall system is obtained by solving the local systems on 𝐴𝑖 (see [SMITH1996]).
Therefore, a distinction is made between iterations on 𝐴 and the local iterations on 𝐴𝑖 .
To solve the local problem on 𝐴𝑖 there are several preconditioners as ilut (Incomplete LU with threshold), iluk (In-
complete LU with level of fill in) and ARMS (Algebraic Recursive Multilevel Solver).
3 // Mesh
4 border C(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
5 mesh Th = buildmesh (C(50));
6
7 // Fespace
8 fespace Vh(Th, P2); Vh u, v;
9
10 // Function
11 func f= x*y;
12
13 // Problem
14 problem Poisson (u, v, solver=sparsesolver)
15 = int2d(Th)(
16 dx(u)*dx(v)
17 + dy(u)*dy(v) )
18 + int2d(Th)(
19 - f*v
20 )
21 + on(1, u=0) ;
22
23 // Solve
24 real cpu = clock();
25 Poisson;
26 cout << " CPU time = " << clock()-cpu << endl;
27
28 // Plot
29 plot(u);
In line 1, the pARMS dynamic library is loaded with interface FreeFEM. After this, in line 15 we specify that the
bilinear form will be solved by the last sparse linear solver load in memory which, in this case, is pARMS.
The parameters used in pARMS in this case are the default one since the user does not have to provide any parameter.
Note: In order to see the plot of a parallel script, run the command FreeFem++-mpi -glut ffglut script.edp
To specify the parameters to apply to the solver, the user can either give an integer vector for integer parameters and
real vectors for real parameters or provide a file which contains those parameters.
1 load "parms_FreeFem"
2
3 // Parameters
4 real nu = 1.;
5 int[int] iparm(16);
6 real[int] dparm(6);
7 for (int ii = 0; ii < 16; ii++)
8 iparm[ii] = -1;
9 for (int ii = 0; ii < 6; ii++)
10 dparm[ii] = -1.0; iparm[0]=0;
11
12 // Mesh
13 mesh Th = square(10, 10);
14 int[int] wall = [1, 3];
15 int inlet = 4;
16
17 // Fespace
18 fespace Vh(Th, [P2, P2, P1]);
19
20 // Function
21 func uc = 1.;
22
23 // Problem
24 varf Stokes ([u, v, p], [ush, vsh, psh], solver=sparsesolver)
25 = int2d(Th)(
26 nu*(
27 dx(u)*dx(ush)
28 + dy(u)*dy(ush)
29 + dx(v)*dx(vsh)
30 + dy(v)*dy(vsh)
31 )
32 - p*psh*(1.e-6)
33 - p*(dx(ush) + dy(vsh))
34 - (dx(u) + dy(v))*psh
35 )
36 + on(wall, wall, u=0., v=0.)
37 + on(inlet, u=uc, v=0) ;
38
We need two vectors to specify the parameters of the linear solver. In line 5-6 of the example, we have declared these
vectors(int[int] iparm(16); real[int] dparm(6);). In line 7-10 we have initialized these vectors by negative
values.
We do this because all parameters values in pARMS are positive and if you do not change the negative values of one
entry of this vector, the default value will be set.
In Table 3.7 and Table 3.8, we have the meaning of different entries of these vectors.
We run this example on a cluster paradent of Grid5000 and report results in Table 3.5.
In this example, we fix the matrix size (in term of finite element, we fix the mesh) and increase the number of processors
used to solve the linear system. We saw that, when the number of processors increases, the time for solving the linear
equation decreases, even if the number of iteration increases. This proves that, using pARMS as solver of linear
systems coming from discretization of partial differential equation in FreeFEM can decrease drastically the total time
of simulation.
HIPS (Hierarchical Iterative Parallel Solver) is a scientific library that provides an efficient parallel iterative solver for
very large sparse linear systems. HIPS is available as free software under the CeCILL-C licence.
HIPS implements two solver classes which are the iteratives class (GMRES, PCG) and the Direct class. Concerning
preconditionners, HIPS implements a type of multilevel ILU. For further informations on those preconditionners see
the HIPS documentation.
1 load "msh3"
2 load "hips_FreeFem" //load Hips library
3
4 // Parameters
5 int nn = 10;
6 real zmin = 0, zmax = 1;
7 int[int] iparm(14);
8 real[int] dparm(6);
9 for (int iii = 0; iii < 14; iii++)
10 iparm[iii] = -1;
11 for (int iii = 0; iii < 6; iii++)
12 dparm[iii] = -1;
13 iparm[0] = 0; //use iterative solver
14 iparm[1] = 1; //PCG as Krylov method
15 iparm[4] = 0; //Matrix are symmetric
(continues on next page)
22 // Functions
23 func ue = 2*x*x + 3*y*y + 4*z*z + 5*x*y + 6*x*z + 1;
24 func uex = 4*x + 5*y + 6*z;
25 func uey = 6*y + 5*x;
26 func uez = 8*z + 6*x;
27 func f = -18.;
28
29 // Mesh
30 mesh Th2 = square(nn, nn);
31
38 // Fespace
39 fespace Vh2(Th2, P2);
40 Vh2 ux, uz, p2;
41
48 // Macro
49 macro Grad3(u) [dx(u), dy(u), dz(u)] //
50
51 // Problem
52 varf va (u, v)
53 = int3d(Th)(
54 Grad3(v)' * Grad3(u)
55 )
56 + int2d(Th, 2)(
57 u*v
58 )
59 - int3d(Th)(
60 f*v
61 )
62 - int2d(Th, 2)(
63 ue*v + (uex*N.x + uey*N.y + uez*N.z)*v
64 )
65 + on(1, u=ue);
66
69 real cpu=clock();
70 matrix Aa = va(Vh, Vh);
71
74 if (mpirank == 0){
75 cout << "Size of A =" << Aa.n << endl;
76 cout << "Non zero coefficients =" << Aa.nbcoef << endl;
77 cout << "CPU TIME FOR FORMING MATRIX =" << clock()-cpu << endl;
78 }
79
82 // Solve
83 u[] = Aa^-1*F[];
84
85 // Plot
86 plot(u);
Tip:
Hypre (High Level Preconditioner) is a suite of parallel preconditioner developed at Lawrence Livermore National Lab.
There are two main classes of preconditioners developed in HYPRE: AMG (Algebraic MultiGrid) and Parasails (Par-
allel Sparse Approximate Inverse).
Now, suppose we want to solve 𝐴𝑥 = 𝑏.
At the heart of AMG there is a series of progressively coarser (smaller) representations of the matrix 𝐴. Given an
approximation 𝑥 ˆ to the solution 𝑥, consider solving the residual equation 𝐴𝑒 = 𝑟 to find the error 𝑒, where 𝑟 = 𝑏 − 𝐴ˆ
𝑥.
A fundamental principle of AMG is that it is an algebraically smooth error. To reduce the algebraically smooth errors
further, they need to be represented by a smaller defect equation (coarse grid residual equation) 𝐴𝑐 𝑒𝑐 = 𝑟𝑐 , which is
cheaper to solve. After solving this coarse equation, the solution is then interpolated in fine grid represented here by
matrix 𝐴. The quality of AMG depends on the choice of coarsening and interpolating operators.
The sparse approximate inverse approximates the inverse of a matrix 𝐴 by a sparse matrix 𝑀 . A technical idea to
construct matrix 𝑀 is to minimize the Frobenuis norm of the residual matrix 𝐼 − 𝑀 𝐴. For more details on this
preconditioner technics see [CHOW1997].
HYPRE implement three Krylov subspace solvers: GMRES, PCG and BiCGStab.
1 load "msh3"
2 load "hipre_FreeFem" //Load Hipre librairy
3
4 // Parameters
5 int nn = 10;
6 int[int] iparm(20);
7 real[int] dparm(6);
8 for (int iii = 0; iii < 20; iii++)
9 iparm[iii] = -1;
10 for (int iii = 0; iii < 6; iii++)
11 dparm[iii] = -1;
12 iparm[0] = 2; //PCG as krylov method
13 iparm[1] = 0; //AMG as preconditionner 2: if ParaSails
14 iparm[7] = 7; //Interpolation
15 iparm[9] = 6; //AMG Coarsen type
16 iparm[10] = 1; //Measure type
17 iparm[16] = 2; //Additive schwarz as smoother
18 dparm[0] = 1e-13; //Tolerance to convergence
19 dparm[1] = 5e-4; //Threshold
20 dparm[2] = 5e-4; //Truncation factor
21
22 ...
23
Table 3.14: Definitions of common entries of iparms and dparms vectors for every preconditioner in HYPRE
Table 3.15: Definitions of other entries of iparms and dparms if preconditioner is BOOMER AMG
AMG interpolation type: Default=6
iparms[7]
Specifies the use of GSMG - geometrically smooth coarsening and
iparms[8]
interpolation: Default=1
AMG coarsen type: Default=6
iparms[9]
Defines whether local or global measures are used: Default=1
iparms[10]
AMG cycle type: Default=1
iparms[11]
AMG Smoother type: Default=1
iparms[12]
AMG number of levels for smoothers: Default=3
iparms[13]
AMG number of sweeps for smoothers: Default=2
iparms[14]
Maximum number of multigrid levels: Default=25
iparms[15]
Defines which variant of the Schwartz method isused:
iparms[16]
0: hybrid multiplicative Schwartz method (no overlap across processor boundaries)
1: hybrid additive Schwartz method (no overlap across processor boundaries)
2: additive Schwartz method
3: hybrid multiplicative Schwartz method (with overlap across processor boundaries)
Default=1
Size of the system of PDEs: Default=1
iparms[17]
Overlap for the Schwarz method: Default=1
iparms[18]
Type of domain used for the Schwarz method
iparms[19]
0: each point is a domain
1: each node is a domain (only of interest in “systems” AMG)
2: each domain is generated by agglomeration (default)
AMG strength threshold: Default=0.25
dparms[1]
Truncation factor for the interpolation: Default=1e-2
dparms[2]
Sets a parameter to modify the definition of strength for
dparms[3]
diagonal dominant portions of the matrix: Default=0.9
Defines a smoothing parameter for the additive Schwartz method. Default=1
dparms[3]
Table 3.16: Definitions of other entries of iparms and dparms if preconditioner is PILUT
iparms[7] Row size in Parallel ILUT: Default=1000
iparms[8] Set maximum number of iterations: Default=30
dparms[1] Drop tolerance in Parallel ILUT: Default=1e-5
Table 3.17: Definitions of other entries of iparms and dparms if preconditioner is ParaSails
Table 3.18: Definitions of other entries of iparms and dparms if preconditionner is Schwartz
Defines which variant of the Schwartz method isused:
iparms[7]
0: hybrid multiplicative Schwartz method (no overlap across processor boundaries)
1: hybrid additive Schwartz method (no overlap across processor boundaries)
2: additive Schwartz method
3: hybrid multiplicative Schwartz method (with overlap across processor boundaries)
Default=1
Overlap for the Schwartz method: Default=1
iparms[8]
Type of domain used for the Schwartz method
iparms[9]
0: each point is a domain
1: each node is a domain (only of interest in “systems” AMG)
2: each domain is generated by agglomeration (default)
Conclusion
With the different runs presented here, we wanted to illustrate the gain in time when we increase the number of pro-
cessors used for the simulations. We saw that in every case the time for the construction of the finite element matrix
is constant. This is normal because until now this phase is sequential in FreeFEM. In contrast, phases for solving the
linear system are parallel. We saw on several examples presented here that when we increase the number of processors,
in general we decrease the time used for solving the linear systems. But this is not true in every case. In several case,
when we increase the number of processors the time to convergence also increases. There are two main reasons for this.
First, the increase of processors can lead to the increase of volume of exchanged data across processors consequently
increasing the time for solving the linear systems.
Furthermore, in decomposition domain type preconditioners, the number of processors generally corresponds to the
number of sub domains. In subdomain methods, generally when we increase the number of subdomains we decrease
convergence quality of the preconditioner. This can increase the time used for solving linear equations.
To end this, we should note that good use of the preconditioners interfaced in FreeFEM is empiric, because it is
difficult to know what is a good preconditioner for some type of problems. Although, the efficiency of preconditioners
sometimes depends on how its parameters are set. For this reason we advise the user to pay attention to the meaning
of the parameters in the user guide of the iterative solvers interfaced in FreeFEM.
Domain decomposition
In the previous section, we saw that the phases to construct a matrix are sequential. One strategy to construct the matrix
in parallel is to divide geometrically the domain into subdomains. In every subdomain we construct a local submatrix
and after that we assemble every submatrix to form the global matrix.
We can use this technique to solve PDE directly in domain Ω. In this case, in every subdomains you have to define
artificial boundary conditions to form consistent equations in every subdomains. After this, you solve equation in every
subdomains and define a strategy to obtain the global solution.
In terms of parallel programming for FreeFEM, with MPI, this means that the user must be able to divide processors
avaible for computation into subgroups of processors and also must be able to realize different type of communications
in FreeFEM script. Here is a wrapper of some MPI functions.
Groups
mpiGroup grpe(mpiGroup gp, KN_<long>): Create MPI_Group from existing group gp by given vector.
Communicators
Communicators is an abstract MPI object which allows MPI user to communicate across group of processors. Com-
municators can be Intra-communicators(involves a single group) or Inter-communicators (involves two groups). When
we not specify type of communicator it will be Intra-communicators
mpiComm cc(mpiComm comm, mpiGroup gp): Creates a new communicator.
comm communicator(handle), gp group which is a subset of the group of comm (handle). Return new communicator
mpiComm cc(mpiGroup gp): Same as previous constructor but default comm here is MPI_COMM_WORLD.
mpiComm cc(mpiComm comm, int color, int key): Creates new communicators based on colors and key. This
constructor is based on MPI_Comm_split routine of MPI.
mpiComm cc(MPIrank p, int key): Same constructor than the last one.
Here colors and comm is defined in MPIrank. This constructor is based on MPI_Comm_split routine of MPI.
mpiComm cc(mpiComm comm, int high): Creates an intracommunicator from an intercommunicator. comm inter-
communicator, high.
Used to order the groups within comm (logical) when creating the new communicator. This constructor is based on
MPI_Intercomm_merge routine of MPI.
mpiComm cc(MPIrank p1, MPIrank p2, int tag): This constructor creates an intercommuncator from two intra-
communicators. p1 defined local (intra)communicator and rank in local_comm of leader (often 0) while p2 defined
remote communicator and rank in peer_comm of remote leader (often 0). tag Message tag to use in constructing
intercommunicator. This constructor is based on MPI_Intercomm_create.
Tip: Merge
Process
In FreeFEM we wrap MPI process by function call processor which create internal FreeFEM object call MPIrank.
This mean that do not use MPIrank in FreeFEM script.
processor(int rk): Keep process rank inside object MPIrank. Rank is inside MPI_COMM_WORLD.
processor(int rk, mpiComm cc) and processor(mpiComm cc, int rk) process rank inside communicator
cc.
processor(int rk, mpiComm cc) and processor(mpiComm cc, int rk) process rank inside communicator
cc.
processorblock(int rk): This function is exactlly the same than processor(int rk) but is use in case of block-
ing communication.
processorblock(int rk, mpiComm cc): This function is exactly the same as processor(int rk, mpiComm
cc) but uses a synchronization point.
Global operations
FreeFEM is interfaced with PETSc and SLEPc which offer a wide range of sequential or parallel linear or nonlinear
solvers, time steppers, optimizers, and eigensolvers. In particular, it gives access transparently (without much changes
to user code) to: distributed and multithreaded direct solvers (PARDISO, MUMPS, SuperLU), multigrid solvers (hypre,
GAMG), domain decomposition methods (block Jacobi, ASM, HPDDM). For a detailed introduction to these tools,
interested readers are referred to the tutorial Introduction to FreeFEM with an emphasis on parallel computing.
In most of the scripts listed below, the following standard procedure is used.
• Load an initial sequential mesh (in 2D or 3D).
• Partition the mesh and generate connectivity information according to the number of processes.
• Provide these information to PETSc so that subsequent computations may be done in a distributed fashion.
Combining the power and flexibility of PETSc with the ease-of-use of FreeFEM may help design multiphysics solvers,
e.g., for Navier–Stokes equations, advanced matrix-free discretizations, and such.
3.7.1 Examples
Linear problems
Nonlinear problems
Eigenvalue problems
Miscellaneous
Reproducible science
Article Source
code
Augmented Lagrangian preconditioner for large-scale hydrodynamic stability analysis GitHub
repository
A multilevel Schwarz preconditioner based on a hierarchy of robust coarse spaces GitHub
repository
KSPHPDDM and PCHPDDM: extending PETSc with advanced Krylov methods and robust multi- GitHub
level overlapping Schwarz preconditioners repository
This is a short overview of the Boundary Element Method, introduced through a simple model example. For a thorough
mathematical description of the Boundary Element Method you can refer to the PhD thesis of Pierre Marchand. Some
figures used in this documentation are taken from the manuscript.
Model problem
The model problem we consider here is the scattering of an incoming acoustic wave 𝑢inc by an obstacle Ω. Thus, we
want to solve the following homogeneous Helmholtz equation written in terms of the scattered field 𝑢:
2
in R3 ∖Ω
⎧
⎨−∆𝑢 − 𝑘 𝑢 = 0
⎪
𝑢 = −𝑢inc on Γ (3.31)
+ radiation condition
⎪
⎩
with the Sommerfeld radiation condition at infinity, which states that there can be only outgoing waves at infinity:
(︂ )︂
𝜕
lim |𝑥| − 𝚤𝑘 𝑢(𝑥) = 0
|𝑥|→∞ 𝜕|𝑥|
First, let us introduce the Green kernel 𝒢, which for the helmholtz equation in 3D is
Let us also introduce the Single Layer Potential SL, which for any 𝑞 ∈ 𝐻 −1/2 (Γ) is defined as
∫︁
SL(𝑞)(𝑥) = 𝒢(𝑥 − 𝑦)𝑞(𝑦)𝑑𝜎(𝑦), ∀𝑥 ∈ R3 ∖Γ. (3.33)
Γ
An interesting property of SL is that it produces solutions of the PDE at hand in R3 ∖Γ which satisfy the necessary
conditions at infinity (here the Helmholtz equation and the Sommerfeld radiation condition).
Thus, we now need to find a so-called ansatz 𝑝 ∈ 𝐻 −1/2 (Γ) such that ∀𝑥 ∈ R3 ∖Ω
∫︁
𝑢(𝑥) = SL(𝑝)(𝑥) = 𝒢(𝑥 − 𝑦)𝑝(𝑦)𝑑𝜎(𝑦), (3.34)
Γ
exp(𝚤𝑘|𝑥 − 𝑦|)
∫︁ ∫︁
𝑢(𝑥)𝑞(𝑥)𝑑𝜎(𝑥) = 𝑝(𝑦)𝑞(𝑥)𝑑𝜎(𝑥, 𝑦) ∀𝑞 : Γ → C.
Γ Γ×Γ 4𝜋|𝑥 − 𝑦|
Using the Dirichlet boundary condition 𝑢 = −𝑢inc on Γ, we end up with the following variational problem to solve:
find 𝑝 : Γ → C such that
exp(𝚤𝑘|𝑥 − 𝑦|)
∫︁ ∫︁
𝑝(𝑦)𝑞(𝑥)𝑑𝜎(𝑥, 𝑦) = − 𝑢inc (𝑥)𝑞(𝑥)𝑑𝜎(𝑥) ∀𝑞 : Γ → C. (3.35)
Γ×Γ 4𝜋|𝑥 − 𝑦| Γ
Note that knowing 𝑝 on Γ, we can indeed compute 𝑢 anywhere using the potential formulation (3.34). Thus, we
essentially gained one space dimension, as we only have to solve for 𝑝 : Γ → C in (3.35). Another advantage of the
boundary element method is that for a given mesh size, it is usually more accurate than the finite element method.
Of course, these benefits of the boundary element method come with a drawback: after discretization of (3.35), for
example with piecewise linear continuous (P1) functions on Γ, we end up with a linear system whose matrix is full:
because 𝒢(𝑥 − 𝑦) never vanishes, every interaction coefficient is nonzero. Thus, the matrix 𝐴 of the linear system
can be very costly to store (𝑁 2 coefficients) and invert (factorization in 𝒪(𝑁 3 )) (𝑁 is the size of the linear system).
Moreover, compared to the finite element method, the matrix coefficients are much more expensive to compute because
of the double integral and the evaluation of the Green function 𝒢. Plus, the choice of the quadrature formulas has to be
made with extra care because of the singularity of 𝒢.
In order to formulate our model Dirichlet problem, we have used the Single Layer Potential SL:
∫︁
𝑞 ↦→ SL(𝑞)(𝑥) = 𝒢(𝑥 − 𝑦)𝑞(𝑦)𝑑𝜎(𝑦).
Γ
Depending on the choice of the boundary integral formulation or boundary condition, the Double Layer Potential DL
can also be of use:
∫︁
𝜕
𝑞 ↦→ DL(𝑞)(𝑥) = 𝒢(𝑥 − 𝑦)𝑞(𝑦)𝑑𝜎(𝑦).
Γ 𝜕𝑛(𝑦)
Similarly, we have used the Single Layer Operator 𝒮ℒ in our variational problem
∫︁
𝑝, 𝑞 ↦→ 𝒮ℒ(𝑝, 𝑞) = 𝑝(𝑥)𝑞(𝑦)𝒢(𝑥 − 𝑦)𝑑𝜎(𝑥, 𝑦).
Γ×Γ
There are three other building blocks that can be of use in the boundary element method, and depending on the problem
and the choice of the formulation a boundary integral method makes use of one or a combination of these building
blocks:
the Double Layer Operator 𝒟ℒ:
∫︁
𝜕
𝑝, 𝑞 ↦→ 𝒟ℒ(𝑝, 𝑞) = 𝑝(𝑥)𝑞(𝑦) 𝒢(𝑥 − 𝑦)𝑑𝜎(𝑥, 𝑦)
Γ×Γ 𝜕𝑛(𝑦)
In order to compute the coefficients of the BEM matrix, FreeFEM is interfaced with the boundary element library
BEMTool. BEMTool is a general purpose header-only C++ library written by Xavier Claeys, which handles
• BEM Potentials and Operators for Laplace, Yukawa, Helmholtz and Maxwell equations
• both in 2D and in 3D
• 1D, 2D and 3D triangulations
• P𝑘 -Lagrange for 𝑘 = 0, 1, 2 and surface RT0
Hierarchical matrices
Although BEMTool can compute the BEM matrix coefficients by accurately and efficiently evaluating the boundary
integral operator, it is very costly and often prohibitive to compute and store all 𝑁 2 coefficients of the matrix. Thus, we
have to rely on a matrix compression technique. To do so, FreeFEM relies on the Hierarchical Matrix, or H-Matrix
format.
Low-rank approximation
where 𝑟 ≤ 𝑁, u𝑗 ∈ C𝑁 , v𝑗 ∈ C𝑁 .
𝑁2
If 𝑟 < 2𝑁 , the computing and storage cost is reduced to 𝒪(𝑟𝑁 ) < 𝒪(𝑁 2 ). We say that B is low rank.
Usually, the matrices we are interested in are not low-rank, but they may be well-approximated by low-rank matrices.
We may start by writing their Singular Value Decomposition (SVD):
𝑁
∑︁
B= 𝜎𝑗 u𝑗 v𝑇𝑗
𝑗=1
where (𝜎𝑗 )𝑁𝑗=1 are the singular values of B in decreasing order, and (u𝑗 )𝑗=1 and (v𝑗 )𝑗=1 its left and right singular
𝑁 𝑁
vectors respectively.
Indeed, if B has fast decreasing singular values 𝜎𝑗 , we can obtain a good approximation of B by truncating the SVD
sum, keeping only the first 𝑟 terms. Although the truncated SVD is actually the best low-rank approximation possible
(Eckart-Young-Mirsky theorem), computing the SVD is costly (𝒪(𝑁 3 )) and requires computing all 𝑁 2 coefficients of
the matrix, which we want to avoid.
Thankfully, there exist several techniques to approximate a truncated SVD by computing only some coefficients of the
initial matrix, such as randomized SVD, or Partially pivoted Adaptive Cross Approximation (partial ACA), which
requires only 2𝑟𝑁 coefficients.
Unfortunately, BEM matrices generally do not have fast decreasing singular values. However, they can exhibit sub-
blocks with rapidly decreasing singular values, thanks to the asymptotically smooth nature of the BEM kernel. Let us
look for example at the absolute value of the matrix coefficients in the 2D (circle) case below:
• blocks near the diagonal contain information about the near-field interactions, which are not low-rank in nature
• blocks away from the diagonal corresponding to the interaction between two clusters of geometric points 𝑋 and
𝑌 satisfying the so-called admissibility condition
max(diam(𝑋), diam(𝑌 )) ≤ 𝜂 dist(𝑋, 𝑌 ) (3.36)
are far-field interactions and have exponentially decreasing singular values. Thus, they can be well-approximated by
low-rank matrices.
The idea is then to build a hierarchical representation of the blocks of the matrix, then identify and compress admis-
sible blocks using low-rank approximation.
We can then build the H-Matrix by taking the following steps:
1. build a hierarchical partition of the geometry, leading to a cluster tree of the unknowns. It can for example be
defined using bisection and principal component analysis.
2. from this hierarchical clustering, define and traverse the block cluster tree representation of the matrix structure,
identifying the compressible blocks using admissibility condition (3.36)
3. compute the low-rank approximation of the identified compressible blocks using e.g. partial ACA ; the remaining
leaves corresponding to near-field interactions are computed as dense blocks.
(0)
J1
(1) (1)
J1 J2
(2)
J1
(1)
J1
⇒ J2
(2)
⇒
(0)
J1
(2)
J3
(1)
J2
(2)
J4
the H-Matrix format is implemented in the C++ library Htool. Htool is a parallel header-only library written by Pierre
Marchand and Pierre-Henri Tournier. It is interfaced with FreeFEM and provides routines to build hierarchical matrix
structures (cluster trees, block trees, low-rank matrices, block matrices) as well as efficient parallel matrix-vector and
matrix-matrix product using MPI and OpenMP. Htool is interfaced with BemTool to allow the compression of BEM
matrices using the H-Matrix format in FreeFEM.
(0)
J1
(1) (1)
J1 J2
J1
(2)
MPI process 1
(1)
J1
J2
(2)
MPI process 2
(0)
J1
J3
(2)
MPI process 3
(1)
J2
J4
(2)
MPI process 4
The geometry of the problem (i.e. the boundary Γ) can be discretized by a line (2D) or surface (3D) mesh:
2D
In 2D, the geometry of the boundary can be defined with the border keyword and discretized by constructing a line
or curve mesh of type meshL using buildmeshL:
With the extract keyword, we can also extract the boundary of a 2D mesh (need to load "msh3"):
1 load "msh3"
2 mesh Th = square(10,10);
3 meshL ThL = extract(Th);
1 load "msh3"
2 meshS ThS = square3(10,10);
3 int[int] labs = [1,2];
4 meshL ThL = extract(ThS, label=labs);
You can find much more information about curve mesh generation here.
3D
In 3D, the geometry of the boundary surface can be discretized with a surface mesh of type meshS, which can be built
by several ways, for example using the square3 constructor:
1 load "msh3"
2 real R = 3, r=1, h=0.2;
3 int nx = R*2*pi/h, ny = r*2*pi/h;
4 func torex = (R+r*cos(y*pi*2))*cos(x*pi*2);
5 func tore y= (R+r*cos(y*pi*2))*sin(x*pi*2);
6 func torez = r*sin(y*pi*2);
7 meshS ThS = square3(nx,ny,[torex,torey,torez],removeduplicate=true);
1 load "msh3"
2 mesh Th = square(10,10);
3 meshS ThS = movemesh23(Th, transfo=[x,y,cos(x)^2+sin(y)^2]);
1 load "msh3"
2 mesh3 Th3 = cube(10,10,10);
3 int[int] labs = [1,2,3,4];
4 meshS ThS = extract(Th3, label=labs);
You can find much more information about surface mesh generation here.
For now, FreeFEM allows to solve the following PDE with the boundary element method:
−∆𝑢 − 𝑘 2 𝑢 = 0, 𝑘 ∈ C,
with
• 𝑘 = 0 (Laplace)
• 𝑘 ∈ R*+ (Helmholtz)
• 𝑘 ∈ 𝚤R*+ (Yukawa)
First, the BEM plugin needs to be loaded:
1 load "bem"
The information about the type of operator and the PDE can be specified by defining a variable of type BemKernel:
1 BemKernel Ker("SL",k=2*pi);
You can choose the type of operator depending on your formulation (see Boundary Integral Operators):
• "SL": Single Layer Operator 𝒮ℒ
• "DL": Double Layer Operator 𝒟ℒ
• "TDL": Transpose Double Layer Operator 𝒯 𝒟ℒ
• "HS": Hyper Singular Operator ℋ𝒮
We can then define the variational form of the BEM problem. The double BEM integral is represented by the int1dx1d
keyword in the 2D case, and by int2dx2d for a 3D problem. The BEM keyword inside the integral takes the BEM kernel
operator as argument:
You can also specify the BEM kernel directly inside the integral:
Depending on the choice of the BEM formulation, there can be additional terms in the variational form. For example,
Second kind formulations have an additional mass term:
We can also define a linear combination of two BEM kernels, which is useful for Combined formulations:
1 complex k=2*pi;
2 BemKernel Ker1("HS", k=k);
3 BemKernel Ker2("DL", k=k);
4 BemKernel Ker = 1./(1i*k) * Ker1 + Ker2;
5 varf vbem(u,v) = int2dx2d(ThS)(ThS)(BEM(Ker,u,v)) - int2d(ThS)(0.5*u*v);
As a starting point, you can find how to solve a 2D scattering problem by a disk using a First kind, Second kind and
Combined formulation, for a Dirichlet (here) and Neumann (here) boundary condition.
Assembling the matrix corresponding to the discretization of the variational form on an fespace Uh is similar to the
finite element case, except that we end up with an HMatrix instead of a sparse matrix:
1 fespace Uh(ThS,P1);
2 HMatrix<complex> H = vbem(Uh,Uh);
Behind the scenes, FreeFEM is using Htool and BEMTool to assemble the H-Matrix.
Note: Since Htool is a parallel library, you need to use FreeFem++-mpi or ff-mpirun to be able to run your BEM
script. The MPI parallelism is transparent to the user. You can speed up the computation by using multiple cores:
You can specify the different Htool parameters as below. These are the default values:
1 HMatrix<complex> H = vbem(Uh,Uh,
2 compressor = "partialACA", // or "fullACA", "SVD"
3 eta = 10., // parameter for the admissibility condition
4 eps = 1e-3, // target compression error for each block
5 minclustersize = 10, // minimum block side size min(n,m)
6 maxblocksize = 1000000, // maximum n*m block size
7 commworld = mpiCommWorld); // MPI communicator
You can also set the default parameters globally in the script by changing the value of the global variables htoolEta,
htoolEpsilon, htoolMinclustersize and htoolMaxblocksize.
Once assembled, the H-Matrix can also be plotted with
1 display(H, wait=true);
FreeFEM can also output some information and statistics about the assembly of H:
Generally, the right-hand-side of the linear system is built as the discretization of a standard linear form:
1 Uh<complex> p, b;
2 varf vrhs(u,v) = -int2d(ThS)(uinc*v);
3 b[] = vrhs(0,Uh);
We can then solve the linear system to obtain 𝑝, with the standard syntax:
1 p[] = H^-1*b[];
Under the hood, FreeFEM solves the linear system with GMRES with a Jacobi (diagonal) preconditioner.
Finally, knowing 𝑝, we can compute the solution 𝑢 of our initial problem (3.31) using the Potential as in (3.34). As for the
BemKernel, the information about the type of potential can be specified by defining a variable of type BemPotential:
In order to benefit from low-rank compression, instead of using (3.34) to sequentially compute the value 𝑢(𝑥) at each
point of interest 𝑥, we can compute the discretization of the Potential on a target finite element space UhOut defined
on an output mesh ThOut with an H-Matrix.
First, let us define the variational form corresponding to the potential that we want to use to reconstruct our solution.
Similarly to the kernel case, the POT keyword takes the potential as argument. Note that we have a single integral, and
that v plays the role of 𝑥.
We can then assemble the rectangular H-Matrix from the potential variational form:
1 fespace UhOut(ThOut,P1);
2 HMatrix<complex> HP = vpot(Uh,UhOut);
Computing 𝑢 on UhOut is then just a matter of performing the matrix-vector product of HP with p:
1 UhOut<complex> u;
2 u[] = HP*p[];
3 plot(u);
Let us summarize what we have learned with a 2D version of our model problem where we study the scattering of a
plane wave by a disc:
1 load "bem"
2
3 real k = 10;
4
5 int n = 100;
6
12 fespace Uh(ThL,P1);
13 HMatrix<complex> H = vbem(Uh,Uh);
14
20 p[] = H^-1*b[];
21
24 int np = 200;
25 int R = 4;
26 border b1(t=-R, R){x=t; y=-R;}
27 border b2(t=-R, R){x=R; y=t;}
28 border b3(t=-R, R){x=-t; y=R;}
29 border b4(t=-R, R){x=-R; y=-t;}
30 mesh ThOut = buildmesh(b1(np)+b2(np)+b3(np)+b4(np)+circle(-n));
31
32 fespace UhOut(ThOut,P1);
33 HMatrix<complex> HP = vpot(Uh,UhOut);
34
35 UhOut<complex> u, utot;
36 u[] = HP*p[];
37
38 utot = u + uinc;
39 plot(utot,fill=1,value=1,cmm="u_total");
3.9 Plugins
3.9.1 gsl
The interface with gsl spline is available in FreeFEM, the seven kind of spline are
0. gslinterpcspline: default type of spline
1. gslinterpakima
2. gslinterpsteffen
3. gslinterplinear
4. gslinterppolynomial
5. gslinterpcsplineperiodic
6. gslinterpakimaperiodic
A brief wing example given all the syntax:
1 load "gsl"
2
3 // Parameters
4 int n = 10;
5 real[int, int] dspline(2,n+1); //data points to define the spline
6 for(int i = 0; i <= n; ++i){ //set data points
7 real xx = square(real(i)/n);
8 real yy = sin(xx*pi*2);
9 dspline(0, i) = xx;
10 dspline(1, i) = yy;
(continues on next page)
13 // GSL splines
14 gslspline spline1(gslinterpcspline, dspline); //define the spline1
15 gslspline spline11(dspline); //define the spline11
16 gslspline spline2(gslinterpsteffen, dspline); //define the spline2
17 gslspline spline3(gslinterpcspline, dspline(0, :), dspline(1, :));
18 gslspline spline33(dspline(0, :), dspline(1, :)); //define the spline3
19 spline1 = spline2; //copy spline2 in spline1
20
21 real t = 1.;
22 real s1 = spline1(t); //evaluate the function spline1 at t
23 cout << "spline1(t) = " << s1 << endl;
24 real ds1 = spline1.d(t); //evaluate the derivative of function spline1 at t
25 cout << "spline1.d(t) = " << ds1 << endl;
26 real dds1 = spline1.dd(t); //evaluate the second derivative of function spline1 at t
27 cout << "spline1.dd(t) = " << dds1 << endl;
3.9.2 ffrandom
1 load "ffrandom"
2
15 srandom(10);
16 cout << random() << endl;
17 cout << random() << endl;
18 cout << random() << endl;
The idea is just try to use Interprocess communication using POSIX Shared Memory in Linux.
We build a small library libff-mmap-semaphore.c and libff-mmap-semaphore.h to easily interface.
• mmap - allocate memory, or map files or devices into memory
• semaphore - allow processes and threads to synchronize their actions
A semaphore is an integer whose value is never allowed to fall below zero. Two operations can be performed
on semaphores: increment the semaphore value by one (sem_post); and decrement the semaphore value by one
(sem_wait).
If the value of a semaphore is currently zero, then a sem_wait operation will block until the value becomes
greater than zero.
The functions of library
First the semaphore interface to make synchronization:
• typedef struct FF_P_sem *ff_Psem; the pointer to data structure
• ff_Psem ffsem_malloc(); malloc an empty data structure
• void ffsem_del(ff_Psem sem); clean and free the pointer
• void ffsem_destroy(ff_Psem sem); clean, close the data structure
• void ffsem_init0(ff_Psem sem); make a correct empty of the data structure
• void ffsem_init(ff_Psem sem,const char *nmm, int crea); create or use a new semaphore
• long ffsem_post(ff_Psem sem); nlocked, the value of the semaphore is incremented, and all threads
which are waiting on the semaphore are awakened
• long ffsem_wait(ff_Psem sem); the semaphore referenced by sem is locked. When calling sem_wait(), if
the semaphore’s value is zero, the calling thread will block until the lock is acquired or until the call is interrupted
by a signal.
Alternatively, the sem_trywait() function will fail if the semaphore is already locked, rather than blocking on
the semaphore
• long ffsem_trywait(ff_Psem p);
Secondly, the mmap functions:
• typedef struct FF_P_mmap *ff_Pmmap; the pointer to data structure
• ff_Psem ffmmap_malloc(); malloc an empty data structure
• void ffmmap_del(ff_Pmmap p); clean and free the pointer
• void ffmmap_destroy(ff_Pmmap p); clean, close the data structure
• void ffmmap_init0(ff_Pmmap p); make a correct empty of the data structure
• long ffmmap_msync(ff_Pmmap p, long off, long ln); call writes modified whole pages back to the
filesystem and updates the file modification time. Only those pages containing addr and len-1 succeeding
locations will be examined.
• void ffmmap_init(ff_Pmmap p, const char *nmm, long len); allocate memory, or map files or de-
vices into memory.
• long ffmmap_read(ff_Pmmap p, void *t, size_t n, size_t off); read n bytes from the mmap at
memory off in pointer t.
• long ffmmap_write(ff_Pmmap p, void *t, size_t n, size_t off); write n bytes to the mmap at
memory off in pointer t.
The FreeFEM corresponding functions:
• Pmmap sharedata(filename, 1024); new type to store the mmap informations of name store in string
filename with 1024 is the size the sharedata zone and file.
• Psemaphore smff("ff-slave", creat); new type to store the semaphore of name ff-slave where creat
is a boolean to create or use a existing semaphore.
• Wait(sem) the semaphore referenced by sem is locked. When calling Wait(sem), if the semaphore’s value is
zero, the calling thread will block until the lock is acquired or until the call is interrupted by a signal. Alternatively,
the trywait(sem) function will fail if the semaphore is already locked, rather than blocking on the semaphore.
• Post(sem) the semaphore referenced by sem is unlocked, the value of the semaphore is incremented, and all
threads which are waiting on the semaphore are awakened.
• Read(sharedata ,offset, data); read the variable data from the place offset in sharedata mmap.
• Write(sharedata, offset, data); write the variable data at the place offset in sharedata mmap.
The full example:
The FFMaster.c file:
1 #include "libff-mmap-semaphore.h"
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 ff_Psem sem_ff, sem_c; //the semaphore for mutex
6
17 sem_ff = ffsem_malloc();
18 sem_c = ffsem_malloc();
19 shd = ffmmap_malloc();
20
25 status = 1;
26 ffmmap_write(shd, &status, sizeof(status), 8);
27 ffmmap_msync(shd, 0, 32);
28
29 char ff[1024];
30 sprintf(ff, "FreeFem++ FFSlave.edp -nw -ns -v %d&", debug);
31 system(ff); //lauch FF++ in batch no graphics
32 if(debug) printf("cc: before wait\n");
(continues on next page)
49 status = 0; //end
50 ffmmap_write(shd, &status, sizeof(status), 8);
51 ffsem_post(sem_c);
52 printf("End Master \n");
53 ffsem_wait(sem_ff);
54 ffsem_del(sem_ff);
55 ffsem_del(sem_c);
56 ffmmap_del(shd);
57 return 0;
58 }
1 load "ff-mmap-semaphore"
2
8 // Mesh
9 mesh Th = square(10, 10);
10 int[int] Lab = [1, 2, 3, 4];
11
12 // Fespace
13 fespace Vh(Th, P1);
14 Vh u, v;
15
16 // Macro
17 macro grad(u) [dx(u), dy(u)] //
18
19 int status = 1;
20 cout << " FF status = " << status << endl;
21 real cff, rff;
22
23 // Problem
(continues on next page)
37 while (1){
38 if (verbosity > 9) cout << " FF: before FF wait \n";
39 Wait(smc); //wait from cint write ok
40 Read(sharedata, 0, cff);
41 Read(sharedata, 8, status);
42
43 cout << " After wait .. FF " << cff << " " << status << endl;
44 if(status <= 0) break;
45
46 // Solve
47 Pb;
48 rff = int2d(Th)(u*u);
49 cout << " ** FF " << cff << " " << rff << endl;
50
51 // Write
52 Write(sharedata, 16, rff);
53 Post(smff); //unlock cc
54 }
55
1 cc -c libff-mmap-semaphore.c
2 cc FFMaster.c -o FFMaster libff-mmap-semaphore.o -g -pthread
3 ff-c++ -auto ff-mmap-semaphore.cpp
4 ./FFMaster
The output:
3.10 Developers
The mesh data structure, output of a mesh generation algorithm, refers to the geometric data structure and in some case
to another mesh data structure.
In this case, the fields are
1 MeshVersionFormatted 0
2
3 Dimension [DIM](int)
4
5 Vertices
6 [Number of vertices](int)
7 X_1(double) Y_1(double) (Z_1(double)) Ref_1(int)
8 ...
9 X_nv(double) Y_nv(double) (Z_nv(double)) Ref_nv(int)
10
11 Edges
12 [Number of edges](int)
13 Vertex1_1(int) Vertex2_1(int) Ref_1(int)
14 ...
15 Vertex1_ne(int) Vertex2_ne(int) Ref_ne(int)
16
17 Triangles
18 [Number of triangles](int)
19 Vertex1_1(int) Vertex2_1(int) Vertex3_1(int) Ref_1(int)
20 ...
21 Vertex1_nt(int) Vertex2_nt(int) Vertex3_nt(int) Ref_nt(int)
22
23 Quadrilaterals
24 [Number of Quadrilaterals](int)
25 Vertex1_1(int) Vertex2_1(int) Vertex3_1(int) Vertex4_1(int) Ref_1(int)
26 ...
27 Vertex1_nq(int) Vertex2_nq(int) Vertex3_nq(int) Vertex4_nq(int) Ref_nq(int)
28
29 Geometry
30 [File name of geometric support](char*)
31
32 VertexOnGeometricVertex
33 [Number of vertex on geometric vertex](int)
34 Vertex_1(int) VertexGeometry_1(int)
35 ...
36 Vertex_nvg(int) VertexGeometry_nvg(int)
37
38 EdgeOnGeometricEdge
39 [Number of geometric edge](int)
40 Edge_1(int) EdgeGeometry_1(int)
41 ...
42 Edge_neg(int) EdgeGeometry_neg(int)
43
44 CrackedEdges
45 [Number of cracked edges](int)
46 Edge1_1(int) Edge2_1(int)
47 ...
48 Edge1_nce(int) Edge2_nce(int)
4 VertexOnSupportVertex
5 [Number of vertex on support vertex](int)
6 Vertex_1(int) VertexSupport_1(int)
7 ...
8 Vertex_nvsv(int) VertexSupport_nvsv(int)
9
10 VertexOnSupportEdge
11 [Number of vertex on support edge](int)
12 Vertex_1(int) EdgeSupport_1(int) USupport_1(double)
13 ...
14 Vertex_nvse(int) EdgeSupport_nvse(int) USupport_nvse(double)
15
16 VertexOnSupportTriangle
17 [Number of vertex on support triangle](int)
18 Vertex_1(int) TriangleSupport_1(int) USupport_1(double) VSupport_1(double)
19 ...
20 Vertex_nvst(int) TriangleSupport_nvst(int) USupport_nvst(double) VSupport_nvst(double)
21
22 VertexOnSupportQuadrilaterals
23 [Number of vertex on support quadrilaterals]
24 Vertex_1(int) TriangleSupport_1(int) USupport_1(double) VSupport_1(double)
25 ...
26 Vertex_nvsq(int) TriangleSupport_nvsq(int) USupport_nvsq(double) VSupport_nvsq(double)
7 ...
8
Metric file
1 [Number of vertices](int) 1
2 h_0(double)
3 ...
4 h_nv(double)
1 [Number of vertices](int) 3
2 a11_0(double) a21_0(double) a22_0(double)
3 ...
4 a11_nv(double) a21_nv(double) a22_nv(double)
(︂ )︂
𝑎11,𝑖 𝑎12,𝑖
• a11_i, a21_i and a22_i represent metric ℳ𝑖 = which define the wanted size in a vicinity
𝑎12𝑖 𝑎22,𝑖
√
of the vertex i such that ℎ in direction 𝑢 ∈ R2 is equal to |𝑢|/ 𝑢 · ℳ𝑖 𝑢, where · is the dot product in R2 , and
| · | is the classical norm.
The mesh is only composed of triangles and can be defined with the help of the following two integers and four arrays:
• nbt the number of triangles
• nbv the number of vertices
• nu(1:3, 1:nbt) an integer array giving the three vertex numbers counterclockwise for each triangle
• c(1:2, 1:nbv) a real array giving tje two coordinates of each vertex
• refs(1:nbv) an integer array giving the reference numbers of the vertices
• reft(1:nbt) an integer array giving the reference numbers of the triangles
AM_FMT Files
In Fortran the am_fmt files are read as follows:
AM Files
In Fortran the am files are read as follows:
AMDBA Files
In Fortran the amdba files are read as follows:
msh Files
First, we add the notions of boundary edges
• nbbe the number of boundary edge
• nube(1:2, 1:nbbe) an integer array giving the two vertex numbers of boundary edges
• refbe(1:nbbe) an integer array giving the reference numbers of boundary edges
In Fortran the msh files are read as follows:
ftq Files
In Fortran the ftq files are read as follows:
1 open(1,file='xxx.ftq',form='formatted',status='old')
2 read (1,*) nbv,nbe,nbt,nbq
3 read (1,*) (k(j),(nu(i,j),i=1,k(j)),reft(j),j=1,nbe)
4 read (1,*) ((c(i,k),i=1,2),refs(k),j=1,nbv)
5 close(1)
where if k(j) = 3 when the element j is a triangle and k(j) = 4 when the the element j is a quadrilateral.
With the keyword savesol, we can store a scalar functions, a scalar finite element functions, a vector fields, a vector
finite element fields, a symmetric tensor and a symmetric finite element tensor.
Such format is used in medit.
Extension file .sol
The first two lines of the file are :
• MeshVersionFormatted 0
• Dimension [DIM](int)
The following fields begin with one of the following keyword: SolAtVertices, SolAtEdges, SolAtTriangles,
SolAtQuadrilaterals, SolAtTetrahedra, SolAtPentahedra, SolAtHexahedra.
In each field, we give then in the next line the number of elements in the solutions (SolAtVertices: number of
vertices, SolAtTriangles: number of triangles, . . . ). In other lines, we give the number of solutions, the type of
solution (1: scalar, 2: vector, 3: symmetric tensor). And finally, we give the values of the solutions on the elements.
The file must be ended with the keyword End.
The real element of symmetric tensor :
3𝑑 3𝑑 3𝑑
⎛ ⎞
𝑆𝑇𝑥𝑥 𝑆𝑇𝑥𝑦 𝑆𝑇𝑥𝑧 (︂ 2𝑑 2𝑑
)︂
𝑆𝑇𝑥𝑥 𝑆𝑇𝑥𝑦
3𝑑
𝑆𝑇 = ⎝ 𝑆𝑇𝑦𝑥 3𝑑 3𝑑
𝑆𝑇𝑦𝑦 3𝑑 ⎠
𝑆𝑇𝑦𝑧 𝑆𝑇 2𝑑 = 2𝑑 2𝑑 (3.37)
3𝑑 3𝑑 3𝑑 𝑆𝑇𝑦𝑥 𝑆𝑇𝑦𝑦
𝑆𝑇𝑧𝑥 𝑆𝑇𝑧𝑦 𝑆𝑇𝑧𝑧
1 SolAtTetrahedra
2 [Number of tetrahedra](int)
3 [Number of solutions](int) [Type of solution 1](int) ... [Type of soution nt](int)
4
9 ...
10
where 𝑉 𝑉 3 = [𝑆𝑇𝑥𝑥
3𝑑 3𝑑
, 𝑆𝑇𝑦𝑥 3𝑑
, 𝑆𝑇𝑦𝑦 3𝑑
, 𝑆𝑇𝑧𝑥 3𝑑
, 𝑆𝑇𝑧𝑦 3𝑑
, 𝑆𝑇𝑧𝑧 ].
For a two dimensional mesh Th, A real scalar functions 𝑓 2, a vector fields Ψ = [Ψ1, Ψ2] and a symmetric tensor 𝑆𝑇 2𝑑
(3.37) at triangles is stored in the file f2PsiST2dTh3.solb using :
where 𝑉 𝑉 2 = [𝑆𝑇𝑥𝑥
2𝑑 2𝑑
, 𝑆𝑇𝑦𝑥 2𝑑
, 𝑆𝑇𝑦𝑦 ]
The arguments of savesol functions are the name of a file, a mesh and solutions. These arguments must be given in
this order.
The parameters of this keyword are :
• order = 0 is the solution is given at the center of gravity of elements. 1 is the solution is given at the vertices
of elements.
In the file, solutions are stored in this order : scalar solutions, vector solutions and finally symmetric tensor solutions.
Some notations
𝑖 = Πℎ 𝜔 𝑖 .
We have the identity 𝜔 𝐾 𝐾
The
∫︀ degrees of freedom are the flux through an edge 𝑒 of the mesh, where the flux of the function f : R −→ R is
2 2
𝑒
f .𝑛𝑒 , 𝑛𝑒 is the unit normal of edge 𝑒 (this implies a orientation of all the edges of the mesh, for example we can use
the global numbering of the edge vertices and we just go to small to large number).
To compute this flux, we use a quadrature formula with one point, the middle point of the edge. Consider a triangle 𝑇
with three vertices (a, b, c).
Let denote the vertices numbers by 𝑖𝑎 , 𝑖𝑏 , 𝑖𝑐 , and define the three edge vectors e0 , e1 , e2 by 𝑠𝑔𝑛(𝑖𝑏 − 𝑖𝑐 )(b − c),
𝑠𝑔𝑛(𝑖𝑐 − 𝑖𝑎 )(c − a), 𝑠𝑔𝑛(𝑖𝑎 − 𝑖𝑏 )(a − b).
The three basis functions are:
𝑠𝑔𝑛(𝑖𝑏 − 𝑖𝑐 ) 𝑠𝑔𝑛(𝑖𝑐 − 𝑖𝑎 ) 𝑠𝑔𝑛(𝑖𝑎 − 𝑖𝑏 )
𝜔𝐾
0 = (𝑥 − 𝑎), 𝜔𝐾
1 = (𝑥 − 𝑏), 𝜔𝐾
2 = (𝑥 − 𝑐),
2|𝑇 | 2|𝑇 | 2|𝑇 |
• 𝛼0 = −e02 , 𝛼1 = e01 ,
𝛼2 = −e12 , 𝛼3 = e11 , 𝛼4 = −e22 , 𝛼5 = e21 (effectively, the vector (−e
∫︀ 2 , e1 ) is orthogonal to the edge
𝑚 𝑚
e = (𝑒1 , 𝑒2 ) with a length equal to the side of the edge or equal to 𝑒𝑚 1).
𝑚 𝑚 𝑚
• 𝑖𝑘 = {0, 0, 1, 1, 2, 2},
• 𝑝𝑘 = {0, 0, 1, 1, 2, 2} , 𝑗𝑘 = {0, 1, 0, 1, 0, 1, 0, 1}.
1 #include "error.hpp"
2 #include "rgraph.hpp"
3 using namespace std;
4 #include "RNM.hpp"
5 #include "fem.hpp"
6 #include "FESpace.hpp"
7 #include "AddNewFE.h"
8
26 void FB(const bool *watdd, const Mesh &Th, const Triangle &K,
27 const R2 &PHat, RNMK_ &val) const;
28
where the array data is formed with the concatenation of five array of size NbDoF and one array of size N.
This array is:
1 int TypeOfFE_RTortho::Data[] = {
2 //for each df 0, 1, 3:
3 3, 4, 5, //the support of the node of the df
4 0, 0, 0, //the number of the df on the node
5 0, 1, 2, //the node of the df
6 0, 0, 0, //the df come from which FE (generally 0)
7 0, 1, 2, //which are the df on sub FE
8 0, 0
9 }; //for each component j=0, N-1 it give the sub FE associated
where the support is a number 0, 1, 2 for vertex support, 3, 4, 5 for edge support, and finally 6 for element support.
The function to defined the function 𝜔 𝐾
𝑖 , this function return the value of all the basics function or this derivatives in
array val, computed at point Phat on the reference triangle corresponding to point R2 P=K(Phat); on the current
triangle K.
The index 𝑖, 𝑗, 𝑘 of the array 𝑣𝑎𝑙(𝑖, 𝑗, 𝑘) correspond to:
• 𝑖 is the basic function number on finite element 𝑖 ∈ [0, 𝑁 𝑜𝐹 [
• 𝑗 is the value of component 𝑗 ∈ [0, 𝑁 [
• 𝑘 is the type of computed value 𝑓 (𝑃 ), 𝑑𝑥(𝑓 )(𝑃 ), 𝑑𝑦(𝑓 )(𝑃 ), ... 𝑖 ∈ [0, last_operatortype[.
Note: For optimization, this value is computed only if whatd[k] is true, and the numbering is defined with
1 enum operatortype {
2 op_id = 0,
3 op_dx = 1, op_dy = 2,
4 op_dxx = 3,op_dyy = 4,
5 op_dyx = 5,op_dxy = 5,
6 op_dz = 6,
7 op_dzz = 7,
8 op_dzx = 8, op_dxz = 8,
9 op_dzy = 9, op_dyz = 9
10 };
11 const int last_operatortype = 10;
1 void TypeOfFE_RTortho::FB(const bool *whatd, const Mesh &Th, const Triangle & K,
2 const R2 &PHat,RNMK_ &val) const
3 {
4 R2 P(K(PHat));
5 R2 A(K[0]), B(K[1]), C(K[2]);
6 R l0 = 1 - P.x-P.y;
7 R l1 = P.x, l2 = P.y;
(continues on next page)
Try with ./load.link command in examples++-load/ and see BernardiRaugel.cpp or Morley.cpp new finite
element examples.
Otherwise with static link (for expert only), add
To inforce in loading of this new finite element, we have to add the two new lines close to the end of files src/femlib/
FESpace.cpp like:
1 autoreconf
2 ./reconfigure
3 make
For codewarrior compilation add the file in the project an remove the flag in panal PPC linker FreeFm++ Setting
Dead-strip Static Initializition Code Flag.
Now, it’s possible to add built-in functionnalites in FreeFEM under the three environnents Linux, Windows and MacOS
X 10.3 or newer.
It is agood idea to first try the example load.edp in directory example++-load.
You will need to install a compiler (generally g++/gcc compiler) to compile your function.
• Windows Install the cygwin environnent or the mingw one
• MacOs Install the developer tools Xcode on the apple DVD
• Linux/Unix Install the correct compiler (gcc for instance)
Now, assume that you are in a shell window (a cygwin window under Windows) in the directory example++-load.
Note: In the sub directory include, they are all the FreeFEM include file to make the link with FreeFEM.
Note: If you try to load dynamically a file with command load "xxx" - Under Unix (Linux or MacOs), the file
xxx.so will be loaded so it must be either in the search directory of routine dlopen (see the environment variable
$LD_LIBRARY_PATH) or in the current directory, and the suffix ".so" or the prefix "./" is automatically added.
• Under Windows, the file xxx.dll will be loaded so it must be in the loadLibary search directory which includes
the directory of the application,
The following defines a new function call myfunction with no parameter, but using the 𝑥, 𝑦 current value.
1 #include <iostream>
2 #include <cfloat>
3 using namespace std;
4 #include "error.hpp"
5 #include "AFunction.hpp"
6 #include "rgraph.hpp"
7 #include "RNM.hpp"
8 #include "fem.hpp"
9 #include "FESpace.hpp"
10 #include "MeshPoint.hpp"
11
Now the Problem is to build the link with FreeFEM, to do that we need two classes, one to call the function
myfunction.
All FreeFEM evaluable expression must be a C++ struct/class which derivate from E_F0. By default this expression
does not depend of the mesh position, but if they derivate from E_F0mps the expression depends of the mesh position,
and for more details see [HECHT2002].
To finish we must add this new function in FreeFEM table, to do that include :
1 void init(){
2 Global.Add("myfunction", "(", new OneOperator0s<double>(myfunction));
3 }
4 LOADFUNC(init);
1 ff-c++ myfunction.cpp
2 g++ -c -g -Iinclude myfunction.cpp
3 g++ -bundle -undefined dynamic_lookup -g myfunction.o -o ./myfunction.dylib
Under Windows, launch FreeFEM with the mouse (or ctrl O) on the example.
This will add FFT to FreeFEM, taken from FFTW. To download and install under download/include just go in
download/fftw and try make.
The 1D dfft (fast discret fourier transform) for a simple array 𝑓 of size 𝑛 is defined by the following formula:
𝑛−1
∑︁
dfft(𝑓, 𝜀)𝑘 = 𝑓𝑖 𝑒𝜀2𝜋𝑖𝑘𝑗/𝑛
𝑗=0
𝑗 ′ =0 𝑗=0
Note: The value 𝑛 is given by 𝑠𝑖𝑧𝑒(𝑓 )/𝑚, and the numbering is row-major order.
√ √
So the classical discrete DFFT is 𝑓ˆ = dfft(𝑓, −1)/ 𝑛 and the reverse dFFT 𝑓 = dfft(𝑓ˆ, 1)/ 𝑛
√ 𝑚−1
∑︁ 𝑛−1
∑︁ ′
𝑓 (𝑥, 𝑦) = 1/ 𝑁 𝑓ˆ𝑖+𝑛𝑗 𝑒𝜀2𝜋𝑖(𝑥𝑗+𝑦𝑗 )
𝑗 ′ =0 𝑗=0
and we have
So
2 ˜ 2 ˜ 2 ̂︁
𝑘𝑙 = −((2𝜋) ((𝑘) + (𝑙) ))𝑓𝑘𝑙
[
∆𝑓
First read the Adding a new finite element section, we add two new finite elements examples in the directory examples++-
load.
The Bernardi-Raugel finite element is meant to solve the Navier Stokes equations in 𝑢, 𝑝 formulation; the velocity space
𝑏𝑟
𝑃𝐾 is minimal to prove the inf-sup condition with piecewise constant pressure by triangle.
The finite element space 𝑉ℎ is
𝑉ℎ = {𝑢 ∈ 𝐻 1 (Ω)2 ; 𝑏𝑟
∀𝐾 ∈ 𝑇ℎ , 𝑢|𝐾 ∈ 𝑃𝐾 }
where
𝑏𝑟
𝑃𝐾 = 𝑠𝑝𝑎𝑛{𝜆𝐾 𝐾 𝐾 𝐾
𝑖 𝑒𝑘 }𝑖=1,2,3,𝑘=1,2 ∪ {𝜆𝑖 𝜆𝑖+1 𝑛𝑖+2 }𝑖=1,2,3
with notation 4 = 1, 5 = 2 and where 𝜆𝐾 𝑖 are the barycentric coordinates of the triangle 𝐾, (𝑒𝑘 )𝑘=1,2 the canonical
basis of R2 and 𝑛𝐾
𝑘 the outer normal of triangle 𝐾 opposite to vertex 𝑘.
See BernardiRaugel.cpp.
A way to check the finite element
1 load "BernardiRaugel"
2
3 // Macro
4 //a macro the compute numerical derivative
5 macro DD(f, hx, hy) ( (f(x1+hx, y1+hy) - f(x1-hx, y1-hy))/(2*(hx+hy)) ) //
6
7 // Mesh
8 mesh Th = square(1, 1, [10*(x+y/3), 10*(y-x/3)]);
9
10 // Parameters
11 real x1 = 0.7, y1 = 0.9, h = 1e-7;
12 int it1 = Th(x1, y1).nuTriangle;
13
14 // Fespace
15 fespace Vh(Th, P2BR);
16 Vh [a1, a2], [b1, b2], [c1, c2];
17
18
A real example using this finite element, just a small modification of the Navier-Stokes P2-P1 example, just the begen-
ning is change to
1 load "BernardiRaugel"
2
3 real s0 = clock();
4 mesh Th = square(10, 10);
5 fespace Vh2(Th, P2BR);
6 fespace Vh(Th, P0);
7 Vh2 [u1, u2], [up1, up2];
8 Vh2 [v1, v2];
And the plot instruction is also changed because the pressure is constant, and we cannot plot isovalues of peacewise
constant functions.
3.11 ffddm
In the acronym ffddm, ff stands for FreeFEM and ddm for domain decomposition methods. The idea behind ffddm is
to simplify the use of parallel solvers in FreeFEM: distributed direct methods and domain decomposition methods.
Parallelism is an important issue because, since about 2004, the clock speed of cores stagnates at 2-3 GHz. The increase
in performance is almost entirely due to the increase in the number of cores per processor. All major processor vendors
are producing multicore chips and now every machine is a parallel machine. Waiting for the next generation machine
does not guarantee anymore a better performance of a software. To keep doubling performance parallelism must double.
It implies a huge effort in algorithmic development.
Thanks to ffddm, FreeFEM users have access to high-level functionalities for specifying and solving their finite element
problems in parallel. The first task handled by ffddm is the data distribution among the processors. This is done via an
overlapping domain decomposition and a related distributed linear algebra. Then, solving a linear system is possible
either via an interface to the parallel MUMPS solver or by using domain decomposition methods as preconditioners
to the GMRES Krylov method. The ffddm framework makes it easy to use scalable Schwarz methods enhanced
by a coarse space correction built either from a coarse mesh or a GenEO (Generalized Eigenvalue in the Overlap)
coarse space, see also the book An Introduction to Domain Decomposition Methods: algorithms, theory, and parallel
implementation. State-of-the-art three level methods are also implemented in ffddm.
The ffddm framework is entirely written in the FreeFEM language and the ‘idp’ scripts can be found here (‘ffddm*.idp’
files). It makes it also a very good tool for learning and prototyping domain decomposition methods without compro-
mising efficiency.
ffddm can also act as a wrapper for the HPDDM library. HPDDM is an efficient implementation of various domain
decomposition methods and a variety of Krylov subspace algorithms, with advanced block and recycling methods for
solving sequences of linear systems with multiple right-hand sides: GMRES and Block GMRES, CG, Block CG, and
Breakdown-Free Block CG, GCRO-DR and Block GCRO-DR. For more details on how to use HPDDM within ffddm,
see the ffddm documentation.
Getting Started
This example solves a Laplace problem in 2D in parallel with a two-level GenEO domain decomposition method. To
try this example, just copy and paste the script above in a file ‘test.edp’ and run it on 2 cores with
When the size of a three dimensional problem is large (whatever it means), it is necessary to distribute data among
several processors especially for solving linear systems. A natural way is to do it via domain decomposition.
Mesh Decomposition
𝑇 ℎ := ∪𝑁
𝑖=1 𝑇 ℎ𝑖 .
These meshes may be overlapping or not. This decomposition induces a natural decomposition of the global finite
element space 𝑉 ℎ on 𝑇 ℎ into 𝑁 local finite element spaces (𝑉 ℎ𝑖 )𝑁
𝑖=1 each of them defined on 𝑇 ℎ𝑖 .
Note By global, we mean that the corresponding structure can be refered to in the code (most often only) by its local
values. In computer science term, it corresponds to a distributed data where each piece of data is stored by a MPI
process.
For a given finite element space 𝑉 ℎ, the domain decomposition induces a natural decomposition of the set of the global
degrees of freedom (d.o.f.) of 𝑉 ℎ into the 𝑁 subsets of d.o.f.’s (𝒩𝑖 )𝑁
𝑖=1 each associated with the local finite element
space 𝑉 ℎ𝑖 . We have thus
𝒩 = ∪𝑁
𝑖=1 𝒩𝑖 ,
Note: In mathematical terms, it can be described as follows for a real valued problem. Let 𝑅𝑖 be the restriction operator
from R#𝒩 to R#𝒩𝑖 , where #𝒩𝑖 denotes the number of elements of 𝒩𝑖 . A collection of local vectors (V𝑖 )1≤𝑖≤𝑁 ∈
Π𝑁𝑖=1 R
#𝒩𝑖
is a distributed vector iff there exists a global vector V ∈ R#𝒩 such that for all subset 1 ≤ 𝑖 ≤ 𝑁 , we
have:
V𝑖 = 𝑅𝑖 V .
We will also say that the collection of local vectors (V𝑖 )1≤𝑖≤𝑁 is consistent. For a complex valued problem, simply
replace R with C.
Let (𝐷𝑖 )1≤𝑖≤𝑁 be square diagonal matrices of size #𝒩𝑖 which form a partition of unity in the sense that:
𝑁
∑︁
𝐼𝑑 = 𝑅𝑖𝑇 𝐷𝑖 𝑅𝑖 in R#𝒩 ×#𝒩 .
𝑖=1
For instance if a degree of freedom is shared by 𝑘 subdomains defining the corresponding entry of the diagonal matrix
𝐷 to be 1/𝑘 yields partition of unity matrices. The matrices 𝑅𝑖 and 𝐷𝑖 are the heart of distributed linear algebra.
For two global vectors U and V of size #𝒩 , the formula for the scalar product V𝑇 U = (U, V) in terms of their
distributed vector counterparts makes use of the partition of unity matrices (𝐷𝑖 )1≤𝑖≤𝑁 introduced above:
(︃ 𝑁
)︃ 𝑁 𝑁
∑︁ ∑︁ ∑︁
𝑇
(U, V) = U, 𝑅𝑖 𝐷𝑖 𝑅𝑖 V = (𝑅𝑖 U, 𝐷𝑖 𝑅𝑖 V) = (U𝑖 , 𝐷𝑖 V𝑖 ) .
𝑖=1 𝑖=1 𝑖=1
Local scalar products are performed concurrently. Thus, the implementation is parallel except for the sum which corre-
sponds to a MPI_Reduce call across the 𝑁 MPI processes. Note also that the implementation relies on the knowledge of
a partition of unity so that the FreeFEM syntax is dscalprod(Di,u,v) or equivalently myFEprefix#scalprod(u,v)
where myFEprefix is a user defined prefix for the finite element space decomposition, see the ffddm documentation.
Update
From a collection of local vectors (U𝑖 )1≤𝑖≤𝑁 , it is possible ensure consistency of the duplicated data by modifying
the distributed vector (U𝑖 )1≤𝑖≤𝑁 by calling the function myFEprefix#update(Ui, TRUE) where myFEprefix is
the user defined prefix that refers to the finite element space decomposition. This function performs the following
operation for all 1 ≤ 𝑖 ≤ 𝑁 :
𝑁
∑︁
U𝑖 ← 𝑅𝑖 𝑅𝑗𝑇 𝐷𝑗 U𝑗
𝑗=1
where 𝒪(𝑖) is the set of neighbors of subdomain 𝑖. Therefore, the matrix vector product is computed in three steps:
• concurrent computing of 𝐷𝑗 U𝑗 for all 1 ≤ 𝑗 ≤ 𝑁 ;
• neighbor to neighbor MPI-communications from subdomain 𝑗 to subdomain 𝑖 (𝑅𝑖 𝑅𝑗𝑇 ) ;
• concurrent sum of neighbor contributions.
The discretization of a variational formulation on the global mesh 𝑇 ℎ yields a global matrix 𝐴 and a global right
hand side RHS. Thanks to the sparsity of finite element matrices for partial differential equations and thanks to the
overlap between subdomains, the knowledge of the local matrix 𝑅𝑖 𝐴𝑅𝑖𝑇 on each subdomain 1 ≤ 𝑖 ≤ 𝑁 is sufficient
to perform the matrix-vector product 𝐴 × U for any global vector U. Once the problem has been set up by a call to
ffddmsetupOperator(myprefix, myFEprefix, myVarf), the matrix-vector product is performed by calling the
function myprefix#A(Ui) where myprefix is a user defined prefix that refers to the problem at hand which itself
implicitly refers to the triplet (domain decomposition, finite element, variational formulation). See more on problem
definition in this documentation and more on distributed linear algebra in chapter 8 of “An Introduction to Domain
Decomposition Methods: algorithms, theory and parallel implementation” SIAM 2015.
In many cases, we are interested in the solution of the problem in terms of the vector of d.o.f.’s X that satisfies:
𝐴 X = RHS .
ffddm offers two parallel solvers: direct factorization and iterative preconditioned solvers via Schwarz domain decom-
position methods.
In order to benefit from the sparsity of the matrix arising from a finite element discretization of a partial differential
equation, a variant of Gauss elimination, the frontal method, that automatically avoids a large number of operations
involving zero terms was developed. A frontal solver builds a 𝐿𝑈 or Cholesky decomposition of a sparse matrix given
as the assembly of element matrices by eliminating equations only on a subset of elements at a time. This subset is
called the front and it is essentially the transition region between the part of the system already finished and the part
not touched yet. These methods are basically sequential since the unknowns are processed the one after another or one
front after another. In order to benefit from multicore processors, a multifrontal solver is an improvement of the frontal
solver that uses several independent fronts at the same time. The fronts can be worked on by different processors, which
enables parallel computing. ffddm provides an interface to the parallel sparse direct solver MUMPS. These methods
have the advantage to be very robust and to have a predictable cost. The main drawback is the memory requirement
which can be prohibitive especially for three-dimensional problems.
Schwarz methods
These methods are part of the large family of preconditioned iterative solvers. When considering the solve of the
equation 𝐴 X = RHS, a preconditioner is a linear operator that approximates the inverse of 𝐴 and whose cost of the
associated matrix-vector product is much cheaper than solving the original linear system. It enables to accelerate the
solution of the latter with Krylov type methods such as the conjugate gradient (in the symmetric positive definite case),
GMRES or BiCGSTAB in the general case. Two options are possible.
Left preconditioning: the preconditioner is applied to the left of the equation
𝑀 −1 𝐴 X = 𝑀 −1 RHS .
and the Krylov method is applied to the left preconditioned system with a residual that is preconditioner dependent.
Right preconditioning: the preconditioner is inserted on the right of the operator:
𝐴 𝑀 −1 Y = RHS where X = 𝑀 −1 Y.
and the Krylov method is applied to the right preconditioned system with a residual that is preconditioner independent.
In both cases, if the preconditioner is efficient the number of iterations to get a converged solution is much smaller
than the number of iterations of the Krylov method applied to the original equation 𝐴 X = RHS. Although right
preconditioning seems more intricate, it is much safer to use since the convergence is checked on a residual that does
not depend on the preconditioner.
In the sequel, we consider the solution of the equation 𝐴 X = RHS preconditioned by domain decomposition methods
and with a flexible GMRES Krylov method which is thus necessarily right preconditioned.
where for each subdomain 𝑗 the restriction matrix 𝑅𝑗 and the partition of unity matrix
𝐷𝑗 ‘ℎ𝑎𝑣𝑒𝑏𝑒𝑒𝑛𝑖𝑛𝑡𝑟𝑜𝑑𝑢𝑐𝑒𝑑𝑎𝑏𝑜𝑣𝑒.𝑁 𝑜𝑡𝑒𝑡ℎ𝑎𝑡𝑖𝑛𝑡ℎ𝑒𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙𝐴𝑆𝑀 (𝑎𝑑𝑑𝑖𝑡𝑖𝑣𝑒𝑆𝑐ℎ𝑤𝑎𝑟𝑧𝑚𝑒𝑡ℎ𝑜𝑑)𝑝𝑟𝑒𝑐𝑜𝑛𝑑𝑖𝑡𝑖𝑜𝑛𝑒𝑟𝑡ℎ𝑒𝑝𝑎𝑟𝑡𝑖𝑡𝑖𝑜𝑛𝑜𝑓 𝑢𝑛𝑖𝑡𝑦𝑖𝑠𝑑𝑟
−1
𝑚𝑎𝑡ℎ : ‘𝑀𝑅𝐴𝑆 to a global right hand side RHS is detailed below. Recall that this global vector is distributed among
processes via the local vectors (RHS𝑖 )𝑁 𝑖=1 . Let 𝐴𝑗 denote the local matrix (𝑅𝑗 𝐴 𝑅𝑗 ). The local vector in subdomain
𝑇
−1
𝑖 resulting from the matrix vector product 𝑀𝑅𝐴𝑆 RHS consists in computing:
𝑁
∑︁ ∑︁
𝑅𝑖 𝑅𝑗𝑇 𝐷𝑗 𝐴−1
𝑗 RHS𝑗 = 𝐷𝑖 𝐴−1
𝑖 RHS𝑖 + (𝑅𝑖 𝑅𝑗𝑇 ) 𝐷𝑗 𝐴−1
𝑗 RHS𝑗 .
𝑗=1 𝑗∈𝒪(𝑖)
This task is performed by first solving concurrently on all subdomains a linear system for Y𝑗 for all 1 ≤ 𝑗 ≤ 𝑁 :
𝐴𝑗 Y𝑗 = RHS𝑗 .
Each local vector Y𝑗 is weighted by the partition of unity matrix 𝐷𝑗 . Then data transfers between neighboring sub-
domains implement the 𝑅𝑖 𝑅𝑗𝑇 𝐷𝑗 Y𝑗 formula. The contribution from neighboring subdomains are summed locally.
This pattern is very similar to that of the update procedure.
The ORAS preconditioner may be seen as a variant of the RAS preconditioner. It reads:
𝑁
∑︁
−1
𝑀𝑅𝐴𝑆 := 𝑅𝑗𝑇 𝐷𝑗 𝐵𝑗−1 𝑅𝑗
𝑗=1
where 𝐵𝑗 are local matrices of size #𝒩𝑗 × #𝒩𝑗 for 1 ≤ 𝑗 ≤ 𝑁 . This variant is very useful when dealing with
wave propagation phenomena such as Helmholtz problems in acoustics or Maxwell system in the frequency domain
for electromagnetism. Defining 𝐵𝑗 as the discretization of the physical equation with impedance conditions on the
boundary of the subdomain has been proved to be a good choice.
The RAS and ORAS methods are called a one-level method in the sense that sub-domains only interact with their
direct neighbors. For some problems such as Darcy problems or static elasticity problems and when the number of
subdomains is large, such one-level methods may suffer from a slow convergence. The fix is to add to the preconditioner
an auxiliary coarse problem that couples all subdomains at each iteration and is inexpensive to calculate.
In mathematical terms, we first choose a full rank rectangular matrix 𝑍 ∈ R#𝒩 ×𝑁 𝐶 where 𝑁 𝐶 ≪ #𝒩 denotes the
dimension of the coarse space spanned by the columns of 𝑍. We also pick a coarse matrix 𝐴𝐶 ∈ R𝑁𝐶 ×𝑁𝐶 . A generic
one-level method preconditioner 𝑀1−1 is enriched by a solve on the coarse space. The simplest correction formula is
additive:
𝑀2−1 := 𝑍 𝐴−1 𝑇 −1
𝐶 𝑍 + 𝑀1
Coarse Mesh
A first possibility is to discretize the problem on a coarse mesh, following the same principle as multi-grid methods.
For 3-D problems, a coarsening of the mesh size by a factor 2, reduces by a factor 23 = 8 the size of the coarse problem
which is then easier to solve by a direct method. Then, 𝑍 is the interpolation matrix from the coarse finite element
space to the fine one.
GenEO
For highly heterogeneous or anisotropic problems, two level methods based on coarse meshes might fail and a more
sophisticated construction must be used. A provable robust coarse space called GenEO is built by first solving the
following local generalized eigenvalue problem in parallel for each subdomain 1 ≤ 𝑖 ≤ 𝑁 , where 𝐴Neu
𝑖 denotes the
local matrix resulting from the variational formulation:
The eigenvectors selected to enter the coarse space correspond to eigenvalues 𝜆𝑖,𝑘 ≥ 𝜏 , where the threshold parameter
𝜏 is user-defined. The precise formulas are given in this documentation. From a mathematical point of view, it has been
proved that for a symmetric positive definite matrix 𝐴, the spectrum of the preconditioned by the two-level method with
1
a GenEO coarse space lies in the interval [ , 𝑘0 ].
1 + 𝑘1 𝜏
Note A heuristic that justifies this construction is as follows. We first introduce the Additive Schwarz method (ASM)
which can be seen as a symmetrized variant of the RAS preconditioner:
𝑁
∑︁
−1
𝑀𝐴𝑆𝑀 := 𝑅𝑗𝑇 𝐴−1
𝑗 𝑅𝑗 .
𝑗=1
−1
It can be proved that the lower bound for the eigenvalue of 𝑀𝐴𝑆𝑀 𝐴 is close to zero (which is bad for convergence)
whereas the upper bound depends only on the number of neigbors of a subdomain (which is good for convergence).
−1
Second, we also introduce the following preconditioner 𝑀𝑁 𝑁:
𝐷𝑖 (𝐴Neu
∑︁
−1 −1
𝑀𝑁 𝑁 := 𝑗 ) 𝐷𝑗 .
1≤𝑗≤𝑁
−1
We have a very good lower bound for the preconditioned operator 𝑀𝑁 𝑁 𝐴 that does not depend on the number of
subdomains but only on the maximum multiplicity of intersections 𝑘1 (which is good for convergence). But the upper
bound for this preconditioner is very large (which is bad for convergence).
−1 −1 Neu −1
Now, if we compare formulas for 𝑀𝑁 𝑁 and 𝑀𝐴𝑆𝑀 , we may suspect that vectors V𝑖𝑘 for which 𝐷𝑖 (𝐴𝑖 ) 𝐷𝑖 V𝑖𝑘
−1
and 𝐴𝑖 V𝑖𝑘 have very different values are responsible for the slow convergence and should contribute to the coarse
space. This is a way to interpret the above generalized eigenvalue problem which controls the lower bound of the
two-level preconditioned system.
Minimal example
3 include "ffddm.idp"
4
5 load "msh3"
6
14 // EOM
15
16 // Domain decomposition
17 ffddmbuildDmesh( LapMesh , ThGlobal , mpiCommWorld )
18
25 real[int] rhsi(0);
26 ffddmbuildrhs( Lap , Varf , rhsi )
27
28 LapFEVhi def(ui);
29
30 //Direct solve
31 ui[] = Lapdirectsolve(rhsi);
32
33 Lapwritesummary
34
35 ffddmplot(LapFE,ui,"u");
1 ffddmbuildDmesh(prmesh,Th,comm)
decomposes the mesh Th into overlapping submeshes. The mesh will be distributed over the mpi ranks of commu-
nicator comm. This will create and expose variables whose names will be prefixed by prmesh, see below (# is the
concatenation operator). The way the initial mesh Th is partitioned depends on the value of ffddmpartitioner.
The size of the overlap between subdomains (its width in terms of number of mesh elements) is given by ffddmoverlap.
The level of refinement of the resulting submeshes with respect to the input mesh Th is given by ffddmsplit.
If ffddmexclude ̸= 0, the first ffddmpCS mpi ranks of comm will be excluded from the spatial domain decomposition,
in order to dedicate them later to the coarse problem (for two-level preconditioners).
The label of the new border of the submeshes (the interface between the subdomains) is given by ffddminterfacelabel.
defines:
• int prmesh#npart number of subdomains for this decomposition; should be equal to mpiSize(comm) - ffd-
dmexclude * ffddmpCS
• int prmesh#pCS equal to ffddmpCS
• int prmesh#exclude equal to ffddmexclude
• int prmesh#excluded true if ffddmexclude is true (̸= 0) and mpiRank(comm) < prmesh#pCS. In this case,
this mpi rank will be excluded from the spatial domain decomposition and will only work on the coarse problem.
• mpiComm prmesh#commddm mpi communicator for ranks participating in the spatial domain decom-
position (ranks 0 to prmesh#npart-1 in comm if prmesh#exclude is false, ranks prmesh#pCS to
prmesh#pCS+prmesh#npart-1 otherwise)
• mpiComm prmesh#commCS mpi communicator for ranks participating in the assembly and resolution of the
coarse problem for two-level preconditioners (ranks 0 to prmesh#pCS - 1 in comm)
• mpiComm prmesh#commself self mpi communicator (this mpi rank only), used for factorizing local matrices
• meshN[int] prmesh#aTh array (size prmesh#npart) of local meshes of the subdomains. In the standard par-
allel case, only the local mesh for this mpi rank prmesh#aTh[mpiRank(prmesh#commddm)] is defined (unless
this mpi rank is excluded from the spatial domain decomposition, i.e. prmesh#excluded = 1, see below). In
the sequential case, all local meshes are defined.
• meshN prmesh#Thi the local mesh of the subdomain for this mpi rank, i. e.
prmesh#aTh[mpiRank(prmesh#commddm)] in the parallel case
• int prmesh#numberIntersection the number of neighbors for this mpi rank
• int[int] prmesh#arrayIntersection the list of neighbor ranks in prmesh#commddm for this mpi rank
Remark for sequential use (see -seqddm):
• meshN[int] prmesh#aTh array (size prmesh#npart) of local meshes of the subdomains
1 ffddmbuildDfespace(prfe,prmesh,scalar,def,init,Pk)
builds the local finite element spaces and associated distributed operators on top of the mesh decomposition prmesh.
This will create and expose variables whose names will be prefixed by prfe, see below. It is assumed that ffddmbuild-
Dmesh has already been called with prefix prmesh in order to build the mesh decomposition.
The local finite element spaces of type Pk (where Pk is the type of finite element: P1, [P2,P2,P1], . . . ) are defined on
the local meshes of the subdomains based on the mesh decomposition previously created with prefix prmesh.
scalar determines the type of data for this finite element: real or complex.
Two macros, def and init, are needed: def specifies how to define a finite element function in the finite element space
Pk, and init specifies how to interpolate a scalar function onto the (possibly multiple) components of Pk. Two examples
are given below:
For scalar P2 finite elements and complex-valued problems:
In practice, this builds the necessary distributed operators associated to the finite element space: the local partition of
unity functions (𝐷𝑖 )𝑖=1,...,𝑁 (see prfe#Dk and prfe#Dih below) as well as the function prfe#update (see below)
which synchronizes local vectors (𝑢𝑖 )𝑖=1,...,𝑁 between neighboring subdomains, performing the equivalent of 𝑢𝑖 =
∑︀𝑁 ∑︀𝑁
𝑅𝑖 ( 𝑗=1 𝑅𝑗𝑇 𝑢𝑗 ) or 𝑢𝑖 = 𝑅𝑖 ( 𝑗=1 𝑅𝑗𝑇 𝐷𝑗 𝑢𝑗 ) in a distributed parallel environment.
prfe#scalprod (see below) performs the parallel scalar product for vectors defined on this finite element.
defines:
• prfe#prmesh macro, saves the parent prefix prmesh of the mesh decomposition
• prfe#K macro, saves the type of data scalar for this finite element space (real or complex)
• func prfe#fPk saves the type of finite element Pk, e.g. P1, [P2,P2,P1], . . .
• fespace prfe#Vhi the local finite element space for this mpi rank, defined on the local mesh prmesh#Thi
• int prfe#Ndofglobal the total number of degrees of freedom 𝑛 for this finite element discretization
• prfe#mdef macro, saves the macro def giving the definition of a finite element function in the finite element
space Pk
• prfe#minit macro, saves the macro init specifying how to interpolate a scalar function onto the (possibly
multiple) components of a finite element function of Pk. This is used to create the local partition of unity function
in prfe#Vhi, by interpolating the local P1 partition of unity function onto the components of prfe#Vhi. For
non Lagrange finite element spaces (e.g. RT0, Edge03d, . . . ), see ffddmbuildDfespaceEdge.
• prfe#K[int][int] prfe#Dk array (size prmesh#npart) of local partition of unity vectors in the subdomains,
equivalent to (𝐷𝑖 )𝑖=1,...,𝑁 . In the standard parallel case, only the local partition of unity vector for this mpi rank
prfe#Dk[mpiRank(prmesh#commddm)] is defined (unless this mpi rank is excluded from the spatial domain
decomposition, i. e. prmesh#excluded = 1). In the sequential case, all local partition of unity vectors are
defined.
• matrix<prfe#K>[int] prfe#Dih array (size prmesh#npart) similar to prfe#Dk but in matrix form, allow-
ing for easier matrix-matrix multiplications. prfe#Dih[i] is a diagonal matrix, with the diagonal equal to
prfe#Dk[i].
• fespace prfe#Vhglob the global finite element space defined on the global mesh prmesh#Thglob. Defined
only if -noGlob is not used.
• matrix<prfe#K>[int] prfe#Rih array (size prmesh#npart) of restriction matrices from the global finite
element space to the local finite element spaces on the local submeshes of the subdomains. In the standard
parallel case, only the restriction matrix for this mpi rank prfe#Rih[mpiRank(prmesh#commddm)] is defined
(unless this mpi rank is excluded from the spatial domain decomposition, i. e. prmesh#excluded = 1). In
the sequential case, all restriction matrices are defined. The restriction matrices prfe#Rih are defined only if
-noGlob is not used.
• func int prfe#update(scalar[int] ui, bool scale) The function prfe#update synchronizes the lo-
cal vector ui between subdomains by exchanging the values of ui shared with neighboring subdomains (in the
overlap region) using point-to-point MPI communications.
∑︀𝑁 If scale is true, ui is multiplied by the∑︀
local partition of
𝑁
unity beforehand. This is equivalent to 𝑢𝑖 = 𝑅𝑖 ( 𝑗=1 𝑅𝑗𝑇 𝑢𝑗 ) when scale is false and 𝑢𝑖 = 𝑅𝑖 ( 𝑗=1 𝑅𝑗𝑇 𝐷𝑗 𝑢𝑗 )
when scale is true.
• func scalar prfe#scalprod(scalar[int] ai, scalar[int] bi) The function prfe#scalprod com-
putes the global scalar product of∑︀
two vectors whose local restriction to the subdomain of this mpi rank are ai
𝑁
and bi. The result is computed as 𝑗=1 (𝐷𝑗 𝑎𝑗 , 𝑏𝑗 ).
1 ffddmsetupOperator(pr,prfe,Varf)
builds the distributed operator associated to the variational problem given by Varf, on top of the distributed finite
element space prfe. This will create and expose variables whose names will be prefixed by pr, see below. It is assumed
that ffddmbuildDfespace has already been called with prefix prfe in order to define the distributed finite element space.
In practice, this builds the so-called local ‘Dirichlet’ matrices 𝐴𝑖 = 𝑅𝑖 𝐴𝑅𝑖𝑇 , the restrictions of the global operator 𝐴
to the subdomains (see pr#aRdbelow). The matrices correspond to the discretization of the bilinear form given by the
macro Varf, which represents the abstract variational form of the problem. These matrices are then used to implement
the action of the global operator 𝐴 on a local vector (the parallel matrix-vector product with 𝐴), see pr#A below.
At this point, we already have the necessary data to be able to solve the problem with a parallel direct solver (MUMPS),
which is the purpose of the function pr#directsolve (see below). See ffddmbuildrhs for building the right-hand side.
The macro Varf is required to have three parameters: the name of the variational form, the mesh, and the finite element
space. The variational form given in this ‘abstract’ format will then be used by ffddm to assemble the discrete operators
by setting the appropriate mesh and finite element space as parameters. An example is given below:
5 ffddmsetupOperator(myprefix,myFEprefix,myVarf)
Remark In this simple example, the third parameter VhName is not used. However, for more complex cases such as
non-linear or time dependent problems where the problem depends on a solution computed at a previous step, it is
useful to know for which discrete finite element space the variational form is being used. See for example TODO
defines:
• pr#prfe macro, saves the parent prefix prfe of the finite element space
• int pr#verbosity the level of verbosity for this problem, initialized with the value of ffddmverbosity
• pr#writesummary macro, prints a summary of timings for this problem, such as the time spent to assemble
local matrices or solve the linear system.
• matrix<prfe#K> pr#Aglobal the global matrix 𝐴 corresponding to the discretization of the variational form
given by the macro Varf on the global finite element space prfe#Vhglob. Defined only in the sequential case.
• matrix<prfe#K>[int] pr#aRd array (size prfe#prmesh#npart) of so-called local ‘Dirichlet’ matrices in
the subdomains; these are the restrictions of the global operator to the subdomains, equivalent to 𝐴𝑖 = 𝑅𝑖 𝐴𝑅𝑖𝑇
with 𝐴 the global matrix corresponding to the discretization of the variational form given by the macro Varf
on the global finite element space. In the standard parallel case, only the local matrix for this mpi rank
pr#aRd[mpiRank(prmesh#commddm)] is defined (unless this mpi rank is excluded from the spatial domain
decomposition, i. e. prmesh#excluded = 1). In the sequential case, all local matrices are defined.
• func prfe#K[int] pr#A(prfe#K[int] &ui) The function pr#A computes the parallel matrix-vector prod-
uct,∑︀
i.e. the action of the global operator 𝐴 on the local vector 𝑢𝑖 . The computation is equivalent to
𝑁
𝑅𝑖 ( 𝑗=1 𝑅𝑗𝑇 𝐷𝑗 𝐴𝑗 𝑢𝑗 ) and is performed in parallel using local matrices pr#aRd and the function prfe#update.
In the sequential case, the global matrix pr#Aglobal is used instead.
• func prfe#K[int] pr#AT(prfe#K[int] &ui) Similarly to pr#A, The function pr#AT computes the action
of 𝐴𝑇 , the transpose of the global operator 𝐴, on 𝑢𝑖 .
• func prfe#K[int] pr#directsolve(prfe#K[int]& rhsi) The function pr#directsolve allows to
solve the linear system 𝐴𝑥 = 𝑏 in parallel using the parallel direct solver MUMPS. The matrix is given to
MUMPS in distributed form through the local matrices pr#aRd. The input rhsi is given as a distributed vector
(rhsi is the restriction of the global right-hand side 𝑏 to the subdomain of this mpi rank, see ffddmbuildrhs) and
the returned vector is local as well.
Remark: rectangular operators
It is possible to define a non-square distributed operator where the variational form takes two different finite element
spaces of unknown and test functions. This is done through macro ffddmsetupOperatorRect which takes two FE
prefixes (which must be defined on the same mesh prefix), see below:
5 ffddmsetupOperatorRect(myprefix,myFEprefixV,myFEprefixP,myVarf)
1 ffddmbuildrhs(pr,Varfrhs,rhs)
builds the right-hand side associated to the variational form given by Varfrhs for the problem corresponding to prefix
pr. The resulting right-hand side vector rhs corresponds to the discretization of the abstract linear form given by the
macro Varfrhs (see ffddmsetupOperator for more details on how to define the abstract variational form as a macro).
The input vector rhs is resized and contains the resulting local right-hand side 𝑅𝑖 𝑏, the restriction of the global right-
hand side 𝑏 to the subdomain of this mpi rank. In the sequential case, the global right-hand side vector 𝑏 is assembled
instead.
An example is given below:
1 ffddmsetupPrecond(pr,VarfPrec)
builds the one level preconditioner for problem pr. This will create and expose variables whose names will be prefixed
by pr, see below. It is assumed that ffddmsetupOperator has already been called with prefix pr in order to define the
problem to solve.
In practice, this builds and performs the factorization of the local matrices used in the one level preconditioner. The
local matrices depend on the choice of ffddmprecond and VarfPrec, see pr#aRbelow.
defines:
• string pr#prec equal to ffddmprecond. Sets the type of one level preconditioner 𝑀1−1 to be used: “asm”
(Additive Schwarz), “ras” (Restricted Additive Schwarz), “oras” (Optimized Restricted Additive Schwarz), “soras”
(Symmetric Optimized Restricted Additive Schwarz) or “none” (no preconditioner).
• matrix<pr#prfe#K>[int] pr#aR array (size prfe#prmesh#npart) of local matrices used for the one level
preconditioner. Each mpi rank of the spatial domain decomposition performs the 𝐿𝑈 (or 𝐿𝐷𝐿𝑇 ) factorization
of the local matrix corresponding to its subdomain using the direct solver MUMPS.
– If VarfPrec is not a previously defined macro (just put null for example), the matrices pr#aR are set
to be equal to the so-called local ‘Dirichlet’ matrices
∑︀𝑁 pr#aRd (see ffddmsetupOperator). This is for the
classical ASM preconditioner 𝑀1−1 = 𝑀ASM −1
= 𝑖=1 𝑅𝑖𝑇 𝐴−1 −1
𝑖 𝑅𝑖 or classical RAS preconditioner 𝑀1 =
−1 ∑︀𝑁 −1
𝑀RAS = 𝑖=1 𝑅𝑖 𝐷𝑖 𝐴𝑖 𝑅𝑖 (it is assumed that ffddmprecond is equal to “asm” or “ras”).
𝑇
– If VarfPrec is a macro, it is assumed that VarfPrec defines an abstract bilinear form (see ffddmsetupOp-
erator for more details on how to define the abstract variational form as a macro).
∗ If ffddmprecond is equal to “asm” or “ras”, the matrices pr#aR will be assembled as local ‘Dirichlet’
matrices in the same manner as pr#aRd, but using the bilinear form defined by VarfPrec instead. This
−1
= 𝑖=1 𝑅𝑖𝑇 (𝐴Prec
∑︀𝑁
defines the ASM preconditioner as 𝑀1−1 = 𝑀ASM −1
𝑖 ) 𝑅𝑖 and the RAS precondi-
−1 −1 𝑁 Prec −1 Prec Prec 𝑇
tioner as 𝑀1 = 𝑀RAS = 𝑖=1 𝑅𝑖 𝐷𝑖 (𝐴𝑖 ) 𝑅𝑖 , where 𝐴𝑖 = 𝑅𝑖 𝐴 𝑅𝑖 .
𝑇
∑︀
∗ If ffddmprecond is equal to “oras” or “soras”, the matrices pr#aR will correspond to the discretization
of the variational form VarfPrec in the subdomains Ω𝑖 . In particular, various boundary conditions can
be imposed at the interface between subdomains (corresponding to mesh boundary of label ffddminter-
facelabel set by the parent call to ffddmbuildDmesh), such as Optimized Robin boundary conditions.
−1
= 𝑖=1 𝑅𝑖𝑇 𝐷𝑖 (𝐵𝑖Prec ) 𝑅𝑖 and the SORAS
∑︀𝑁
We note the ORAS preconditioner as 𝑀1−1 = 𝑀ORAS −1
−1
= 𝑖=1 𝑅𝑖𝑇 𝐷𝑖 (𝐵𝑖Prec ) 𝐷𝑖 𝑅𝑖 .
𝑁
preconditioner as 𝑀1−1 = 𝑀SORAS
−1 ∑︀
• func pr#prfe#K[int] pr#PREC1(pr#prfe#K[int] &ui) The function pr#PREC1 computes the parallel
application of the one level preconditioner 𝑀1−1 , i.e. the action of 𝑀1−1 on the local vector 𝑢𝑖 . In the sequential
case, it computes the action of 𝑀1−1 on a global vector. The action of the inverse of local matrices pr#aRd is
computed by forward-backward substitution using their 𝐿𝑈 (or 𝐿𝐷𝐿𝑇 ) decomposition.
• func pr#prfe#K[int] pr#PREC(pr#prfe#K[int] &ui) The function pr#PREC corresponds to the action
of the preconditioner 𝑀 −1 for problem pr. It coincides with the one level preconditioner pr#PREC1 after the
call to ffddmsetupPrecond. If a second level is subsequently added (see the next section about Two level precon-
ditioners), it will then coincide with the two level preconditioner 𝑀2−1 (see pr#PREC2level).
• func pr#prfe#K[int] pr#fGMRES(pr#prfe#K[int]& x0i, pr#prfe#K[int]& bi, real eps, int
nbiter, string sprec) The function pr#fGMRES allows to solve the linear system 𝐴𝑥 = 𝑏 in parallel using
the flexible GMRES method preconditioned by 𝑀 −1 . The action of the global operator 𝐴 is given by pr#A, the
action of the preconditioner 𝑀 −1 is given by pr#PREC and the scalar products are computed by pr#scalprod.
More details are given in the section Solving the linear system.
The main ingredient of a two level preconditioner is the so-called ‘coarse space’ matrix 𝑍.
𝑍 is a rectangular matrix of size 𝑛 × 𝑛𝑐 , where usually 𝑛𝑐 ≪ 𝑛.
𝑍 is used to build the ‘coarse space operator’ 𝐸 = 𝑍 𝑇 𝐴𝑍, a square matrix of size 𝑛𝑐 × 𝑛𝑐 . We can then define the
‘coarse space correction operator’ 𝑄 = 𝑍𝐸 −1 𝑍 𝑇 = 𝑍(𝑍 𝑇 𝐴𝑍)−1 𝑍 𝑇 , which can then be used to enrich the one level
preconditioner through a correction formula. The simplest one is the additive coarse correction: 𝑀2−1 = 𝑀1−1 + 𝑄.
See pr#corr below for all other available correction formulas.
There are multiple ways to define a relevant coarse space 𝑍 for different classes of problems. ffddmgeneosetup defines
a coarse space correction operator by building the GenEO coarse space, while ffddmcoarsemeshsetup builds the coarse
space using a coarse mesh.
After a call to either ffddmgeneosetup or ffddmcoarsemeshsetup, the following variables and functions are set up:
• int pr#ncoarsespace the size of the coarse space 𝑛𝑐 .
• string pr#corr initialized with the value of ffddmcorrection. Specifies the type of coarse correction formula
to use for the two level preconditioner. The possible values are:
"AD" : Additive, 𝑀 −1 = 𝑀2−1 = 𝑀1−1 +𝑄
"BNN" : Balancing Neumann-Neumann, 𝑀 −1 = 𝑀2−1 = (𝐼 − 𝑄𝐴)𝑀1−1 (𝐼 − 𝐴𝑄) + 𝑄
"ADEF1" : Adapted Deflation Variant 1, 𝑀 −1 = 𝑀2−1 = 𝑀1−1 (𝐼 − 𝐴𝑄) + 𝑄
"ADEF2" : Adapted Deflation Variant 2, 𝑀 −1 = 𝑀2−1 = (𝐼 − 𝑄𝐴)𝑀1−1 +𝑄
"RBNN1" : Reduced Balancing Variant 1, 𝑀 −1 = 𝑀2−1 = (𝐼 − 𝑄𝐴)𝑀1−1 (𝐼 − 𝐴𝑄)
"RBNN2" : Reduced Balancing Variant 2, 𝑀 −1 = 𝑀2−1 = (𝐼 − 𝑄𝐴)𝑀1−1
"none" : no coarse correction, 𝑀 −1 = 𝑀2−1 = 𝑀1−1
• Note that AD, ADEF1 and RBNN2 only require one application of 𝑄, while BNN, ADEF2 and RBNN1 require
two. The default coarse correction is ADEF1, which is cheaper and generally as robust as BNN or ADEF2.
• func pr#prfe#K[int] pr#Q(pr#prfe#K[int] &ui) The function pr#Q computes the parallel application
of the coarse correction operator 𝑄, i.e. the action of 𝑄 = 𝑍𝐸 −1 𝑍 𝑇 on the local vector 𝑢𝑖 . In the sequential
case, it computes the action of 𝑄 on a global vector. The implementation differs depending on the method used
to build the coarse space (with GenEO or using a coarse mesh), but the idea is the same: the action of the
transpose of the distributed operator 𝑍 on the distributed vector 𝑢𝑖 is computed in parallel, with the contribution
of all subdomains being gathered in a vector of size 𝑛𝑐 in the mpi process of rank 0. The action of the inverse
of the coarse space operator 𝐸 is then computed by forward-backward substitution using its 𝐿𝑈 (or 𝐿𝐷𝐿𝑇 )
decomposition previously computed by the first pr#prfe#prmesh#pCS ranks of the mpi communicator. The
result is then sent back to all subdomains to perform the last application of 𝑍 and obtain the resulting local vector
in each subdomain.
• func pr#prfe#K[int] pr#PREC2level(pr#prfe#K[int] &ui) The function pr#PREC2level computes
the parallel application of the two level preconditioner 𝑀2−1 , i.e. the action of 𝑀2−1 on the local vector 𝑢𝑖 . In
the sequential case, it computes the action of 𝑀2−1 on a global vector. The two level preconditioner depends on
the choice of the coarse correction formula which is determined by pr#corr, see above.
1 ffddmgeneosetup(pr,Varf)
This builds the GenEO coarse space for problem pr. This will create and expose variables whose names will be prefixed
by pr, see below. It is assumed that ffddmsetupPrecond has already been called for prefix pr in order to define the one
level preconditioner for problem pr. The GenEO coarse space is 𝑍 = (𝑅𝑖𝑇 𝐷𝑖 𝑉𝑖,𝑘 )𝑖=1,...,𝑁
𝜆𝑖,𝑘 ≥𝜏 , where 𝑉𝑖,𝑘 are eigenvectors
corresponding to eigenvalues 𝜆𝑖,𝑘 of the following local generalized eigenvalue problem in subdomain 𝑖:
𝐷𝑖 𝐴𝑖 𝐷𝑖 𝑉𝑖,𝑘 = 𝜆𝑖,𝑘 𝐴Neu
𝑖 𝑉𝑖,𝑘 ,
where 𝐴Neu
𝑖 is the local Neumann matrix of subdomain 𝑖 (with Neumann boundary conditions at the subdomain inter-
face).
In practice, this builds and factorizes the local Neumann matrices 𝐴Neu
𝑖 corresponding to the abstract bilinear form given
by the macro Varf (see ffddmsetupOperator for more details on how to define the abstract variational form as a macro).
In the GenEO method, the abstract bilinear form Varf is assumed to be the same as the one used to define the problem
pr through the previous call to ffddmsetupOperator. The local generalized eigenvalue problem is then solved in each
subdomain to find the eigenvectors 𝑉𝑖,𝑘 corresponding to the largest eigenvalues 𝜆𝑖,𝑘 (see pr#Z below). The number
of computed eigenvectors 𝜈 is given by ffddmnu. The eigenvectors selected to enter 𝑍 correspond to eigenvalues 𝜆𝑖,𝑘
larger than 𝜏 , where the threshold parameter 𝜏 is given by ffddmtau. If ffddmtau = 0, all ffddmnu eigenvectors are
selected. Finally, the coarse space operator 𝐸 = 𝑍 𝑇 𝐴𝑍 is assembled and factorized (see pr#E below).
defines:
• pr#prfe#K[int][int] pr#Z array of local eigenvectors 𝑍𝑖,𝑘 = 𝐷𝑖 𝑉𝑖,𝑘 obtained by solving the local gener-
alized eigenvalue problem above in the subdomain of this mpi rank using Arpack. The number of computed
eigenvectors 𝜈 is given by ffddmnu. The eigenvectors selected to enter 𝑍 correspond to eigenvalues 𝜆𝑖,𝑘 larger
than 𝜏 , where the threshold parameter 𝜏 is given by ffddmtau. If ffddmtau = 0, all ffddmnu eigenvectors are
selected.
• matrix<pr#prfe#K> pr#E the coarse space operator 𝐸 = 𝑍 𝑇 𝐴𝑍. The matrix pr#E is assembled in parallel
and is factorized by the parallel direct solver MUMPS using the first pr#prfe#prmesh#pCS ranks of the mpi
communicator, with mpi rank 0 as the master process. The number of mpi processes dedicated to the coarse
problem is set by the underlying mesh decomposition of problem pr, which also specifies if these mpi ranks are
excluded from the spatial decomposition or not. These parameters are set by ffddmpCS and ffddmexclude when
calling ffddmbuildDmesh (see ffddmbuildDmesh for more details).
1 ffddmcoarsemeshsetup(pr,Thc,VarfEprec,VarfAprec)
builds the coarse space for problem pr from a coarse problem which corresponds to the discretization of a variational
form on a coarser mesh Thc of Ω. This will create and expose variables whose names will be prefixed by pr, see below.
It is assumed that ffddmsetupPrecond has already been called for prefix pr in order to define the one level preconditioner
for problem pr. The abstract variational form for the coarse problem can differ from the original problem pr and is
given by macro VarfEprec (see ffddmsetupOperator for more details on how to define the abstract variational form as
a macro). For example, absorption can be added in the preconditioner for wave propagation problems, see examples
for Helmholtz and Maxwell equations in the Examples section.
The coarse space 𝑍 corresponds to the interpolation operator from the coarse finite element space to the original finite
element space of the problem. Thus, the coarse space operator 𝐸 = 𝑍 𝑇 𝐴Eprec 𝑍 corresponds to the matrix of the
problem given by VarfEprec discretized on the coarse mesh Thc and is assembled as such.
Similarly, VarfAprec specifies the global operator involved in multiplicative coarse correction formulas. For example,
−1
𝑀2,ADEF1 = 𝑀1−1 (𝐼 − 𝐴Aprec 𝑄) + 𝑄 (where 𝑄 = 𝑍𝐸 −1 𝑍 𝑇 ). 𝐴Aprec defaults to 𝐴 if VarfAprec is not a valid macro
solves the linear system for problem pr using the flexible GMRES algorithm with preconditioner 𝑀 −1 (corresponding
to pr#PREC). Returns the local vector corresponding to the restriction of the solution to pr#prfe#Vhi. x0i and bi are
local distributed vectors corresponding respectively to the initial guess and the right-hand side (see ffddmbuildrhs). eps
is the stopping criterion in terms of the relative decrease in residual norm. If eps < 0, the residual norm itself is used
instead. itmax sets the maximum number of iterations. sp selects between the "left" or "right" preconditioning
variants: left preconditioned GMRES solves 𝑀 −1 𝐴𝑥 = 𝑀 −1 𝑏, while right preconditioned GMRES solves 𝐴𝑀 −1 𝑦 =
𝑏 for 𝑦, with 𝑥 = 𝑀 −1 𝑦.
ffddm allows you to use HPDDM to solve your problem, effectively replacing the ffddm implementation of all parallel
linear algebra computations. ffddm can then be viewed as a finite element interface for HPDDM.
You can use HPDDM features unavailable in ffddm such as advanced Krylov subspace methods implementing block
and recycling techniques.
To switch to HPDDM, simply define the macro pr#withhpddm before using ffddmsetupOperator. You can then pass
HPDDM options with command-line arguments or directly to the underlying HPDDM operator pr#hpddmOP. Options
need to be prefixed by the operator prefix:
You can also choose to replace only the Krylov solver, by defining the macro pr#withhpddmkrylov before using
ffddmsetupOperator. Doing so, a call to pr#fGMRES will call the HPDDM Krylov solver, with ffddm providing the
operator and preconditioner through pr#A and pr#PREC. You can then pass HPDDM options to the Krylov solver
through command-line arguments:
For example, using restarted GCRO-DR(40) and recycling 10 Ritz vectors at each restart:
Advanced use
The parallel interpolation of a distributed finite element function to another distributed finite element space can be
computed using the prfe#transferfromVhi macro. Internally, it uses the transfer macro from the macro_ddm.
idp script. The macro is prefixed by the source finite element prefix prfe and is used a follows:
1 prfe#transferfromVhi(us,Vht,Pkt,rest)
where us is distributed source FE function defined on prfe#Vhi, Vht is the target local finite element space, Pkt is
the approximation space corresponding to Vht and rest is the target local FE function defined on Vht. You can find
an example below:
1 macro dimension()2//
2
3 include "ffddm.idp"
4
9 ffddmbuildDmesh(Ms,Ths,mpiCommWorld)
(continues on next page)
11 ffddmbuildDmesh(Mt,Tht,mpiCommWorld)
12
13 func Pk = [P2,P2];
14
15 macro def(u)[u,u#2]//
16 macro init(u)[u,u]//
17 ffddmbuildDfespace(FEs,Ms,real,def,init,Pk)
18 ffddmbuildDfespace(FEt,Mt,real,def,init,Pk)
19
22 FEtVhi def(ut);
23
24 FEstransferfromVhi(us,FEtVhi,Pk,ut)
25
26 ffddmplot(FEs,us,"u source");
27 ffddmplot(FEt,ut,"u target");
For Lagrange finite elements, the partition of unity (𝐷𝑖 )𝑖=1,...,𝑁 (see prfe#Dk and prfe#Dih) is built by interpolating
the local P1 partition of unity function onto the components of the Pk finite element space prfe#Vhi. For non Lagrange
finite element spaces, such as Raviart–Thomas or Nédélec edge elements, the definition of the degrees of freedom can
be more involved, and interpolating the P1 partition of unity functions directly is inappropriate. The idea is then to use
a “pseudo” finite element Pkpart derived from Pk which is suitable for interpolating the P1 partition of unity, in the
sense that it will produce a partition of unity for Pk.
For example, for first-order Nédélec edge elements (Edge03d), whose degrees of freedom are the circulations along
the edges, we define the “pseudo” finite element Edge03ds0 which can be seen as a scalar Lagrange counterpart: the
numbering of the degrees of freedom is the same, but they correspond to the value at the edge midpoints.
For Lagrange finite elements, the distributed finite element spaces are built using ffddmbuildDfespace. Here you must
use ffddmbuildDfespaceEdge, which builds the distributed finite element space using a “pseudo” finite element to
build the partition of unity:
1 ffddmbuildDfespaceEdge(prfe,prmesh,scalar,def,init,Pk,defpart,initpart,Pkpart)
where macros defpart and initpart specify how to define and interpolate a function in the ‘pseudo’ finite element
space Pkpart, similar to def and init for Pk.
An example with first-order Nédélec edge elements (Edge03d + Edge03ds0) for Maxwell equations can be found in
Maxwell-3d-simple.edp, see the Examples section.
We have seen in the Two level preconditioners section that two level methods produce a ‘coarse space operator’ 𝐸 that
needs to be inverted at each iteration. By default the coarse space operator matrix is factorized by the direct solver
MUMPS. This can become a bottleneck and hinder scalability for large problems, where 𝐸 can become too large to be
factorized efficiently. To remedy this, we can instead opt to use an iterative method to solve the coarse problem at each
iteration. Moreover, in order to retain robustness, a DD preconditioner can be used to solve the inner coarse problem
more efficiently.
When the coarse problem comes from a coarse mesh discretization, a natural way to do inexact coarse solve is to use a
one level domain decomposition method on the coarse problem, with the same subdomain partitioning for the coarse
and fine meshes. This means that each processor is associated to one spatial subdomain and hosts the two local (nested)
coarse and fine submeshes corresponding to this subdomain, as well as the corresponding local matrices for the two
discretizations. This natural choice offers interesting benefits:
• We naturally recover a load-balanced parallel implementation, provided that the initial partitioning is balanced.
• The communication pattern between neighboring subdomains is the same for the coarse and fine discretizations.
• The assembly and the application of the interpolation operator 𝑍 (and 𝑍 𝑇 ) between the fine and the coarse spaces
can be computed locally in each subdomain and require no communication.
In ffddm, the first step is to build the two nested mesh decompositions using ffddmbuildDmeshNested:
1 ffddmbuildDmeshNested(prmesh,Thc,s,comm)
decomposes the coarse mesh Thc into overlapping submeshes and creates the fine decomposition by locally refining
submeshes by a factor of s, i.e. splitting each mesh element into 𝑠𝑑 elements, 𝑠 ≥ 1. This will create and expose
variables corresponding to both decompositions, prefixed by prmesh for the fine mesh and by prmesh#Coarse for the
coarse mesh (see ffddmbuildDmesh). It also sets the integer variable prmesh#binexactCS to 1, which specifies that
any two level method defined on mesh prefix prmesh will use inexact coarse solves.
The distributed finite element spaces, operators and preconditioners can then be defined for both decompositions. Here
is an example where the coarse problem is solved using a one level method:
1 ffddmbuildDmeshNested(M, Thc, 3, mpiCommWorld)
2
Remarks:
• Note that the different prefixes need to match: prefixes for the coarse decomposition have to be those of the fine
decomposition, appended with Coarse.
• The operator and preconditioner for the coarse problem have to be defined before those of the fine problem,
because the pr#Q function is actually defined by ffddmsetupPrecond and involves a call to pr#CoarsefGMRES
(which is defined by ffddmsetupPrecond for the coarse problem) for the iterative solution of the coarse problem
if pr#prfe#prmesh#binexactCS ̸= 0.
• In this case, ffddmcoarsemeshsetup does not use Thc or VarfEprec and only builds the local interpolation
matrices between fine and coarse local finite element spaces pr#prfe#Vhi and pr#prfe#CoarseVhi to be able
to apply 𝑍 and 𝑍 𝑇 .
• The GMRES tolerance for the inner solution of the coarse problem is set by ffddminexactCStol and is equal to
0.1 by default.
In practice, these methods can give good results for wave propagation problems, where the addition of artificial ab-
sorption in the preconditioner helps with the convergence of the one level method for the inner solution of the coarse
problem. You can find an example for Maxwell equations in Maxwell_Cobracavity.edp, see the Examples section.
More details can be found here and in
M. Bonazzoli, V. Dolean, I. G. Graham, E. A. Spence, P.-H. Tournier. Domain decomposition precondi-
tioning for the high-frequency time-harmonic Maxwell equations with absorption. Mathematics of Com-
putation, 2019. DOI: https://doi.org/10.1090/mcom/3447
3.11.3 Parameters
Command-line arguments
• -ffddm_geneo_nu N, number of local eigenvectors to compute in each subdomain when solving the local gen-
eralized eigenvalue problem for the GenEO method, see ffddmnu (default 20).
• -ffddm_geneo_threshold R, threshold parameter for selecting local eigenvectors when solving the local gen-
eralized eigenvalue problems for the GenEO method, see ffddmtau (default 0.5). If the command-line parameter
-ffddm_geneo_nu N is used, then ffddmtau is initialized to 0.
• -ffddm_schwarz_coarse_correction S, specifies the coarse correction formula to use for the two level
preconditioner: “AD” (Additive), “BNN” (Balancing Neumann-Neumann), “ADEF1” (Adapted Deflation Variant
1), “ADEF2” (Adapted Deflation Variant 2), “RBNN1” (Reduced Balancing Variant 1), “RBNN2” (Reduced
Balancing Variant 2) or “none” (no coarse correction), see ffddmcorrection (default “ADEF1”).
• -ffddm_inexactCS_tol R, specifies the GMRES tolerance for the inner solution of the coarse problem when
using a two level method with approximate coarse solves, see ffddminexactCStol (default 0.1).
Global parameters
3.11.4 Tutorial
What is ffddm ?
• ffddm implements a class of parallel solvers in FreeFEM: overlapping Schwarz domain decomposition methods
• The entire ffddm framework is written in the FreeFEM language ffddm aims at simplifying the use of
parallel solvers in FreeFEM
You can find the ffddm scripts here (‘ffddm*.idp’ files) and examples here
• ffddm provides a set of high-level macros and functions to
– handle data distribution: distributed meshes and linear algebra
– build DD preconditioners for your variational problems
– solve your problem using preconditioned Krylov methods
• ffddm implements scalable two level Schwarz methods, with a coarse space correction built either from a coarse
mesh or a GenEO coarse space Ongoing research: approximate coarse solves and three level methods
• ffddm can also act as a wrapper for the HPDDM library.
HPDDM is an efficient C++11 implementation of various domain decomposition methods and Krylov
subspace algorithms with advanced block and recycling techniques More details on how to use HPDDM
within ffddm here
See documentation
Build a collection of 𝑁 overlapping sub-meshes (𝑇 ℎ𝑖 )𝑁
𝑖=1 from the global mesh 𝑇 ℎ
3 include "ffddm.idp"
4
10 medit("Th"+mpirank, MThi);
See documentation
builds the local finite element spaces and associated distributed operators on top of the mesh decomposition prmesh
• scalar: type of data for this finite element: real or complex
• Pk: your type of finite element: P1, [P2,P2,P1], . . .
• def, init: macros specifying how to define and initialize a Pk FE function
prfe#Vhi is the local FE space defined on prmesh#Thi for each mpi process
Example for P2 complex:
Natural decomposition of the set of d.o.f.’s 𝒩 of 𝑉 ℎ into the 𝑁 subsets of d.o.f.’s (𝒩𝑖 )𝑁
𝑖=1 each associated with the
local FE space 𝑉 ℎ𝑖
𝒩 = ∪𝑁
𝑖=1 𝒩𝑖 ,
V𝑖 = 𝑅𝑖 V, 𝑖 = 1, ..., 𝑁
where V is the corresponding global vector and 𝑅𝑖 is the restriction operator from 𝒩 into 𝒩𝑖
Remark 𝑅𝑖𝑇 is the extension operator: extension by 0 from 𝒩𝑖 into 𝒩
Partition of unity
synchronizes local vectors V𝑖 between subdomains ⇒ exchange the values of 𝑚𝑎𝑡ℎ𝑏𝑓 𝑉𝑖 shared with neighbors in the
overlap region
⎛ ⎞
𝑁
∑︁ ∑︁
V𝑖 ← 𝑅𝑖 ⎝ 𝑅𝑗𝑇 𝐷𝑗 V𝑗 ⎠ = 𝐷𝑖 V𝑖 + 𝑅𝑖 𝑅𝑗𝑇 𝐷𝑗 V𝑗
𝑗=1 𝑗∈𝒪(𝑖)
where 𝒪(𝑖) is the set of neighbors of subdomain $i$. Exchange operators 𝑅𝑖 𝑅𝑗𝑇 correspond to neighbor-to-neighbor
MPI communications
1 FEupdate(vi, false);
⎛ ⎞
𝑁
∑︁
V𝑖 ← 𝑅𝑖 ⎝ 𝑅𝑗𝑇 V𝑗 ⎠
𝑗=1
1 FEupdate(vi, true);
⎛ ⎞
𝑁
∑︁
V𝑖 ← 𝑅𝑖 ⎝ 𝑅𝑗𝑇 𝐷𝑗 V𝑗 ⎠
𝑗=1
3 include "ffddm.idp"
4
15 FEVhi vi = x;
16 medit("v"+mpirank, MThi, vi);
17
18 vi[] = FEDk[mpirank];
19 medit("D"+mpirank, MThi, vi);
20
21 vi = 1;
(continues on next page)
25 FEupdate(vi[],false);
26 ffddmplot(FE,vi,"multiplicity")
See documentation
builds the distributed operator associated to your variational form on top of the distributed FE prfe
Varf is a macro defining your abstract variational form
Warning: only true because 𝐷𝑖 𝑅𝑖 𝐴 = 𝐷𝑖 𝐴𝑖 𝑅𝑖 due to the fact that 𝐷𝑖 vanishes at the interface !!
∑︀𝑁
pr#A applies 𝐴 to a distributed vector: U𝑖 ← 𝑅𝑖 𝑗=1 𝑅𝑗𝑇 𝐷𝑗 𝐴𝑗 V𝑗
⇒ multiply by 𝐴𝑖 + prfe#update
3 include "ffddm.idp"
4
25 ui[] = PBA(bi[]);
26 ffddmplot(FE, ui, "A*b")
See documentation
1 mesh Th = square(100,100);
1 mesh Th = square(100,100);
2 ffddmbuildDmesh(M, Th, mpiCommWorld)
See documentation
1 ui[] = PBdirectsolve(bi[]);
Solve the linear system with the parallel direct solver MUMPS
See documentation
We have 𝐴 and 𝑏 in distributed form, we can solve the linear system 𝐴𝑢 = 𝑏 using the parallel direct solver MUMPS
See documentation
1 ffddmsetupPrecond( pr , VarfPrec )
⇒ apply 𝐴−1
𝑖 (forward/backward substitutions) + prfe#update
See documentation
1 func K[int] pr#fGMRES(K[int]& x0i, K[int]& bi, real eps, int itmax, string sp)
See documentation
Goal improve scalability of the one level method
⇒ enrich the one level preconditioner with a coarse problem coupling all subdomains
Main ingredient is a rectangular matrix 𝑍 of size 𝑛 × 𝑛𝑐 , where 𝑛𝑐 ≪ 𝑛 𝑍 is the coarse space matrix
The coarse space operator 𝐸 = 𝑍 𝑇 𝐴𝑍 is a square matrix of size 𝑛𝑐 × 𝑛𝑐
The simplest way to enrich the one level preconditioner is through the additive coarse correction formula:
𝑀2−1 = 𝑀1−1 + 𝑍𝐸 −1 𝑍 𝑇
How to choose 𝑍 ?
See documentation
1 ffddmgeneosetup( pr , Varf )
The GenEO method builds a robust coarse space for highly heterogeneous or anisotropic SPD problems
⇒ solve a local generalized eigenvalue problem in each subdomain
with 𝐴Neu
𝑖 the local Neumann matrices built from Varf (same Varf as Step 3)
The GenEO coarse space is 𝑍 = (𝑅𝑖𝑇 𝐷𝑖 𝑉𝑖,𝑘 )𝑖=1,...,𝑁
𝜆𝑖,𝑘 ≥𝜏 The eigenvectors 𝑉𝑖,𝑘 selected to enter the coarse space corre-
spond to eigenvalues 𝜆𝑖,𝑘 ≥ 𝜏 , where 𝜏 is a threshold parameter
1
Theorem the spectrum of the preconditioned operator lies in the interval [ , 𝑘0 ] where 𝑘0 − 1 is
1 + 𝑘1 𝜏
the # of neighbors and 𝑘1 is the multiplicity of intersections ⇒ 𝑘0 and 𝑘1 do not depend on 𝑁 nor on the
PDE
See documentation
For non SPD problems, an alternative is to build the coarse space by discretizing the PDE on a coarser mesh Thc
𝑍 will be the interpolation matrix from the coarse FE space 𝑉 ℎ𝑐 to the original FE space 𝑉 ℎ
⇒ 𝐸 = 𝑍 𝑇 𝐴𝑍 is the matrix of the problem discretized on the coarse mesh
The variational problem to be discretized on Thc is given by macro VarfEprec
VarfEprec can differ from the original Varf of the problem
Example: added absorption for wave propagation problems
Similarly, VarfAprec specifies the global operator involved in multiplicative coarse correction formulas. It defaults to
𝐴 if VarfAprec is not defined
See documentation
ffddm allows you to use HPDDM to solve your problem, effectively replacing the ffddm implementation of all parallel
linear algebra computations
⇒ define your problem with ffddm, solve it with HPDDM
⇒ ffddm acts as a finite element interface for HPDDM
You can use HPDDM features unavailable in ffddm such as advanced Krylov subspace methods implementing block
and recycling techniques
To switch to HPDDM, simply define the macro pr#withhpddm before using ffddmsetupOperator (Step 3). You can
then pass HPDDM options with command-line arguments or directly to the underlying HPDDM operator. Options
need to be prefixed by the operator prefix:
Or, define pr#withhpddmkrylov to use HPDDM only for the Krylov method
Example here: Helmholtz problem with multiple rhs solved with Block GMRES
Heterogeneous 3D linear elasticity equation discretized with P2 FE solved on 4096 MPI processes 𝑛 ≈ 262 million
Some results: 2-level DD for Maxwell equations, scattering from the COBRA cavity
f = 10 GHz
f = 16 GHz
Some results: 2-level DD for Maxwell equations, scattering from the COBRA cavity
3.11.5 Examples
FOUR
LANGUAGE REFERENCES
In essence FreeFEM is a compiler: its language is typed, polymorphic, with exception and reentrant. Every variable
must be declared of a certain type, in a declarative statement; each statement are separated from the next by a semicolon
;.
The language allows the manipulation of basic types integers (int), reals (real), strings (string), arrays (example:
real[int]), bi-dimensional (2D) finite element meshes (mesh), 2D finite element spaces (fespace), analytical func-
tions (func), arrays of finite element functions (func[basic_type]), linear and bilinear operators, sparse matrices,
vectors , etc. For example:
The life of a variable is the current block {...}, except the fespace variable, and the variables local to a block are
destroyed at the end of the block as follows.
Tip: Example
1 real r = 0.01;
2 mesh Th = square(10, 10); //unit square mesh
3 fespace Vh(Th, P1); //P1 Lagrange finite element space
4 Vh u = x + exp(y);
5 func f = z*x + r*log(y);
6 plot(u, wait=true);
7 { // new block
8 real r = 2; //not the same r
9 fespace Vh(Th, P1); //error because Vh is a global name
10 }// end of block
11 //here r back to 0.01
The type declarations are mandatory in FreeFEM; in the end this feature is an asset because it is easy to make bugs in
a language with many implicit types.
The variable name is just an alphanumeric string, the underscore character _ is not allowed, because it will be used as
an operator in the future.
379
FreeFEM Documentation, Release 4.8
4.1 Types
int
1 int i = 0;
bool
Boolean value.
1 bool b = true;
real
1 real r = 0.;
complex
1 complex c = 0. + 1i;
Tip: Example
a + b = (2,4)
a - b = (-2,-2)
a*b = (-3,2)
a/b = (0.230769,0.153846)
string
String value.
1 int i = 1;
2 real r = 1.;
3 string s = "the int i = " + i +", the real r = " + r + ", the complex z = " + (1. + 1i);
1 s(4:3) = "+++";
1 string s2 = s1(5:10);
border
Border type.
Note: Label
A label can be defined with the border:
mesh
1 mesh Th;
mesh3
1 mesh3 Th;
fespace
A finite element space is based on a mesh (Th) with an element definition, scalar (P1) or vector ([P2, P2, P1]).
Available finite element space:
Generic:
• P0 / P03d
• P0Edge
• P1 / P13d
• P1dc
• P1b / P1b3d
• P1bl / P1bl3d
• P1nc
• P2 / P23d
• P2b
• P2dc
• P2h
• RT0 / RT03d
• RT0Ortho
• Edge03d
Using Element_P3:
• P3
Using Element_P3dc:
• P3dc
Using Element_P4:
• P4
Using Element_P4dc:
• P4dc
Using Element_PkEdge:
• P1Edge
• P2Edge
• P3Edge
• P4Edge
• P5Edge
Using Morlay:
• P2Morley
Using HCT :
• HCT
Using BernardiRaugel:
• P2BR
Using Element_Mixte:
• RT1
• RT1Ortho
• RT2
• RT2Ortho
• BDM1
• BDM1Ortho
Using Element_Mixte3d:
• Edge13d
• Edge23d
Using Element_QF:
• FEQF
macro
Macro type.
NewMacro / EndMacro
IFMACRO
ENDIFMACRO
func
Function type.
Function without parameters (𝑥, 𝑦 and 𝑧 are implicitly considered):
Elementary functions
Class of basic functions (polynomials, exponential, logarithmic, trigonometric, circular) and the functions obtained
from those by the four arithmetic operations
Random functions
FreeFEM includes the Mersenne Twister random number generator. It is a very fast and accurate random number
generator of period 2219937 − 1.
See randint32(), randint31(), randreal1(), randreal2(), randreal3(), randres53(), randinit(seed).
In addition, the ffrandom plugin interface random, srandom and srandomdev functions of the Unix libc library.
The range is 0 − −231 − 1.
Note: If srandomdev is not defined, a seed based on the current time is used.
gsl plugin equally allows usage of all random functions of the gsllib, see gsl external library.
FE-functions
Finite element functions are also constructed like elementary functions by an arithmetic formula involving elementary
functions.
The difference is that they are evaluated at declaration time and FreeFEM stores the array of its values at the places
associated with he degree of freedom of the finite element type. By opposition, elementary functions are evaluated only
when needed. Hence FE-functions are not defined only by their formula but also by the mesh and the finite element
which enter in their definitions.
If the value of a FE-function is requested at a point which is not a degree of freedom, an interpolation is used, leading
to an interpolation error, while by contrast, an elementary function can be evaluated at any point exactly.
Warning: The plot command only works for real or complex FE-functions, not for elementary functions.
problem
Problem type.
1 Laplacian;
Note: Solver
A solver can be specified in the problem definition:
|𝜀|
||𝐴𝑥 − 𝑏|| <
||𝐴𝑥0 − 𝑏||
Note: Reconstruction
The keyword init controls the reconstruction of the internal problem matrix.
If init is set to false or 0, the matrix is reconstructed et each problem calls (or after a mesh modification), else the
previously constructed matrix is used.
Note: Preconditioning
A preconditioner can be specified in the problem definition:
Note: UMFPACK
Two specific parameters for the UMFPACK can be modifed:
Note: dimKrylov
Dimension of the Krylov space
solve
Solve type.
Identical to problem but automatically solved.
Usage of solve is detailled in the tutorials.
varf
4.1.7 Array
Note: Quantiles are points taken at regular intervals from the cumulative distribution function of a random variable.
Here the array values are random.
This statistical function a.quantile(q) computes 𝑣 from an array 𝑎 of size 𝑛 for a given number 𝑞 ∈]0, 1[ such that:
#{𝑖/𝑎[𝑖] < 𝑣} ∼ 𝑞 * 𝑛
1 int n = 5;
2 real[int] Ai(n);
3 for (int i = 0; i < n; i++)
4 Ai[i] = i;
5 cout << Ai << endl;
5
0 1 2 3 4
Array index
Array size
Array sort
To sort an array:
1 Ai.sort;
Double array
The minimum and maximum values of an array (simple or double) can be obtained using:
1 real[int] aU = U[];
Array of FE functions
It is also possible to make an array of FE functions, with the same syntax, and we can treat them as vector valued
function if we need them.
The syntax for space or vector finite function is
8 // Array of Array
9 real [int][int] V(10);
10 matrix[int] B(10);
11 real [int, int][int] A(10);
Tip: Example
In the following example, Poisson’s equation is solved for 3 different given functions 𝑓 = 1, sin(𝜋𝑥) cos(𝜋𝑦), |𝑥 −
1||𝑦 − 1|, whose solutions are stored in an array of FE function.
1 // Mesh
2 mesh Th = square(20, 20, [2*x, 2*y]);
3
4 // Fespace
5 fespace Vh(Th, P1);
6 Vh u, v, f;
7
8 // Problem
(continues on next page)
34 // Plot
35 for (int i = 0; i < 3; i++)
36 plot(uu[i], wait=true);
Map arrays
3 map["1"] = 2.0;
4 map[2] = 3.0; //2 is automatically cast to the string "2"
5
It is just a map of the standard template library so no operations on vector are allowed, except the selection of an item.
4.1.8 matrix
1 A = [I, J, C];
Note: I and J are int[int] and C is real[int]. The matrix is defined as:
∑︁
𝐴= 𝐶[𝑘]𝑀𝐼[𝑘],𝐽[𝑘]
𝑘
1 matrix<real> A = ...
2 matrix<complex> Ai = ...
Note: Solver
See problem.
The default solver is GMRES.
or
1 set(A , solver=sparsesolver);
Note: Factorize
If true, the factorization is done for LU, Cholesky or Crout.
or
Note: Preconditioning
See problem.
Note: UMFPACK
See problem.
Note: dimKrylov
See problem.
Note: datafilename
Name of the file containing solver parameters, see Parallel sparse solvers
Note: lparams
Vector of integer parameters for the solver, see Parallel sparse solvers
Note: dparams
Vector of real parameters for the solver, see Parallel sparse solvers
Note: sparams
Tip: To modify the solver, the stop test,. . . after the matrix construction, use the set keyword.
Matrix size
Matrix resize
1 A.resize(n, m);
Matrix diagonal
Matrix renumbering
Complex matrix
Use .im and .re to get the imaginary and real part of a complex matrix, respectively:
1 matrix<complex> C = ...
2 matrix R = C.re;
3 matrix I = C.im;
1 real d = A' * B;
1 matrix C = A * B'
Matrix inversion
4.2.1 area
4.2.2 ARGV
4.2.3 BoundaryEdge
1 real B = int2d(Th)(BoundaryEdge);
4.2.4 CG
Or in matrix construction
Or in set function
1 set(A, solver=CG);
4.2.5 Cholesky
Cholesky solver.
4.2.6 Crout
Crout solver.
4.2.7 edgeOrientation
4.2.8 false
1 bool b = false;
4.2.9 GMRES
4.2.10 hTriangle
4.2.11 include
1 include "iovtk"
4.2.12 InternalEdge
1 real I = int2d(Th)(InternalEdge);
4.2.13 label
4.2.14 lenEdge
4.2.15 load
Load a script.
1 load "Element_P3"
4.2.16 LU
LU solver.
4.2.17 N
Outward unit normal at the current point if it is on a curve defined by a border. N.x, N.y, N.z are respectively the
𝑥, 𝑦 and 𝑧 components of the normal.
1 func Nx = N.x;
2 func Ny = N.y;
3 func Nz = N.z;
4.2.18 nTonEdge
4.2.19 nuEdge
1 real nE = int2d(Th)(nuEdge);
4.2.20 nuTriangle
4.2.21 P
Current point.
1 real cx = P.x;
2 real cy = P.y;
3 real cz = P.z;
4.2.22 pi
Pi = 3.14159.
1 real Pi = pi;
4.2.23 region
Region number of the current point. If the point is outside, then region == notaregion where notaregion is a
FreeFEM integer constant.
4.2.24 sparsesolver
4.2.25 true
1 bool b = true;
4.2.26 verbosity
Verbosity level.
4.2.27 version
FreeFEM version.
4.2.28 volume
4.2.29 x
1 real CurrentX = x;
4.2.30 y
1 real CurrentY = y;
4.2.31 z
1 real CurrentZ = z;
4.3.1 int1d
Notations
𝑓 (x) = 𝑔(𝑡)
qf1pE
or
qf2pE
or
qf3pE (default)
or
qf4pE
or
qf5pE
or
qf1pElump
|𝐷|
∫︁
𝑓 (x) ≈ (𝑔 (0) + 𝑔 (1))
𝐷 2
4.3.2 int2d
qf1pT
or
qf2pT
or
qf5pT (default)
or
qf1pTlump
qf2pT4P1
qf7pT
or
qf9pT
or
4.3.3 int3d
qfV1
or
qfV2
or
qfV5 (default)
or
qfV1lump
4.4 Operators
1 real a = 1. + 2.;
Pre-increment:
1 int i = 0;
2 ++i;
Post-increment:
1 int i = 0;
2 i++;
1 real a = 1. - 2.;
Pre-decrement:
1 int i = 0;
2 --i;
Post-decrement:
1 int i = 0;
2 i--;
1 real[int] b;
2 matrix A
3 real[int] x = A^-1*b;
1 real a = 1.;
1 real a = 1.;
2 real b = 1.;
3
1 real a = 1.;
2 real b = 2.;
3
1 real a = 1.;
2 real b = 2.;
3
1 real a = 3.;
2 real b = 2.;
3
1 real a = 1;
2 a += 2.;
3 a -= 1.;
4 a *= 3.;
5 a /= 2.;
1 matrix A = B .* C;
1 real a = 1. / 2.;
1 matrix A = B ./ C;
1 int a = 1 % 2;
1 real a = 2.^2;
Warning: This operator can not be used to directly create a matrix, see Matrix inversion.
1 real[int] a = b';
Note: For matrix<complex>, the ::freefem`’` operator return the Hermitian tranpose.
3 cout << a << " + " << b << " = " << a + b << endl;
4 cout << a << " - " << b << " = " << a - b << endl;
5 cout << a << " * " << b << " = " << a * b << endl;
6 cout << a << " / " << b << " = " << a / b << endl;
7 cout << a << " % " << b << " = " << a % b << endl;
(continues on next page)
12 + 5 = 17
12 - 5 = 7
12 * 5 = 60
12 / 5 = 2
12 % 5 = 2
12 ^ 5 = 248832
( 12 < 5 ? 12 : 5) = 5
3 cout << a << " + " << b << " = " << a + b << endl;
4 cout << a << " - " << b << " = " << a - b << endl;
5 cout << a << " * " << b << " = " << a * b << endl;
6 cout << a << " / " << b << " = " << a / b << endl;
7 cout << a << " % " << b << " = " << a % b << endl;
8 cout << a << " ^ " << b << " = " << a ^ b << endl;
9 cout << "( " << a << " < " << b << " ? " << a << " : " << b << ") = " << (a < b ? a : b)
˓→<< endl;
4.5 Loops
4.5.1 for
For loop.
4.5.2 if
If condition.
1 if (condition){
2 ...
3 }
4 else{
5 ...
6 }
4.5.3 else
See if .
4.5.4 while
While loop.
1 while (condition){
2 ...
3 }
4.5.5 continue
Continue a loop.
4.5.6 break
Break a loop.
1 while (condition1){
2 ...
3 if (condition) break;
4 ...
5 }
4.5.7 try
1 try{
2 ...
3 }
4 catch(...){
5 ...
6 }
4.5.8 catch
1 for [i, ai : a]
If real[int] a(10, 11), then i=0:9, j=1:10 and aij is a reference to a(i, j).
See Implicit loop example.
4.6 I/O
4.6.1 cout
4.6.2 cin
4.6.3 endl
End of line.
4.6.4 ifstream
1 ifstream file("file.txt");
4.6.5 ofstream
1 ofstream file("file.txt");
4.6.6 append
4.6.7 binary
4.6.8 seekg
1 file.seekg(Pos);
4.6.9 tellg
4.6.10 flush
1 file.flush
4.6.11 getline
1 string s;
2 getline(file, s);
precision
Set the number of digits printed to the right of the decimal point. This applies to all subsequent floating point numbers
written to that output stream. However, this won’t make floating-point “integers” print with a decimal point. It’s
necessary to use fixed for that effect.
1 int np = f.precision(n)
scientific
1 f.scientific
fixed
1 f.fixed
showbase
Converts insertions to an external form that can be read according to the C++ lexical conventions for integral constants.
By default, showbase is not set.
1 f.showbase
noshowbase
1 f.noshowbase
showpos
Inserts a plus sign (+) into a decimal conversion of a positive integral value.
1 f.showpos
noshowpos
1 f.noshowpos
default
1 f.default
setw
Behaves as if member width were called with n as argument on the stream on which it is inserted as a manipulator (it
can be inserted on output streams).
1 f.setw(n)
4.7 Functions
4.7.1 abs
1 real a = abs(b);
Parameters:
• b (int, real, complex, fespace function, real[int] or real[int, int])
Output:
• a (int, real, real[int] or real[int, int])
4.7.2 acos
arccos function.
Parameter:
• x (real, real[int] or real[int, int])
Output:
• theta (real, real[int] or real[int, int])
4.7.3 acosh
4.7.4 adaptmesh
1 mesh Thnew = adaptmesh(Th, [fx, fy], hmin=HMin, hmax=HMax, err=Err, errg=ErrG, nbvx=NbVx,
˓→ nbsmooth=NbSmooth, nbjacoby=NbJacoby, ratio=Ratio, omega=Omega, iso=Iso,␣
˓→periodic=Periodic);
Parameters:
• Th (mesh) Mesh to refine
• [fx, fy] (func or fespace function), scalar or vectorial Function to follow for the mesh adaptation
• hmin= (real) Minimum edge size
• hmax= (real) Maximum edge size
• err= (real) Error level (P1 interpolation)
• errg= (real) Relative geometrical error
• nbvx= (int) Maximum number of vertices
• nbsmooth= (int) Number of smoothing iterations
• nbjacoby= (int) Number of iterations for the smoothing procedure
• ratio= (real) Ratio of the triangles
• omega= (real) Relaxation parameter for the smoothing procedure
• iso= (bool) Isotropic adaptation (if true)
• abserror= (bool) Error (if true) - Relative error (if false)
• cutoff= (real) Lower limit of the relative error evaluation
• verbosity= (real) Verbosity level
• inquire= (bool) If true, inquire graphically
• splitpbedge= (bool) If true, split all internal edges in half
• maxsubdiv= (int) Bound the maximum subdivisions
• rescaling= (bool) Rescale the function in [0, 1]
• keepbackvertices= (bool) If true, try to keep vertices of the original mesh
• IsMetric= (bool) If true, the metric is defined explicitly
• power= (int) Exponent of the Hessian
• thetamax= (int) Minimum corner angle (in degree)
• splitin2= (bool) Split all triangles into 4 sub-triangles if true
• metric= ([real[int], real[int], real[int]]) Array of 3 real arrays defining the metric
• nomeshgeneration= (bool) If true, the mesh is not generated
• periodic= (real[int, int]) Build an adapted periodic mesh
Output:
• Thnew (mesh or mesh3)
4.7.5 adj
1 int T = Th[k].adj(e);
Parameter:
• e (int) Edge number
Output:
• T (int) Triangle number
4.7.6 AffineCG
Parameters:
• A (matrix) Matrix of the problem 𝐴𝑥 = 𝑏
• x (real[int]) Solution vector
• precon= (real[int]) Preconditionning function
• nbiter= (int) Maximum number of iterations
• eps= (real)
Convergence criterion
If 𝜀 > 0: test ||𝐴(𝑥)||𝑝 ≤ 𝜖||𝐴(𝑥0 )||𝑝
If 𝜀 < 0: test ||𝐴(𝑥)||2𝑝 ≤ |𝜖|
• veps= (real) Same as eps, but return -eps
• stop= (func) Convergence criterion as a function
Prototype is func bool StopFunc (int Iter, real[int] U, real[int] g)
u: current solution, g: current gradient (not preconditionned)
Output:
• Conv (int) 0: converged - !0: not converged
4.7.7 AffineGMRES
4.7.8 arg
1 real a = arg(c);
Parameters:
• c (complex)
Output:
• r (real)
4.7.9 asin
arcsin function.
Parameter:
• x (real, real[int] or real[int, int])
Output:
• theta (real, real[int] or real[int, int])
4.7.10 asinh
4.7.11 assert
1 assert(x==0)
Parameter:
• Boolean condition
Output:
• None
4.7.12 atan
arctan function.
Parameter:
• x (real)
Output:
• theta (real)
4.7.13 atan2
(︁ 𝑦 )︁
arctan function, returning the correct sign for 𝜃.
𝑥
1 real theta = atan2(y, x)
Parameter:
• x (real)
Output:
• theta (real)
4.7.14 atanh
Parameter:
• x (real)
Output:
• theta (real)
4.7.15 atoi
1 int a = atoi(s);
Parameter:
• s (string)
Output:
• a (int)
4.7.16 atof
1 real a = atof(s);
Parameter:
• s (string)
Output:
• a (real)
4.7.17 BFGS
Todo: todo
4.7.18 buildmesh
Parameters:
• b1, b2, b3, b4 (border)
Geometry border, b1(nn) means b1 border discretized by nn vertices
• points (real[int, int]) [Optional]
Specify a set of points
The size of Points array is (nbp, 2), containing a set of nbp points with x and y coordinates
• nbvx= (int) [Optional]
Maximum number of vertices Default: 9000
• fixedborder= (bool) [Optional]
If true, mesh generator cannot change the boundary mesh
Default: false
Output:
• Th (mesh) Resulting mesh
4.7.19 ceil
Round fractions up of 𝑥.
1 int c = ceil(x);
Parameter:
• x (real)
Output:
• c (int)
4.7.20 change
Parameters:
• Th (mesh) Original mesh
• label= L (int[int]) Pair of old and new label
• region= R (int[int]) Pair of old and new region
• flabel= l (func int) Function of int given the new label
• fregion= r (func int) Function of int given the new region
Output:
• Thnew (mesh) Mesh with changed parameters
4.7.21 checkmovemesh
Parameters:
Same as movemesh
Output:
• minT (real) Minimum triangle area
4.7.22 chi
Parameters:
• Th (mesh or mesh3)
• x (real) Position 𝑥
• y (real) Position 𝑦
Output:
• IsInMesh (int) 1 if (𝑥, 𝑦) ∈ Th 0 if (𝑥, 𝑦) ̸∈ Th
4.7.23 clock
1 real t = clock();
Parameter:
• None
Output:
• t (real) Current CPU time
4.7.24 complexEigenValue
4.7.25 conj
1 complex C1 = 1 + 1i;
2 complex C2 = conj(C1);
Parameter:
• C1 (complex) Complex number
Output:
• C2 (complex) Conjuguate of C1
4.7.26 convect
ẋ𝜏 = u(x𝜏 )
x𝜏 = x
Parameters:
• ux (fespace function) Velocity: 𝑥 component
• uy (fespace function) Velocity: 𝑦 component
• uz (fespace function) 3D only
Velocity: 𝑧 component
• dt (real) Time step
• c (fespace function) Function to convect
Output:
• cgm (real) Result
4.7.27 copysign
4.7.28 cos
cos function.
1 real x = cos(theta);
Parameters:
• theta (real or complex)
Output:
• x (real or complex)
4.7.29 cosh
cosh function.
1 real x = cosh(theta);
𝑒𝑥 + 𝑒−𝑥
cosh(𝑥) =
2
Parameters:
• theta (real)
Output:
• x (real)
4.7.30 diffnp
4.7.31 diffpos
4.7.32 dist
4.7.33 dumptable
1 dumptable(out);
Parameters:
• out (ostream) cout of ofstream file.
Output:
• None
4.7.34 dx
𝑥 derivative.
1 Uh up = dx(u);
𝜕𝑢
𝜕𝑥
Parameters:
• u (fespace function)
Output:
• up (fespace function)
4.7.35 dxx
𝑥 double derivative.
1 Uh upp = dxx(u);
𝜕2𝑢
𝜕𝑥2
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.36 dxy
𝑥𝑦 derivative.
1 Uh upp = dxy(u);
𝜕2𝑢
𝜕𝑥𝜕𝑦
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.37 dxz
𝑥𝑧 derivative.
1 Uh upp = dxz(u);
𝜕2𝑢
𝜕𝑥𝜕𝑧
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.38 dy
𝑦 derivative.
1 Uh up = dy(u);
𝜕𝑢
𝜕𝑦
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.39 dyx
𝑦𝑥 derivative.
1 Uh upp = dyx(u);
𝜕2𝑢
𝜕𝑦𝜕𝑥
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.40 dyy
𝑦 double derivative.
1 Uh upp = dyy(u);
𝜕2𝑢
𝜕𝑥2
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.41 dyz
𝑦𝑧 derivative.
1 Uh upp = dyz(u);
𝜕2𝑢
𝜕𝑦𝜕𝑧
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.42 dz
𝑧 derivative.
1 Uh up = dz(u);
𝜕𝑢
𝜕𝑧
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.43 dzx
𝑧𝑥 derivative.
1 Uh upp = dzx(u);
𝜕2𝑢
𝜕𝑧𝜕𝑥
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.44 dzy
𝑧𝑦 derivative.
1 Uh upp = dzy(u);
𝜕2𝑢
𝜕𝑧𝜕𝑦
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.45 dzz
𝑧 double derivative.
1 Uh upp = dzz(u);
𝜕2𝑢
𝜕𝑧 2
Parameters:
• u (fespace function)
Output:
• upp (fespace function)
4.7.46 EigenValue
Compute the generalized eigenvalue of 𝐴𝑢 = 𝜆𝐵𝑢. The shifted-inverse method is used by default with sigma=𝜎, the
shift of the method. The function EigenValue can be used for either matrices or functions returning a matrix vector
product. The use of the matrix version is shown below.
Parameters:
• A, B: matrices of same size
• nev=n: number of desired eigenvalues given by an integer n
• sym=: the problem is symmetric or not
• tol=: the relative accuracy to which eigenvalues are to be determined
• value=: an array to store the real part of the eigenvalues
• ivalue=: an array to store the imaginary part of the eigenvalues
• vector=: a Finite Element function array to store the eigenvectors
• sigma=: the shift value
• Other parameters are available for more advanced use: see the ARPACK documentation.
Output: The output is the number of converged eigenvalues, which can be different than the number of requested
eigenvalues given by nev=. Note that the eigenvalues and the eigenvectors are stored for further purposes using the
parameters value= and vector=.
4.7.47 emptymesh
Parameters:
• Th (mesh) Mesh to empty
• ssd (int[int]) Pseudo subregion label
Output:
• eTh (mesh) Empty mesh
4.7.48 erf
Parameters:
• x (real)
Output:
• err (real)
4.7.49 erfc
Parameters:
• x (real)
Output:
• err (real)
4.7.50 exec
1 int v = exec(command);
Parameters:
• command (string) Command to execute
Output:
• v (int) Value returned by the command
4.7.51 exit
1 exit(N);
Parameters:
• N (int) Return value
Output:
• None
4.7.52 exp
Exponential function.
1 real a = exp(b);
Parameters:
• b (real or complex)
Output:
• a (real or complex)
4.7.53 fdim
Parameters:
• a (real)
• b (real)
Output:
• fd (real) If 𝑥 > 𝑦, return 𝑥 − 𝑦If 𝑥 ≤ 𝑦, return 0
4.7.54 floor
Floor function.
1 real a = floor(b);
4.7.55 fmax
Parameters:
• a (real)
• b (real)
Output:
• Max (real)
4.7.56 fmin
Parameters:
• a (real)
• b (real)
Output:
• Min (real)
4.7.57 fmod
Parameters:
• a (real)
• b (real)
Output:
• Mod (real)
4.7.58 imag
1 complex c = 1. + 1i;
2 real Im = imag(c);
4.7.59 int1d
1D integral.
Used in problem, solve or varf definition to impose a boundary condition only (FreeFEM does not support 1D simu-
lation), or outside to calculate a quantity.
Parameters:
• Th (mesh) Mesh where the integral is calculated
• Label (int) [Optional]
Label of the 1D border Default: all borders of the mesh
• qfe= (quadrature formula) [Optional] (qf3E by default)
Quadrature formula, see quadrature formulae
• qforder= (quadrature formula) [Optional]
Quadrature order, see quadrature formulae
Output:
• Depending on the situation: In a problem, solve or varf definition: Non relevant.
Outside: real (example: real l = int1d(Th, 1)(1.);).
Warning: In a problem, solve or varf definition, the content of int1d must be a linear or bilinear form.
4.7.60 int2d
2D integral.
Or
Used in problem, solve or varf definition to: - Calculate integral in 2D simulation - Impose a boundary condition in
3D simulation Or outside to calculate a quantity.
Parameters:
• Th (mesh, mesh3 , meshS`or :freefem:`meshL) Mesh where the integral is calculated
• Region (int) [Optional] Label of the 2D region (2D simulation or Surface simulation) Default: all regions of
the mesh
• Label (int) [Optional] Label of the 2D border (3D simulation) Default: all borders of the mesh
• qft= (quadrature formula) [Optional] (qf5T by default)
Quadrature formula, see quadrature formulae
• qforder= (quadrature formula) [Optional]
Quadrature order, see quadrature formulae
Output:
• Depending on the situation: In a problem, solve or varf definition: Non relevant. Outside: real (example:
real s = int2d(Th, 1)(1.);).
Warning: In a problem, solve or varf definition, the content of the int2d must be a linear or bilinear form.
4.7.61 int3d
3D integral.
Used in problem, solve or varf definition to calculate integral in 3D simulation, or outside to calculate a quantity.
Parameters:
• Th (mesh3) Mesh where the integral is calculated
• Region (int) [Optional]
Label of the 3D region
Default: all regions of the mesh
• qfV= (quadrature formula) [Optional] (qf5V by default)
Quadrature formula, see quadrature formulae
• qforder= (quadrature formula) [Optional]
Quadrature order, see quadrature formulae
Output:
• Depending on the situation: In a problem, solve or varf definition: Non relevant. Outside: real (example:
real v = int3d(Th, 1)(1.);).
Warning: In a problem, solve or varf definition, the content of the int3d must be a linear or bilinear form.
4.7.62 intalledges
1 intalledges(Th, [Region])(
2 ...
3 )
Parameters:
• Th (mesh) Mesh where the integral is calculated
• Region (int) [Optional]
Label of the region
Default: all regions of the mesh
Output:
• Non relevant
4.7.63 intallfaces
4.7.64 interpolate
Parameters:
• Wh (fespace) Target finite element space
• Vh (fespace) Original finite element space
• inside= (bool) If true, create a zero extension outside the Vh domain
• t= (bool) If true, return the transposed matrix
• op= (int) 0: interpolate the function (default value) 1: interpolate 𝜕𝑥 2: interpolate 𝜕𝑦 3: interpolate 𝜕𝑧
• U2Vc= (int[int]) Array of the same size of Wh describing which component of Vhis interpolated in Wh
Output:
• I (matrix) Interpolation matrix operator
4.7.65 invdiff
4.7.66 invdiffnp
4.7.67 invdiffpos
4.7.68 isInf
1 bool b = isInf(a);
4.7.69 isNaN
1 bool b = isNaN(a);
4.7.70 isNormal
4.7.71 j0
1 real b = j0(x);
Parameters:
• x (real)
Output:
• b (real)
4.7.72 j1
1 real b = j1(x);
Parameters:
• x (real)
Output:
• b (real)
4.7.73 jn
Parameters:
• n (int)
• x (real)
Output:
• b (real)
4.7.74 jump
1 intalledges(
2 ... jump(c) ...
3 )
Parameters:
• c (fespace function) Discontinuous function
Output:
• Non relevant
4.7.75 LinearCG
Linear CG solver
Parameters and output are the same as AffineCG
4.7.76 LinearGMRES
4.7.77 lgamma
1 real lg = lgamma(x);
Parameters:
• x (real)
Output:
• lg (real)
4.7.78 log
Natural logarithm.
1 real l = log(x);
Parameters:
• x (real or complex)
Output:
• l (real or complex)
4.7.79 log10
Common logarithm.
1 real l = log10(x);
Parameters:
• x (real)
Output:
• l (real)
4.7.80 lrint
1 int l = lrint(a);
Parameters:
• a (real)
Output:
• l (int)
4.7.81 lround
1 int l = lround(a);
Parameters:
• a (real)
Output:
• l (int)
4.7.82 ltime
1 int t = ltime();
Parameter:
• None
Output:
• t (int)
4.7.83 max
Parameters:
• a (int or real)
• b (int or real)
• c (int or real) [Optional]
• d (int or real) [Optional]
Output:
• b (int or real)
4.7.84 min
Parameters:
• a (int or real)
• b (int or real)
• c (int or real) [Optional]
• d (int or real) [Optional]
Output:
• b (int or real)
4.7.85 movemesh
Move a mesh.
Parameters:
• Th (mesh of mesh3) Mesh to move
• Dx (fespace function) Displacement along 𝑥
• Dy (fespace function) Displacement along 𝑦
4.7.86 NaN
1 real n = NaN([String]);
Parameters:
• String (string) Default: ""
4.7.87 NLCG
4.7.88 on
1 problem (u, v)
2 ...
3 + on(Label, u=uD)
4 ...
Parameters:
4.7.89 plot
Note: Only one of Th, u or [Ux, Uy] / [Ux, Uy, Uz] is needed for the plot command.
Parameters:
• Th (mesh or mesh3) Mesh to display
• u (fespace function) Scalar fespace function to display
• [Ux, Uy] / [Ux, Uy, Uz] (fespace function array) Vectorial fespace function to display
• [Ux, Uy] ([real[int], real[int]]) Couple a real array to display a curve
• wait= (bool) If true, wait before continue
• ps= (string) Name of the file to save the plot (.ps or .eps format)
• coef= (real) Arrow size
• fill= (bool) If true, fill color between isovalue (usable with scalar fespace function only)
• cmm= (string) Text comment in the graphic window
• value= (bool) If true, show the value scale
• aspectratio= (bool) If true, preserve the aspect ratio
• bb= ([real[int], real[int]]) Specify a bounding box using two corner points
• nbiso= (int) Number of isovalues
• nbarrow= (int) Number of colors of arrows values
• viso= (real[int]) Specify an array of isovalues
• varrow= (real[int]) Specify an array of arrows values color
• bw= (bool) If true, the plot is in black and white
• grey= (bool) If true, the plot is in grey scale
• hsv= (real[int]) Array of 3 × 𝑛 values defining HSV color model [ℎ1 , 𝑠1 , 𝑣1 , ..., ℎ𝑛 , 𝑠𝑛 , 𝑣𝑛 ]
• boundary= (bool) If true, display the boundary of the domain
4.7.90 polar
Polar coordinates.
Parameters:
• a (real)
• b (real)
Output:
• p (complex)
4.7.91 pow
Power function.
𝑝 = 𝑎𝑏
Parameters:
• a (real)
• b (real)
Output:
• p (real)
4.7.92 projection
1 projection(a, b, x) = min(max(a, x), b)*(a < b) + min(max(b, x), a)*(1-(a < b));
Parameters:
• a (real)
• b (real)
• x (real)
Output:
• p (real)
4.7.93 randinit
1 randinit(seed);
Parameters:
• seed (int)
Output:
• None
4.7.94 randint31
1 int r = randint31();
Parameters:
• None
Output:
• r (int)
4.7.95 randint32
1 int r = randint32();
Parameters:
• None
Output:
• r (int)
4.7.96 randreal1
1 real r = randreal1();
Parameters:
• None
Output:
• r (real)
4.7.97 randreal2
1 real r = randreal2();
Parameters:
• None
Output:
• r (real)
4.7.98 randreal3
1 real r = randreal3();
Parameters:
• None
Output:
• r (real)
4.7.99 randres53
1 real r = randres53();
Parameters:
• None
Output:
• r (real)
4.7.100 readmesh
1 mesh Th = readmesh(MeshFileName);
Parameters:
• MeshFileName (string)
Output:
• Th (mesh)
4.7.101 readmesh3
1 mesh3 Th = readmesh3(MeshFileName);
Parameters:
• MeshFileName (string)
Output:
• Th (mesh3)
4.7.102 real
1 real r = real(c);
Parameters:
• c (complex)
Output:
• r (real)
4.7.103 rint
1 real r = rint(a);
Parameters:
• a (real)
Output:
• r (real)
4.7.104 round
1 real r = round(a);
Parameters:
• a (real)
Output:
• r (real)
4.7.105 savemesh
Save a 2D or 3D mesh in different formats (see Mesh Generation 2D and Mesh Generation 3D).
1 savemesh(Th, MeshFileName);
Parameters:
• Th (mesh or mesh3)
• MeshFileName (string)
Output:
• None
4.7.106 set
4.7.107 sign
Sign of a value.
1 int s = sign(a);
Parameters:
• a (real or int)
Output:
• s (int)
4.7.108 signbit
1 int s = signbit(a);
4.7.109 sin
sin function.
1 real x = sin(theta);
Parameter:
• theta (real or complex)
Output:
• x (real or complex)
4.7.110 sinh
sinh function.
1 real x = sinh(theta);
𝑒𝑥 − 𝑒−𝑥
sinh(𝑥) =
2
Parameter:
• theta (real)
Output:
• x (real)
4.7.111 sort
1 sort(A, B);
Parameters:
• A (real[int])
• B (int[int])
Output:
• None
A is sorted in ascending order, B is sorted as A.
4.7.112 splitmesh
1 Th = splitmesh(Th0, f);
Parameters:
• Th0 (mesh)
• f (func or fespace function)
Output:
• Th (mesh)
4.7.113 sqrt
Square root
1 real s = sqrt(a);
Parameter:
• a (real)
Output:
• s (real)
4.7.114 square
1. Square of a number.
1 real S = square(a);
Parameter:
• a (real)
Output:
• S (real)
2. Build a structured square mesh.
Parameters:
• nnX (int) Discretization along 𝑥
• nnY (int) Discretization along 𝑦
• L (real) [Optional] Length along 𝑥
• H (real) [Optional] Height along 𝑦
• flags= (int) [Optional]
• label= (int[int]) [Optional]
• region= (int) [Optional]
Structured mesh type, see Mesh Generation chapter for more information
Output:
• Th (mesh)
4.7.115 storagetotal
4.7.116 storageused
4.7.117 strtod
Parameter:
• text (string)
Output:
• number (real)
4.7.118 strtol
Parameter:
• text (string)
• base (int) Base [Optional]
Output:
• number (int)
4.7.119 swap
Swap values.
1 swap(a, b);
Parameters:
• a (real)
• b (real)
Output:
• None
4.7.120 system
Parameter:
• Command (string) System command
Output:
• Res (int) Value returned by the system command
Note: On Windows, the full path of the command is needed. For example, to execute ls.exe:
4.7.121 tan
tan function.
1 real x = tan(theta);
Parameter:
• theta (real)
Output:
• x (real)
4.7.122 tanh
tanh function.
1 real x = tanh(theta);
Parameter:
• theta (real)
Output:
• x (real)
4.7.123 tgamma
1 real tg = tgamma(x);
Parameter:
• x (real)
Output:
• tg (real)
4.7.124 time
1 real t = time();
Parameter:
• None
Output:
• t (real)
4.7.125 trace
Matrix trace
Parameters:
• Matrix
Output:
• Trace of the matrix (real)
4.7.126 trunc
Parameters:
• Th0 (mesh)
• R (bool or int) Split triangles where R is true or different from 0
• split= (int) [Optional]
Level of splitting Default: 1
• label= (int) [Optional]
Label number of new boundary item Default: 1
Output:
• Th (mesh)
4.7.127 y0
1 real B = y0(x);
Parameters:
• x (real)
Output:
• b (real)
4.7.128 y1
1 real B = y1(x);
Parameters:
• x (real)
Output:
• b (real)
4.7.129 yn
4.8.1 aniso
boundaniso
Todo: todo
4.8.2 BEC
BECtrap
Todo: todo
GPvortex
Todo: todo
dxGPVortex
Todo: todo
dyGPVortex
Todo: todo
LoadVec
Todo: todo
LoadFlag
Todo: todo
SaveVec
Todo: todo
flag
Todo: todo
4.8.4 buildlayer
buildlayers
Todo: todo
4.8.5 ClosePoints
radiusSearch
Todo: todo
Voisinage
Todo: todo
neighborhood
Todo: todo
ClosePoints2
Todo: todo
ClosePoint
Todo: todo
ClosePoints1
Todo: todo
4.8.6 Curvature
extractborder
Parameters:
• Th (mesh or mesh3)
• Label (int) Label of the border to extract
• Points (real[int, int]) Extracted points Must be allocated as real[int, int] Points(3, 1);
Output:
• Res (real) Length of the extracted border
curvature
Todo: todo
raxicurvature
Todo: todo
curves
Todo: todo
setecurveabcisse
Todo: todo
equiparameter
Todo: todo
Tresca
Todo: todo
VonMises
Todo: todo
4.8.7 dfft
plandfft
Todo: todo
execute
Todo: todo
delete
Todo: todo
dfft
Todo: todo
map
Todo: todo
4.8.8 distance
Need
1 load "distance"
distance
Parameters:
• Th (mesh)
• d
• dist (real[int])
Output:
•
Todo: todo
checkdist
Todo: todo
4.8.9 DxWriter
Dxaddmesh
Todo: todo
Dxaddtimeseries
Todo: todo
Dxaddsol2ts
Todo: todo
4.8.10 Element_P1bl
expert
Todo: todo
4.8.11 exactpartition
exactpartition
Todo: todo
4.8.12 ff-AiryBiry
airy
Todo: todo
biry
Todo: todo
4.8.13 ff-cmaes
cmaes
Todo: todo
4.8.14 ff_gsl_awk
gslcdfugaussianP
Link to:
1 gsl_cdf_ugaussian_P(a)
gslcdfugaussianQ
Link to:
1 gsl_cdf_ugaussian_Q(a)
gslcdfugaussianPinv
Link to:
1 gsl_cdf_ugaussian_Pinv(a)
gslcdfugaussianQinv
Link to:
1 gsl_cdf_ugaussian_Qinv(a)
gslcdfgaussianP
Link to:
1 gsl_cdf_gaussian_P(a, b)
gslcdfgaussianQ
Link to:
1 gsl_cdf_gaussian_Q(a, b)
gslcdfgaussianPinv
Link to:
1 gsl_cdf_gaussian_Pinv(a, b)
gslcdfgaussianQinv
Link to:
1 gsl_cdf_gaussian_Qinv(a, b)
gslcdfgammaP
Link to:
1 gsl_cdf_gamma_P(a, b, c)
gslcdfgammaQ
Link to:
1 gsl_cdf_gamma_Q(a, b, c)
gslcdfgammaPinv
Link to:
1 gsl_cdf_gamma_Pinv(a, b, c)
gslcdfgammaQinv
Link to:
1 gsl_cdf_gamma_Pinv(a, b, c)
gslcdfcauchyP
Link to:
1 gsl_cdf_cauchy_P(a, b)
gslcdfcauchyQ
Link to:
1 gsl_cdf_cauchy_Q(a, b)
gslcdfcauchyPinv
Link to:
1 gsl_cdf_cauchy_Pinv(a, b)
gslcdfcauchyQinv
Link to:
1 gsl_cdf_cauchy_Qinv(a, b)
gslcdflaplaceP
Link to:
1 gsl_cdf_lapalce_P(a, b)
gslcdflaplaceQ
Link to:
1 gsl_cdf_lapalce_Q(a, b)
gslcdflaplacePinv
Link to:
1 gsl_cdf_lapalce_Pinv(a, b)
gslcdflaplaceQinv
Link to:
1 gsl_cdf_lapalce_Qinv(a, b)
gslcdfrayleighP
Link to:
1 gsl_cdf_rayleigh_P(a, b)
gslcdfrayleighQ
Link to:
1 gsl_cdf_rayleigh_Q(a, b)
gslcdfrayleighPinv
Link to:
1 gsl_cdf_rayleigh_Pinv(a, b)
gslcdfrayleighQinv
Link to:
1 gsl_cdf_rayleigh_Qinv(a, b)
gslcdfchisqP
Link to:
1 gsl_cdf_chisq_P(a, b)
gslcdfchisqQ
Link to:
1 gsl_cdf_chisq_Q(a, b)
gslcdfchisqPinv
Link to:
1 gsl_cdf_chisq_Pinv(a, b)
gslcdfchisqQinv
Link to:
1 gsl_cdf_chisq_Qinv(a, b)
gslcdfexponentialP
Link to:
1 gsl_cdf_exponential_P(a, b)
gslcdfexponentialQ
Link to:
1 gsl_cdf_exponential_Q(a, b)
gslcdfexponentialPinv
Link to:
1 gsl_cdf_exponential_Pinv(a, b)
gslcdfexponentialQinv
Link to:
1 gsl_cdf_exponential_Qinv(a, b)
gslcdfexppowP
Link to:
1 gsl_cdf_exppow_P(a, b, c)
gslcdfexppowQ
Link to:
1 gsl_cdf_exppow_Q(a, b, c)
gslcdftdistP
Link to:
1 gsl_cdf_t_dist_P(a, b)
gslcdftdistQ
Link to:
1 gsl_cdf_t_dist_Q(a, b)
gslcdftdistPinv
Link to:
1 gsl_cdf_t_dist_Pinv(a, b)
gslcdftdistQinv
Link to:
1 gsl_cdf_t_dist_Qinv(a, b)
gslcdffdistP
Link to:
1 gsl_cdf_fdist_P(a, b, c)
gslcdffdistQ
Link to:
1 gsl_cdf_fdist_Q(a, b, c)
gslcdffdistPinv
Link to:
1 gsl_cdf_fdist_Pinv(a, b, c)
gslcdffdistQinv
Link to:
1 gsl_cdf_fdist_Qinv(a, b, c)
gslcdfbetaP
Link to:
1 gsl_cdf_beta_P(a, b, c)
gslcdfbetaQ
Link to:
1 gsl_cdf_beta_Q(a, b, c)
gslcdfbetaPinv
Link to:
1 gsl_cdf_beta_Pinv(a, b, c)
gslcdfbetaQinv
Link to:
1 gsl_cdf_beta_Qinv(a, b, c)
gslcdfflatP
Link to:
1 gsl_cdf_flat_P(a, b, c)
gslcdfflatQ
Link to:
1 gsl_cdf_flat_Q(a, b, c)
gslcdfflatPinv
Link to:
1 gsl_cdf_flat_Pinv(a, b, c)
gslcdfflatQinv
Link to:
1 gsl_cdf_flat_Qinv(a, b, c)
gslcdflognormalP
Link to:
1 gsl_cdf_lognormal_P(a, b, c)
gslcdflognormalQ
Link to:
1 gsl_cdf_lognormal_Q(a, b, c)
gslcdflognormalPinv
Link to:
1 gsl_cdf_lognormal_Pinv(a, b, c)
gslcdflognormalQinv
Link to:
1 gsl_cdf_lognormal_Qinv(a, b, c)
gslcdfgumbel1P
Link to:
1 gsl_cdf_gumbel1_P(a, b, c)
gslcdfgumbel1Q
Link to:
1 gsl_cdf_gumbel1_Q(a, b, c)
gslcdfgumbel1Pinv
Link to:
1 gsl_cdf_gumbel1_Pinv(a, b, c)
gslcdfgumbel1Qinv
Link to:
1 gsl_cdf_gumbel1_Qinv(a, b, c)
gslcdfgumbel2P
Link to:
1 gsl_cdf_gumbel2_P(a, b, c)
gslcdfgumbel2Q
Link to:
1 gsl_cdf_gumbel2_Q(a, b, c)
gslcdfgumbel2Pinv
Link to:
1 gsl_cdf_gumbel2_Pinv(a, b, c)
gslcdfgumbel2Qinv
Link to:
1 gsl_cdf_gumbel2_Qinv(a, b, c)
gslcdfweibullP
Link to:
1 gsl_cdf_weibull_P(a, b, c)
gslcdfweibullQ
Link to:
1 gsl_cdf_weibull_Q(a, b, c)
gslcdfweibullPinv
Link to:
1 gsl_cdf_weibull_Pinv(a, b, c)
gslcdfweibullQinv
Link to:
1 gsl_cdf_weibull_Qinv(a, b, c)
gslcdfparetoP
Link to:
1 gsl_cdf_pareto_P(a, b, c)
gslcdfparetoQ
Link to:
1 gsl_cdf_pareto_Q(a, b, c)
gslcdfparetoPinv
Link to:
1 gsl_cdf_pareto_Pinv(a, b, c)
gslcdfparetoQinv
Link to:
1 gsl_cdf_pareto_Qinv(a, b, c)
gslcdflogisticP
Link to:
1 gsl_cdf_logistic_P(a, b)
gslcdflogisticQ
Link to:
1 gsl_cdf_logistic_Q(a, b)
gslcdflogisticPinv
Link to:
1 gsl_cdf_logistic_Pinv(a, b)
gslcdflogisticQinv
Link to:
1 gsl_cdf_logistic_Qinv(a, b)
gslcdfbinomialP
Link to:
1 gsl_cdf_binomial_P(a, b, c)
gslcdfbinomialQ
Link to:
1 gsl_cdf_binomial_Q(a, b, c)
gslcdfpoissonP
Link to:
1 gsl_cdf_poisson_P(a, b)
gslcdfpoissonQ
Link to:
1 gsl_cdf_poisson_Q(a, b)
gslcdfgeometricP
Link to:
1 gsl_cdf_geometric_P(a, b)
gslcdfgeometricQ
Link to:
1 gsl_cdf_geometric_Q(a, b)
gslcdfnegativebinomialP
Link to:
1 gsl_cdf_negative_binomial_P(a, b, c)
gslcdfnegativebinomialQ
Link to:
1 gsl_cdf_negative_binomial_Q(a, b, c)
gslcdfpascalP
Link to:
1 gsl_cdf_pascal_P(a, b, c)
gslcdfpascalQ
Link to:
1 gsl_cdf_pascal_Q(a, b, c)
gslranbernoullipdf
Link to:
1 gsl_ran_bernoulli_pdf(a, b)
gslranbeta
Link to:
1 gsl_ran_beta(a, b, c)
gslranbetapdf
Link to:
1 gsl_ran_beta_pdf(a, b, c)
gslranbinomialpdf
Link to:
1 gsl_ran_binomial_pdf(a, b, c)
gslranexponential
Link to:
1 gsl_ran_exponential(a, b)
gslranexponentialpdf
Link to:
1 gsl_ran_exponential_pdf(a, b)
gslranexppow
Link to:
1 gsl_ran_exppow(a, b, c)
gslranexppowpdf
Link to:
1 gsl_ran_exppow_pdf(a, b, c)
gslrancauchy
Link to:
1 gsl_ran_cauchy(a, b)
gslrancauchypdf
Link to:
1 gsl_ran_cauchy_pdf(a, b)
gslranchisq
Link to:
1 gsl_ran_chisq(a, b)
gslranchisqpdf
Link to:
1 gsl_ran_chisq_pdf(a, b)
gslranerlang
Link to:
1 gsl_ran_erlang(a, b, c)
gslranerlangpdf
Link to:
1 gsl_ran_erlang_pdf(a, b, c)
gslranfdist
Link to:
1 gsl_ran_fdist(a, b, c)
gslranfdistpdf
Link to:
1 gsl_ran_fdist_pdf(a, b, c)
gslranflat
Link to:
1 gsl_ran_flat(a, b, c)
gslranflatpdf
Link to:
1 gsl_ran_flat_pdf(a, b, c)
gslrangamma
Link to:
1 gsl_ran_gamma(a, b, c)
gslrangammaint
Link to:
1 gsl_ran_gamma_int(a, b, c)
gslrangammapdf
Link to:
1 gsl_ran_gamma_pdf(a, b, c)
gslrangammamt
Link to:
1 gsl_ran_gamma_mt(a, b, c)
gslrangammaknuth
Link to:
1 gsl_ran_gamma_knuth(a, b, c)
gslrangaussian
Link to:
1 gsl_ran_gaussian(a, b)
gslrangaussianratiomethod
Link to:
1 gsl_ran_gaussian_ratio_method(a, b)
gslrangaussianziggurat
Link to:
1 gsl_ran_gaussian_ziggurat(a, b)
gslrangaussianpdf
Link to:
1 gsl_ran_gaussian_pdf(a, b)
gslranugaussian
Link to:
1 gsl_ran_ugaussian(a)
gslranugaussianratiomethod
Link to:
1 gsl_ran_ugaussian_ratio_method(a)
gslranugaussianpdf
Link to:
1 gsl_ran_ugaussian_pdf(a)
gslrangaussiantail
Link to:
1 gsl_ran_gaussian_tail(a, b, c)
gslrangaussiantailpdf
Link to:
1 gsl_ran_gaussian_tail_pdf(a, b, c)
gslranugaussiantail
Link to:
1 gsl_ran_ugaussian_tail(a, b)
gslranugaussiantailpdf
Link to:
1 gsl_ran_ugaussian_tail_pdf(a, b)
gslranlandau
Link to:
1 gsl_ran_landau(a)
gslranlandaupdf
Link to:
1 gsl_ran_landau_pdf(a)
gslrangeometricpdf
Link to:
1 gsl_ran_geometric_pdf(a, b)
gslrangumbel1
Link to:
1 gsl_ran_gumbel1(a, b, c)
gslrangumbel1pdf
Link to:
1 gsl_ran_gumbel1_pdf(a, b, c)
gslrangumbel2
Link to:
1 gsl_ran_gumbel2(a, b, c)
gslrangumbel2pdf
Link to:
1 gsl_ran_gumbel2_pdf(a, b, c)
gslranlogistic
Link to:
1 gsl_ran_logistic(a, b)
gslranlogisticpdf
Link to:
1 gsl_ran_logistic_pdf(a, b)
gslranlognormal
Link to:
1 gsl_ran_lognormal(a, b, c)
gslranlognormalpdf
Link to:
1 gsl_ran_lognormal_pdf(a, b, c)
gslranlogarithmicpdf
Link to:
1 gsl_ran_logarithmic_pdf(a, b)
gslrannegativebinomialpdf
Link to:
1 gsl_ran_negative_binomial_pdf(a, b, c)
gslranpascalpdf
Link to:
1 gsl_ran_pascal_pdf(a, b, c)
gslranpareto
Link to:
1 gsl_ran_pareto(a, b, c)
gslranparetopdf
Link to:
1 gsl_ran_pareto_pdf(a, b, c)
gslranpoissonpdf
Link to:
1 gsl_ran_poisson_pdf(a, b)
gslranrayleigh
Link to:
1 gsl_ran_rayleigh(a, b)
gslranrayleighpdf
Link to:
1 gsl_ran_rayleigh_pdf(a, b)
gslranrayleightail
Link to:
1 gsl_ran_rayleigh_tail(a, b, c)
gslranrayleightailpdf
Link to:
1 gsl_ran_rayleigh_tail_pdf(a, b, c)
gslrantdist
Link to:
1 gsl_ran_tdsit(a, b)
gslrantdistpdf
Link to:
1 gsl_ran_tdsit_pdf(a, b)
gslranlaplace
Link to:
1 gsl_ran_laplace(a, b)
gslranlaplacepdf
Link to:
1 gsl_ran_laplace_pdf(a, b)
gslranlevy
Link to:
1 gsl_ran_levy(a, b, c)
gslranweibull
Link to:
1 gsl_ran_weibull(a, b, c)
gslranweibullpdf
Link to:
1 gsl_ran_weibull_pdf(a, b, c)
gslsfairyAi
Link to:
1 gsl_sf_airy_Ai(a, b)
gslsfairyBi
Link to:
1 gsl_sf_airy_Bi(a, b)
gslsfairyAiscaled
Link to:
1 gsl_sf_airy_Ai_scaled(a, b)
gslsfairyBiscaled
Link to:
1 gsl_sf_airy_Bi_scaled(a, b)
gslsfairyAideriv
Link to:
1 gsl_sf_airy_Ai_deriv(a, b)
gslsfairyBideriv
Link to:
1 gsl_sf_airy_Bi_deriv(a, b)
gslsfairyAiderivscaled
Link to:
1 gsl_sf_airy_Ai_deriv_scaled(a, b)
gslsfairyBiderivscaled
Link to:
1 gsl_sf_airy_Bi_deriv_scaled(a, b)
gslsfairyzeroAi
Link to:
1 gsl_sf_airy_Ai(a, b)
gslsfairyzeroBi
Link to:
1 gsl_sf_airy_aero_Bi(a)
gslsfairyzeroAideriv
Link to:
1 gsl_sf_airy_aero_Ai_deriv(a)
gslsfairyzeroBideriv
Link to:
1 gsl_sf_airy_aero_Bi_deriv(a)
gslsfbesselJ0
Link to:
1 gsl_sf_bessel_J0(a)
gslsfbesselJ1
Link to:
1 gsl_sf_bessel_J1(a)
gslsfbesselJn
Link to:
1 gsl_sf_bessel_Jn(a, b)
gslsfbesselY0
Link to:
1 gsl_sf_bessel_Y0(a)
gslsfbesselY1
Link to:
1 gsl_sf_bessel_Y1(a)
gslsfbesselYn
Link to:
1 gsl_sf_bessel_Yn(a, b)
gslsfbesselI0
Link to:
1 gsl_sf_bessel_I0(a)
gslsfbesselI1
Link to:
1 gsl_sf_bessel_I1(a)
gslsfbesselIn
Link to:
1 gsl_sf_bessel_In(a, b)
gslsfbesselI0scaled
Link to:
1 gsl_sf_bessel_I0_scaled(a)
gslsfbesselI1scaled
Link to:
1 gsl_sf_bessel_I1_scaled(a)
gslsfbesselInscaled
Link to:
1 gsl_sf_bessel_In_scaled(a, b)
gslsfbesselK0
Link to:
1 gsl_sf_bessel_K0(a)
gslsfbesselK1
Link to:
1 gsl_sf_bessel_K1(a)
gslsfbesselKn
Link to:
1 gsl_sf_bessel_Kn(a, b)
gslsfbesselK0scaled
Link to:
1 gsl_sf_bessel_K0_scaled(a)
gslsfbesselK1scaled
Link to:
1 gsl_sf_bessel_K1_scaled(a)
gslsfbesselKnscaled
Link to:
1 gsl_sf_bessel_Kn_scaled(a, b)
gslsfbesselj0
Link to:
1 gsl_sf_bessel_j0(a)
gslsfbesselj1
Link to:
1 gsl_sf_bessel_j1(a)
gslsfbesselj2
Link to:
1 gsl_sf_bessel_j2(a)
gslsfbesseljl
Link to:
1 gsl_sf_bessel_jl(a, b)
gslsfbessely0
Link to:
1 gsl_sf_bessel_y0(a)
gslsfbessely1
Link to:
1 gsl_sf_bessel_y0(a)
gslsfbessely2
Link to:
1 gsl_sf_bessel_y0(a)
gslsfbesselyl
Link to:
1 gsl_sf_bessel_jl(a, b)
gslsfbesseli0scaled
Link to:
1 gsl_sf_bessel_i0_scaled(a)
gslsfbesseli1scaled
Link to:
1 gsl_sf_bessel_i1_scaled(a)
gslsfbesseli2scaled
Link to:
1 gsl_sf_bessel_i2_scaled(a)
gslsfbesselilscaled
Link to:
1 gsl_sf_bessel_il_scaled(a, b)
gslsfbesselk0scaled
Link to:
1 gsl_sf_bessel_k0_scaled(a)
gslsfbesselk1scaled
Link to:
1 gsl_sf_bessel_k1_scaled(a)
gslsfbesselk2scaled
Link to:
1 gsl_sf_bessel_k2_scaled(a)
gslsfbesselklscaled
Link to:
1 gsl_sf_bessel_kl_scaled(a, b)
gslsfbesselJnu
Link to:
1 gsl_sf_bessel_Jnu(a, b)
gslsfbesselYnu
Link to:
1 gsl_sf_bessel_Ynu(a, b)
gslsfbesselInuscaled
Link to:
1 gsl_sf_bessel_Inu_scaled(a, b)
gslsfbesselInu
Link to:
1 gsl_sf_bessel_Inu(a, b)
gslsfbesselKnuscaled
Link to:
1 gsl_sf_bessel_Knu_scaled(a, b)
gslsfbesselKnu
Link to:
1 gsl_sf_bessel_Knu(a, b)
gslsfbessellnKnu
Link to:
1 gsl_sf_bessel_lnKnu(a, b)
gslsfbesselzeroJ0
Link to:
1 gsl_sf_bessel_zero_J0(a)
gslsfbesselzeroJ1
Link to:
1 gsl_sf_bessel_zero_J1(a)
gslsfbesselzeroJnu
Link to:
1 gsl_sf_bessel_zero_Jnu(a, b)
gslsfclausen
Link to:
1 gsl_sf_clausen(a)
gslsfhydrogenicR1
Link to:
1 gsl_sf_hydrogenicR_1(a, b)
gslsfdawson
Link to:
1 gsl_sf_dawnson(a)
gslsfdebye1
Link to:
1 gsl_sf_debye_1(a)
gslsfdebye2
Link to:
1 gsl_sf_debye_2(a)
gslsfdebye3
Link to:
1 gsl_sf_debye_3(a)
gslsfdebye4
Link to:
1 gsl_sf_debye_4(a)
gslsfdebye5
Link to:
1 gsl_sf_debye_5(a)
gslsfdebye6
Link to:
1 gsl_sf_debye_6(a)
gslsfdilog
Link to:
1 gsl_sf_dilog(a)
gslsfmultiply
Link to:
1 gsl_sf_multiply(a, b)
gslsfellintKcomp
Link to:
1 gsl_sf_ellint_Kcomp(a, b)
gslsfellintEcomp
Link to:
1 gsl_sf_ellint_Ecomp(a, b)
gslsfellintPcomp
Link to:
1 gsl_sf_ellint_Pcomp(a, b, c)
gslsfellintDcomp
Link to:
1 gsl_sf_ellint_Dcomp(a, b)
gslsfellintF
Link to:
1 gsl_sf_ellint_F(a, b, c)
gslsfellintE
Link to:
1 gsl_sf_ellint_E(a, b, c)
gslsfellintRC
Link to:
1 gsl_sf_ellint_RC(a, b, c)
gslsferfc
Link to:
1 gsl_sf_erfc(a)
gslsflogerfc
Link to:
1 gsl_sf_log_erfc(a)
gslsferf
Link to:
1 gsl_sf_erf(a)
gslsferfZ
Link to:
1 gsl_sf_erf_Z(a)
gslsferfQ
Link to:
1 gsl_sf_erf_Q(a)
gslsfhazard
Link to:
1 gsl_sf_hazard(a)
gslsfexp
Link to:
1 gsl_sf_exp(a)
gslsfexpmult
Link to:
1 gsl_sf_exp_mult(a, b)
gslsfexpm1
Link to:
1 gsl_sf_expm1(a)
gslsfexprel
Link to:
1 gsl_sf_exprel(a)
gslsfexprel2
Link to:
1 gsl_sf_exprel_2(a)
gslsfexpreln
Link to:
1 gsl_sf_exprel_n(a, b)
gslsfexpintE1
Link to:
1 gsl_sf_expint_E1(a)
gslsfexpintE2
Link to:
1 gsl_sf_expint_E2(a)
gslsfexpintEn
Link to:
1 gsl_sf_expint_En(a, b)
gslsfexpintE1scaled
Link to:
1 gsl_sf_expint_E1_scaled(a)
gslsfexpintE2scaled
Link to:
1 gsl_sf_expint_E1_scaled(a)
gslsfexpintEnscaled
Link to:
1 gsl_sf_expint_En_scaled(a, b)
gslsfexpintEi
Link to:
1 gsl_sf_expint_Ei(a)
gslsfexpintEiscaled
Link to:
1 gsl_sf_expint_Ei_scaled(a)
gslsfShi
Link to:
1 gsl_sf_Shi(a)
gslsfChi
Link to:
1 gsl_sf_Chi(a)
gslsfexpint3
Link to:
1 gsl_sf_expint_3(a)
gslsfSi
Link to:
1 gsl_sf_Si(a)
gslsfCi
Link to:
1 gsl_sf_Ci(a)
gslsfatanint
Link to:
1 gsl_sf_atanint(a)
gslsffermidiracm1
Link to:
1 gsl_sf_fermi_dirac_m1(a)
gslsffermidirac0
Link to:
1 gsl_sf_fermi_dirac_0(a)
gslsffermidirac1
Link to:
1 gsl_sf_fermi_dirac_1(a)
gslsffermidirac2
Link to:
1 gsl_sf_fermi_dirac_2(a)
gslsffermidiracint
Link to:
1 gsl_sf_fermi_dirac_int(a, b)
gslsffermidiracmhalf
Link to:
1 gsl_sf_fermi_dirac_mhalf(a)
gslsffermidirachalf
Link to:
1 gsl_sf_fermi_dirac_half(a)
gslsffermidirac3half
Link to:
1 gsl_sf_fermi_dirac_3half(a)
gslsffermidiracinc0
Link to:
1 gsl_sf_fermi_dirac_inc_0(a, b)
gslsflngamma
Link to:
1 gsl_sf_lngamma(a)
gslsfgamma
Link to:
1 gsl_sf_gamma(a)
gslsfgammastar
Link to:
1 gsl_sf_gammastar(a)
gslsfgammainv
Link to:
1 gsl_sf_gammainv(a)
gslsftaylorcoeff
Link to:
1 gsl_sf_taylorcoeff(a, b)
gslsffact
Link to:
1 gsl_sf_fact(a)
gslsfdoublefact
Link to:
1 gsl_sf_doublefact(a)
gslsflnfact
Link to:
1 gsl_sf_lnfact(a)
gslsflndoublefact
Link to:
1 gsl_sf_lndoublefact(a)
gslsflnchoose
Link to:
1 gsl_sf_lnchoose(a, b)
gslsfchoose
Link to:
1 gsl_sf_choose(a, b)
gslsflnpoch
Link to:
1 gsl_sf_lnpoch(a, b)
gslsfpoch
Link to:
1 gsl_sf_poch(a, b)
gslsfpochrel
Link to:
1 gsl_sf_pochrel(a, b)
gslsfgammaincQ
Link to:
1 gsl_sf_gamma_inc_Q(a, b)
gslsfgammaincP
Link to:
1 gsl_sf_gamma_inc_P(a, b)
gslsfgammainc
Link to:
1 gsl_sf_gamma_inc(a, b)
gslsflnbeta
Link to:
1 gsl_sf_lnbeta(a, b)
gslsfbeta
Link to:
1 gsl_sf_beta(a, b)
gslsfbetainc
Link to:
1 gsl_sf_betaçinc(a, b, c)
gslsfgegenpoly1
Link to:
1 gsl_sf_gegenpoly_1(a, b)
gslsfgegenpoly2
Link to:
1 gsl_sf_gegenpoly_2(a, b)
gslsfgegenpoly3
Link to:
1 gsl_sf_gegenpoly_3(a, b)
gslsfgegenpolyn
Link to:
1 gsl_sf_gegenpoly_n(a, b, c)
gslsfhyperg0F1
Link to:
1 gsl_sf_hyperg_0F1(a, b)
gslsfhyperg1F1int
Link to:
1 gsl_sf_hyperg_1F1_inc(a, b, c)
gslsfhyperg1F1
Link to:
1 gsl_sf_hyperg_1F1(a, b, c)
gslsfhypergUint
Link to:
1 gsl_sf_hyperg_U_inc(a, b, c)
gslsfhypergU
Link to:
1 gsl_sf_hyperg_U(a, b, c)
gslsfhyperg2F0
Link to:
1 gsl_sf_hyperg_U_2F0(a, b, c)
gslsflaguerre1
Link to:
1 gsl_sf_laguerre_1(a, b)
gslsflaguerre2
Link to:
1 gsl_sf_laguerre_2(a, b)
gslsflaguerre3
Link to:
1 gsl_sf_laguerre_3(a, b)
gslsflaguerren
Link to:
1 gsl_sf_laguerre_n(a, b, c)
gslsflambertW0
Link to:
1 gsl_sf_lambert_W0(a)
gslsflambertWm1
Link to:
1 gsl_sf_lambert_Wm1(a)
gslsflegendrePl
Link to:
1 gsl_sf_legendre_Pl(a, b)
gslsflegendreP1
Link to:
1 gsl_sf_legendre_P1(a)
gslsflegendreP2
Link to:
1 gsl_sf_legendre_P2(a)
gslsflegendreP3
Link to:
1 gsl_sf_legendre_P3(a)
gslsflegendreQ0
Link to:
1 gsl_sf_legendre_Q0(a)
gslsflegendreQ1
Link to:
1 gsl_sf_legendre_Q1(a)
gslsflegendreQl
Link to:
1 gsl_sf_legendre_Ql(a, b)
gslsflegendrePlm
Link to:
1 gsl_sf_legendre_Plm(a, b, c)
gslsflegendresphPlm
Link to:
1 gsl_sf_legendre_sphP1m(a, b, c)
gslsflegendrearraysize
Link to:
1 gsl_sf_legendre_array_size(a, b)
gslsfconicalPhalf
Link to:
1 gsl_sf_conicalP_half(a, b)
gslsfconicalPmhalf
Link to:
1 gsl_sf_conicalP_mhalf(a, b)
gslsfconicalP0
Link to:
1 gsl_sf_conicalP_0(a, b)
gslsfconicalP1
Link to:
1 gsl_sf_conicalP_1(a, b)
gslsfconicalPsphreg
Link to:
1 gsl_sf_conicalP_sph_reg(a, b, c)
gslsfconicalPcylreg
Link to:
1 gsl_sf_conicalP_cyl_reg(a, b, c)
gslsflegendreH3d0
Link to:
1 gsl_sf_legendre_H3d_0(a, b)
gslsflegendreH3d1
Link to:
1 gsl_sf_legendre_H3d_1(a, b)
gslsflegendreH3d
Link to:
1 gsl_sf_legendre_H3d(a, b, c)
gslsflog
Link to:
1 gsl_sf_log(a)
gslsflogabs
Link to:
1 gsl_sf_log_abs(a)
gslsflog1plusx
Link to:
1 gsl_sf_log_1plusx(a)
gslsflog1plusxmx
Link to:
1 gsl_sf_log_1plusx_mx(a)
gslsfpowint
Link to:
1 gsl_sf_pow_int(a, b)
gslsfpsiint
Link to:
1 gsl_sf_psi_int(a)
gslsfpsi
Link to:
1 gsl_sf_psi(a)
gslsfpsi1piy
Link to:
1 gsl_sf_psi_1piy(a)
gslsfpsi1int
Link to:
1 gsl_sf_psi_1_int(a)
gslsfpsi1
Link to:
1 gsl_sf_psi_1(a)
gslsfpsin
Link to:
1 gsl_sf_psi_n(a, b)
gslsfsynchrotron1
Link to:
1 gsl_sf_synchrotron_1(a)
gslsfsynchrotron2
Link to:
1 gsl_sf_synchrotron_2(a)
gslsftransport2
Link to:
1 gsl_sf_transport_2(a)
gslsftransport3
Link to:
1 gsl_sf_transport_3(a)
gslsftransport4
Link to:
1 gsl_sf_transport_4(a)
gslsftransport5
Link to:
1 gsl_sf_transport_5(a)
gslsfsin
Link to:
1 gsl_sf_sin(a)
gslsfcos
Link to:
1 gsl_sf_cos(a)
gslsfhypot
Link to:
1 gsl_sf_hypot(a, b)
gslsfsinc
Link to:
1 gsl_sf_sinc(a)
gslsflnsinh
Link to:
1 gsl_sf_lnsinh(a)
gslsflncosh
Link to:
1 gsl_sf_lncosh(a)
gslsfanglerestrictsymm
Link to:
1 gsl_sf_andle_restrict_symm(a)
gslsfanglerestrictpos
Link to:
1 gsl_sf_angle_restrict_pos(a)
gslsfzetaint
Link to:
1 gsl_sf_zeta_int(a)
gslsfzeta
Link to:
1 gsl_sf_zeta(a)
gslsfzetam1
Link to:
1 gsl_sf_zetam1(a)
gslsfzetam1int
Link to:
1 gsl_sf_zetam1_int(a)
gslsfhzeta
Link to:
1 gsl_sf_hzeta(a, b)
gslsfetaint
Link to:
1 gsl_sf_eta_int(a)
gslsfeta
Link to:
1 gsl_sf_eta(a)
4.8.15 ff-Ipopt
IPOPT
Todo: todo
4.8.16 fflapack
inv
Todo: todo
dgeev
Todo: todo
zgeev
Todo: todo
geev
Todo: todo
geev
Todo: todo
dggev
Todo: todo
zggev
Todo: todo
dsygvd
Todo: todo
dgesdd
Todo: todo
zhegv
Todo: todo
dsyev
Todo: todo
zheev
Todo: todo
4.8.17 ff-mmap-semaphore
Wait
Todo: todo
trywait
Todo: todo
Post
Todo: todo
msync
Todo: todo
Read
Todo: todo
Write
Todo: todo
4.8.18 ffnewuoa
newuoa
Todo: todo
4.8.19 ff-NLopt
nloptDIRECT
Todo: todo
nloptDIRECTL
Todo: todo
nloptDIRECTLRand
Todo: todo
nloptDIRECTScal
Todo: todo
nloptDIRECTNoScal
Todo: todo
nloptDIRECTLNoScal
Todo: todo
nloptDIRECTLRandNoScal
Todo: todo
nloptOrigDIRECT
Todo: todo
nloptOrigDIRECTL
Todo: todo
nloptStoGO
Todo: todo
nloptStoGORand
Todo: todo
nloptLBFGS
Todo: todo
nloptPRAXIS
Todo: todo
nloptVar1
Todo: todo
nloptVar2
Todo: todo
nloptTNewton
Todo: todo
nloptTNewtonRestart
Todo: todo
nloptTNewtonPrecond
Todo: todo
nloptTNewtonPrecondRestart
Todo: todo
nloptCRS2
Todo: todo
nloptMMA
Todo: todo
nloptCOBYLA
Todo: todo
nloptNEWUOA
Todo: todo
nloptNEWUOABound
Todo: todo
nloptNelderMead
Todo: todo
nloptSbplx
Todo: todo
nloptBOBYQA
Todo: todo
nloptISRES
Todo: todo
nloptSLSQP
Todo: todo
nloptMLSL
Todo: todo
nloptMLSLLDS
Todo: todo
nloptAUGLAG
Todo: todo
nloptAUGLAGEQ
Todo: todo
4.8.20 ffrandom
srandomdev
Todo: todo
srandom
Todo: todo
random
Todo: todo
4.8.21 FreeFemQA
MeshGenQA
Todo: todo
4.8.22 freeyams
freeyams
Todo: todo
4.8.23 gmsh
Need
1 load "gmsh"
gmshload
Parameters:
• MeshFile (string) Mesh file name
• reftri= (.. todo:: todo)
• renum= (.. todo:: todo)
Output:
• Th (mesh)
gmshload3
Parameters:
• MeshFile (string) Mesh file name
• reftet= (.. todo:: todo)
• renum= (.. todo:: todo)
Output:
• Th (mesh3)
savegmsh
Todo: todo
4.8.24 gsl
gslpolysolvequadratic
Todo: todo
gslpolysolvecubic
Todo: todo
gslpolycomplexsolve
Todo: todo
gslrnguniform
Todo: todo
gslrnguniformpos
Todo: todo
gslname
Todo: todo
gslrngget
Todo: todo
gslrngmin
Todo: todo
gslrngmax
Todo: todo
gslrngset
Todo: todo
gslrngtype
Todo: todo
4.8.25 ilut
applyIlutPrecond
Todo: todo
makeIlutPrecond
Todo: todo
4.8.26 iohdf5
savehdf5sol
Todo: todo
4.8.27 iovtk
savevtk
Parameters:
• FileName (string) File name: *.vtk or *.vtu
• Th (mesh or mesh3)
• [Ux, Uy, Uz], p (fespace function of vector of fespace functions) Solutions to save, as much as wanted
• dataname= (string) Name of solutions, seprated by a space
• withsurfacemesh= (bool) .. todo:: todo
• order= (int[int]) Order of solutions.
Available: 0 or 1
• floatmesh= (bool) .. todo:: todo
• floatsol= (bool) .. todo:: todo
• bin= (bool) If true, save file in binary format
• swap (bool) .. todo:: todo
Output:
• None
vtkload
Todo: todo
vtkload3
Todo: todo
4.8.28 isoline
Need
1 load "isoline"
isoline
Todo: todo
Curve
Todo: todo
Area
Todo: todo
findallocalmin
Todo: todo
4.8.29 lapack
inv
Todo: todo
dgeev
Todo: todo
zgeev
Todo: todo
geev
Todo: todo
dggev
Todo: todo
zggev
Todo: todo
dsygvd
Todo: todo
dgesdd
Todo: todo
zhegv
Todo: todo
dsyev
Todo: todo
zheev
Todo: todo
dgelsy
Todo: todo
4.8.30 lgbmo
bmo
Todo: todo
4.8.31 mat_dervieux
MatUpWind1
Todo: todo
4.8.32 mat_psi
MatUpWind0
Todo: todo
4.8.33 medit
medit
Todo: todo
savesol
Todo: todo
readsol
Todo: todo
4.8.34 metis
metisnodal
Todo: todo
metisdual
Todo: todo
4.8.35 MetricKuate
MetricKuate
Todo: todo
4.8.36 MetricPk
MetricPk
Todo: todo
4.8.37 mmg3d
mmg3d
Todo: todo
4.8.38 mmg3d-v4.0
mmg3d
Todo: todo
4.8.39 msh3
change
Todo: todo
movemesh23
Todo: todo
movemesh2D3Dsurf
Todo: todo
movemesh3
Todo: todo
movemesh
Todo: todo
movemesh3D
Todo: todo
deplacement
Todo: todo
checkbemesh
Todo: todo
buildlayers
Todo: todo
bcube
Todo: todo
cube
Parameters:
• nnX (int) Number of discretization point along 𝑥
• nnY (int) Number of discretization point along 𝑦
• nnZ (int) Number of discretization point along 𝑧
• X(x) (func) [Optional] Affine function of 𝑥 to define the length Default: x
• Y(y) (func) [Optional] Affine function of 𝑦 to define the width Default: y
• Z(z) (func) [Optional] Affine function of 𝑧 to define the height Default: z
• label= (int[int]) [Optional]
List of surface labels Default: [1, 2, 3, 4, 5, 6]
• flags= (int) [Optional]
Refer to square
• region= (int) [Optional]
Region number of the cube volume Default: 0
Output:
• Th (mesh3) Cube mesh
trunc
Todo: todo
gluemesh
Todo: todo
extract
Todo: todo
showborder
Todo: todo
getborder
Todo: todo
AddLayers
Todo: todo
4.8.40 mshmet
mshmet
Todo: todo
4.8.41 MUMPS
defaulttoMUMPSseq
Todo: todo
4.8.42 MUMPS_seq
defaulttoMUMPSseq
Todo: todo
4.8.43 netgen
netg
Todo: todo
netgstl
Todo: todo
netgload
Todo: todo
4.8.44 NewSolver
defaulttoUMFPACK
Todo: todo
4.8.45 PARDISO
defaulttoPARDISO
Todo: todo
ompsetnumthreads
Todo: todo
ompgetnumthreads
Todo: todo
ompgetmaxthreads
Todo: todo
4.8.46 pcm2rnm
readpcm
Todo: todo
4.8.47 pipe
flush
Todo: todo
sleep
Todo: todo
usleep
Todo: todo
4.8.48 qf11to25
QF1d
Todo: todo
QF2d
Todo: todo
QF3d
Todo: todo
tripleQF
4.8.49 scotch
scotch
Todo: todo
4.8.50 shell
readdir
Todo: todo
unlink
Todo: todo
rmdir
Todo: todo
cddir
Todo: todo
chdir
Todo: todo
basename
Todo: todo
dirname
Todo: todo
mkdir
Todo: todo
chmod
Todo: todo
cpfile
Todo: todo
stat
Todo: todo
isdir
Todo: todo
getenv
Todo: todo
setenv
Todo: todo
unsetenv
Todo: todo
4.8.51 splitedges
SplitedgeMesh
Todo: todo
4.8.52 splitmesh12
splitmesh12
Todo: todo
4.8.53 splitmesh3
splitmesh3
Todo: todo
4.8.54 splitmesh4
splimesh4
Todo: todo
4.8.55 splitmesh6
splitmesh6
Todo: todo
4.8.56 SuperLu
defaulttoSuperLu
Todo: todo
4.8.57 symmetrizeCSR
symmetrizeCSR
Todo: todo
4.8.58 tetgen
tetgconvexhull
Todo: todo
tetgtransfo
Todo: todo
tetg
Todo: todo
tetgreconstruction
Todo: todo
4.8.59 UMFPACK64
defaulttoUMFPACK64
Todo: todo
4.8.60 VTK_writer_3d
Vtkaddmesh
Todo: todo
Vtkaddscalar
Todo: todo
4.8.61 VTK_writer
Vtkaddmesh
Todo: todo
Vtkaddscalar
FIVE
MATHEMATICAL MODELS
Summary:
This chapter goes deeper into a number of problems that FreeFEM can solve. It is a complement to the Tutorial part
which was only an introduction.
Users are invited to contribute to make this models database grow.
Our starting point here will be the mathematical model to find the shape of soap film which is glued to the ring on the
𝑥𝑦−plane:
We assume the shape of the film is described by the graph (𝑥, 𝑦, 𝑢(𝑥, 𝑦)) of the vertical displacement 𝑢(𝑥, 𝑦) (𝑥2 +𝑦 2 <
1) under a vertical pressure 𝑝 in terms of force per unit area and an initial tension 𝜇 in terms of force per unit length.
Consider the “small plane” ABCD, A:(𝑥, 𝑦, 𝑢(𝑥, 𝑦)), B:(𝑥, 𝑦, 𝑢(𝑥 + 𝛿𝑥, 𝑦)), C:(𝑥, 𝑦, 𝑢(𝑥 + 𝛿𝑥, 𝑦 + 𝛿𝑦)) and
D:(𝑥, 𝑦, 𝑢(𝑥, 𝑦 + 𝛿𝑦)).
Denote by ⃗𝑛(𝑥, 𝑦) = (𝑛𝑥 (𝑥, 𝑦), 𝑛𝑦 (𝑥, 𝑦), 𝑛𝑧 (𝑥, 𝑦)) the normal vector of the surface 𝑧 = 𝑢(𝑥, 𝑦). We see that the
vertical force due to the tension 𝜇 acting along the edge AD is −𝜇𝑛𝑥 (𝑥, 𝑦)𝛿𝑦 and the the vertical force acting along
the edge AD is:
(︂ )︂
𝜕𝑛𝑥
𝜇𝑛𝑥 (𝑥 + 𝛿𝑥, 𝑦)𝛿𝑦 ≃ 𝜇 𝑛𝑥 (𝑥, 𝑦) + 𝛿𝑥 (𝑥, 𝑦)𝛿𝑦
𝜕𝑥
543
FreeFEM Documentation, Release 4.8
The force in the vertical direction on the surface ABCD due to the tension 𝜇 is given by:
Letting 𝛿𝑥 → 𝑑𝑥, 𝛿𝑦 → 𝑑𝑦, we have the equilibrium of the vertical displacement of soap film on ABCD by 𝑝:
Using the Laplace operator ∆ = 𝜕 2 /𝜕𝑥2 + 𝜕 2 /𝜕𝑦 2 , we can find the virtual displacement write the following:
−∆𝑢 = 𝑓 in Ω
𝑢 = 0 on 𝜕Ω
1 // Parameters
2 int nn = 50;
3 func f = -1;
4 func ue = (x^2+y^2-1)/4; //ue: exact solution
5
6 // Mesh
7 border a(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
8 mesh disk = buildmesh(a(nn));
9 plot(disk);
10
11 // Fespace
12 fespace femp1(disk, P1);
13 femp1 u, v;
14
15 // Problem
16 problem laplace (u, v)
17 = int2d(disk)( //bilinear form
18 dx(u)*dx(v)
19 + dy(u)*dy(v)
20 )
21 - int2d(disk)( //linear form
22 f*v
23 )
24 + on(1, u=0) //boundary condition
(continues on next page)
27 // Solve
28 laplace;
29
30 // Plot
31 plot (u, value=true, wait=true);
32
33 // Error
34 femp1 err = u - ue;
35 plot(err, value=true, wait=true);
36
39
42 // Mesh adaptation
43 disk = adaptmesh(disk, u, err=0.01);
44 plot(disk, wait=true);
45
46 // Solve
47 laplace;
48 plot (u, value=true, wait=true);
49
50 // Error
51 err = u - ue; //become FE-function on adapted mesh
52 plot(err, value=true, wait=true);
53
In the 37th line, the 𝐿2 -error estimation between the exact solution 𝑢𝑒 ,
(︂∫︁ )︂1/2
‖𝑢ℎ − 𝑢𝑒 ‖0,Ω = |𝑢ℎ − 𝑢𝑒 | d𝑥d𝑦
2
Ω
are done on the initial mesh. The results are ‖𝑢ℎ − 𝑢𝑒 ‖0,Ω = 0.000384045, |𝑢ℎ − 𝑢𝑒 |1,Ω = 0.0375506.
After the adaptation, we have ‖𝑢ℎ − 𝑢𝑒 ‖0,Ω = 0.000109043, |𝑢ℎ − 𝑢𝑒 |1,Ω = 0.0188411. So the numerical solution is
improved by adaptation of mesh.
5.1.2 Electrostatics
We assume that there is no current and a time independent charge distribution. Then the electric field E satisfies:
divE = 𝜌/𝜖
(5.1)
curlE = 0
where 𝜌 is the charge density and 𝜖 is called the permittivity of free space.
From the equation (5.1) We can introduce the electrostatic potential such that E = −∇𝜑. Then we have Poisson’s
equation −∆𝜑 = 𝑓 , 𝑓 = −𝜌/𝜖.
We now obtain the equipotential line which is the level curve of 𝜑, when there are no charges except conductors
{𝐶𝑖 }1,··· ,𝐾 . Let us assume 𝐾 conductors 𝐶1 , · · · , 𝐶𝐾 within an enclosure 𝐶0 .
Each one is held at an electrostatic potential 𝜙𝑖 . We assume that the enclosure 𝐶0 is held at potential 0. In order to
know 𝜙(𝑥) at any point 𝑥 of the domain Ω, we must solve:
−∆𝜙 = 0 in Ω
∑︀𝑁
where Ω is the interior of 𝐶0 minus the conductors 𝐶𝑖 , and Γ is the boundary of Ω, that is 𝑖=0 𝐶𝑖 .
Here 𝑔 is any function of 𝑥 equal to 𝜙𝑖 on 𝐶𝑖 and to 0 on 𝐶0 . The boundary equation is a reduced form for:
𝜙 = 𝜙𝑖 on 𝐶𝑖 , 𝑖 = 1...𝑁, 𝜙 = 0 on 𝐶0 .
First we give the geometrical informations; 𝐶0 = {(𝑥, 𝑦); 𝑥2 + 𝑦 2 = 52 }, 𝐶1 = {(𝑥, 𝑦) : 1
0.32 (𝑥 − 2)
2
+ 312 𝑦 2 = 1},
1 2 1 2
𝐶2 = {(𝑥, 𝑦) : 0.3 2 (𝑥 + 2) + 32 𝑦 = 1}.
Let Ω be the disk enclosed by 𝐶0 with the elliptical holes enclosed by 𝐶1 and 𝐶2 . Note that 𝐶0 is described coun-
terclockwise, whereas the elliptical holes are described clockwise, because the boundary must be oriented so that the
computational domain is to its left.
1 // Mesh
2 border C0(t=0, 2*pi){x=5*cos(t); y=5*sin(t);}
3 border C1(t=0, 2*pi){x=2+0.3*cos(t); y=3*sin(t);}
4 border C2(t=0, 2*pi){x=-2+0.3*cos(t); y=3*sin(t);}
5
9 // Fespace
10 fespace Vh(Th, P1);
11 Vh uh, vh;
12
13 // Problem
14 problem Electro (uh, vh)
15 = int2d(Th)( //bilinear
16 dx(uh)*dx(vh)
17 + dy(uh)*dy(vh)
18 )
19 + on(C0, uh=0) //boundary condition on C_0
20 + on(C1, uh=1) //+1 volt on C_1
21 + on(C2, uh=-1) //-1 volt on C_2
22 ;
23
24 // Solve
25 Electro;
26 plot(uh);
(a) Disk with two elliptical holes (b) Equipotential lines where 𝐶1 is located in right hand side
5.1.3 Aerodynamics
Let us consider a wing profile 𝑆 in a uniform flow. Infinity will be represented by a large circle Γ∞ . As previously, we
must solve:
where Ω is the area occupied by the fluid, 𝑢∞ is the air speed at infinity, 𝑐 is a constant to be determined so that 𝜕𝑛 𝜙
is continuous at the trailing edge 𝑃 of 𝑆 (so-called Kutta-Joukowski condition). Lift is proportional to 𝑐.
To find 𝑐 we use a superposition method. As all equations in (5.2) are linear, the solution 𝜙𝑐 is a linear function of 𝑐
𝜙𝑐 = 𝜙0 + 𝑐𝜙1
where 𝜙0 is a solution of (5.2) with 𝑐 = 0 and 𝜙1 is a solution with 𝑐 = 1 and zero speed at infinity.
With these two fields computed, we shall determine 𝑐 by requiring the continuity of 𝜕𝜙/𝜕𝑛 at the trailing edge. An
equation for the upper surface of a NACA0012 (this is a classical wing profile in aerodynamics; the rear of the wing is
called the trailing edge) is:
√
𝑦 = 0.17735 𝑥 − 0.075597𝑥 − 0.212836𝑥2 + 0.17363𝑥3 − 0.06254𝑥4
where Γ2 is the wing profile and Γ1 is an approximation of infinity. One finds 𝑐 by solving:
−∆𝜙0 = 0 in Ω , 𝜙0 |Γ1 = 𝑦 − 0.1𝑥 , 𝜙0 |Γ2 = 0,
−∆𝜙1 = 0 in Ω , 𝜙1 |Γ1 =0 , 𝜙1 |Γ2 =1
The solution 𝜙 = 𝜙0 + 𝑐𝜙1 allows us to find 𝑐 by writing that 𝜕𝑛 𝜙 has no jump at the trailing edge 𝑃 = (1, 0).
We have 𝜕𝑛𝜙 − (𝜙(𝑃 + ) − 𝜙(𝑃 ))/𝛿 where 𝑃 + is the point just above 𝑃 in the direction normal to the profile at a
distance 𝛿. Thus the jump of 𝜕𝑛 𝜙 is (𝜙0 |𝑃 + + 𝑐(𝜙1 |𝑃 + − 1)) + (𝜙0 |𝑃 − + 𝑐(𝜙1 |𝑃 − − 1)) divided by 𝛿 because the
normal changes sign between the lower and upper surfaces. Thus
𝜙0 |𝑃 + + 𝜙0 |𝑃 −
𝑐=− ,
(𝜙1 |𝑃 + + 𝜙1 |𝑃 − − 2)
which can be programmed as:
𝜙0 (0.99, 0.01) + 𝜙0 (0.99, −0.01)
𝑐=− .
(𝜙1 (0.99, 0.01) + 𝜙1 (0.99, −0.01) − 2)
1 // Mesh
2 border a(t=0, 2*pi){x=5*cos(t); y=5*sin(t);}
3 border upper(t=0, 1) {
4 x=t;
5 y=0.17735*sqrt(t)-0.075597*t - 0.212836*(t^2) + 0.17363*(t^3) - 0.06254*(t^4);
6 }
7 border lower(t=1, 0) {
8 x=t;
9 y=-(0.17735*sqrt(t) - 0.075597*t - 0.212836*(t^2) + 0.17363*(t^3) - 0.06254*(t^4));
10 }
11 border c(t=0, 2*pi){x=0.8*cos(t)+0.5; y=0.8*sin(t);}
12
16 // Fespace
17 fespace Vh(Th, P2);
18 Vh psi0, psi1, vh;
19
22 // Problem
23 solve Joukowski0(psi0, vh)
24 = int2d(Th)(
25 dx(psi0)*dx(vh)
26 + dy(psi0)*dy(vh)
27 )
28 + on(a, psi0=y-0.1*x)
29 + on(upper, lower, psi0=0)
30 ;
31
32 plot(psi0);
33
34 solve Joukowski1(psi1,vh)
35 = int2d(Th)(
36 dx(psi1)*dx(vh)
37 + dy(psi1)*dy(vh)
38 )
39 + on(a, psi1=0)
40 + on(upper, lower, psi1=1);
41
42 plot(psi1);
43
There are famous estimation between the numerical result 𝑢ℎ and the exact solution 𝑢 of the Poisson’s problem:
If triangulations {𝒯ℎ }ℎ↓0 is regular (see Regular Triangulation), then we have the estimates:
|∇𝑢 − ∇𝑢ℎ |2 d𝑥d𝑦 = ∫︀Ω ∇𝑢 · ∇(𝑢 − 2𝑢ℎ ) d𝑥d𝑦 ∫︀ + Ω ∇𝑢ℎ · ∇𝑢ℎ d𝑥d𝑦
∫︀ ∫︀ ∫︀
Ω
= Ω 𝑓 (𝑢 − 2𝑢ℎ ) d𝑥d𝑦 + Ω 𝑓 𝑢ℎ d𝑥d𝑦
1 // Parameters
2 func f = x*y;
3
4 //Mesh
5 mesh Th0 = square(100, 100);
6
7 // Fespace
8 fespace V0h(Th0, P2);
9 V0h u0, v0;
(continues on next page)
11 // Problem
12 solve Poisson0 (u0, v0)
13 = int2d(Th0)(
14 dx(u0)*dx(v0)
15 + dy(u0)*dy(v0)
16 )
17 - int2d(Th0)(
18 f*v0
19 )
20 + on(1, 2, 3, 4, u0=0)
21 ;
22 plot(u0);
23
24 // Error loop
25 real[int] errL2(10), errH1(10);
26 for (int i = 1; i <= 10; i++){
27 // Mesh
28 mesh Th = square(5+i*3,5+i*3);
29
30 // Fespace
31 fespace Vh(Th, P1);
32 Vh u, v;
33 fespace Ph(Th, P0);
34 Ph h = hTriangle; //get the size of all triangles
35
36 // Problem
37 solve Poisson (u, v)
38 = int2d(Th)(
39 dx(u)*dx(v)
40 + dy(u)*dy(v)
41 )
42 - int2d(Th)(
43 f*v
44 )
45 + on(1, 2, 3, 4, u=0)
46 ;
47
48 // Error
49 V0h uu = u; //interpolate solution on first mesh
50 errL2[i-1] = sqrt( int2d(Th0)((uu - u0)^2) )/h[].max^2;
51 errH1[i-1] = sqrt( int2d(Th0)(f*(u0 - 2*uu + uu)) )/h[].max;
52 }
53
54 // Display
55 cout << "C1 = " << errL2.max << "("<<errL2.min<<")" << endl;
56 cout << "C2 = " << errH1.max << "("<<errH1.min<<")" << endl;
We can guess that 𝐶1 = 0.0179253(0.0173266) and 𝐶2 = 0.0729566(0.0707543), where the numbers inside the
parentheses are minimum in calculation.
on the square ]0, 2𝜋[2 under bi-periodic boundary condition 𝑢(0, 𝑦) = 𝑢(2𝜋, 𝑦) for all 𝑦 and 𝑢(𝑥, 0) = 𝑢(𝑥, 2𝜋) for all
𝑥.
These boundary conditions are achieved from the definition of the periodic finite element space.
1 // Parameters
2 func f = sin(x+pi/4.)*cos(y+pi/4.); //right hand side
3
4 // Mesh
5 mesh Th = square(10, 10, [2*x*pi, 2*y*pi]);
6
7 // Fespace
8 //defined the fespace with periodic condition
9 //label: 2 and 4 are left and right side with y the curve abscissa
10 // 1 and 2 are bottom and upper side with x the curve abscissa
11 fespace Vh(Th, P2, periodic=[[2, y], [4, y], [1, x], [3, x]]);
12 Vh uh, vh;
13
14 // Problem
15 problem laplace (uh, vh)
16 = int2d(Th)(
17 dx(uh)*dx(vh)
18 + dy(uh)*dy(vh)
19 )
20 + int2d(Th)(
21 - f*vh
22 )
23 ;
24
25 // Solve
26 laplace;
27
28 // Plot
29 plot(uh, value=true);
The periodic condition does not necessarily require parallel boundaries. The following example give such example.
1 // Parameters
2 int n = 10;
3 real r = 0.25;
4 real r2 = 1.732;
5 func f = (y+x+1)*(y+x-1)*(y-x+1)*(y-x-1);
6
7 // Mesh
8 border a(t=0, 1){x=-t+1; y=t; label=1;};
9 border b(t=0, 1){x=-t; y=1-t; label=2;};
(continues on next page)
16 // Fespace
17 //warning for periodic condition:
18 //side a and c
19 //on side a (label 1) $ x \in [0,1] $ or $ x-y\in [-1,1] $
20 //on side c (label 3) $ x \in [-1,0]$ or $ x-y\in[-1,1] $
21 //so the common abscissa can be respectively $x$ and $x+1$
22 //or you can can try curviline abscissa $x-y$ and $x-y$
23 //1 first way
24 //fespace Vh(Th, P2, periodic=[[2, 1+x], [4, x], [1, x], [3, 1+x]]);
25 //2 second way
26 fespace Vh(Th, P2, periodic=[[2, x+y], [4, x+y], [1, x-y], [3, x-y]]);
27 Vh uh, vh;
28
29 // Problem
30 real intf = int2d(Th)(f);
31 real mTh = int2d(Th)(1);
32 real k = intf / mTh;
33 problem laplace (uh, vh)
34 = int2d(Th)(
35 dx(uh)*dx(vh)
36 + dy(uh)*dy(vh)
37 )
38 + int2d(Th)(
(continues on next page)
43 // Solve
44 laplace;
45
46 // Plot
47 plot(uh, wait=true);
Fig. 5.6: The isovalue of solution 𝑢 for ∆𝑢 = ((𝑦 + 𝑥)2 + 1)((𝑦 − 𝑥)2 + 1) − 𝑘, in Ω and 𝜕𝑛 𝑢 = 0 on hole, and with
two periodic boundary condition on external border
An other example with no equal border, just to see if the code works.
1 // Macro
2 //irregular boundary condition to build border AB
3 macro LINEBORDER(A, B, lab)
4 border A#B(t=0,1){ real t1=1.-t;
5 x=A#x*t1+B#x*t;
6 y=A#y*t1+B#y*t;
7 label=lab; } //EOM
8 // compute \||AB|\| A=(ax,ay) et B =(bx,by)
9 macro dist(ax, ay, bx, by)
10 sqrt(square((ax)-(bx)) + square((ay)-(by))) //EOM
11 macro Grad(u) [dx(u), dy(u)] //EOM
12
22 // Mesh
23 LINEBORDER(A,B,1)
24 LINEBORDER(B,C,2)
25 LINEBORDER(C,D,3)
26 LINEBORDER(D,A,4)
27 mesh Th=buildmesh(AB(n)+BC(n)+CD(n)+DA(n),fixedborder=1);
28
29 // Fespace
30 real l1 = dist(Ax,Ay,Bx,By);
31 real l2 = dist(Bx,By,Cx,Cy);
32 real l3 = dist(Cx,Cy,Dx,Dy);
33 real l4 = dist(Dx,Dy,Ax,Ay);
34 func s1 = dist(Ax,Ay,x,y)/l1; //absisse on AB = ||AX||/||AB||
35 func s2 = dist(Bx,By,x,y)/l2; //absisse on BC = ||BX||/||BC||
36 func s3 = dist(Cx,Cy,x,y)/l3; //absisse on CD = ||CX||/||CD||
37 func s4 = dist(Dx,Dy,x,y)/l4; //absisse on DA = ||DX||/||DA||
38 verbosity = 6; //to see the abscisse value of the periodic condition
39 fespace Vh(Th, P1, periodic=[[1, s1], [3, s3], [2, s2], [4, s4]]);
40 verbosity = 1; //reset verbosity
41 Vh u, v;
42
43 real cc = 0;
44 cc = int2d(Th)((x-gx)*(y-gy)-cc)/Th.area;
45 cout << "compatibility = " << int2d(Th)((x-gx)*(y-gy)-cc) <<endl;
46
47 // Problem
48 solve Poisson (u, v)
49 = int2d(Th)(
50 Grad(u)'*Grad(v)
51 + 1e-10*u*v
52 )
53 -int2d(Th)(
54 10*v*((x-gx)*(y-gy)-cc)
55 )
56 ;
57
58 // Plot
59 plot(u, value=true);
3 // Parameters
4 real hs = 0.1; //mesh size on sphere
5 int[int] N = [20, 20, 20];
6 real [int,int] B = [[-1, 1], [-1, 1], [-1, 1]];
7 int [int,int] L = [[1, 2], [3, 4], [5, 6]];
8
12 // Mesh
13 bool buildTh = 0;
14 mesh3 Th;
15 try { //a way to build one time the mesh or read it if the file exist
16 Th = readmesh3("Th-hex-sph.mesh");
17 }
18 catch (...){
19 buildTh = 1;
20 }
21
22 if (buildTh){
23 include "MeshSurface.idp"
24
25 // Surface Mesh
26 mesh3 ThH = SurfaceHex(N, B, L, 1);
27 mesh3 ThS = Sphere(0.5, hs, 7, 1);
28
35 savemesh(Th, "Th-hex-sph.mesh");
36 }
37
38 // Fespace
39 fespace Ph(Th, P0);
40 Ph reg = region;
41 cout << " centre = " << reg(0,0,0) << endl;
42 cout << " exterieur = " << reg(0,0,0.7) << endl;
43
44 verbosity = 50;
45 fespace Vh(Th, P1, periodic=[[3, x, z], [4, x, z], [1, y, z], [2, y, z], [5, x, y], [6,␣
˓→x, y]]);
46 verbosity = 1;
47 Vh uh,vh;
48
49 // Macro
50 macro Grad(u) [dx(u),dy(u),dz(u)] // EOM
51
52 // Problem
(continues on next page)
65 // Solve
66 Poisson;
67
68 // Plot
69 plot(uh, wait=true, nbiso=6);
70 medit("uh", Th, uh);
−∆𝑢 = 𝑓 in Ω
𝑢 = 𝑔 on Γ𝐷
𝜕𝑢/𝜕𝑛 = 0 on Γ𝑁
When Ω = {(𝑥, 𝑦); −1 < 𝑥 < 1, 0 < 𝑦 < 1}, Γ𝑁 = {(𝑥, 𝑦); −1 ≤ 𝑥 < 0, 𝑦 = 0}, Γ𝐷 = 𝜕Ω ∖ Γ𝑁 , the singularity
will appear at 𝛾1 = (0, 0), 𝛾2 (−1, 0), and 𝑢 has the expression:
𝑢 = 𝐾𝑖 𝑢𝑆 + 𝑢𝑅 , 𝑢𝑅 ∈ 𝐻 2 (near 𝛾𝑖 ), 𝑖 = 1, 2
with a constants 𝐾𝑖 .
1/2
Here 𝑢𝑆 = 𝑟𝑗 sin(𝜃𝑗 /2) by the local polar coordinate (𝑟𝑗 , 𝜃𝑗 at 𝛾𝑗 such that (𝑟1 , 𝜃1 ) = (𝑟, 𝜃).
Instead of polar coordinate system (𝑟, 𝜃), we use that 𝑟 = sqrt (𝑥2 + 𝑦 2 ) and 𝜃 = atan2 (𝑦, 𝑥) in FreeFEM.
Assume that 𝑓 = −2 × 30(𝑥2 + 𝑦 2 ) and 𝑔 = 𝑢𝑒 = 10(𝑥2 + 𝑦 2 )1/4 sin [tan−1 (𝑦/𝑥)]/2 + 30(𝑥2 𝑦 2 ), where 𝑢𝑒 S is
(︀ )︀
1 // Parameters
2 func f = -2*30*(x^2+y^2); //given function
3 //the singular term of the solution is K*us (K: constant)
4 func us = sin(atan2(y,x)/2)*sqrt( sqrt(x^2+y^2) );
5 real K = 10.;
6 func ue = K*us + 30*(x^2*y^2);
7
8 // Mesh
9 border N(t=0, 1){x=-1+t; y=0; label=1;};
10 border D1(t=0, 1){x=t; y=0; label=2;};
11 border D2(t=0, 1){x=1; y=t; label=2;};
12 border D3(t=0, 2){x=1-t; y=1; label=2;};
13 border D4(t=0, 1){x=-1; y=1-t; label=2;};
14
18 // Fespace
19 fespace V0h(T0h, P1);
20 V0h u0, v0;
21
22 //Problem
23 solve Poisson0 (u0, v0)
24 = int2d(T0h)(
25 dx(u0)*dx(v0)
26 + dy(u0)*dy(v0)
27 )
28 - int2d(T0h)(
29 f*v0
30 )
31 + on(2, u0=ue)
32 ;
33
39 // Fespace
40 fespace Vh(Th, P1);
41 Vh u, v;
(continues on next page)
43 // Problem
44 solve Poisson (u, v)
45 = int2d(Th)(
46 dx(u)*dx(v)
47 + dy(u)*dy(v)
48 )
49 - int2d(Th)(
50 f*v
51 )
52 + on(2, u=ue)
53 ;
54
55 // Plot
56 plot(Th);
57 plot(u, wait=true);
58
59 // Error in H1 norm
60 Vh uue = ue;
61 real H1e = sqrt( int2d(Th)(dx(uue)^2 + dy(uue)^2 + uue^2) );
62 Vh err0 = u0 - ue;
63 Vh err = u - ue;
64 Vh H1err0 = int2d(Th)(dx(err0)^2 + dy(err0)^2 + err0^2);
65 Vh H1err = int2d(Th)(dx(err)^2 + dy(err)^2 + err^2);
66 cout << "Relative error in first mesh = "<< int2d(Th)(H1err0)/H1e << endl;
67 cout << "Relative error in adaptive mesh = "<< int2d(Th)(H1err)/H1e << endl;
From line 35 to 37, mesh adaptations are done using the base of singular term.
In line 61, H1e = |𝑢𝑒 |1,Ω is calculated.
In lines 64 and 65, the relative errors are calculated, that is:
‖𝑢0ℎ − 𝑢𝑒 ‖1,Ω /𝐻1𝑒 = 0.120421
‖𝑢𝑎ℎ − 𝑢𝑒 ‖1,Ω /𝐻1𝑒 = 0.0150581
where 𝑢0ℎ is the numerical solution in T0h and 𝑢𝑎ℎ is u in this program.
Here we consider the Poisson equation with mixed boundary value problems:
For given functions 𝑓 , 𝑔𝑑 , 𝑔𝑛 , find 𝑝 such that
−∆𝑝 = 1 in Ω
𝑝 = 𝑔𝑑 on Γ𝐷
𝜕𝑝/𝜕𝑛 = 𝑔𝑛 on Γ𝑁
and:
V0 = {v ∈ V; v.𝑛 = 0 on Γ𝑁 }
To write the FreeFEM example, we have just to choose the finites elements spaces.
Here V space is discretize with Raviart-Thomas finite element RT0 and P is discretize by constant finite element P0.
Example 9.10 LaplaceRT.edp
1 // Parameters
2 func gd = 1.;
3 func g1n = 1.;
4 func g2n = 1.;
5
6 // Mesh
7 mesh Th = square(10, 10);
8
9 // Fespace
10 fespace Vh(Th, RT0);
11 Vh [u1, u2];
12 Vh [v1, v2];
13
17 // Problem
18 problem laplaceMixte ([u1, u2, p], [v1, v2, q], solver=GMRES, eps=1.0e-10, tgv=1e30,␣
˓→dimKrylov=150)
19 = int2d(Th)(
20 p*q*1e-15 //this term is here to be sure
21 // that all sub matrix are inversible (LU requirement)
22 + u1*v1
23 + u2*v2
24 + p*(dx(v1)+dy(v2))
25 + (dx(u1)+dy(u2))*q
26 )
27 + int2d(Th) (
28 q
29 )
30 - int1d(Th, 1, 2, 3)(
31 gd*(v1*N.x +v2*N.y)
32 )
33 + on(4, u1=g1n, u2=g2n)
(continues on next page)
36 // Solve
37 laplaceMixte;
38
39 // Plot
40 plot([u1, u2], coef=0.1, wait=true, value=true);
41 plot(p, fill=1, wait=true, value=true);
We do metric mesh adaption and compute the classical residual error indicator 𝜂𝑇 on the element 𝑇 for the Poisson
problem.
First, we solve the same problem as in a previous example.
1 // Parameters
2 real[int] viso(21);
3 for (int i = 0; i < viso.n; i++)
4 viso[i] = 10.^(+(i-16.)/2.);
5 real error = 0.01;
6 func f = (x-y);
7
8 // Mesh
9 border ba(t=0, 1.0){x=t; y=0; label=1;}
10 border bb(t=0, 0.5){x=1; y=t; label=2;}
11 border bc(t=0, 0.5){x=1-t; y=0.5; label=3;}
12 border bd(t=0.5, 1){x=0.5; y=t; label=4;}
13 border be(t=0.5, 1){x=1-t; y=1; label=5;}
14 border bf(t=0.0, 1){x=0; y=1-t; label=6;}
15 mesh Th = buildmesh(ba(6) + bb(4) + bc(4) + bd(4) + be(4) + bf(6));
16
17 // Fespace
18 fespace Vh(Th, P2);
19 Vh u, v;
20
24 // Problem
25 problem Probem1 (u, v, solver=CG, eps=1.0e-6)
26 = int2d(Th, qforder=5)(
27 u*v*1.0e-10
28 + dx(u)*dx(v)
29 + dy(u)*dy(v)
30 )
31 + int2d(Th, qforder=5)(
32 - f*v
33 )
34 ;
where ℎ𝑇 is the longest edge of 𝑇 , ℰ𝑇 is the set of 𝑇 edge not on Γ = 𝜕Ω, 𝑛𝑇 is the outside unit normal to 𝐾, ℎ𝑒 is
the length of edge 𝑒, [𝑔] is the jump of the function 𝑔 across edge (left value minus right value).
Of course, we can use a variational form to compute 𝜂𝑇2 , with test function constant function in each triangle.
1 // Error
2 varf indicator2 (uu, chiK)
3 = intalledges(Th)(
4 chiK*lenEdge*square(jump(N.x*dx(u) + N.y*dy(u)))
5 )
6 + int2d(Th)(
7 chiK*square(hTriangle*(f + dxx(u) + dyy(u)))
8 )
9 ;
10
18 // Error
19 rho[] = indicator2(0, Nh);
20 rho = sqrt(rho);
21 cout << "rho = min " << rho[].min << " max=" << rho[].max << endl;
22 plot(rho, fill=true, wait=true, cmm="indicator density", value=true, viso=viso,␣
˓→nbiso=viso.n);
23
24 // Mesh adaptation
25 plot(Th, wait=true, cmm="Mesh (before adaptation)");
26 Th = adaptmesh(Th, [dx(u), dy(u)], err=error, anisomax=1);
27 plot(Th, wait=true, cmm="Mesh (after adaptation)");
28 u = u;
29 rho = rho;
30 error = error/2;
31 }
If the method is correct, we expect to look the graphics by an almost constant function 𝜂 on your computer as in Fig.
5.8a and Fig. 5.8b.
(a) Density of the error indicator with isotropic 𝑃2 metric (b) Density of the error indicator with isotropic 𝑃2 metric
In the previous example we compute the error indicator, now we use it, to adapt the mesh. The new mesh size is given
by the following formulae:
ℎ𝑛 (𝑥)
ℎ𝑛+1 (𝑥) =
𝑓𝑛 (𝜂𝐾 (𝑥))
where 𝜂𝑛 (𝑥) is the level of error at point 𝑥 given by the local error indicator, ℎ𝑛 is the previous “mesh size” field, and
𝑓𝑛 is a user function define by 𝑓𝑛 = 𝑚𝑖𝑛(3, 𝑚𝑎𝑥(1/3, 𝜂𝑛 /𝜂𝑛* )) where 𝜂𝑛* = 𝑚𝑒𝑎𝑛(𝜂𝑛 )𝑐, and 𝑐 is an user coefficient
generally close to one.
First a macro MeshSizecomputation is defined to get a 𝑃1 mesh size as the average of edge length.
19 /*etaK is discontinous*/
20 /*we use P1 L2 projection with mass lumping*/
21 Vh fn, sigma;
22 varf veta(unused, v) = int2d(Th)(etak*v);
23 varf vun(unused, v) = int2d(Th)(1*v);
24 fn[] = veta(0, Vh);
25 sigma[] = vun(0, Vh);
26 fn[] = fn[]./ sigma[];
27 fn = max(min(fn/etastar,3.),0.3333);
28
1 // Parameters
2 real hinit = 0.2; //initial mesh size
3 func f=(x-y);
4
5 // Mesh
6 border ba(t=0, 1.0){x=t; y=0; label=1;}
7 border bb(t=0, 0.5){x=1; y=t; label=2;}
8 border bc(t=0, 0.5){x=1-t; y=0.5; label=3;}
9 border bd(t=0.5, 1){x=0.5; y=t; label=4;}
10 border be(t=0.5, 1){x=1-t; y=1; label=5;}
11 border bf(t=0.0, 1){x=0; y=1-t; label=6;}
12 mesh Th = buildmesh(ba(6) + bb(4) + bc(4) + bd(4) + be(4) + bf(6));
13
14 // Fespace
15 fespace Vh(Th, P1); //for the mesh size and solution
16 Vh h = hinit; //the FE function for the mesh size
17 Vh u, v;
18
25 // Problem
26 problem Poisson (u, v)
27 = int2d(Th, qforder=5)(
28 u*v*1.0e-10
29 + dx(u)*dx(v)
30 + dy(u)*dy(v)
31 )
32 - int2d(Th, qforder=5)(
33 f*v
34 )
35 ;
36
50 // Solve
51 Poisson;
52 plot(Th, u, wait=true);
53
54 real cc = 0.8;
55 if (i > 5) cc=1;
56 ReMeshIndicator(Th, Ph, Vh, indicator2, cc);
57 plot(Th, wait=true);
58 }
5.2 Elasticity
(a) The error indicator with isotropic 𝑃1 (b) The mesh and isovalue of the solution
where 𝜈𝑖 = ∆𝑥𝑖 |∆x|−1 . If the deformation is small, then we may consider that:
where 𝜎𝑖𝑗 (x) is called stress tensor at x. Hooke’s law is the assumption of a linear relation between 𝜎𝑖𝑗 and 𝜀𝑖𝑗 such
as:
We assume that the elastic plate is fixed on Γ𝐷 ×] − ℎ, ℎ[, Γ𝐷 ⊂ 𝜕Ω. If the body force f = (𝑓1 , 𝑓2 , 𝑓3 ) is given in
Ω×] − ℎ, ℎ[ and surface force g is given in Γ𝑁 ×] − ℎ, ℎ[, Γ𝑁 = 𝜕Ω ∖ Γ𝐷 , then the equation of equilibrium is given
as follows:
−𝜕𝑗 𝜎𝑖𝑗 = 𝑓𝑖 in Ω×] − ℎ, ℎ[, 𝑖 = 1, 2, 3
(5.4)
𝜎𝑖𝑗 𝑛𝑗 = 𝑔𝑖 on Γ𝑁 ×] − ℎ, ℎ[, 𝑢𝑖 = 0 on Γ𝐷 ×] − ℎ, ℎ[, 𝑖 = 1, 2, 3
𝜎3𝑖 = 0, 𝑥3 = ±ℎ, 𝑖 1, 2, 3
The assumption leads that 𝜎3𝑖 = 0 in Ω×] − ℎ, ℎ[ and u(𝑥1 , 𝑥2 , 𝑥3 ) = 𝑢(𝑥1 , 𝑥2 ) for all −ℎ < 𝑥3 < ℎ.
• Generalized plain stress:
The cylinder is subjected to no load at 𝑥3 = ±ℎ. Introducing the mean values with respect to thickness,
∫︁ ℎ
1
𝑢𝑖 (𝑥1 , 𝑥2 ) = 𝑢(𝑥1 , 𝑥2 , 𝑥3 )𝑑𝑥3
2ℎ −ℎ
and we derive 𝑢3 ≡ 0. Similarly we define the mean values 𝑓 , 𝑔 of the body force and surface force as well as
the mean values 𝜀𝑖𝑗 and 𝜎 𝑖𝑗 of the components of stress and strain, respectively.
In what follows we omit the overlines of 𝑢, 𝑓 , 𝑔, 𝜀𝑖𝑗 and 𝜀𝑖𝑗 . Then we obtain similar equation of equilibrium given in
(5.4) replacing Ω×] − ℎ, ℎ[ with Ω and changing 𝑖 = 1, 2. In the case of plane stress, 𝜎𝑖𝑗 = 𝜆* 𝛿𝑖𝑗 div𝑢 + 2𝜇𝜀𝑖𝑗 , 𝜆* =
(2𝜆𝜇)/(𝜆 + 𝜇).
The equations of elasticity are naturally written in variational form for the displacement vector u(x) ∈ 𝑉 as:
∫︁ ∫︁ ∫︁
[2𝜇𝜖𝑖𝑗 (u)𝜖𝑖𝑗 (v) + 𝜆𝜖𝑖𝑖 (u)𝜖𝑗𝑗 (v)] = f · v + g · v, ∀v ∈ 𝑉
Ω Ω Γ
Tip: Beam
Consider an elastic plate with the undeformed rectangle shape ]0, 10[×]0, 2[. The body force is the gravity force f and
the boundary force g is zero on lower and upper side. On the two vertical sides of the beam are fixed.
1 // Parameters
2 real E = 21.5;
3 real sigma = 0.29;
4 real gravity = -0.05;
5
6 // Mesh
7 border a(t=2, 0){x=0; y=t; label=1;}
8 border b(t=0, 10){x=t; y=0; label=2;}
9 border c(t=0, 2){ x=10; y=t; label=1;}
10 border d(t=0, 10){ x=10-t; y=2; label=3;}
(continues on next page)
13 // Fespace
14 fespace Vh(th, [P1, P1]);
15 Vh [uu, vv];
16 Vh [w, s];
17
18 // Macro
19 real sqrt2 = sqrt(2.);
20 macro epsilon(u1, u2) [dx(u1), dy(u2), (dy(u1)+dx(u2))/sqrt2] //
21 macro div(u,v) (dx(u) + dy(v)) //
22
23 // Problem
24 real mu = E/(2*(1+sigma));
25 real lambda = E*sigma/((1+sigma)*(1-2*sigma));
26 solve Elasticity ([uu, vv], [w, s])
27 = int2d(th)(
28 lambda*div(w,s)*div(uu,vv)
29 + 2.*mu*( epsilon(w,s)'*epsilon(uu,vv) )
30 )
31 + int2d(th)(
32 - gravity*s
33 )
34 + on(1, uu=0, vv=0)
35 ;
36
37 // Plot
38 plot([uu, vv], wait=true);
39 plot([uu,vv], wait=true, bb=[[-0.5, 2.5], [2.5, -0.5]]);
40
41 // Movemesh
42 mesh th1 = movemesh(th, [x+uu, y+vv]);
43 plot(th1, wait=true);
Tip: Beam 3D
Consider elastic box with the undeformed parallelepiped shape ]0, 5[×]0, 1[×]0, 1[. The body force is the gravity force
f and the boundary force g is zero on all face except one the one vertical left face where the beam is fixed.
1 include "cube.idp"
2
3 // Parameters
4 int[int] Nxyz = [20, 5, 5];
5 real [int, int] Bxyz = [[0., 5.], [0., 1.], [0., 1.]];
6 int [int, int] Lxyz = [[1, 2], [2, 2], [2, 2]];
7
8 real E = 21.5e4;
9 real sigma = 0.29;
10 real gravity = -0.05;
11
12 // Mesh
(continues on next page)
15 // Fespace
16 fespace Vh(Th, [P1, P1, P1]);
17 Vh [u1, u2, u3], [v1, v2, v3];
18
19 // Macro
20 real sqrt2 = sqrt(2.);
21 macro epsilon(u1, u2, u3) [
22 dx(u1), dy(u2), dz(u3),
23 (dz(u2) + dy(u3))/sqrt2,
24 (dz(u1) + dx(u3))/sqrt2,
25 (dy(u1) + dx(u2))/sqrt2] //
26 macro div(u1, u2, u3) (dx(u1) + dy(u2) + dz(u3)) //
27
28 // Problem
29 real mu = E/(2*(1+sigma));
30 real lambda = E*sigma/((1+sigma)*(1-2*sigma));
31
43 // Display
44 real dmax = u1[].max;
45 cout << "max displacement = " << dmax << endl;
46
47 // Movemesh
48 real coef = 0.1/dmax;
49 int[int] ref2 = [1, 0, 2, 0];
50 mesh3 Thm = movemesh3(Th, transfo=[x+u1*coef, y+u2*coef, z+u3*coef], label=ref2);
51 Thm = change(Thm, label=ref2);
52
53 // Plot
54 plot(Th, Thm, wait=true, cmm="coef amplification = "+coef);
Consider the plate with the crack whose undeformed shape is a curve Σ with the two edges 𝛾1 , 𝛾2 .
We assume the stress tensor 𝜎𝑖𝑗 is the state of plate stress regarding (𝑥, 𝑦) ∈ ΩΣ = Ω ∖ Σ. Here Ω stands for the
undeformed shape of elastic plate without crack.
If the part Γ𝑁 of the boundary 𝜕Ω is fixed and a load ℒ = (f , g) ∈ 𝐿2 (Ω)2 × 𝐿2 (Γ𝑁 )2 is given, then the displacement
u is the minimizer of the potential energy functional:
∫︁ ∫︁
ℰ(v; ℒ, ΩΣ ) = {𝑤(𝑥, v) − f · v} − g·v
ΩΣ Γ𝑁
𝑉 (ΩΣ ) = v ∈ 𝐻 1 (ΩΣ )2 ; v = 0 on Γ𝐷 = 𝜕Ω ∖ Γ𝑁 ,
{︀ }︀
𝜎𝑖𝑗 (v) = 𝐶𝑖𝑗𝑘𝑙 (𝑥)𝜀𝑘𝑙 (v), 𝜀𝑖𝑗 (v) = (𝜕𝑣𝑖 /𝜕𝑥𝑗 + 𝜕𝑣𝑗 /𝜕𝑥𝑖 )/2, (𝐶𝑖𝑗𝑘𝑙 : Hooke’s tensor).
If the elasticity is homogeneous isotropic, then the displacement u(𝑥) is decomposed in an open neighborhood 𝑈𝑘 of
𝛾𝑘 as in (see e.g. [OHTSUKA2000])
2
1/2
∑︁
u(𝑥) = 𝐶
𝐾𝑙 (𝛾𝑘 )𝑟𝑘 𝑆𝑘𝑙 (𝜃𝑘 ) + u𝑘,𝑅 (𝑥) for 𝑥 ∈ ΩΣ ∩ 𝑈𝑘 , 𝑘 = 1, 2 (5.5)
𝑙=1
with u𝑘,𝑅 ∈ 𝐻 2 (ΩΣ ∩𝑈𝑘 )2 , where 𝑈𝑘 , 𝑘 = 1, 2 are open neighborhoods of 𝛾𝑘 such that 𝜕𝐿1 ∩𝑈1 = 𝛾1 , 𝜕𝐿𝑚 ∩𝑈2 =
𝛾2 , and
[︂ ]︂
𝐶 1 1 [2𝜅 − 1] cos(𝜃𝑘 /2) − cos(3𝜃𝑘 /2)
𝑆𝑘1 (𝜃𝑘 ) = 4𝜇 ,
[︂ −[2𝜅 + 1] sin(𝜃𝑘 /2) + sin(3𝜃𝑘 /2) ]︂
(2𝜋)1/2
where 𝜇 is the shear modulus of elasticity, 𝜅 = 3 − 4𝜈 (𝜈 is the Poisson’s ratio) for plane strain and 𝜅 = 3−𝜈
1+𝜈 for plane
stress.
The coefficients 𝐾1 (𝛾𝑖 ) and 𝐾2 (𝛾𝑖 ), which are important parameters in fracture mechanics, are called stress intensity
factors of the opening mode (mode I) and the sliding mode (mode II), respectively.
For simplicity, we consider the following simple crack
with only one crack tip 𝛾 = (0, 0). Unfortunately, FreeFEM cannot treat crack, so we use the modification of the
domain with U-shape channel (see U-shape example, Fig. 3.19) with 𝑑 = 0.0001. The undeformed crack Σ is approx-
imated by
• Adaptivity is an important technique here, because a large singularity occurs at 𝛾 as shown in (5.5).
The first example creates mode I deformation by the opposed surface force on B and T in the vertical direction of Σ,
and the displacement is fixed on R.
In a laboratory, fracture engineers use photoelasticity to make stress field visible, which shows the principal stress
difference
√︁
𝜎1 − 𝜎2 = (𝜎11 − 𝜎22 )2 + 4𝜎12 2
1 //Parameters
2 real d = 0.0001; int n = 5; real cb = 1, ca = 1, tip = 0.0;
3
4 real E = 21.5;
5 real sigma = 0.29;
6
7 // Mesh
8 border L1(t=0, ca-d){x=-cb; y=-d-t;}
9 border L2(t=0, ca-d){x=-cb; y=ca-t;}
10 border B(t=0, 2){x=cb*(t-1); y=-ca;}
11 border C1(t=0, 1){x=-ca*(1-t)+(tip-10*d)*t; y=d;}
12 border C21(t=0, 1){x=(tip-10*d)*(1-t)+tip*t; y=d*(1-t);}
13 border C22(t=0, 1){x=(tip-10*d)*t+tip*(1-t); y=-d*t;}
14 border C3(t=0, 1){x=(tip-10*d)*(1-t)-ca*t; y=-d;}
15 border C4(t=0, 2*d){x=-ca; y=-d+t;}
16 border R(t=0, 2){x=cb; y=cb*(t-1);}
17 border T(t=0, 2){x=cb*(1-t); y=ca;}
(continues on next page)
22 cb=0.1; ca=0.1;
23 mesh Zoom = buildmesh(L1(n/2) + L2(n/2) + B(n) + C1(n)
24 + C21(3) + C22(3) + C3(n) + R(n) + T(n));
25 plot(Zoom, wait=true);
26
27 // Fespace
28 fespace Vh(Th, [P2, P2]);
29 Vh [u, v];
30 Vh [w, s];
31
35 // Problem
36 real mu = E/(2*(1+sigma));
37 real lambda = E*sigma/((1+sigma)*(1-2*sigma));
38 solve Problem ([u, v], [w, s])
39 = int2d(Th)(
40 2*mu*(dx(u)*dx(w) + ((dx(v)+dy(u))*(dx(s)+dy(w)))/4)
41 + lambda*(dx(u) + dy(v))*(dx(w) + dy(s))/2
42 )
43 -int1d(Th, T)(
44 0.1*(1-x)*s
45 )
46 +int1d(Th, B)(
47 0.1*(1-x)*s
48 )
49 +on(R, u=0, v=0)
50 ;
51
52 // Loop
53 for (int i = 1; i <= 5; i++){
54 mesh Plate = movemesh(Zoom, [x+u, y+v]); //deformation near gamma
55 Sx = lambda*(dx(u) + dy(v)) + 2*mu*dx(u);
56 Sy = lambda*(dx(u) + dy(v)) + 2*mu*dy(v);
57 Sxy = mu*(dy(u) + dx(v));
58 N = 0.1*1*sqrt((Sx-Sy)^2 + 4*Sxy^2); //principal stress difference
59 if (i == 1){
60 plot(Plate, bw=1);
61 plot(N, bw=1);
62 }
63 else if (i == 5){
64 plot(Plate, bw=1);
65 plot(N, bw=1);
66 break;
67 }
68
69 // Adaptmesh
(continues on next page)
(a) Crack open displacement (COD) on the first mesh (b) Principal stress difference on the first mesh
(c) COD on the last adaptive mesh (d) Principal stress difference on the last adaptive mesh
72 // Solve
73 Problem;
74 }
It is difficult to create mode II deformation by the opposed shear force on B and T that is observed in a laboratory. So
we use the body shear force along Σ, that is, the 𝑥-component 𝑓1 of the body force f is given by
1 // Parameters
2 real d = 0.0001; int n = 5; real cb = 1, ca = 1, tip = 0.0;
3
4 real E = 21.5;
5 real sigma = 0.29;
6
22 cb=0.1; ca=0.1;
23 mesh Zoom = buildmesh(L1(n/2) + L2(n/2) + B(n) + C1(n)
24 + C21(3) + C22(3) + C3(n) + R(n) + T(n));
25 plot(Zoom, wait=true);
26
27 // Fespace
28 fespace Vh(Th, [P2, P2]);
29 Vh [u, v];
30 Vh [w, s];
31
35 fespace Vh1(Th,P1);
36 Vh1 fx = ((y>0.001)*(y<0.1))-((y<-0.001)*(y>-0.1));
37
38 // Problem
39 real mu = E/(2*(1+sigma));
40 real lambda = E*sigma/((1+sigma)*(1-2*sigma));
41 solve Problem ([u, v], [w, s])
42 = int2d(Th)(
43 2*mu*(dx(u)*dx(w) + ((dx(v) + dy(u))*(dx(s)+ dy(w)))/4)
44 + lambda*(dx(u) + dy(v))*(dx(w) + dy(s))/2
45 )
46 -int2d(Th)(
47 fx*w
48 )
49 +on(R, u=0, v=0)
50 ;
51
52 // Loop
53 for (int i = 1; i <= 3; i++){
54 mesh Plate = movemesh(Zoom, [x+u, y+v]); //deformation near gamma
55 Sx = lambda*(dx(u) + dy(v)) + 2*mu*dx(u);
56 Sy = lambda*(dx(u) + dy(v)) + 2*mu*dy(v);
57 Sxy = mu*(dy(u) + dx(v));
58 N = 0.1*1*sqrt((Sx-Sy)^2 + 4*Sxy^2); //principal stress difference
(continues on next page)
(a) COD on the first mesh (b) Principal stress difference in the first mesh
(c) COD on the last adaptive mesh (d) Principal stress difference on the last adaptive mesh
69 // Adaptmesh
70 Th=adaptmesh(Th, [u, v]);
71
72 // Solve
73 Problem;
74 }
Here we propose to solve the following non-linear academic problem of minimization of a functional:
∫︁
1
𝐽(𝑢) = 𝑓 (|∇𝑢|2 ) − 𝑢 * 𝑏
Ω 2
Now, we solve the Euler problem ∇𝐽(𝑢) = 0 with Newton-Raphson algorithm, that is:
1 // Parameters
2 real a = 0.001;
3 func b = 1.;
4
5 // Mesh
6 mesh Th = square(10, 10);
7 Th = adaptmesh(Th, 0.05, IsMetric=1, splitpbedge=1);
8 plot(Th, wait=true);
9
10 // Fespace
11 fespace Vh(Th, P1);
12 Vh u=0;
13 Vh v, w;
14
19 // Function
20 func real f (real u){
21 return u*a + u - log(1.+u);
22 }
23 func real df (real u){
24 return a +u/(1.+u);
25 }
26 func real ddf (real u){
27 return 1. / ((1.+u)*(1.+u));
28 }
29
30 // Problem
31 //the variational form of evaluate dJ = nabla J
32 //dJ = f'()*(dx(u)*dx(vh) + dy(u)*dy(vh))
33 varf vdJ (uh, vh)
34 = int2d(Th)(
(continues on next page)
52 // Newton algorithm
53 for (int i = 0; i < 100; i++){
54 // Compute f' and f''
55 alpha = df(dx(u)*dx(u) + dy(u)*dy(u));
56 dalpha = 2*ddf(dx(u)*dx(u) + dy(u)*dy(u));
57
58 // nabla J
59 v[]= vdJ(0, Vh);
60
61 // Residual
62 real res = v[]'*v[];
63 cout << i << " residu^2 = " << res << endl;
64 if( res < 1e-12) break;
65
66 // HJ
67 matrix H = vhJ(Vh, Vh, factorize=1, solver=LU);
68
69 // Newton
70 w[] = H^-1*v[];
71 u[] -= w[];
72 }
73
74 // Plot
75 plot (u, wait=true, cmm="Solution with Newton-Raphson");
This section depends on your installation of FreeFEM; you need to have compiled ARPACK. This tool is available in
FreeFEM if the word eigenvalue appears in line Load:, like:
This tool is based on arpack++, the object-oriented version of ARPACK eigenvalue package [LEHOUCQ1998].
The function EigenValue computes the generalized eigenvalue of 𝐴𝑢 = 𝜆𝐵𝑢. The Shift-invert method is used by
default, with sigma =𝜎 the shift of the method.
The matrix 𝑂𝑃 is defined with 𝐴 − 𝜎𝐵.
The return value is the number of converged eigenvalues (can be greater than the number of requested eigenvalues
nev=)
where the matrix 𝑂𝑃 = 𝐴 − 𝜎𝐵 with a solver and boundary condition, and the matrix 𝐵.
There is also a functional interface:
where 𝑛 is the size of the problem, and the operators are now defined through functions, defining respectively the matrix
product of 𝑂𝑃 −1 and 𝐵, as in
1 int n = OP1.n;
2 func real[int] FOP1(real[int] & u){ real[int] Au = OP^-1*u; return Au; }
3 func real[int] FB(real[int] & u){ real[int] Au = B*u; return Au; }
If you want finer control over the method employed in ARPACK, you can specify which mode ARPACK will work with
(mode= , see ARPACK documentation [LEHOUCQ1998]). The operators necessary for the chosen mode can be passed
through the optional parameters A=, A1=, B=, B1=, (see below).
• mode=1: Regular mode for solving 𝐴𝑢 = 𝜆𝑢
where the functions FOP, FB and FB1 define respectively the matrix product of 𝐴, 𝐵 and 𝐵 −1
• mode=3: Shift-invert mode for solving 𝐴𝑢 = 𝜆𝐵𝑢
where the functions FOP1 and FB define respectively the matrix product of 𝑂𝑃 −1 = (𝐴 − 𝜎𝐵)−1 and 𝐵
You can also specify which subset of eigenvalues you want to compute (which=). The default value is which="LM",
for eigenvalues with largest magnitude. "SM" is for smallest magnitude, "LA" for largest algebraic value, "SA" for
smallest algebraic value, and "BE" for both ends of the spectrum.
Remark: For complex problems, you need to use the keyword complexEigenValue instead of EigenValue when
passing operators through functions.
• sym= The problem is symmetric (all the eigen value are real)
• nev= The number desired eigenvalues (nev) close to the shift.
• value= The array to store the real part of the eigenvalues
• ivalue= The array to store the imaginary part of the eigenvalues
• vector= The FE function array to store the eigenvectors
• rawvector= An array of type real[int,int] to store eigenvectors by column.
For real non symmetric problems, complex eigenvectors are given as two consecutive vectors, so if eigenvalue 𝑘
and 𝑘 + 1 are complex conjugate eigenvalues, the 𝑘th vector will contain the real part and the 𝑘 + 1th vector the
imaginary part of the corresponding complex conjugate eigenvectors.
• tol= The relative accuracy to which eigenvalues are to be determined;
• sigma= The shift value;
• maxit= The maximum number of iterations allowed;
• ncv= The number of Arnoldi vectors generated at each iteration of ARPACK;
• mode= The computational mode used by ARPACK (see above);
• which= The requested subset of eigenvalues (see above).
The exact eigenvalues are 𝜆𝑛,𝑚 = (𝑛2 + 𝑚2 ), (𝑛, 𝑚) ∈ N* 2 with the associated eigenvectors are 𝑢𝑚,𝑛 = sin(𝑛𝑥) *
sin(𝑚𝑦).
We use the generalized inverse shift mode of the arpack++ library, to find 20 eigenvalues and eigenvectors close to the
shift value 𝜎 = 20.
1 // Parameters
2 verbosity=0;
3 real sigma = 20; //value of the shift
4 int nev = 20; //number of computed eigen value close to sigma
(continues on next page)
6 // Mesh
7 mesh Th = square(20, 20, [pi*x, pi*y]);
8
9 // Fespace
10 fespace Vh(Th, P2);
11 Vh u1, u2;
12
13 // Problem
14 // OP = A - sigma B ; // the shifted matrix
15 varf op (u1, u2)
16 = int2d(Th)(
17 dx(u1)*dx(u2)
18 + dy(u1)*dy(u2)
19 - sigma* u1*u2
20 )
21 + on(1, 2, 3, 4, u1=0)
22 ;
23
26 matrix OP = op(Vh, Vh, solver=Crout, factorize=1); //crout solver because the matrix in␣
˓→not positive
29 // important remark:
30 // the boundary condition is make with exact penalization:
31 // we put 1e30=tgv on the diagonal term of the lock degree of freedom.
32 // So take Dirichlet boundary condition just on $a$ variational form
33 // and not on $b$ variational form.
34 // because we solve $ w=OP^-1*B*v $
35
36 // Solve
37 real[int] ev(nev); //to store the nev eigenvalue
38 Vh[int] eV(nev); //to store the nev eigenvector
39
(a) Isovalue of 11th eigenvector 𝑢4,3 − 𝑢3,4 (b) Isovalue of 12th eigenvector 𝑢4,3 + 𝑢3,4
We use the definition of the partial derivative of the solution in the time derivative:
𝜕𝑢 𝑢(𝑥, 𝑦, 𝑡) − 𝑢(𝑥, 𝑦, 𝑡 − 𝜏 )
(𝑥, 𝑦, 𝑡) = lim
𝜕𝑡 𝜏 →0 𝜏
which indicates that 𝑢𝑚 (𝑥, 𝑦) = 𝑢(𝑥, 𝑦, 𝑚𝜏 ) will satisfy approximatively:
Using the identity just above, we can calculate the finite element approximation 𝑢𝑚
ℎ of 𝑢
𝑚
in a step-by-step manner
with respect to 𝑡.
Tip: Example
We now solve the following example with the exact solution 𝑢(𝑥, 𝑦, 𝑡) = 𝑡𝑥4 , Ω =]0, 1[2 .
𝜕𝑢
𝜕𝑡 − 𝜇∆𝑢 = 𝑥4 − 𝜇12𝑡𝑥2 in Ω×]0, 3[
𝑢(𝑥, 𝑦, 0) = 0 on Ω
𝑢|𝜕Ω = 𝑡 * 𝑥4
1 // Parameters
2 real dt = 0.1;
3 real mu = 0.01;
4
5 // Mesh
6 mesh Th = square(16, 16);
7
8 // Fespace
9 fespace Vh(Th, P1);
10 Vh u, v, uu, f, g;
11
12 // Problem
13 problem dHeat (u, v)
(continues on next page)
25 // Time loop
26 real t = 0;
27 uu = 0;
28 for (int m = 0; m <= 3/dt; m++){
29 // Update
30 t = t+dt;
31 f = x^4 - mu*t*12*x^2;
32 g = t*x^4;
33 uu = u;
34
35 // Solve
36 dHeat;
37
38 // Plot
39 plot(u, wait=true);
40 cout << "t=" << t << " - L^2-Error=" << sqrt(int2d(Th)((u-t*x^4)^2)) << endl;
41 }
(︁∫︀ ⃒ ⃒2 )︁1/2
In the last statement, the 𝐿2 -error Ω ⃒𝑢 − 𝑡𝑥4 ⃒ is calculated at 𝑡 = 𝑚𝜏, 𝜏 = 0.1. At 𝑡 = 0.1, the error is
0.000213269. The errors increase with 𝑚 and 0.00628589 at 𝑡 = 3.
The iteration of the backward Euler (5.7) is made by for loop.
Note: The stiffness matrix in the loop is used over and over again. FreeFEM support reuses of stiffness matrix.
In this section, we show the advantage of implicit schemes. Let 𝑉, 𝐻 be separable Hilbert space and 𝑉 is dense in 𝐻.
Let 𝑎 be a continuous bilinear form over 𝑉 × 𝑉 with coercivity and symmetry.
Then 𝑎(𝑣, 𝑣) become equivalent to the norm ‖𝑣‖ of 𝑉 .
√︀
Let us denote the time step by 𝜏 > 0, 𝑁𝑇 = [𝑇 /𝜏 ]. For the discretization, we put 𝑢𝑛 = 𝑢(𝑛𝜏 ) and consider the time
difference for each 𝜃 ∈ [0, 1]
1
(︀ 𝑛+1
− 𝑢𝑛ℎ , 𝜑𝑖 + 𝑎 𝑢ℎ𝑛+𝜃 , 𝜑𝑖
)︀ (︀ )︀
𝜏 𝑢ℎ = ⟨𝑓 𝑛+𝜃 , 𝜑𝑖 ⟩
𝑖 = 1, · · · , 𝑚, 𝑛 = 0, · · · , 𝑁𝑇
𝑢𝑛+𝜃
ℎ = 𝜃𝑢𝑛+1
ℎ + (1 − 𝜃)𝑢𝑛ℎ ,
𝑛+𝜃 𝑛+1
𝑓 = 𝜃𝑓 + (1 − 𝜃)𝑓 𝑛
Formula (5.8) is the forward Euler scheme if 𝜃 = 0, Crank-Nicolson scheme if 𝜃 = 1/2, the backward Euler scheme if
𝜃 = 1.
ℎ ) in
Unknown vectors 𝑢𝑛 = (𝑢1ℎ , · · · , 𝑢𝑀 𝑇
Refer [TABATA1994], pp.70–75 for solvability of (5.8). The stability of (5.8) is in [TABATA1994], Theorem 2.13:
Let {𝒯ℎ }ℎ↓0 be regular triangulations (see Regular Triangulation). Then there is a number 𝑐0 > 0 independent of ℎ
such that,
{︃ {︁ ∑︀𝑛−1 𝑘+𝜃 2 }︁
1 0 2
|𝑢ℎ | + 𝜏 𝑘=0 ‖𝑓 ‖𝑉 ′ 𝜃 ∈ [0, 1/2)
|𝑢𝑛ℎ |2 ≤ 𝛿
0 2
∑︀𝑛−1 𝑘+𝜃 2 ℎ
2(1 − 𝛿) 2
𝜏< ℎ
(1 − 2𝜃)𝑐20
Tip: Example
1 // Parameters
2 real tau = 0.1; real
3 theta = 0.;
4
5 // Mesh
6 mesh Th = square(12, 12);
7
8 // Fespace
9 fespace Vh(Th, P1);
10 Vh u, v, oldU;
11 Vh f1, f0;
12
16 // Function
17 func real f (real t){
18 return x^2*(x-1)^2 + t*(-2 + 12*x - 11*x^2 - 2*x^3 + x^4);
19 }
20
21 // File
22 ofstream out("err02.csv"); //file to store calculations
23 out << "mesh size = " << h[].max << ", time step = " << tau << endl;
24 for (int n = 0; n < 5/tau; n++)
25 out << n*tau << ",";
26 out << endl;
27
28 // Problem
29 problem aTau (u, v)
30 = int2d(Th)(
31 u*v
32 + theta*tau*(dx(u)*dx(v) + dy(u)*dy(v) + u*v)
33 )
34 - int2d(Th)(
35 oldU*v
36 - (1-theta)*tau*(dx(oldU)*dx(v) + dy(oldU)*dy(v) + oldU*v)
37 )
38 - int2d(Th)(
39 tau*(theta*f1 + (1-theta)*f0)*v
40 )
41 ;
42
43 // Theta loop
44 while (theta <= 1.0){
45 real t = 0;
46 real T = 3;
47 oldU = 0;
48 out << theta << ",";
49 for (int n = 0; n < T/tau; n++){
50 // Update
51 t = t + tau;
52 f0 = f(n*tau);
53 f1 = f((n+1)*tau);
54
55 // Solve
56 aTau;
57 oldU = u;
58
59 // Plot
60 plot(u);
61
62 // Error
63 Vh uex = t*x^2*(1-x)^2; //exact solution = tx^2(1-x)^2
64 Vh err = u - uex; // err = FE-sol - exact
65 out << abs(err[].max)/abs(uex[].max) << ",";
66 }
(continues on next page)
Fig. 5.14: max𝑥∈Ω |𝑢𝑛ℎ (𝜃) − 𝑢𝑒𝑥 (𝑛𝜏 )| max𝑥∈Ω |𝑢𝑒𝑥 (𝑛𝜏 )|𝑎𝑡𝑛 = 0, 1, · · · , 29
We can see in Fig. 5.14 that 𝑢𝑛ℎ (𝜃) become unstable at 𝜃 = 0.4, and figures are omitted in the case 𝜃 < 0.4.
5.5.2 Convection
appears frequently in scientific problems, for example in the Navier-Stokes equations, in the Convection-Diffusion
equation, etc.
In the case of 1-dimensional space, we can easily find the general solution (𝑥, 𝑡) ↦→ 𝑢(𝑥, 𝑡) = 𝑢0 (𝑥 − 𝛼𝑡) of the
following equation, if 𝛼 is constant,
𝜕𝑡 𝑢 + 𝛼𝜕𝑥 𝑢 = 0
(5.9)
𝑢(𝑥, 0) = 𝑢0 (𝑥),
In this equation 𝜏 is the variable and 𝑥, 𝑡 are parameters, and we denote the solution by 𝑋𝑥,𝑡 (𝜏 ). Then it is noticed that
(𝑥, 𝑡) → 𝑣(𝑋(𝜏 ), 𝜏 ) in 𝜏 = 𝑡 satisfies the equation
𝜕𝑡 𝑣 + 𝛼𝜕𝑥 𝑣 = 𝜕𝑡 𝑋 𝑣˙ + 𝑎𝜕𝑥 𝑋 𝑣˙ = 0
𝜕𝑡 𝑢 + 𝛼 · ∇𝑢 = 0 in Ω × (0, 𝑇 )
where a(𝑥, 𝑡) ∈ R𝑑 .
FreeFEM implements the Characteristic-Galerkin method for convection operators. Recall that the equation (5.8) can
be discretized as
𝐷𝑢 𝑑𝑢 𝑑𝑋
= 𝑓 i.e. (𝑋(𝑡), 𝑡) = 𝑓 (𝑋(𝑡), 𝑡) where (𝑡) = 𝛼(𝑋(𝑡), 𝑡)
𝐷𝑡 𝑑𝑡 𝑑𝑡
where 𝐷 is the total derivative operator. So a good scheme is one step of backward convection by the method of
Characteristics-Galerkin
1 (︀ 𝑚+1
(𝑥) − 𝑢𝑚 (𝑋 𝑚 (𝑥)) = 𝑓 𝑚 (𝑥) (5.10)
)︀
𝑢
𝜏
where 𝑋 𝑚 (𝑥) is an approximation of the solution at 𝑡 = 𝑚𝜏 of the ordinary differential equation
𝑑X
(𝑡) = 𝛼𝑚 (X(𝑡)), X((𝑚 + 1)𝜏 ) = 𝑥.
𝑑𝑡
where 𝛼𝑚 (𝑥) = (𝛼1 (𝑥, 𝑚𝜏 ), 𝛼2 (𝑥, 𝑚𝜏 )). Because, by Taylor’s expansion, we have
∑︀𝑑 𝑚
𝜕𝑋𝑖
𝑢𝑚 (X(𝑚𝜏 )) = 𝑢𝑚 (X((𝑚 + 1)𝜏 )) − 𝜏 𝑖=1 𝜕𝑢
𝜕𝑥𝑖 (X((𝑚 + 1)𝜏 )) 𝜕𝑡 ((𝑚 + 1)𝜏 ) + 𝑜(𝜏 )
𝑚 𝑚 𝑚
= 𝑢 (𝑥) − 𝜏 𝛼 (𝑥) · ∇𝑢 (𝑥) + 𝑜(𝜏 )
where 𝑋𝑖 (𝑡) are the i-th component of X(𝑡), 𝑢𝑚 (𝑥) = 𝑢(𝑥, 𝑚𝜏 ) and we used the chain rule and 𝑥 = X((𝑚 + 1)𝜏 ).
From (5.11), it follows that
Putting
convect(𝛼, −𝜏, 𝑢𝑚 ) ≈ 𝑢𝑚 (𝑥 − 𝛼𝑚 𝜏 )
we can get the approximation
1 , 𝑎2 ], −𝜏, 𝑢 ) by 𝑋
𝑢𝑚 (𝑋 𝑚 (𝑥)) ≈ convect ([𝑎𝑚 𝑚 𝑚 𝑚
≈ 𝑥 ↦→ 𝑥 − 𝜏 [𝑎𝑚 𝑚
1 (𝑥), 𝑎2 (𝑥)]
A classical convection problem is that of the “rotating bell” (quoted from [LUCQUIN1998], p.16).
Let Ω be the unit disk centered at 0, with its center rotating with speed 𝛼1 = 𝑦, 𝛼2 = −𝑥. We consider the problem
(5.8) with 𝑓 = 0 and the initial condition 𝑢(𝑥, 0) = 𝑢0 (𝑥), that is, from (5.10)
𝑢𝑚+1 (𝑥) = 𝑢𝑚 (𝑋 𝑚 (𝑥)) ≈ convect(𝛼, −𝜏, 𝑢𝑚 )
The exact solution is 𝑢(𝑥, 𝑡) = 𝑢(X(𝑡)) where X equals 𝑥 rotated around the origin by an angle 𝜃 = −𝑡 (rotate in
clockwise). So, if 𝑢0 in a 3D perspective looks like a bell, then 𝑢 will have exactly the same shape, but rotated by the
same amount. The program consists in solving the equation until 𝑇 = 2𝜋, that is for a full revolution and to compare
the final solution with the initial one; they should be equal.
Tip: Convect
1 // Parameters
2 real dt = 0.17;
3
4 // Mesh
5 border C(t=0, 2*pi){x=cos(t); y=sin(t);}
6 mesh Th = buildmesh(C(70));
7
8 // Fespace
9 fespace Vh(Th, P1);
10 Vh u0;
11 Vh a1 = -y, a2 = x; //rotation velocity
12 Vh u;
13
14 // Initialization
15 u = exp(-10*((x-0.3)^2 +(y-0.3)^2));
16
17 // Time loop
18 real t = 0.;
19 for (int m = 0; m < 2*pi/dt; m++){
20 // Update
21 t += dt;
22 u0 = u;
23
24 // Convect
25 u = convect([a1, a2], -dt, u0); //u^{m+1}=u^m(X^m(x))
26
27 // Plot
28 plot(u, cmm=" t="+t+", min="+u[].min+", max="+u[].max);
29 }
Note: The scheme convect is unconditionally stable, then the bell become lower and lower (the maximum of 𝑢37 is
0.406 as shown in Fig. 5.15a.
(a) 𝑢0 = 𝑒−10((𝑥−0.3)
2
+(𝑦−0.3)2 ) (b) The bell at 𝑡 = 6.29
In mathematical finance, an option on two assets is modeled by a Black-Scholes equations in two space variables, (see
for example [WILMOTT1995] or [ACHDOU2005]).
(𝜎1 𝑥)2 𝜕 2 𝑢 (𝜎2 𝑦)2 𝜕 2 𝑢
𝜕𝑡 𝑢 + 2 𝜕𝑥2 + 2 𝜕𝑦 2
𝜕2𝑢
+ 𝜌𝑥𝑦 𝜕𝑥𝜕𝑦 + 𝑟𝑆1 𝜕𝑢
𝜕𝑥 + 𝑟𝑆2 𝜕𝑢
𝜕𝑦 − 𝑟𝑃 = 0
Boundary conditions for this problem may not be so easy to device. As in the one dimensional case the PDE contains
boundary conditions on the axis 𝑥1 = 0 and on the axis 𝑥2 = 0, namely two one dimensional Black-Scholes equations
driven respectively by the data 𝑢 (0, +∞, 𝑇 ) and 𝑢 (+∞, 0, 𝑇 ). These will be automatically accounted for because they
are embedded in the PDE. So if we do nothing in the variational form (i.e. if we take a Neumann boundary condition
at these two axis in the strong form) there will be no disturbance to these. At infinity in one of the variable, as in 1D,
it makes sense to impose 𝑢 = 0. We take
An implicit Euler scheme is used and a mesh adaptation is done every 10 time steps. To have an unconditionally stable
scheme, the first order terms are treated by the Characteristic Galerkin method, which, roughly, approximates
𝜕𝑢 𝜕𝑢 𝜕𝑢 1 (︀ 𝑛+1
(𝑥) − 𝑢𝑛 (𝑥 − 𝛼𝜏 )
)︀
+ 𝑎1 + 𝑎2 ≈ 𝑢
𝜕𝑡 𝜕𝑥 𝜕𝑦 𝜏
Tip: Black-Scholes
1 // Parameters
2 int m = 30; int L = 80; int LL = 80; int j = 100; real sigx = 0.3; real sigy = 0.3; real␣
˓→rho = 0.3; real r = 0.05; real K = 40; real dt = 0.01;
4 // Mesh
5 mesh th = square(m, m, [L*x, LL*y]);
6
7 // Fespace
(continues on next page)
12 // Time loop
13 for (int n = 0; n*dt <= 1.0; n++){
14 // Mesh adaptation
15 if (j > 20){
16 th = adaptmesh(th, u, verbosity=1, abserror=1, nbjacoby=2,
17 err=0.001, nbvx=5000, omega=1.8, ratio=1.8, nbsmooth=3,
18 splitpbedge=1, maxsubdiv=5, rescaling=1);
19 j = 0;
20 xveloc = -x*r + x*sigx^2 + x*rho*sigx*sigy/2;
21 yveloc = -y*r + y*sigy^2 + y*rho*sigx*sigy/2;
22 u = u;
23 }
24
25 // Update
26 uold = u;
27
28 // Solve
29 solve eq1(u, v, init=j, solver=LU)
30 = int2d(th)(
31 u*v*(r+1/dt)
32 + dx(u)*dx(v)*(x*sigx)^2/2
33 + dy(u)*dy(v)*(y*sigy)^2/2
34 + (dy(u)*dx(v) + dx(u)*dy(v))*rho*sigx*sigy*x*y/2
35 )
36 - int2d(th)(
37 v*convect([xveloc, yveloc], dt, uold)/dt
38 )
39 + on(2, 3, u=0)
40 ;
41
42 // Update
43 j = j+1;
44 };
45
46 // Plot
47 plot(u, wait=true, value=true);
(a) The adapted triangulation (b) The level line of the European basquet put option
where u = (𝑢1 , 𝑢2 ) is the velocity vector and 𝑝 the pressure. For simplicity, let us choose Dirichlet boundary conditions
on the velocity, u = uΓ on Γ.
In [TEMAM1977], Theorem 2.2, there is a weak form of (5.11):
Find v = (𝑣1 , 𝑣2 ) ∈ V(Ω):
which satisfy:
2 ∫︁
∑︁ ∫︁
∇𝑢𝑖 · ∇𝑣𝑖 = f ·w for all 𝑣 ∈ 𝑉
𝑖=1 Ω Ω
By multiplying the first equation in (5.11) with 𝑣 ∈ 𝑉 and the second with 𝑞 ∈ 𝑊 , subsequent integration over Ω, and
an application of Green’s formula, we have:
∇u · ∇v − ∫︀Ω divv 𝑝 = Ω f · v
∫︀ ∫︀ ∫︀
Ω
Ω
divu 𝑞 = 0
𝑎(u, v) + 𝑏(v, 𝑝) = (f , v)
𝑏(u, 𝑞) = 0
Now, we consider finite element spaces Vℎ ⊂ V and 𝑊ℎ ⊂ 𝑊 , and we assume the following basis functions:
where:
𝑏(vℎ , 𝑞ℎ )
sup ≥ 𝛽ℎ ‖𝑞ℎ ‖0,Ω for all 𝑞ℎ ∈ 𝑊ℎ
vℎ ∈Vℎ ‖vℎ ‖1,Ω
B*
(︂ )︂ (︂ )︂ (︂ )︂
A Uℎ Fℎ
=
B 0 {𝑝ℎ } 0
where:
𝐵𝑥𝑇
(︂ )︂ {︂ }︂ {︂ }︂ {︂ ∫︀ }︂
𝐴 0 * {𝑢1,ℎ } {∫︀Ω 𝑓1 𝜑𝑖 }
A= B = Uℎ = Fℎ =
0 𝐴 𝐵𝑦 𝑇 {𝑢2,ℎ } { Ω 𝑓2 𝜑𝑖 }
Penalty method: This method consists of replacing (5.12) by a more regular problem:
Find (vℎ𝜖 , 𝑝𝜖ℎ ) ∈ Vℎ × 𝑊
˜ ℎ satisfying:
where 𝑊
˜ ℎ ⊂ 𝐿2 (Ω). Formally, we have:
divu𝜖ℎ = 𝜖𝑝𝜖ℎ
A 𝐵* U𝜖ℎ
(︂ )︂ (︂ )︂ (︂ )︂
Fℎ
=
𝐵 −𝜖𝐼 {𝑝𝜖ℎ } 0
Since the matrix 𝐴 + (1/𝜖)𝐵 * 𝐵 is symmetric, positive-definite, and sparse, (5.14) can be solved by known technique.
There is a constant 𝐶 > 0 independent of 𝜖 such that:
Tip: Cavity
The driven cavity flow problem is solved first at zero Reynolds number (Stokes flow) and then at Reynolds 100. The
velocity pressure formulation is used first and then the calculation is repeated with the stream function vorticity formu-
lation.
We solve the driven cavity problem by the penalty method (5.13) where uΓ · n = 0 and uΓ · s = 1 on the top boundary
and zero elsewhere (n is the unit normal to Γ, and s the unit tangent to Γ).
The mesh is constructed by:
and:
𝑀ℎ = 𝑣 ∈ 𝐻 1 (]0, 1[2 ) ⃒ ∀𝐾 ∈ 𝒯ℎ
{︀ ⃒ }︀
𝑣|𝐾 ∈ 𝑃1
The Stokes operator is implemented as a system-solve for the velocity (𝑢1, 𝑢2) and the pressure 𝑝. The test function
for the velocity is (𝑣1, 𝑣2) and 𝑞 for the pressure, so the variational form (5.12) in freefem language is:
−∆𝜓 = ∇ × 𝑢
1 Xh psi, phi;
2
The term 𝑢𝑛 ∘ 𝑋 𝑛 (𝑥) ≈ 𝑢𝑛 (𝑥 − 𝑢𝑛 (𝑥)𝜏 ) will be computed by the operator convect, so we obtain:
1 int i=0;
2 real alpha=1/dt;
3 problem NS (u1, u2, p, v1, v2, q, solver=Crout, init=i)
4 = int2d(Th)(
5 alpha*(u1*v1 + u2*v2)
6 + nu * (
7 dx(u1)*dx(v1) + dy(u1)*dy(v1)
8 + dx(u2)*dx(v2) + dy(u2)*dy(v2)
9 )
10 - p*q*(0.000001)
11 - p*dx(v1) - p*dy(v2)
12 - dx(u1)*q - dy(u2)*q
13 )
14 + int2d(Th)(
15 - alpha*convect([up1,up2],-dt,up1)*v1
16 - alpha*convect([up1,up2],-dt,up2)*v2
17 )
18 + on(3, u1=1, u2=0)
19 + on(1, 2, 4,u1=0, u2=0)
20 ;
21
22 // Time loop
23 for (i = 0; i <= 10; i++){
24 // Update
25 up1 = u1;
26 up2 = u2;
27
28 // Solve
29 NS;
30
31 // Plot
32 if (!(i % 10))
33 plot(coef=0.2, cmm="[u1,u2] and p", p, [u1, u2]);
34 }
We solve Stokes problem without penalty. The classical iterative method of Uzawa is described by the algorithm (see
e.g. [ROBERTS1993], 17.3, [GLOWINSKI1979], 13 or [GLOWINSKI1985], 13):
• Initialize: Let 𝑝0ℎ be an arbitrary chosen element of 𝐿2 (Ω).
• Calculate :math:`mathbf{u}_h`: Once 𝑝𝑛ℎ is known, vℎ𝑛 is the solution of:
𝑝𝑛+1
ℎ = 𝑝𝑛ℎ + 𝜌𝑛 Bu𝑛ℎ
There is a constant 𝛼 > 0 such that 𝛼 ≤ 𝜌𝑛 ≤ 2 for each 𝑛, then u𝑛ℎ converges to the solution uℎ , and then 𝐵vℎ𝑛 → 0
as 𝑛 → ∞ from the Advance 𝑝ℎ . This method in general converges quite slowly.
First we define mesh, and the Taylor-Hood approximation. So 𝑋ℎ is the velocity space, and 𝑀ℎ is the pressure space.
1 // Mesh
2 mesh Th = square(10, 10);
3
4 // Fespace
5 fespace Xh(Th, P2);
6 Xh u1, u2;
7 Xh bc1, bc2;
8 Xh b;
9
14 // Problem
15 varf bx (u1, q) = int2d(Th)(-(dx(u1)*q));
16 varf by (u1, q) = int2d(Th)(-(dy(u1)*q));
17 varf a (u1, u2)
18 = int2d(Th)(
19 dx(u1)*dx(u2)
20 + dy(u1)*dy(u2)
21 )
22 + on(3, u1=1)
23 + on(1, 2, 4, u1=0) ;
24 //remark: put the on(3,u1=1) before on(1,2,4,u1=0)
25 //because we want zero on intersection
26
51 return ppp[] ;
52 }
53
54 // Initialization
55 p=0; //p_h^0 = 0
56 LinearCG(divup, p[], eps=1.e-6, nbiter=50); //p_h^{n+1} = p_h^n + B u_h^n
57 // if n> 50 or |p_h^{n+1} - p_h^n| <= 10^-6, then the loop end
58 divup(p[]); //compute the final solution
59
5.6.2 NSUzawaCahouetChabart.edp
In this example we solve the Navier-Stokes equation past a cylinder with the Uzawa algorithm preconditioned by the
Cahouet-Chabart method (see [GLOWINSKI2003] for all the details).
The idea of the preconditioner is that in a periodic domain, all differential operators commute and the Uzawa algorithm
comes to solving the linear operator ∇.((𝛼𝐼𝑑 + 𝜈∆)−1 ∇, where 𝐼𝑑 is the identity operator. So the preconditioner
suggested is 𝛼∆−1 + 𝜈𝐼𝑑.
To implement this, we do:
1 // Parameters
2 verbosity = 0;
3 real D = 0.1;
4 real H = 0.41;
5 real cx0 = 0.2;
6 real cy0 = 0.2; //center of cylinder
7 real xa = 0.15;
8 real ya = 0.2;
9 real xe = 0.25;
10 real ye = 0.2;
11 int nn = 15;
12
13 //TODO
14 real Um = 1.5; //max velocity (Rey 100)
15 real nu = 1e-3;
16
24 // Variables
25 func Ub = Um*2./3.;
26 real alpha = 1/dt;
27 real Rey = Ub*D/nu;
28 real t = 0.;
29
30 // Mesh
31 border fr1(t=0, 2.2){x=t; y=0; label=1;}
32 border fr2(t=0, H){x=2.2; y=t; label=2;}
33 border fr3(t=2.2, 0){x=t; y=H; label=1;}
34 border fr4(t=H, 0){x=0; y=t; label=1;}
35 border fr5(t=2*pi, 0){x=cx0+D*sin(t)/2; y=cy0+D*cos(t)/2; label=3;}
36 mesh Th = buildmesh(fr1(5*nn) + fr2(nn) + fr3(5*nn) + fr4(nn) + fr5(-nn*3));
37
38 // Fespace
39 fespace Mh(Th, [P1]);
40 Mh p;
41
48 // Macro
49 macro grad(u) [dx(u), dy(u)] //
50 macro div(u1, u2) (dx(u1) + dy(u2)) //
51
52 // Problem
53 varf von1 ([u1, u2, p], [v1, v2, q])
54 = on(3, u1=0, u2=0)
55 + on(1, u1=U1, u2=U2)
56 ;
57
58 //remark : the value 100 in next varf is manualy fitted, because free outlet.
59 varf vA (p, q) =
60 int2d(Th)(
61 grad(p)' * grad(q)
62 )
63 + int1d(Th, 2)(
64 100*p*q
65 )
66 ;
67
68 varf vM (p, q)
69 = int2d(Th, qft=qf2pT)(
70 p*q
(continues on next page)
121 // Initialization
122 p = 0;
(continues on next page)
131 // Solve
132 int res = LinearCG(JUzawa, p[], precon=Precon, nbiter=100, verbosity=10, veps=eps);
133 assert(res==1);
134 eps = -abs(eps);
135
136 // Vorticity
137 w = -dy(u1) + dx(u2);
138 plot(w, fill=true, wait=0, nbiso=40);
139
140 // Update
141 dt = min(dt, T-t);
142 t += dt;
143 if(dt < 1e-10*T) break;
144 }
145
146 // Plot
147 plot(w, fill=true, nbiso=40);
148
149 // Display
150 cout << "u1 max = " << u1[].linfty
151 << ", u2 max = " << u2[].linfty
152 << ", p max = " << p[].max << endl;
Fig. 5.17: The vorticity at Reynolds number 100 a time 2s with the Cahouet-Chabart method.
The projection on a convex satisfy clearly ∀𝑣 ∈ 𝒞, ((𝑢 − 𝑣, 𝑢 − 𝑓˜)) ≤ 0, and after expanding, we get the classical
inequality:
∫︁ ∫︁
∀𝑣 ∈ 𝒞, ∇(𝑢 − 𝑣)∇𝑢 ≤ (𝑢 − 𝑣)𝑓
Ω Ω
where <, > is the duality bracket between 𝐻01 (Ω) and 𝐻 −1 (Ω), and 𝑐 is a penalty constant (large enough).
You can find all the mathematics about this algorithm in [ITO2003] [HINTERMULLER2002].
Now how to do that in FreeFEM? The full example is:
1 load "medit"
2
3 // Parameters
4 real eps = 1e-5;
5 real c = 1000; //penalty parameter of the algoritm
6 real tgv = 1e30; //a huge value for exact penalization
7 func f = 1; //right hand side function
8 func fd = 0; //Dirichlet boundary condition function
9
10 // Mesh
11 mesh Th = square(20, 20);
12
13 // Fespace
14 fespace Vh(Th, P1);
15 int n = Vh.ndof; //number of degree of freedom
16 Vh uh, uhp; //u^n+1 and u^n
17 Vh Ik; //to define the set where the containt is reached.
18 Vh g = 0.05; //discret function g
19 Vh lambda = 0;
20
21 // Problem
22 varf a (uh, vh)
23 = int2d(Th)(
24 dx(uh)*dx(vh)
25 + dy(uh)*dy(vh)
26 )
27 - int2d(Th)(
28 f*vh
29 )
30 + on(1, 2, 3, 4, uh=fd)
31 ;
32
42 real[int] Aiin(n);
43 real[int] Aii = A.diag; //get the diagonal of the matrix
44 real[int] rhs = a(0, Vh, tgv=tgv);
45
46 // Initialization
47 Ik = 0;
(continues on next page)
50 // Loop
51 for(int iter = 0; iter < 100; ++iter){
52 // Update
53 real[int] b = rhs; //get a copy of the Right hand side
54 real[int] Ak(n); //the complementary of Ik ( !Ik = (Ik-1))
55 Ak = 1.; Ak -= Ik[];
56 //adding new locking condition on b and on the diagonal if (Ik ==1 )
57 b = Ik[] .* g[]; b *= tgv; b -= Ak .* rhs;
58 Aiin = Ik[] * tgv; Aiin += Ak .* Aii; //set Aii= tgv i in Ik
59 A.diag = Aiin; //set the matrix diagonal
60 set(A, solver=CG); //important to change preconditioning for solving
61
62 // Solve
63 uh[] = A^-1* b; //solve the problem with more locking condition
64
65 // Residual
66 lambda[] = AA * uh[]; //compute the residual (fast with matrix)
67 lambda[] += rhs; //remark rhs = -\int f v
68
71 // Plot
72 plot(Ik, wait=true, cmm=" lock set ", value=true, fill=true);
73 plot(uh, wait=true, cmm="uh");
74
75 // Error
76 //trick to compute L^2 norm of the variation (fast method)
77 real[int] diff(n), Mdiff(n);
78 diff = uh[] - uhp[];
79 Mdiff = M*diff;
80 real err = sqrt(Mdiff'*diff);
81 cout << "|| u_{k=1} - u_{k} ||_2 = " << err << endl;
82
83 // Stop test
84 if(err < eps) break;
85
86 // Update
87 uhp[] = uh[];
88 }
89
90 // Plot
91 medit("uh", Th, uh);
Note: As you can see on this example, some vector, or matrix operator are not implemented so a way is to skip the
expression and we use operator +=, -= to merge the result.
We present three classic examples of domain decomposition technique: first, Schwarz algorithm with overlapping,
second Schwarz algorithm without overlapping (also call Shur complement), and last we show to use the conjugate
gradient to solve the boundary problem of the Shur complement.
To solve:
−∆𝑢 = 𝑓, in Ω = Ω1 ∪ Ω2 𝑢|Γ = 0
−∆𝑢𝑛+1
1 = 𝑓 in Ω1 𝑢𝑛+1
1 |Γ1 = 𝑢𝑛2
−∆𝑢𝑛+1
2 = 𝑓 in Ω2 𝑛+1
𝑢2 |Γ2 = 𝑢𝑛1
where Γ𝑖 is the boundary of Ω𝑖 and on the condition that Ω1 ∩ Ω2 ̸= ∅ and that 𝑢𝑖 are zero at iteration 1.
Here we take Ω1 to be a quadrangle, Ω2 a disk and we apply the algorithm starting from zero.
1 // Parameters
2 int inside =2; //inside boundary
3 int outside = 1; //outside boundary
4 int n = 4;
5
6 // Mesh
7 border a(t=1, 2){x=t; y=0; label=outside;}
(continues on next page)
17 // Fespace
18 fespace vh(th, P1);
19 vh u=0, v;
20
24 // Problem
25 int i = 0;
26 problem PB (U, V, init=i, solver=Cholesky)
27 = int2d(TH)(
28 dx(U)*dx(V)
29 + dy(U)*dy(V)
30 )
31 + int2d(TH)(
32 - V
33 )
34 + on(inside, U=u)
35 + on(outside, U=0)
36 ;
37
50 // Calculation loop
51 for (i = 0 ; i < 10; i++){
52 // Solve
53 PB;
54 pb;
55
56 // Plot
57 plot(U, u, wait=true);
58 }
(a) Isovalues of the solution at iteration 0 (b) Isovalues of the solution at iteration 0
To solve:
−∆𝑢 = 𝑓 in Ω = Ω1 ∪ Ω2 𝑢|Γ = 0
the Schwarz algorithm for domain decomposition without overlapping runs like this
Let introduce Γ𝑖 is common the boundary of Ω1 and Ω2 and Γ𝑖𝑒 = 𝜕Ω𝑖 ∖ Γ𝑖 .
The problem find 𝜆 such that (𝑢1 |Γ𝑖 = 𝑢2 |Γ𝑖 ) where 𝑢𝑖 is solution of the following Laplace problem:
1 // Parameters
2 int inside = 2; int outside = 1; int n = 4;
3
4 // Mesh
5 border a(t=1, 2){x=t; y=0; label=outside;};
6 border b(t=0, 1){x=2; y=t; label=outside;};
7 border c(t=2, 0){x=t; y=1; label=outside;};
8 border d(t=1, 0){x=1-t; y=t; label=inside;};
9 border e(t=0, 1){x=1-t; y=t; label=inside;};
10 border e1(t=pi/2, 2*pi){x=cos(t); y=sin(t); label=outside;};
(continues on next page)
15 // Fespace
16 fespace vh(th, P1);
17 vh u=0, v;
18 vh lambda=0;
19
23 // Problem
24 int i = 0;
25 problem PB (U, V, init=i, solver=Cholesky)
26 = int2d(TH)(
27 dx(U)*dx(V)
28 + dy(U)*dy(V)
29 )
30 + int2d(TH)(
31 - V
32 )
33 + int1d(TH, inside)(
34 lambda*V
35 )
36 + on(outside, U= 0 )
37 ;
38
(a) Isovalues of the solution at iteration 0 without overlap- (b) Isovalues of the solution at iteration 9 without overlap-
ping ping
59 // Plot
60 plot(U,u,wait=true);
61 }
62
63 // Plot
64 plot(U, u);
To solve −∆𝑢 = 𝑓 in Ω = Ω1 ∪ Ω2 𝑢|Γ = 0 the Schwarz algorithm for domain decomposition without overlapping
runs like this
Let introduce Γ𝑖 is common the boundary of Ω1 and Ω2 and Γ𝑖𝑒 = 𝜕Ω𝑖 ∖ Γ𝑖 .
The problem find 𝜆 such that (𝑢1 |Γ𝑖 = 𝑢2 |Γ𝑖 ) where 𝑢𝑖 is solution of the following Laplace problem:
The version of this example uses the Shur complement. The problem on the border is solved by a conjugate gradient
method.
1 // Parameters
2 int inside = 2; int outside = 1; int n = 4;
3
4 // Mesh
5 border Gamma1(t=1, 2){x=t; y=0; label=outside;}
6 border Gamma2(t=0, 1){x=2; y=t; label=outside;}
7 border Gamma3(t=2, 0){x=t; y=1; label=outside;}
8 border GammaInside(t=1, 0){x=1-t; y=t; label=inside;}
9 border GammaArc(t=pi/2, 2*pi){x=cos(t); y=sin(t); label=outside;}
10 mesh Th1 = buildmesh(Gamma1(5*n) + Gamma2(5*n) + GammaInside(5*n) + Gamma3(5*n));
11 mesh Th2 = buildmesh(GammaInside(-5*n) + GammaArc(25*n));
12 plot(Th1, Th2);
1 // Fespace
2 fespace Vh1(Th1, P1);
3 Vh1 u1, v1;
4 Vh1 lambda;
5 Vh1 p=0;
6
7 fespace Vh2(Th2,P1);
8 Vh2 u2, v2;
Note: It is impossible to define a function just on a part of boundary, so the 𝜆 function must be defined on the all
domain Ω1 such as:
1 Vh1 lambda;
And, we define a border matrix, because the 𝜆 function is none zero inside the domain Ω1 :
Note: The difference between the two notations v1 and v1[] is: v1 is the finite element function and v1[] is the
vector in the canonical basis of the finite element function v1.
1 // Solve
2 real cpu=clock();
(continues on next page)
This problem involves the Lamé system of elasticity and the Stokes system for viscous fluids with velocity u and
pressure 𝑝:
−∆u + ∇𝑝 = 0 in Ω
∇·u = 0 in Ω
u = uΓ on Γ = 𝜕Ω
where 𝑢Γ is the velocity of the boundaries. The force that the fluid applies to the boundaries is the normal stress
h = (∇u + ∇u𝑇 )n − 𝑝n
Elastic solids subject to forces deform: a point in the solid at (x,y) goes to (X,Y) after. When the displacement vector
v = (𝑣1 , 𝑣2 ) = (𝑋 − 𝑥, 𝑌 − 𝑦) is small, Hooke’s law relates the stress tensor 𝜎 inside the solid to the deformation
tensor 𝜖:
1 𝜕𝑣𝑖 𝜕𝑣𝑗
𝜎𝑖𝑗 = 𝜆𝛿𝑖𝑗 ∇.v + 2𝜇𝜖𝑖𝑗 , 𝜖𝑖𝑗 = ( + )
2 𝜕𝑥𝑗 𝜕𝑥𝑖
where 𝛿 is the Kronecker symbol and where 𝜆, 𝜇 are two constants describing the material mechanical properties in
terms of the modulus of elasticity, and Young’s modulus.
The equations of elasticity are naturally written in variational form for the displacement vector 𝑣(𝑥) ∈ 𝑉 as:
∫︁ ∫︁ ∫︁
[2𝜇𝜖𝑖𝑗 (v)𝜖𝑖𝑗 (w) + 𝜆𝜖𝑖𝑖 (𝑣)𝜖𝑗𝑗 (w)] = g · w + h · w, ∀w ∈ 𝑉
Ω Ω Γ
The data are the gravity force g and the boundary stress h.
Tip: Fluide-structure In our example, the Lamé system and the Stokes system are coupled by a common boundary
on which the fluid stress creates a displacement of the boundary and hence changes the shape of the domain where the
Stokes problem is integrated. The geometry is that of a vertical driven cavity with an elastic lid. The lid is a beam with
weight so it will be deformed by its own weight and by the normal stress due to the fluid reaction. The cavity is the
10 × 10 square and the lid is a rectangle of height 𝑙 = 2.
A beam sits on a box full of fluid rotating because the left vertical side has velocity one. The beam is bent by its own
weight, but the pressure of the fluid modifies the bending.
The bending displacement of the beam is given by (𝑢𝑢, 𝑣𝑣) whose solution is given as follows.
1 // Parameters
2 int bottombeam = 2; //label of bottombeam
3 real E = 21.5;
(continues on next page)
8 // Mesh (solid)
9 border a(t=2, 0){x=0; y=t; label=1;}
10 border b(t=0, 10){x=t; y=0; label=bottombeam;}
11 border c(t=0, 2){x=10; y=t; label=1;}
12 border d(t=0, 10){x=10-t; y=2; label=3;}
13 mesh th = buildmesh(b(20) + c(5) + d(20) + a(5));
14
15 // Fespace (solid)
16 fespace Vh(th, P1);
17 Vh uu, w, vv, s;
18
19 // Macro
20 real sqrt2 = sqrt(2.);
21 macro epsilon(u1, u2) [dx(u1), dy(u2), (dy(u1)+dx(u2))/sqrt2] //
22 macro div(u1, u2) (dx(u1) + dy(u2)) //
23
24 // Problem (solid)
25 real mu = E/(2*(1+sigma));
26 real lambda = E*sigma/((1+sigma)*(1-2*sigma));
27 solve Elasticity([uu, vv], [w, s])
28 = int2d(th)(
29 lambda*div(w,s)*div(uu,vv)
30 + 2.*mu*(epsilon(w,s)'*epsilon(uu,vv))
31 )
32 + int2d(th)(
33 - gravity*s
34 )
35 + on(1, uu=0, vv=0)
36 ;
37
Then Stokes equation for fluids at low speed are solved in the box below the beam, but the beam has deformed the box
(see border h):
1 // Mesh (fluid)
2 border e(t=0, 10){x=t; y=-10; label= 1;}
3 border f(t=0, 10){x=10; y=-10+t ; label= 1;}
4 border g(t=0, 10){x=0; y=-t; label= 2;}
5 border h(t=0, 10){x=t; y=vv(t,0)*( t>=0.001 )*(t <= 9.999); label=3;}
6 mesh sh = buildmesh(h(-20) + f(10) + e(10) + g(10));
7 plot(sh, wait=true);
We use the Uzawa conjugate gradient to solve the Stokes problem like in Navier-Stokes equations.
1 // Fespace (fluid)
(continues on next page)
11 // Problem (fluid)
12 varf bx (u1, q) = int2d(sh)(-(dx(u1)*q));
13 varf by (u1, q) = int2d(sh)(-(dy(u1)*q));
14 varf Lap (u1, u2)
15 = int2d(sh)(
16 dx(u1)*dx(u2)
17 + dy(u1)*dy(u2)
18 )
19 + on(2, u1=1)
20 + on(1, 3, u1=0)
21 ;
22
29
1 // Coupling loop
2 for(int step = 0; step < 10; ++step){
3 // Solve (fluid)
4 LinearCG(divup, p[], eps=1.e-3, nbiter=50);
5 divup(p[]);
Now the beam will feel the stress constraint from the fluid:
1 // Forces
2 Vh sigma11, sigma22, sigma12;
3 Vh uu1=uu, vv1=vv;
4
1 // Solve (solid)
2 solve Elasticity2 ([uu, vv], [w, s], init=step)
3 = int2d(th)(
4 lambda*div(w,s)*div(uu,vv)
5 + 2.*mu*(epsilon(w,s)'*epsilon(uu,vv))
6 )
7 + int2d(th)(
8 - gravity*s
9 )
10 + int1d(th, bottombeam)(
11 - coef*(sigma11*N.x*w + sigma22*N.y*s + sigma12*(N.y*w+N.x*s))
12 )
13 + on(1, uu=0, vv=0)
14 ;
15
16 // Plot
17 plot([uu, vv], wait=1);
18
19 // Error
20 real err = sqrt(int2d(th)((uu-uu1)^2 + (vv-vv1)^2));
21 cout << "Erreur L2 = " << err << endl;
Notice that the matrix generated by Elasticity2 is reused (see init=i). Finally we deform the beam:
1 // Movemesh
2 th1 = movemesh(th, [x+0.2*uu, y+0.2*vv]);
3 plot(th1, wait=true);
Fluid velocity and pressure, displacement vector of the structure and displaced geometry in the fluid-structure interac-
tion of a soft side and a driven cavity are shown Fig. 5.22, Fig. 5.23a and Fig. 5.23b
Consider an elastic plate whose displacement change vertically, which is made up of three plates of different materials,
welded on each other.
Let Ω𝑖 , 𝑖 = 1, 2, 3 be the domain occupied by 𝑖-th material with tension 𝜇𝑖 (see Soap film).
The computational domain Ω is the interior of Ω1 ∪ Ω2 ∪ Ω3 . The vertical displacement 𝑢(𝑥, 𝑦) is obtained from:
−𝜇𝑖 ∆𝑢 = 𝑓 in Ω𝑖
(5.15)
𝜇𝑖 𝜕𝑛 𝑢|Γ𝑖 = −𝜇𝑗 𝜕𝑛 𝑢|Γ𝑗 on Ω𝑖 ∩ Ω𝑗 if 1 ≤ 𝑖 < 𝑗 ≤ 3
where 𝜕𝑛 𝑢|Γ𝑖 denotes the value of the normal derivative 𝜕𝑛 𝑢 on the boundary Γ𝑖 of the domain Ω𝑖 .
By introducing the characteristic function 𝜒𝑖 of Ω𝑖 , that is:
𝜒𝑖 (𝑥) = 1 if 𝑥 ∈ Ω𝑖 ; 𝜒𝑖 (𝑥) = 0 if 𝑥 ̸∈ Ω𝑖
we can easily rewrite (5.15) to the weak form. Here we assume that 𝑢 = 0 on Γ = 𝜕Ω.
Transmission problem: For a given function 𝑓 , find 𝑢 such that:
∫︀
𝑎(𝑢, 𝑣) = ∫︀Ω 𝜇∇𝑢 · ∇𝑣
ℓ(𝑓, 𝑣) = Ω 𝑓𝑣
where 𝜇 = 𝜇1 𝜒1 + 𝜇2 𝜒2 + 𝜇3 𝜒3 . Here we notice that 𝜇 become the discontinuous function.
This example explains the definition and manipulation of region, i.e. sub-domains of the whole domain. Consider this
L-shaped domain with 3 diagonals as internal boundaries, defining 4 sub-domains:
1 // Mesh
2 border a(t=0, 1){x=t; y=0;};
3 border b(t=0, 0.5){x=1; y=t;};
4 border c(t=0, 0.5){x=1-t; y=0.5;};
5 border d(t=0.5, 1){x=0.5; y=t;};
6 border e(t=0.5, 1){x=1-t; y=1;};
7 border f(t=0, 1){x=0; y=1-t;};
8 border i1(t=0, 0.5){x=t; y=1-t;};
9 border i2(t=0, 0.5){x=t; y=t;};
10 border i3(t=0, 0.5){x=1-t; y=t;};
11 mesh th = buildmesh(a(6) + b(4) + c(4) +d(4) + e(4)
12 + f(6) + i1(6) + i2(6) + i3(6));
13
14 // Fespace
15 fespace Ph(th, P0); //constant discontinuous functions / element
16 Ph reg=region; //defined the P0 function associated to region number
17
18 // Plot
19 plot(reg, fill=true, wait=true, value=true);
region is a keyword of FreeFEM which is in fact a variable depending of the current position (is not a function today,
use Ph reg=region; to set a function). This variable value returned is the number of the sub-domain of the current
position. This number is defined by buildmesh which scans while building the mesh all its connected component.
So to get the number of a region containing a particular point one does:
1 // Characteristic function
2 int nupper = reg(0.4, 0.9); //get the region number of point (0.4,0.9)
3 int nlower = reg(0.9, 0.1); //get the region number of point (0.4,0.1)
4 cout << "nlower = " << nlower << ", nupper = " << nupper<< endl;
5 Ph nu = 1 + 5*(region==nlower) + 10*(region==nupper);
6
7 // Plot
8 plot(nu, fill=true,wait=true);
This is particularly useful to define discontinuous functions such as might occur when one part of the domain is copper
and the other one is iron, for example.
We this in mind we proceed to solve a Laplace equation with discontinuous coefficients (𝜈 is 1, 6 and 11 below).
1 // Problem
2 solve lap (u, v)
3 = int2d(th)(
4 nu*(dx(u)*dx(v) + dy(u)*dy(v))
5 )
6 + int2d(th)(
7 - 1*v
8 )
9 + on(a, b, c, d, e, f, u=0)
10 ;
11
12 // Plot
13 plot(u);
1 // Parameters
2 real L = 10; //length
3 real hl = 2.1; //left height
4 real hr = 0.35; //right height
5 int n = 4;
6
7 // Mesh
8 border a(t=0, L){x=t; y=0;}; //bottom: Gamma_a
9 border b(t=0, hr){x=L; y=t;}; //right: Gamma_b
10 border f(t=L, 0){x=t; y=t*(hr-hl)/L+hl;}; //free surface: Gamma_f
11 border d(t=hl, 0){x=0; y=t;}; // left: Gamma_d
12 mesh Th = buildmesh(a(10*n) + b(6*n) + f(8*n) + d(3*n));
13 plot(Th);
in Ω𝑛
⎧
⎪ −∆𝑢 = 0
⎪
⎨ 𝑢 = 𝑦 on Γ𝑛𝑏
⎪ 𝜕𝑢
𝜕𝑛 = 0 on Γ𝑛𝑑 ∪ Γ𝑛𝑎
on Γ𝑛𝑓
⎪
𝑢 = 𝑦
⎩
And secondly to construct a domain deformation ℱ(𝑥, 𝑦) = [𝑥, 𝑦 − 𝑣(𝑥, 𝑦)] where 𝑣 is solution of the following
problem:
in Ω𝑛
⎧
⎪ −∆𝑣 = 0
on Γ𝑛𝑎
⎪
⎨ 𝑣 = 0
⎪ 𝜕𝑣
𝜕𝑛 = 0 on Γ𝑛𝑏 ∪ Γ𝑛𝑑
𝑞
on Γ𝑛𝑓
⎪ 𝜕𝑣 𝜕𝑢
= − 𝐾 𝑛𝑥
⎩
𝜕𝑛 𝜕𝑛
1 // Parameters
2 real L = 10; //length
3 real hr = 2.1; //left height
4 real hl = 0.35; //right height
5 int n = 4;
6
10 // Mesh
(continues on next page)
18 // Fespace
19 fespace Vh(Th, P1);
20 Vh u, v;
21 Vh uu, vv;
22
23 // Problem
24 problem Pu (u, uu, solver=CG)
25 = int2d(Th)(
26 dx(u)*dx(uu)
27 + dy(u)*dy(uu)
28 )
29 + on(b, f, u=y)
30 ;
31
43 // Loop
44 int j = 0;
45 real errv = 1.;
46 real erradap = 0.001;
47 while (errv > 1e-6){
48 // Update
49 j++;
50
51 // Solve
52 Pu;
53 Pv;
54
55 // Plot
56 plot(Th, u, v);
57
58 // Error
59 errv = int1d(Th, f)(v*v);
60
61 // Movemesh
62 real coef = 1.;
(continues on next page)
(a) The final solution on the new domain Ω72 (b) The adapted mesh of the domain Ω72
71 while (1){
72 real mint = checkmovemesh(Th, [x, y-v*coef]);
73
82 // Display
83 cout << endl << j << " - errv = " << errv << endl;
84 }
85
86 // Plot
87 plot(Th);
88 plot(u, wait=true);
The nonlinear elasticity problem is: find the displacement (𝑢1 , 𝑢2 ) minimizing 𝐽:
∫︁ ∫︁
min 𝐽(𝑢1 , 𝑢2 ) = 𝑓 (𝐹 2) − 𝑃𝑎 𝑢2
Ω Γ𝑝
where 𝐹 2(𝑢1 , 𝑢2 ) = 𝐴(𝐸[𝑢1 , 𝑢2 ], 𝐸[𝑢1 , 𝑢2 ]) and 𝐴(𝑋, 𝑌 ) is bilinear symmetric positive form with respect two matrix
𝑋, 𝑌 .
where 𝑓 is a given 𝒞 2 function, and 𝐸[𝑢1 , 𝑢2 ] = (𝐸𝑖𝑗 )𝑖=1,2, 𝑗=1,2 is the Green-Saint Venant deformation tensor defined
with:
(︀ ∑︁ )︀
𝐸𝑖𝑗 = 0.5 (𝜕𝑖 𝑢𝑗 + 𝜕𝑗 𝑢𝑖 ) + 𝜕𝑖 𝑢𝑘 ×𝜕𝑗 𝑢𝑘
𝑘
where:
3 macro ENL(u, v) [
4 (dx(u)*dx(u) + dx(v)*dx(v))*0.5,
5 (dx(u)*dy(u) + dx(v)*dy(v)),
6 (dy(u)*dy(u) + dy(v)*dy(v))*0.5
7 ] //
8
– 𝑢𝑛 = 𝑢𝑛 − 𝑑𝑢, 𝑣𝑛 = 𝑣𝑛 − 𝑑𝑣
– until (𝑑𝑢, 𝑑𝑣) small is enough
The way to implement this algorithm in FreeFEM is use a macro tool to implement 𝐴 and 𝐹 2, 𝑓 , 𝑓 ′ ,𝑓 ′′ .
A macro is like in ccp preprocessor of C++, but this begin by macro and the end of the macro definition is before the
comment //. In this case the macro is very useful because the type of parameter can be change. And it is easy to make
automatic differentiation.
1 // Macro
2 macro EL(u, v) [dx(u), (dx(v)+dy(u)), dy(v)] //is [epsilon_11, 2epsilon_12, epsilon_22]
3
4 macro ENL(u, v) [
5 (dx(u)*dx(u) + dx(v)*dx(v))*0.5,
6 (dx(u)*dy(u) + dx(v)*dy(v)),
7 (dy(u)*dy(u) + dy(v)*dy(v))*0.5
8 ] //
9
30 // Parameters
31 real mu = 0.012e5; //kg/cm^2
(continues on next page)
58 //the matrix A
59 func A = [[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]];
60
61 // Mesh
62 int n = 30;
63 int m = 10;
64 mesh Th = square(n, m, [x, .3*y]); //label: 1 bottom, 2 right, 3 up, 4 left;
65 int bottom = 1, right = 2, upper = 3, left = 4;
66 plot(Th);
67
68 // Fespace
69 fespace Wh(Th, P1dc);
70 Wh e2, fe2, dfe2, ddfe2;
71
78 // Problem
79 varf vmass ([uu, vv], [w, s], solver=CG) = int2d(Th)(uu*w + vv*s);
80 matrix M = vmass(Vh, Vh);
81 problem NonLin([uu, vv], [w, s], solver=LU)
82 = int2d(Th, qforder=1)( //(D^2 J(un))
(continues on next page)
95 // Newton's method
96 for (int i = 0; i < 10; i++){
97 cout << "Loop " << i << endl;
98
99 // Update
100 e2 = F2(un, vn);
101 dfe2 = df(e2) ;
102 ddfe2 = ddf(e2);
103 cout << "e2 max = " <<e2[].max << ", min = " << e2[].min << endl;
104 cout << "de2 max = "<< dfe2[].max << ", min = " << dfe2[].min << endl;
105 cout << "dde2 max = "<< ddfe2[].max << ", min = " << ddfe2[].min << endl;
106
107 // Solve
108 NonLin;
109 w[] = M*uu[];
110
111 // Residual
112 real res = sqrt(w[]' * uu[]); //L^2 norm of [uu, vv]
113 cout << " L^2 residual = " << res << endl;
114
115 // Update
116 v1 = vv;
117 u1 = uu;
118 cout << "u1 min = " <<u1[].min << ", u1 max = " << u1[].max << endl;
119 cout << "v1 min = " <<v1[].min << ", v2 max = " << v1[].max << endl;
120
121 // Plot
122 plot([uu, vv], wait=true, cmm="uu, vv");
123
124 // Update
125 un[] -= uu[];
126 plot([un, vn], wait=true, cmm="displacement");
127
131 // Plot
132 plot([un, vn], wait=true);
133
134 // Movemesh
(continues on next page)
137 // Plot
138 plot(th1, wait=true);
5.13.1 Notation
In what follows, the symbols u, F, B, C, 𝜎 denote, respectively, the displacement field, the deformation gradient, the
left Cauchy-Green strain tensor B = FF𝑇 , the right Cauchy-Green strain tensor C = F𝑇 F, and the Cauchy stress
tensor.
We also introduce the symbols 𝐼1 := tr C and 𝐽 := det F. Use will be made of the identity:
𝜕𝐽
= 𝐽C−1
𝜕C
The symbol I denotes the identity tensor. The symbol Ω0 denotes the reference configuration of the body to be de-
formed. The unit volume in the reference (resp., deformed) configuration is denoted 𝑑𝑉 (resp., 𝑑𝑉0 ); these two are
related by:
𝑑𝑉 = 𝐽𝑑𝑉0 ,
which allows an integral over Ω involving the Cauchy stress T to be rewritten as an integral of the Kirchhoff stress
𝜅 = 𝐽T over Ω0 .
For an exposition of nonlinear elasticity and of the underlying linear and tensor algebra, see [OGDEN1984]. For an
advanced mathematical analysis of the Finite Element Method, see [RAVIART1998].
𝜅 = FSF𝑇 = 𝜇(B − I)
𝜕𝜅
(F𝑛 )𝛿F𝑛+1 = 𝜇 F𝑛 (𝛿F𝑛+1 )𝑇 + 𝛿F𝑛+1 (F𝑛 )𝑇
[︀ ]︀
𝜕F
The Weak Form of the BVP in the Absence of Body (External) Forces
The Ω0 we are considering is an elliptical annulus, whose boundary consists of two concentric ellipses (each allowed
to be a circle as a special case), with the major axes parallel. Let 𝑃 denote the dead stress load (traction) on a portion
𝜕Ω𝑡0 (= the inner ellipse) of the boundary 𝜕Ω0 . On the rest of the boundary, we prescribe zero displacement.
The weak formulation of the boundary value problem is:
}︀ }︃
0 = Ω0 𝜅[F] : (∇ ⊗ w)(F)−1
∫︀ {︀
ˆ0
∫︀
− 𝜕Ω𝑡 𝑃 · 𝑁
0
For brevity, in the rest of this section we assume 𝑃 = 0. The provided FreeFEM code, however, does not rely on this
assumption and allows for a general value and direction of 𝑃 .
Given a Newton approximation u𝑛 of the displacement field u satisfying the BVP, we seek the correction 𝛿u𝑛+1 to
obtain a better approximation:
u𝑛+1 = u𝑛 + 𝛿u𝑛+1
𝛿F𝑛+1 = ∇ ⊗ 𝛿u𝑛+1
Note: Contrary to standard notational use, the symbol 𝛿 here bears no variational context. By 𝛿 we mean simply an
increment in the sense of Newton’s Method. The role of a variational virtual displacement here is played by w.
Introducing the code-like notation, where a string in <>’s is to be read as one symbol, the individual components of
the tensor:
𝜕𝜅
< 𝑇 𝑎𝑛𝐾 >:= [F𝑛 ]𝛿F𝑛+1
𝜕F
will be implemented as the macros < 𝑇 𝑎𝑛𝐾11 >, < 𝑇 𝑎𝑛𝐾12 >, . . .
The individual components of the tensor quantities:
D2 := F−𝑇
𝑛 𝛿F𝑛+1 ,
D3 := (∇ ⊗ w)F−2
𝑛 𝛿F𝑛+1 ,
and
D4 := (∇ ⊗ w)F−1
𝑛 ,
respectively.
In the above notation, the tangent Kirchhoff stress term becomes
𝜕𝜅
(F𝑛 ) 𝛿F𝑛+1 = 𝜇 D1
𝜕F
while the weak BVP formulation acquires the form:
∫︀ ⎫
0 = ∫︀Ω0 𝜅[F𝑛 ] : D4 ⎬
− Ω0 𝜅[F𝑛 ] : D3
∫︀ {︀ 𝜕𝜅 }︀ for all test functionsw
+ Ω0 𝜕F [F𝑛 ]𝛿F𝑛+1 : D4
⎭
1 // Macro
2 //Macros for the gradient of a vector field (u1, u2)
3 macro grad11(u1, u2) (dx(u1)) //
4 macro grad21(u1, u2) (dy(u1)) //
5 macro grad12(u1, u2) (dx(u2)) //
6 macro grad22(u1, u2) (dy(u2)) //
7
86 //The macros for the auxiliary tensors (D0, D1, D2, ...): Begin
87 //The tensor quantity D0 = F{n} (delta F{n+1})^T
88 macro d0Aux11 (u1, u2, varu1, varu2) (
89 dF11(varu1, varu2) * F11(u1, u2)
90 + dF12(varu1, varu2) * F12(u1, u2)
91 )//
92
246
249 // Mesh
250 border InnerEdge(t=0, 2.*pi){x=(1.0 + InnerEllipseExtension)*InnerRadius*cos(t);␣
˓→y=InnerRadius*sin(t); label=1;}
256 // Fespace
257 fespace Wh(Th, P1dc);
258 fespace Vh(Th, [P1, P1]);
259 Vh [w1, w2], [u1n,u2n], [varu1, varu2];
260 Vh [ehat1x, ehat1y], [ehat2x, ehat2y];
261 Vh [auxVec1, auxVec2]; //The individual elements of the total 1st Piola-Kirchoff stress
262 Vh [ef1, ef2];
263
269 // Problem
270 varf vfMass1D(p, q) = int2d(Th)(p*q);
271 matrix Mass1D = vfMass1D(Sh, Sh, solver=CG);
272
273 p[] = 1;
274 ppp[] = Mass1D * p[];
275
330 //Initialization:
331 [varu1, varu2] = [0., 0.];
332 [u1n, u2n] = [0., 0.];
333 real res = 2. * tol;
334 real eforceres;
335 int loopcount = 0;
336 int loopmax = 45;
337
343 // Solve
344 neoHookeanInc;
345
346 // Update
347 u1 = varu1;
348 u2 = varu2;
349
350 // Residual
351 w1[] = Mass*varu1[];
352 res = sqrt(w1[]' * varu1[]); //L^2 norm of [varu1, varu2]
353 cout << " L^2 residual = " << res << endl;
354
355 // Newton
356 u1n[] += varu1[];
357
358 // Plot
359 plot([u1n,u2n], cmm="displacement");
360 }
361
362 // Plot
363 plot(Th, wait=true);
364
365 // Movemesh
366 mesh Th1 = movemesh(Th, [x+u1n, y+u2n]);
367
368 // Plot
369 plot(Th1, wait=true);
370 plot([u1n,u2n]);
Author: I. S. Grudinin
In whispering gallery mode (WGM) resonators, which are typically spheres or disks, electromagnetic field is trapped
by total internal reflections from the boundary. Modes of such resonators are distinguished by compact volume and
record high quality factors (Q) in a broad range of frequencies.
Modern applications of such resonators include microwave and optical cavities for atomic clocks, cavity optomechanics,
nonlinear and quantum optics. Analytical solutions for WG modes are only available for a limited number of idealized
geometries, such as sphere or ellipsoid. Since resonator dimensions are typically much larger than optical wavelength,
direct application of numerical 3D finite difference time domain (FDTD) or finite element methods (FEM) is not
practical. It’s possible to solve the vectorial wave equation by reducing it to a two dimensional case by taking axial
symmetry into account.
Such reduction leads to a system of 3 equations to be solved in a 2D “𝜌 − 𝑧” section of a resonator. Please refer to
[OXBORROW2007] for a detailed derivation and to [GRUDININ2012] for an example of using FreeFEM to compute
WGMs.
Since electric field is discontinuous on the surface of a dielectric and magnetic field is typically not, we derive our
equations for the magnetic field. The electric field can be easily derived at a later stage from 𝐸⃗ = 𝑖 𝜖ˆ−1 ∇ × 𝐻.
𝜔𝜖0
⃗
Following a standard procedure starting with Maxwell equations we derive a wave equation in a single-axis anisotropic
medium such as an optical crystal:
(︁ )︁ (︁ )︁
∇ × 𝜖ˆ−1 ∇ × 𝐻 ⃗ − 𝑘02 𝐻 ⃗ − 𝛼∇ ∇ · 𝐻 ⃗ =0 (5.16)
Here 𝑘0 = 𝜔/𝑐 is the wavenumber, 𝛼 is the penalty term added to fight spurious FEM solutions. For anisotropic
single-axis medium with 𝜕ˆ
𝜖/𝜕𝜑 = 0 in cylindrical system of coordinates we have:
⎛ ⎞
𝜖𝜌 0 0
𝜖ˆ = ⎝ 0 𝜖𝜌 0 ⎠ .
0 0 𝜖𝑧
We now assume axial symmetry of our electromagnetic fields and insert an imaginary unity in front of the 𝐻𝜑 to
allow all field components to be real numbers and also to account for the phase shift of this component 𝐻(𝜌,
⃗ 𝜑, 𝑧) =
{𝐻𝜌 (𝜌, 𝑧), 𝑖𝐻𝜑 (𝜌, 𝑧), 𝐻𝑧 (𝜌, 𝑧)} × 𝑒 𝑖𝑚𝜑
.
We write the wave equation (5.16) explicitly in cylindrical coordinates, thus obtaining a set of three differential equa-
tions for the domain Ω given by the resonator’s cross section and some space outside:
The numerical solutions of these equations and boundary conditions can be found with FreeFEM if we write the system
in the weak, or integral form.
In general, to obtain the integral or “weak” statements equivalent to system (5.17) and boundary conditions we form
a scalar dot product between an arbitrary magnetic field test function H𝑡 = {𝐻𝜌𝑡 , 𝐻𝜑𝑡 , 𝐻𝑧𝑡 } and the components of
our vectorial equation 𝐴1 , 𝐴2 , 𝐴3 , and integrate over the resonator’s cross section domain Ω (and its boundary for the
boundary conditions):
∫︁
(𝐻𝜌𝑡 𝐴1 + 𝐻𝜑𝑡 𝐴2 + 𝐻𝑧𝑡 𝐴3 )𝑑Ω
Ω
We can reduce the order of partial derivatives in this integral by using the Green’s formula for integration by parts. For
example:
2
𝜕𝐻𝑧𝑡 𝜕𝐻𝑧
∫︁ ∫︁ ∮︁
𝑡 𝜕 𝐻𝑧 𝜕𝐻𝑧
𝐻𝑧 𝑑Ω = − 𝑑Ω + 𝐻𝑧𝑡 𝑛𝜌 𝑑Γ
𝜕𝜌2 𝜕𝜌 𝜕𝜌 𝜕𝜌
Ω Ω
Thus converting equations (5.17) we obtain a large expression for the weak form.
We now compute the fundamental mode frequency for a fused silica sphere. The sphere is 36 micrometer in diameter,
the refractive index is 1.46, the boundary condition is the magnetic wall (which can actually be omitted as it holds
automatically).
1 // Parameters
2 real radius = 36; //approximate radius of the cavity
3 real yb = -10, yt = -yb; //window yb=bottom and yt=top coordinates
4 real xl = radius-5, xr = radius+3; //window xl=left and xr=right coordinates
5 real angle = asin((yt)/radius); //angle of the sphere segment to model in radians
6 int Nm = 60; //number of mesh vertices per border
7 real ne = 1.46; //n_e-extraordinary refractive index (root of permittivity parallel to z-
˓→axis, epara)
16 // Mesh
17 border W1l(t=0, 1){x=xl+(radius*cos(angle)-xl)*(1-t); y=yt; label=1;}
18 border W1r(t=0, 1){x=xr-(xr-radius*cos(angle))*(t); y=yt; label=1;}
19 border W2(t=0, 1){x=xr; y=yb+(yt-yb)*t; label=1;}
20 border W3l(t=0, 1){x=xl+(radius*cos(angle)-xl)*(t); y=yb; label=1;}
21 border W3r(t=0, 1){x=xr-(xr-radius*cos(angle))*(1-t); y=yb; label=1;}
22 border W4(t=0, 1){x=xl; y=yt-(yt-yb)*t; label=1;}
23 border S(t=0, 1){x=radius*cos((t-0.5)*2*angle); y=radius*sin((t-0.5)*2*angle); label=2;}
24 mesh Th = buildmesh(W1r(Nm/4) + W1l(Nm/4) + W4(Nm) + W3l(Nm/4) + W3r(Nm/4) + W2(Nm) +␣
˓→S(Nm));
25 plot(Th, WindowIndex=0);
26
27 // Fespace
28 fespace Ph(Th, P0);
29 Ph reg = region;
30
43
44 // Macro
45 //boundary condition macros
46 macro EWall(Hr, Hphi, Hz) (
47 dy(Hr) - dx(Hz) + Hr*N.x + Hz*N.y
48 - epara*(Hz*M - dy(Hphi)*x)*N.y
49 + eorto*(Hphi - Hr*M+dx(Hphi)*x)*N.x) //
50 macro MWall(Hr, Hphi, Hz) (
51 Hphi + Hz*N.x - Hr*N.y
52 + epara*(Hz*M - dy(Hphi)*x)*N.x
53 + eorto*(Hphi - Hr*M+dx(Hphi)*x)*N.y ) //
54
55 // Problem
56 real sigma =(M/(ne*radius))^2+2; // value of the shift (k^2), where the modes will be␣
˓→found
69 )/(eorto*epara)
70 + alpha*(
71 (vHr*Hr - M*(vHphi*Hr + Hphi*vHr) + M^2*vHphi*Hphi)/x //D/r
72 + (dx(vHr) + dy(vHz))*(Hr - M*Hphi) + (vHr - M*vHphi)*(dx(Hr) + dy(Hz)) //E
73 + x*(dx(vHr) + dy(vHz))*(dx(Hr) + dy(Hz)) //F
74 )
75 -sigma*x*(vHr*Hr + vHphi*Hphi + vHz*Hz)
76 )
77 //electric wall boundary condition on the boundary of computation domain
78 +int1d(Th, 1)(
79 EWall(Hr, Hphi, Hz)*EWall(vHr, vHphi, vHz)
80 )
81 ;
82 //setting sparce matrices and assigning the solver UMFPACK to solve eigenvalue problem
83 matrix B = b(Vh, Vh, solver=UMFPACK);
84 matrix OP = op(Vh, Vh, solver=UMFPACK);
85
86 // Solve
87 real[int] ev(nev); //to store the nev eigenvalue
88 Vh[int] [eHr, eHphi, eHz](nev); //to store the nev eigenvector
(continues on next page)
91
105 f << "Mode "<< i << ", ka=" << sqrt(ev[i])*radius << endl;
106 }
SIX
EXAMPLES
6.1 Misc
1 // Parameters
2 int nn = 20;
3 real L = 1.;
4 real H = 1.;
5 real l = 0.5;
6 real h = 0.5;
7
8 func f = 1.;
9 func g = 0.;
10
13 // Mesh
14 border b1(t=0, L){x=t; y=0;};
15 border b2(t=0, h){x=L; y=t;};
16 border b3(t=L, l){x=t; y=h;};
17 border b4(t=h, H){x=l; y=t;};
18 border b5(t=l, 0){x=t; y=H;};
19 border b6(t=H, 0){x=0; y=t;};
20
22
23 // Fespace
24 fespace Vh(Th, P1); // Change P1 to P2 to test P2 finite element
25 Vh u, v;
26
27 // Macro
28 macro grad(u) [dx(u), dy(u)] //
29
30 // Problem
31 problem Poisson (u, v, solver=CG, eps=-1.e-6)
32 = int2d(Th)(
33 grad(u)' * grad(v)
34 )
(continues on next page)
641
FreeFEM Documentation, Release 4.8
48 // Plot
49 plot(Th, u);
50
51 // Adaptmesh
52 Th = adaptmesh(Th, u, inquire=1, err=error);
53 error = error * coef;
54 }
1 load "tetgen"
2
3 // Parameters
4 real hh = 0.1;
5 func ue = 2.*x*x + 3.*y*y + 4.*z*z + 5.*x*y + 6.*x*z + 1.;
6 func f= -18.;
7
8 // Mesh
9 mesh Th = square(10, 20, [x*pi-pi/2, 2*y*pi]); // ]-pi/2, pi/2[X]0,2pi[
10 func f1 = cos(x)*cos(y);
11 func f2 = cos(x)*sin(y);
12 func f3 = sin(x);
13 func f1x = sin(x)*cos(y);
14 func f1y = -cos(x)*sin(y);
15 func f2x = -sin(x)*sin(y);
16 func f2y = cos(x)*cos(y);
17 func f3x = cos(x);
18 func f3y = 0;
19 func m11 = f1x^2 + f2x^2 + f3x^2;
20 func m21 = f1x*f1y + f2x*f2y + f3x*f3y;
21 func m22 = f1y^2 + f2y^2 + f3y^2;
22 func perio = [[4, y], [2, y], [1, x], [3, x]];
23 real vv = 1/square(hh);
24 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
25 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
26 plot(Th);
27
35 // Fespace
36 fespace Vh(Th3, P23d);
37 Vh u, v;
38 Vh uhe = ue;
39 cout << "uhe min: " << uhe[].min << " - max: " << uhe[].max << endl;
40 cout << uhe(0.,0.,0.) << endl;
41
45 // Macro
46 macro Grad3(u) [dx(u), dy(u), dz(u)] //
47
48 // Problem
49 problem Lap3d (u, v, solver=CG)
50 = int3d(Th3)(
51 Grad3(v)' * Grad3(u)
(continues on next page)
59 // Solve
60 Lap3d;
61 cout << "u min: " << u[]. min << " - max: " << u[].max << endl;
62
63 // Error
64 real err = int3d(Th3)(square(u-ue));
65 cout << int3d(Th3)(1.) << " = " << Th3.measure << endl;
66 Vh d = ue - u;
67 cout << " err = " << err << " - diff l^intfy = " << d[].linfty << endl;
68
69 // Plot
70 u2 = u;
71 u2e = ue;
72 plot(u2, wait=true);
73 plot(u2, u2e,wait=true);
1 load "msh3"
2 load "medit" // Dynamically loaded tools for 3D
3
4 // Parameters
5 int nn = 8;
6
7 // Mesh
8 mesh Th0 = square(nn, nn);
(continues on next page)
18 // Fespaces
19 fespace Vh2(Th0, P2);
20 Vh2 ux, uz, p2;
21
26 // Macro
27 macro Grad(u) [dx(u), dy(u), dz(u)] //
28 macro div(u1,u2,u3) (dx(u1) + dy(u2) + dz(u3)) //
29
44 // Plot
45 plot(p, wait=1, nbiso=5); // 3D visualization of pressure isolines
46
6.1.4 Cavity
1 //Parameters
2 int m = 300;
3 real L = 1;
4 real rho = 500.;
5 real mu = 0.1;
6
7 real uin = 1;
8 func fx = 0;
9 func fy = 0;
10 int[int] noslip = [1, 2, 4];
11 int[int] inflow = [3];
12
13 real dt = 0.1;
14 real T = 50;
15
18 //Macros
19 macro div(u) (dx(u#x) + dy(u#y))//
20 macro grad(u) [dx(u), dy(u)]//
21 macro Grad(u) [grad(u#x), grad(u#y)]//
22
23 //Time
24 real cpu;
25 real tabcpu;
26
27 //mesh
28 border C1(t = 0, L){ x = t; y = 0; label = 1; }
29 border C2(t = 0, L){ x = L; y = t; label = 2; }
30 border C3(t = 0, L){ x = L-t; y = L; label = 3; }
(continues on next page)
39 //Solve
40 varf navierstokes([ux, uy, p], [uhx, uhy, ph])
41 = int2d(th)(
42 rho/dt* [ux, uy]'* [uhx, uhy]
43 + mu* (Grad(u):Grad(uh))
44 - p* div(uh)
45 - ph* div(u)
46 - 1e-10 *p*ph
47 )
48
49 + int2d(th) (
50 [fx, fy]' * [uhx, uhy]
51 + rho/dt* [convect([upx, upy], -dt, upx), convect([upx, upy], -dt, upy)]'* [uhx, uhy]
52 )
53
58 //Initialization
59 [ux, uy, p]=[0, 0, 0];
60
64 //Time loop
65 for(int j = 0; j < T/dt; j++){
66 [upx, upy, pp]=[ux, uy, p];
67
74 //CPU
75 cout << " CPU = " << clock()-cpu << endl ;
76 tabcpu = clock()-cpu;
5 real x0 = 1.2;
6 real x1 = 1.8;
7 real y0 = 0;
8 real y1 = 1;
9 int n = 5;
10 real m = 20;
11 mesh Th2 = square(n, m, [x0+(x1-x0)*x, y0+(y1-y0)*y]);
12
1 // Parameters
2 real eps = 0.0001;
3 real h = 1;
4 real hmin = 0.05;
5 func f = 10.0*x^3 + y^3 + h*atan2(eps, sin(5.0*y)-2.0*x);
6
7 // Mesh
8 mesh Th = square(5, 5, [-1+2*x, -1+2*y]);
9
10 // Fespace
11 fespace Vh(Th,P1);
12 Vh fh = f;
13 plot(fh);
14
15 // Adaptmesh
16 for (int i = 0; i < 2; i++){
17 Th = adaptmesh(Th, fh);
18 fh = f; //old mesh is deleted
19 plot(Th, fh, wait=true);
20 }
1 // Parameters
2 real error = 0.1;
3
4 // Mesh
5 border ba(t=0, 1){x=t; y=0; label=1;}
6 border bb(t=0, 0.5){x=1; y=t; label=1;}
7 border bc(t=0, 0.5){x=1-t; y=0.5; label=1;}
8 border bd(t=0.5, 1){x=0.5; y=t; label=1;}
9 border be(t=0.5, 1){x=1-t; y=1; label=1;}
10 border bf(t=0, 1){x=0; y=1-t; label=1;}
11 mesh Th = buildmesh(ba(6) + bb(4) + bc(4) + bd(4) + be(4) + bf(6));
12
13 // Fespace
14 fespace Vh(Th, P1);
15 Vh u, v;
16
17 // Function
18 func f = 1;
19
20 // Problem
21 problem Poisson(u, v, solver=CG, eps=1.e-6)
22 = int2d(Th)(
23 dx(u)*dx(v)
24 + dy(u)*dy(v)
25 )
26 - int2d(Th)(
27 f*v
28 )
29 + on(1, u=0);
30
31 // Adaptmesh loop
32 for (int i = 0; i < 4; i++){
33 Poisson;
(continues on next page)
38 // Plot
39 plot(u);
7 Th = adaptmesh(Th, 1./30., IsMetric=1, nbvx=10000); // More than one time due to the
8 Th = adaptmesh(Th, 1./30., IsMetric=1, nbvx=10000); // adaptation bound `maxsubdiv=`
9 plot(Th, wait=true);
6.2.5 Borders
1 {
2 int upper = 1;
3 int others = 2;
4 int inner = 3;
5
16 int n = 10;
17 plot(C01(-n) + C02(-n) + C03(-n) + C04(-n) + C05(-n)
18 + C06(-n) + C11(n) + C12(n) + C13(n), wait=true);
19
23 plot(Th, wait=true);
24
25 cout << "Part 1 has region number " << Th(0.75, -0.25).region << endl;
26 cout << "Part 2 has redion number " << Th(0.25, -0.25).region << endl;
27 }
28
29 {
30 border a(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
31 border b(t=0, 2*pi){x=0.3+0.3*cos(t); y=0.3*sin(t); label=2;}
32 plot(a(50) + b(30)); //to see a plot of the border mesh
33 mesh Thwithouthole = buildmesh(a(50) + b(30));
(continues on next page)
39 {
40 real r=1;
41 border a(t=0, 2*pi){x=r*cos(t); y=r*sin(t); label=1;}
42 r=0.3;
43 border b(t=0, 2*pi){x=r*cos(t); y=r*sin(t); label=1;}
44 // mesh Thwithhole = buildmesh(a(50) + b(-30)); // do not do this because the two
45 // circles have the same radius = $0.3$
46 }
6.2.6 Change
1 verbosity=3;
2
3 // Mesh
4 mesh Th1 = square(10, 10);
5 mesh Th2 = square(20, 10, [x+1, y]);
6
27 solve P(u, v)
28 = int2d(Th)(
29 Grad(u)'*Grad(v)
30 )
31 -int2d(Th)(
32 v
33 )
34 + on(1, 3, u=0)
35 ;
36
37 plot(u, wait=1);
6.2.7 Cube
1 load "msh3"
2
7 cout << "Volume = " << Th.measure << ", border area = " << Th.bordermeasure << endl;
8
9 int err = 0;
(continues on next page)
16 if(s){
17 int ix = (sx/s+1.5);
18 int iy = (sy/s+1.5);
19 int iz = (sz/s+1.5);
20 int ii = (ix + 4*(iy+1) + 16*(iz+1) );
21 //value of ix,iy,iz => face min 0, face max 2, no face 1
22 cout << "Label = " << i << ", s = " << s << " " << ix << iy << iz << " : " << ii <
˓→< endl;
23 if( i != ii ) err++;
24 }
25 }
26 real volr11 = int3d(Th,r11)(1.);
27 cout << "Volume region = " << 11 << ": " << volr11 << endl;
28 if((volr11 - Th.measure )>1e-8) err++;
29 plot(Th, fill=false);
30 cout << "Nb err = " << err << endl;
31 assert(err==0);
1 {
2 border a(t=0, 2*pi){x=cos(t); y=sin(t); label=1;}
3 mesh Th = buildmesh(a(20));
4 Th = emptymesh(Th);
5 plot(Th);
6 }
7 {
8 mesh Th = square(10, 10);
9 int[int] ssd(Th.nt);
10 // Builds the pseudo region numbering
11 for(int i = 0; i < ssd.n; i++){
12 int iq = i/2; // Because we have 2 triangles per quad
13 int ix = iq%10;
14 int iy = iq/10;
15 ssd[i] = 1 + (ix>=5) + (iy>=5)*2;
16 }
17 // Builds an emtpy mesh with all edges that satisfy e=T1 cap T2 and ssd[T1] != ssd[T2]
18 Th = emptymesh(Th, ssd);
19 // Plot
20 plot(Th);
21 }
6.2.9 3 points
6.2.10 Bezier
1 // A cubic Bezier curve connecting two points with two control points
2 func real bzi(real p0, real p1, real q1, real q2, real t){
3 return p0*(1-t)^3 + q1*3*(1-t)^2*t + q2*3*(1-t)*t^2 + p1*t^3;
4 }
5
6 real[int] p00 = [0, 1], p01 = [0, -1], q00 = [-2, 0.1], q01 = [-2, -0.5];
7 real[int] p11 = [1,-0.9], q10 = [0.1, -0.95], q11=[0.5, -1];
8 real[int] p21 = [2, 0.7], q20 = [3, -0.4], q21 = [4, 0.5];
9 real[int] q30 = [0.5, 1.1], q31 = [1.5, 1.2];
10 border G1(t=0, 1){
11 x=bzi(p00[0], p01[0], q00[0], q01[0], t);
12 y=bzi(p00[1], p01[1], q00[1], q01[1], t);
13 }
14 border G2(t=0, 1){
15 x=bzi(p01[0], p11[0], q10[0], q11[0], t);
16 y=bzi(p01[1], p11[1], q10[1], q11[1], t);
17 }
18 border G3(t=0, 1){
19 x=bzi(p11[0], p21[0], q20[0], q21[0], t);
20 y=bzi(p11[1], p21[1], q20[1], q21[1], t);
21 }
22 border G4(t=0, 1){
23 x=bzi(p21[0], p00[0], q30[0], q31[0], t);
24 y=bzi(p21[1], p00[1], q30[1], q31[1], t);
25 }
26 int m = 5;
27 mesh Th = buildmesh(G1(2*m) + G2(m) + G3(3*m) + G4(m));
28 plot(Th, bw=true);
1 load "msh3"
2 load "tetgen"
3 load "medit"
4
5 // Parameters
6 int C1 = 99;
7 int C2 = 98;
8
9 // 2D mesh
10 border C01(t=0, pi){x=t; y=0; label=1;}
11 border C02(t=0, 2*pi){ x=pi; y=t; label=1;}
12 border C03(t=0, pi){ x=pi-t; y=2*pi; label=1;}
13 border C04(t=0, 2*pi){ x=0; y=2*pi-t; label=1;}
14
37 func XX = x*cos(y);
38 func YY = x*sin(y);
39 func ZZ = z;
40
6.2.12 Sphere
1 // Parameter
2 real hh = 0.1;
3
4 // Mesh 2D
5 mesh Th = square(10, 20, [x*pi-pi/2, 2*y*pi]); // ]-pi/2, pi/2[X]0, 2pi[
6 // A parametrization of a sphere
7 func f1 = cos(x)*cos(y);
8 func f2 = cos(x)*sin(y);
9 func f3 = sin(x);
10 // Partial derivative of the parametrization DF
11 func f1x = sin(x)*cos(y);
12 func f1y = -cos(x)*sin(y);
13 func f2x = -sin(x)*sin(y);
14 func f2y = cos(x)*cos(y);
15 func f3x = cos(x);
16 func f3y = 0;
17 //M = DF^t DF
18 func m11 = f1x^2 + f2x^2 + f3x^2;
(continues on next page)
22 // Periodic condition
23 func perio = [[4, y], [2, y], [1, x], [3, x]];
24
25 // Mesh adaptation
26 real vv = 1/square(hh);
27 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, inquire=1, periodic=perio);
28 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
29 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
30 Th = adaptmesh(Th, m11*vv, m21*vv, m22*vv, IsMetric=1, periodic=perio);
31
32 // Sphere
33 mesh3 Th3 = movemesh23(Th, transfo=[f1, f2, f3]);
34 plot(Th3);
6.3.1 Periodic 3D
1 load "msh3"
2 load "medit"
3
4 // Parameters
5 searchMethod=1; // More safe seach algo
6 real a = 1, d = 0.5, h = 0.5;
7 int nnb = 7, nni = 10;
8 int nz = 3;
9 func zmin = 0;
10 func zmax = h;
(continues on next page)
12 // Mesh 2D
13 border b1(t=0.5, -0.5){x=a*t; y=-a/2; label=1;}
14 border b2(t=0.5, -0.5){x=a/2; y=a*t; label=2;}
15 border b3(t=0.5, -0.5){x=a*t; y=a/2; label=3;}
16 border b4(t=0.5, -0.5){x=-a/2; y=a*t; label=4;}
17 border i1(t=0, 2.*pi){x=d/2*cos(t); y=-d/2*sin(t); label=7;}
18 mesh Th = buildmesh(b1(-nnb) + b3(nnb) + b2(-nnb) + b4(nnb) + i1(nni));
19
36 // Mesh 3D
37 int[int] rup = [0, 5], rlow = [0, 6], rmid = [1, 1, 2, 2, 3, 3, 4, 4, 7, 7], rtet = [0,␣
˓→41];
42
43 plot(Th3, wait=true);
44 medit("Th3", Th3);
45
46 fespace Vh(Th3, P2, periodic=[[1, x, z], [3, x, z], [2, y, z], [4, y, z], [5, x, y], [6,␣
˓→x, y]]);
1 // Parameters
2 func f = 1 + x - y;
3
4 // Mesh
5 mesh Th = square(10, 10);
6
7 // Fespace
8 fespace Vh(Th, P1);
(continues on next page)
13 // Problem
14 varf va (uh, vh)
15 = int2d(Th)(
16 dx(uh)*dx(vh)
17 + dy(uh)*dy(vh)
18 )
19 ;
20
28 // Block matrix
29 matrix AA = [ [ A, B ], [ B', 0 ] ];
30 set(AA, solver=sparsesolver);
31
37 // Solve
38 xx = AA^-1 * bb;
39
40 // Set values
41 [uh[],l] = xx;
42
46 // Plot
47 plot(uh);
6.4 Visualization
6.4.1 Plot
1 mesh Th = square(5,5);
2 fespace Vh(Th, P1);
3
8 // Zoom on box defined by the two corner points [0.1,0.2] and [0.5,0.6]
9 plot(uh, [uh, vh], bb=[[0.1, 0.2], [0.5, 0.6]],
10 wait=true, grey=true, fill=true, value=true);
11
12 // Compute a cut
13 int n = 10;
14 real[int] xx(10), yy(10);
15 for (int i = 0; i < n; i++){
16 x = i/real(n);
17 y = i/real(n);
18 xx[i] = i;
19 yy[i] = uh; // Value of uh at point (i/10., i/10.)
20 }
21 plot([xx, yy], wait=true);
22
(c) Gnuplot
29 // Calls the gnuplot command, waits 5 seconds and generates a postscript plot (UNIX ONLY)
30 exec("echo 'plot \"plot.gp\" w l \n pause 5 \n set term postscript \n set output \
˓→"gnuplot.eps\" \n replot \n quit' | gnuplot");
6.4.2 HSV
1 // From: http://en.wikipedia.org/wiki/HSV_color_space
2 // The HSV (Hue, Saturation, Value) model defines a color space
3 // in terms of three constituent components:
4 // HSV color space as a color wheel
5 // Hue, the color type (such as red, blue, or yellow):
6 // Ranges from 0-360 (but normalized to 0-100% in some applications like here)
7 // Saturation, the "vibrancy" of the color: Ranges from 0-100%
(continues on next page)
6.4.3 Medit
1 load "medit"
2
10 // Old way
11 savemesh(Th, "u", [x, y, u*.5]); // Saves u.points and u.faces file
12 // build a u.bb file for medit
13 {
14 ofstream file("u.bb");
15 file << "2 1 1 " << u[].n << " 2 \n";
16 for (int j = 0; j < u[].n; j++)
17 file << u[][j] << endl;
18 }
19 // Calls medit command
20 exec("ffmedit u");
21 // Cleans files on unix-like OS
22 exec("rm u.bb u.faces u.points");
6.4.4 Paraview
1 load "iovtk"
2
6.5.1 Algorithms
1 // Parameters
2 int nerr = 0;
3 int debugJ = 0;
4 int debugdJ = 0;
5 real umax = 0;
6
7 // Algorithms tests
8 {
9 func bool stop (int iter, real[int] u, real[int] g){
10 cout << " stop = " << iter << " " << u.linfty << " " << g.linfty << endl;
11 return g.linfty < 1e-5 || iter > 15;
12 }
13 // minimization of J(u) = 1./2 * sum (i+1) u_i^2 - b_i
14 real[int] b(10), u(10);
15
16 //J
17 func real J (real[int] & u){
18 real s = 0;
19 for (int i = 0; i < u.n; i++)
20 s += (i+1)*u[i]*u[i]*0.5 - b[i]*u[i];
21 if (debugJ)
22 cout << "J = " << s << ", u = " << u[0] << " " << u[1] << endl;
23 return s;
24 }
25
47 //erro calculation
48 func real error (real[int] & u, real[int] & b){
49 real s = 0;
50 for (int i = 0; i < u.n; i++)
51 s += abs((i+1)*u[i] - b[i]);
52 return s;
53 }
54
66 b = 1;
67 u = 0;
68 LinearCG(DJ, u, eps=1.e-15, nbiter=20, precon=matId, verbosity=verb, stop=stop);
69 cout << "LinearGC (Affine with stop) : J(u) = " << J(u) << ", err = " << error(u, b)
˓→<< endl;
73 b = 1;
74 u = 0;
75 LinearCG(DJ0, u, b, eps=1.e-6, nbiter=20, precon=matId, verbosity=verb);
76 cout << "LinearGC (Linear) : J(u) = " << J(u) << ", err = " << error(u, b) << endl;
77 nerr += !(error(u,b) < 1e-5);
78 if(nerr) cout << "sol: u = " << u[0] << " " << u[1] << " " << u[2] << endl;
79
80
81 b = 1;
(continues on next page)
88 b=1;
89 u=0;
90 LinearGMRES(DJ0, u, b, eps=1.e-6, nbiter=20, precon=matId, verbosity=verb);
91 cout << "LinearGMRES (Linear) : J(u) = " << J(u) << ", err = " << error(u, b) <<␣
˓→endl;
95
96 b=1;
97 u=0;
98 NLCG(DJ, u, eps=1.e-6, nbiter=20, precon=matId, verbosity=verb);
99 cout << "NLCG: J(u) = " << J(u) << ", err = " << error(u, b) << endl;
100 nerr += !(error(u,b) < 1e-5);
101 if(nerr) cout << "sol: u =" << u[0] << " " << u[1] << " " << u[2] << endl;
102
103
104 //warning: BFGS use a full matrix of size nxn (where n=u.n)
105 b=1;
106 u=2;
107 BFGS(J, DJ, u, eps=1.e-6, nbiter=20, nbiterline=20);
108 cout << "BFGS: J(u) = " << J(u) << ", err = " << error(u, b) << endl;
109 assert(error(u,b) < 1e-5);
110 if(nerr) cout << "sol: u =" << u[0] << " " << u[1] << " " << u[2] << endl;
111
112 assert(nerr==0);
113 }
114
124 // Mesh
125 mesh Th = square(20, 20);
126
127 // Fespace
128 fespace Vh(Th, P1);
129 Vh b = 1;
130 Vh u = 0;
131
162 // Problem
163 alpha = df(dx(u)*dx(u) + dy(u)*dy(u));
164 varf alap (uh, vh)
165 = int2d(Th)(
166 alpha*(dx(uh)*dx(vh) + dy(uh)*dy(vh))
167 )
168 + on(1, 2, 3, 4, uh=0)
169 ;
170
181 // Preconditionner
182 func real[int] C(real[int] & u){
183 real[int] w = u;
(continues on next page)
188 // Solve
189 int conv=0;
190 for(int i = 0; i < 20; i++){
191 conv = NLCG(dJ, u[], nbiter=10, precon=C, veps=eps, verbosity=5);
192 if (conv) break;
193
199 // Plot
200 plot (u, wait=true, cmm="solution with NLCG");
201 umax = u[].max;
202
207 assert(nerr==0);
1 load "ff-cmaes"
2
3 // Parameters
4 int NN = 7;
5 func f1 = 1.;
6 func f2 = -1.;
7 func g1 = 0.;
8 func g2 = 0.1;
9 int iter = 0;
10 int nadapt = 1;
11 real starttol = 1e-10;
12 real bctol = 6.e-12;
13 real pena = 1000.;
14
15 // Mesh
16 mesh Th = square(NN, NN);
17
18 // Fespace
19 fespace Vh(Th, P1);
20 Vh ou1, ou2;
21
40 Vh In, Bord;
41 Bord[] = Vbord(0, Vh, tgv=1);
42 In[] = Bord[] ? 0:1;
43 Vh gh1 = Bord*g1;
44 Vh gh2 = Bord*g2;
45
98 return val ;
99 }
100
101 // Solve
102 real[int] start(2*In[].sum);
(continues on next page)
1 load "msh3";
2 load "medit";
3 load "ff-Ipopt";
4
5 // Parameters
6 int nadapt = 3;
7 real alpha = 0.9;
8 int np = 30;
(continues on next page)
19 // Mesh
20 mesh Th = square(2*np, np, [2*pi*x, pi*y]);
21
22 // Fespace
23 fespace Vh(Th, P1, periodic=[[2, y], [4, y]]);
24 //Initial shape definition
25 //outside of the mesh adaptation loop to initialize with the previous optimial shape␣
˓→found on further iterations
26 Vh startshape = 5;
27 Vh uz = 1., lz = 1.;
28
46 if(ffplot)
47 plot(Sphere);
48 else
49 medit(cmm, Sphere);
50 }
51 catch(...){
52 cout << "PLOT ERROR" << endl;
53 }
54 return 1;
55 }
56
57 // Surface computation
(continues on next page)
66 ++iter;
67 if(1)
68 plot(rho, value=true, fill=true, cmm="rho(theta,phi) on [0,2pi]x[0,pi] - S=
˓→"+res, dim=3);
69 else
70 Plot3D(rho[], "shape_evolution", 1);
71 return res;
72 }
73
94 matrix hessianA;
95 func matrix HessianArea (real[int] &X){
96 Vh rho, rho2;
97 rho[] = X;
98 rho2 = square(rho);
99 Vh sqrtPsi, sqrtPsi3, C00, C01, C02, C11, C12, C22, A;
100 {
101 Vh C0, C1, C2;
102 Vh dxrho2 = dx(rho)*dx(rho), dyrho2 = dy(rho)*dy(rho);
103 sqrtPsi = sqrt( rho2*rho2*sin2 + rho2*dxrho2 + rho2*dyrho2*sin2);
104 sqrtPsi3 = (rho2*rho2*sin2 + rho2*dxrho2 + rho2*dyrho2*sin2)*sqrtPsi;
105 C0 = 2*rho2*rho*sin2 + rho*dxrho2 + rho*dyrho2*sin2;
106 C1 = rho2*dx(rho);
(continues on next page)
190 func real Gaussian (real X, real Y, real theta, real phi){
191 real deltax2 = square((X-theta)*sin(Y)), deltay2 = square(Y-phi);
192 return exp(-0.5 * (deltax2 + deltay2) / (sigma*sigma));
193 }
194
198 if(1){
199 lb = r0;
200 for (int q = 0; q < 5; ++q){
201 func f = rr*Gaussian(x, y, 2*q*pi/5., pi/3.);
202 func g = rr*Gaussian(x, y, 2*q*pi/5.+pi/5., 2.*pi/3.);
203 lb = max(max(lb, f), g);
204 }
205 lb = max(lb, rr*Gaussian(x, y, 2*pi, pi/3));
206 }
207 lb = max(lb, max(disc1, disc2));
208 real Vobj = Volume(lb[]);
209 real Vnvc = 4./3.*pi*pow(lb[].linfty,3);
(continues on next page)
211 if(1)
212 Plot3D(lb[], "object_inside", 1);
213 real[int] clb = 0., cub = [(1-alpha)*Vobj + alpha*Vnvc];
214
221 // Plot
222 Plot3D(rc[], "Shape_at_"+kkk, 1);
223 Plot3D(GradArea(rc[]), "ShapeGradient", 1);
224
Command:
1 load "mpi-cmaes"
2
3 // Parameters
4 int NN = 10;
5 func f1 = 1.;
6 func f2 = -1.;
7 func g1 = 0.;
8 func g2 = 0.1;
9 int iter = 0;
10 int nadapt = 1;
11 real starttol = 1e-10;
12 real bctol = 6.e-12;
13 real pena = 1000;
14
15 // Mesh
16 mesh Th = square(NN, NN);
17
18 // Fespace
19 fespace Vh(Th, P1);
20 Vh ou1, ou2;
21
39 Vh In, Bord;
40 Bord[] = Vbord(0, Vh, tgv=1);
41 In[] = Bord[] ? 0:1;
42 Vh gh1 = Bord*g1, gh2 = Bord*g2;
43
95 return val ;
96 }
97
98 // Solve
(continues on next page)
101 if (al==0){
102 start(0:In[].sum-1) = 0.;
103 start(In[].sum:2*In[].sum-1) = 0.1;
104 }
105 else
106 FEFToSSP(ou1[], ou2[], start);
107
6.6 Parallelization
6.6.1 MPI-GMRES 2D
1 //usage :
2 //ff-mpirun [mpi parameter] MPIGMRES2d.edp [-glut ffglut] [-n N] [-k K] [-d D] [-ns] [-
˓→gmres [0|1]
3 //arguments:
4 //-glut ffglut : to see graphicaly the process
5 //-n N: set the mesh cube split NxNxN
6 //-d D: set debug flag D must be one for mpiplot
7 //-k K: to refined by K all element
8 //-ns: remove script dump
9 //-gmres
10 //0: use iterative schwarz algo.
11 //1: Algo GMRES on residu of schwarz algo
12 //2: DDM GMRES
13 //3: DDM GMRES with coarse grid preconditionner (Good one)
14
15 load "MPICG"
16 load "medit"
17 load "metis"
18 include "getARGV.idp"
19 include "MPIplot.idp"
20 include "MPIGMRESmacro.idp"
21
22 searchMethod = 0; //more safe seach algo (warning can be very expensive in case of lot␣
˓→of ouside point)
28 // Arguments
29 verbosity = getARGV("-vv", 0);
30 int vdebug = getARGV("-d", 1);
31 int ksplit = getARGV("-k", 3);
32 int nloc = getARGV("-n", 10);
33 string sff = getARGV("-p", "");
34 int gmres = getARGV("-gmres", 2);
35 bool dplot = getARGV("-dp", 0);
36 int nC = getARGV("-N", max(nloc/10, 4));
37
47
48 // Parameters
49 int withplot = 0;
50 bool withmetis = 1;
(continues on next page)
57 // MPI function
58 func bool plotMPIall(mesh &Th, real[int] &u, string cm){
59 if(vdebug)
60 PLOTMPIALL(mesh, Pk, Th, u, {cmm=cm, nbiso=20, fill=1, dim=3, value=1});
61 return 1;
62 }
63
64 // MPI
65 mpiComm comm(mpiCommWorld,0,0); //trick : make a no split mpiWorld
66
70 int njpart = 0; //Number of part with intersection (a jpart) with ipart without ipart
71 int[int] jpart(npart); //list of jpart
72 if(ipart==0)
73 cout << " Final N = " << ksplit*nloc << ", nloc = " << nloc << ", split = " <<␣
˓→ksplit << endl;
74 settt
75
76 // Mesh
77 mesh Thg = square(nloc, nloc, label=l111);
78 mesh ThC = square(nC, nC, label=l111);// Coarse mesh
79
82 // Fespace
83 fespace Phg(Thg, P0);
84 Phg part;
85
91 // Partitioning
92 {
93 int[int] nupart(Thg.nt);
94 nupart = 0;
95 if (npart > 1 && ipart == 0)
96 metisdual(nupart, Thg, npart);
97
109 Thin = trunc(Thg, suppi>0, label=10); // non-overlapping mesh, interfaces have label 10
110 int nnn = sizeoverlaps*2;// to be sure
111 AddLayers(Thg, suppi[], nnn, unssd[]); //see above! suppi and unssd are modified
112 unssd[] *= nnn; //to put value nnn a 0
113 real nnn0 = nnn - sizeoverlaps + 0.001;
114 Thi = trunc(Thg, unssd>nnn0, label=10); //overlapping mesh, interfaces have label 10
115
116 settt
117
118 // Fespace
119 fespace Vhi(Thi,P1);
120 int npij = npart;
121 Vhi[int] pij(npij); //local partition of unit + pii
122 Vhi pii;
123
139
140 {
141 //find all j mes (supp(p_j) cap supp(p_i)) >0
142 //compute all phi_j on Thii
143 //remark: supp p_i include in Thi
144
145 // Fespace
146 fespace Phii(Thii, P0);
147 fespace Vhii(Thii, P1);
148 Vhi sumphi = 0;
149 Vhii phii = 0;
150
151 jpart = 0;
152 njpart = 0;
(continues on next page)
186 if(dplot)
187 plot(sumphi, wait=1, dim=3, cmm="sum ", fill=1);
188 pii[] = pii[] ./ sumphi[];
189 for (int j = 0; j < njpart; ++j)
190 pij[j][] = pij[j][] ./ sumphi[];
191 jpart.resize(njpart);
192 for (int j = 0; j < njpart; ++j)
193 assert(pij[j][].max <= 1);
194 {
195 cout << ipart << " number of jpart " << njpart << " : ";
196 for (int j = 0; j < njpart; ++j)
197 cout << jpart[j] << " ";
198 cout << endl;
199 }
200 sumphi[] = pii[];
201 for (int j = 0; j < njpart; ++j)
202 sumphi[] += pij[j][];
203 if(vdebug > 2)
(continues on next page)
228 if(vdebug)
229 plotMPIall(Thi, pii[], "pi_i");
230
248 settt
249
250 if (ipart == 0)
251 cout << "End build mesh intersection" << endl;
252
265 {Whi uuu=1; Whij vvv=-1; vvv[]+=rMj[jp]'*uuu[]; cout << jp << " ### " <<␣
˓→vvv[].linfty << endl; assert(vvv[].linfty < 1e-6);}
266 }
267 }
268 }
269 if (ipart == 0)
270 cout << "End build transfert matrix" << endl;
271
304 if (mpiRank(comm) == 0)
(continues on next page)
367 Whi u = 0, v;
368 { //verification
369 Whi u = 1, v;
370 Update(u[], v[]);
371 u[] -= v[];
372 assert(u[].linfty < 1e-6);
373 }
374
375 settt
376 u[] = vBC(0, Whi, tgv=1); //set u with tgv BC value
377
409 if (ipart == 0)
410 cout << ipart << " err = " << errg << " u. max " << umaxg << endl;
411 if (errg < 1e-5) break;
412 }
413
414 if (vdebug)
415 plotMPIall(Thi, u[], "u-final");
416
417 settt
418
427 }
428
437 /*
438 1 mpirank
439 2 mpisize
440 3 ksplit
441 4 nloc
442 5 sizeoverlaps
443 6 kiter
444 7 mesh & part build
445 8 build the partion
446 9 build mesh, transfere , and the fine mesh ..
447 10 build the matrix, the trans matrix, factorizatioon
448 11 GMRES
449 */
450
451 ff << mpirank << " " << mpisize << " " << sPk << " ";
452 ff << ksplit << " " << nloc << " " << sizeoverlaps << " " << kiter;
453 for (int i = 1; i < ittt; ++i)
454 ff << " " << ttt[i]-ttt[i-1] << " ";
455 ff << epss << " " << Ai.nbcoef << " " << Ai.n << " " << gmres << endl;
(continues on next page)
6.6.2 MPI-GMRES 3D
Todo: todo
1 load "MUMPS_FreeFem"
2 //default solver: real-> MUMPS, complex -> MUMPS
3 load "real_SuperLU_DIST_FreeFem"
4 default solver: real-> SuperLU_DIST, complex -> MUMPS
5 load "real_pastix_FreeFem"
6 //default solver: real-> pastix, complex -> MUMPS
7
1 load "MUMPS_FreeFem"
2
3 // Parameters
4 int[int] ICNTL(40); //declaration of ICNTL parameter for MUMPS
5
18 broadcast(processor(0), ICNTL);
19
23 {
24 // Problem
25 int SYM = 0;
26 int PAR = 1;
27 matrix A =
28 [
29 [40, 0, 45, 0, 0],
(continues on next page)
50 // Solve
51 if (mpirank == 0)
52 cout << "Solve" << endl;
53 x = A^-1*b;
54 if (mpirank == 0)
55 cout << "b = " << b << endl;
56 if (mpirank == 0)
57 cout << "x = " << endl; cout << x << endl;
58 di = xx-x;
59 if (mpirank == 0){
60 cout << "x-xx = " << endl;
61 cout << "Linf = " << di.linfty << ", L2 = " << di.l2 << endl;
62 }
63 }
64
86 // Solve
87 if (mpirank == 0)
88 cout << "Solve" << endl;
89 x = A^-1*b;
90
91 if (mpirank == 0){
92 cout << "b = " << b << endl;
93 cout << "x = " << x << endl;
94 }
95 di = xx-x;
96 if (mpirank == 0){
97 cout << "x-xx = " << endl;
98 cout << "Linf = " << di.linfty << ", L2 = " << di.l2 << endl;
99 }
100 }
6.7 Developers
6.7.1 FFT
1 load "dfft"
2
3 // Parameters
4 int nx = 32;
5 real ny = 16;
6 real N = nx*ny;
7 func f1 = cos(2*x*2*pi)*cos(3*y*2*pi);
8
9 // Mesh
10 //warning: the fourier space is not exactly the unit square due to periodic condition
(continues on next page)
15 // Fespace
16 fespace Vh(Th,P1);
17 Vh<complex> u = f1, v;
18 Vh w = f1;
19 Vh ur, ui;
20
21 // FFT
22 //in dfft the matrix n, m is in row-major order and array n, m is
23 //store j + m*i (the transpose of the square numbering)
24 v[] = dfft(u[], ny, -1);
25 u[] = dfft(v[], ny, +1);
26 cout << "||u||_\infty " << u[].linfty << endl;
27
28 u[] *= 1./N;
29 cout << "||u||_\infty " << u[].linfty << endl;
30
31 ur = real(u);
32
33 // Plot
34 plot(w, wait=1, value=1, cmm="w");
35 plot(ur, wait=1, value=1, cmm="u");
36 v = w - u;
37 cout << "diff = " << v[].max << " " << v[].min << endl;
38 assert( norm(v[].max) < 1e-10 && norm(v[].min) < 1e-10);
39
40 // Other example
41 //FFT Lapacian
42 //-\Delta u = f with biperiodic condition
43 func f = cos(3*2*pi*x)*cos(2*2*pi*y);
44 func ue = (1./(square(2*pi)*13.))*cos(3*2*pi*x)*cos(2*2*pi*y); //the exact solution
45 Vh<complex> ff = f;
46 Vh<complex> fhat;
47 Vh<complex> wij;
48
49 // FFT
50 fhat[] = dfft(ff[],ny,-1);
51
52 //warning in fact we take mode between -nx/2, nx/2 and -ny/2, ny/2
53 //thanks to the operator ?:
54 wij = square(2.*pi)*(square(( x<0.5?x*nx:(x-1)*nx)) + square((y<0.5?y*ny:(y-1)*ny)));
55 wij[][0] = 1e-5; //to remove div / 0
56 fhat[] = fhat[] ./ wij[];
57 u[] = dfft(fhat[], ny, 1);
58 u[] /= complex(N);
59 ur = real(u); //the solution
60 w = real(ue); //the exact solution
61
62 // Plot
(continues on next page)
65 // Error
66 w[] -= ur[];
67 real err = abs(w[].max) + abs(w[].min);
68 cout << "err = " << err << endl;
69 assert(err < 1e-6);
70
6.7.2 Complex
14 cout << "Standard output of the complex " << pc(z1) << " is the pair: " << z1 << endl;
15 cout << pc(z1) << " + " << pc(z2) << " = " << pc(z1+z2) << endl;
16 cout << pc(z1) << " - " << pc(z2) << " = " << pc(z1-z2) << endl;
17 cout << pc(z1) << " * " << pc(z2) << " = " << pc(z1*z2) << endl;
18 cout << pc(z1) << " / " << pc(z2) << " = " << pc(z1/z2) << endl;
19 cout << "Real part of " << pc(z1) << " = " << real(z1) << endl;
20 cout << "Imaginary part of " << pc(z1) << " = " << imag(z1) << endl;
21 cout << "abs(" << pc(z1) << ") = " << abs(z1) << endl;
22 cout << "Polar coordinates of " << pc(z2) << " = " << toPolar(z2) << endl;
23 cout << "de Moivre formula: " << pc(z2) << "^3 = " << toPolar(z2^3) << endl;
24 cout << " and polar(" << abs(z2) << ", " << arg(z2) << ") = " << pc(polar(abs(z2),␣
˓→arg(z2))) << endl;
25 cout << "Conjugate of " <<pc(z2) << " = " << pc(conj(z2)) <<endl;
26 cout << pc(z1) << " ^ " << pc(z2) << " = " << pc(z1^z2) << endl;
6.7.3 String
1 // Concatenation
2 string tt = "toto1" + 1 + " -- 77";
3
4 // Append
5 string t1 = "0123456789";
6 t1(4:3) = "abcdefghijk-";
7
8 // Sub string
9 string t55 = t1(4:14);
10
1 tt = toto11 -- 77
2 t1 = 0123abcdefghijk-456789
3 t1.find(abc) = 4
4 t1.rfind(abc) = 4
5 t1.find(abc, 10) = -1
6 t1.ffind(abc, 10) = 4
7 t1.length = 22
8 t55 = abcdefghijk
1 real b = 1.;
2 real a = b;
3 func real phix(real t){
4 return (a+b)*cos(t) - b*cos(t*(a+b)/b);
5 }
6 func real phiy(real t){
7 return (a+b)*sin(t) - b*sin(t*(a+b)/b);
8 }
9
6.7.5 Array
12 tab.resize(12); //change the size of array tab to 12 with preserving first value
13 tab(10:11) = 3.14; //set values 10 & 11
14 cout << "resized tab: " << tab << endl;
15
42
43 {
44 int[int] A1(2:10); //2, 3, 4, 5, 6, 7, 8, 9, 10
45 int[int] A2(2:3:10); //2, 5, 8
46 cout << "A1(2:10): " << A1 << endl;
47 cout << "A2(2:3:10): " << A1 << endl;
48 A1 = 1:2:5;
49 cout << "1:2:5 => " << A1 << endl;
50 }
51 {
52 real[int] A1(2:10); //2, 3, 4, 5, 6, 7, 8, 9, 10
53 real[int] A2(2:3:10); //2, 5, 8
54 cout << "A1(2:10): " << A1 << endl;
55 cout << "A2(2:3:10): " << A1 << endl;
56 A1 = 1.:0.5:3.999;
57 cout << "1.:0.5:3.999 => " << A1 << endl;
58 }
59 {
60 complex[int] A1(2.+0i:10.+0i); //2, 3, 4, 5, 6, 7, 8, 9, 10
61 complex[int] A2(2.:3.:10.); //2, 5, 8
62 cout << " A1(2.+0i:10.+0i): " << A1 << endl;
63 cout << " A2(2.:3.:10.)= " << A2 << endl;
64 cout << " A1.re real part array: " << A1.re << endl ;
65 // he real part array of the complex array
66 cout << " A1.im imag part array: " << A1.im << endl ;
67 //the imaginary part array of the complex array
68 }
69
103 c = a .* b;
104 cout << "c = a .* b: " << c << endl;
105 c = a ./ b;
106 cout << "c = a ./ b: " << c << endl;
107 c = 2 * b;
108 cout << "c = 2 * b: " << c << endl;
109 c = b * 2;
110 cout << "c = b * 2: " << c << endl;
111
138 }
139
140 {
141 // Array versus matrix
142 int N = 3, M = 4;
143
190 i = A.imax;
191 j = A.jmax;
192
193 cout << "Max " << i << " " << j << ", = " << A.max << endl;
194
198 ii = A.imin;
199 jj = A.jmin;
200
201 cout << "Min " << ii << " " << jj << ", = " << A.min << endl;
202 }
1 tab: 10
2 1.03 2.15 1.03 1.03 1.03
3 1.03 1.03 1.03 1.03 1.03
4
5 min: 1.03
6 max: 2.15
7 sum: 11.42
8 resized tab: 12
9 1.03 2.15 1.03 1.03 1.03
10 1.03 1.03 1.03 1.03 1.03
11 3.14 3.14
12 sorted tab:12
13 1.03 1.03 1.03 1.03 1.03
14 1.03 1.03 1.03 1.03 2.15
15 3.14 3.14
16 tt["a"] = 0
17 tt["+"] = 1.5
18 d = ( a ? b : c ) is 5
19 2 2 3 2 2
20
24 ii: 5
25 4 3 2 1 0
26
27 A1(2:10): 9
28 2 3 4 5 6
29 7 8 9 10
30 A2(2:3:10): 9
31 2 3 4 5 6
32 7 8 9 10
33 1:2:5 => 3
34 1 3 5
35 A1(2:10): 9
36 2 3 4 5 6
37 7 8 9 10
38 A2(2:3:10): 9
39 2 3 4 5 6
40 7 8 9 10
41 1.:0.5:3.999 => 6
42 1 1.5 2 2.5 3
43 3.5
44 A1(2.+0i:10.+0i): 9
45 (2,0) (3,0) (4,0) (5,0) (6,0)
46 (7,0) (8,0) (9,0) (10,0)
47 A2(2.:3.:10.)= 3
48 (2,0) (5,0) (8,0)
49 A1.re real part array: 9
50 2 3 4 5 6
51 7 8 9 10
52 A1.im imag part array: 9
53 0 0 0 0 0
54 0 0 0 0
55 a: 5
56 2 1 2 4 4
57
58 b = a + a: 5
59 4 2 4 8 8
60
61 b += a: 5
62 6 3 6 12 12
63
64 b += 2*a: 5
65 10 5 10 20 20
66
67 b /= 2: 5
68 5 2.5 5 10 10
69
70 b .*= a: 5
71 10 2.5 10 40 40
72
76 c = a + b: 5
77 7 3.5 7 14 14
78
79 c = 2*a + 4b: 5
80 24 12 24 48 48
81
82 c = a + 4b: 5
83 22 11 22 44 44
84
85 c = -a + 4b: 5
86 18 9 18 36 36
87
88 c = -a - 4b: 5
89 -22 -11 -22 -44 -44
90
91 c = -a -b: 5
92 -7 -3.5 -7 -14 -14
93
94 c = a .* b: 5
95 10 2.5 10 40 40
96
97 c = a ./ b: 5
98 0.4 0.4 0.4 0.4 0.4
99
100 c = 2 * b: 5
101 10 5 10 20 20
102
103 c = b * 2: 5
104 10 5 10 20 20
105
106 ||a||_1 = 13
107 ||a||_2 = 6.40312
108 ||a||_infty = 4
109 sum a_i = 13
110 max a_i = 4 a[ 3 ] = 4
111 min a_i = 1 a[ 1 ] = 1
112 a' * a = 41
113 a quantile 0.2 = 2
114 b = a(I) : 5
115 2 4 4 -3 4
116
117 c(I) = a 5
118 -3 -3 2 4 2
119
120 b = a(I) : 5
121 2 4 4 -3 4
122
123 c(I) = a 5
124 -3 -3 4 9 4
(continues on next page)
126 A = 3 4
127 1 5 2 1
128 3 3 3 1
129 4 5 2 4
130
131 C = 3 4
132 (-50,-40) (-100,-80) (-150,-120) (-200,-160)
133 (-100,-80) (-200,-160) (-300,-240) (-400,-320)
134 (-150,-120) (-300,-240) (-450,-360) (-600,-480)
135
136 A = 3 4
137 8 10 12 14
138 16 20 24 28
139 24 30 36 42
140
1 // Parameters
2 real f1 = 1.;
3 real f2 = 1.5;
4
5 // Mesh
6 mesh Th1 = square(10, 10);
7 mesh Th2 = square(10, 10, [1+x, -1+y]);
8 plot(Th1, Th2);
9
10 // Fespace
11 fespace Uh1(Th1, P1);
12 Uh1 u1;
13
17 // Macro
18 macro grad(u) [dx(u), dy(u)] //
19
20 // Problem
21 varf vPoisson1 (u, v)
22 = int2d(Th1)(
23 grad(u)' * grad(v)
24 )
25 - int2d(Th1)(
26 f1 * v
27 )
28 + on(1, 2, 3, 4, u=0)
29 ;
30
46 //block matrix
47 matrix<real> G = [[Poisson1, 0], [0, Poisson2]];
48 set(G, solver=sparsesolver);
49
53 // Solve
54 real[int] sol = G^-1 * Gb;
55
56 // Dispatch
57 [u1[], u2[]] = sol;
58
59 // Plot
60 plot(u1, u2);
1 // Mesh
2 mesh Th = square(2, 1);
3
4 // Fespace
5 fespace Vh(Th, P1);
6 Vh f, g;
7 f = x*y;
8 g = sin(pi*x);
9
14 // Problem
15 varf mat (u, v)
16 = int2d(Th)(
17 1*dx(u)*dx(v)
18 + 2*dx(u)*dy(v)
19 + 3*dy(u)*dx(v)
20 + 4*dy(u)*dy(v)
21 )
22 + on(1, 2, 3, 4, u=1)
23 ;
24
38 // Operations
39 Vh m0; m0[] = A*f[];
40 Vh m01; m01[] = A'*f[];
41 Vh m1; m1[] = f[].*g[];
42 Vh m2; m2[] = f[]./g[];
43
44 // Display
45 cout << "f = " << f[] << endl;
46 cout << "g = " << g[] << endl;
47 cout << "A = " << A << endl;
48 cout << "m0 = " << m0[] << endl;
49 cout << "m01 = " << m01[] << endl;
50 cout << "m1 = "<< m1[] << endl;
51 cout << "m2 = "<< m2[] << endl;
52 cout << "dot Product = "<< f[]'*g[] << endl;
53 cout << "hermitien Product = "<< ff[]'*gg[] << endl;
54 cout << "outer Product = "<< (A=f[]*g[]') << endl;
55 cout << "hermitien outer Product = "<< (AA=ff[]*gg[]') << endl;
56
57 // Diagonal
58 real[int] diagofA(A.n);
59 diagofA = A.diag; //get the diagonal of the matrix
60 A.diag = diagofA ; //set the diagonal of the matrix
61
66 [I, J, C] = A; //get the sparse term of the matrix A (the array are resized)
67 cout << "I = " << I << endl;
68 cout << "J = " << J << endl;
69 cout << "C = " << C << endl;
70
1 f = 6
2 0 0 0 0 0.5
3 1
(continues on next page)
36 m0 = 6
37 -1.25 -2.25 0.5 0 5e+29
38 1e+30
39 m01 = 6
40 -1.25 -2.25 0 0.25 5e+29
41 1e+30
42 m1 = 6
43 0 0 0 0 0.5
44 1.224646799e-16
45 m2 = 6
46 -nan 0 0 -nan 0.5
47 8.165619677e+15
48 dot Product = 0.5
49 hermitien Product = (1.11022e-16,2.5)
50 outer Product = # Sparse Matrix (Morse)
51 # first line: n m (is symmetic) nbcoef
52 # after for each nonzero coefficient: i j a_ij where (i,j) \in {1,...,n}x{1,...,m}
53 6 6 0 8
54 5 2 0.5
55 5 3 6.1232339957367660359e-17
(continues on next page)
92 I = 8
93 4 4 4 4 5
94 5 5 5
95 J = 8
96 1 2 4 5 1
97 2 4 5
98 C = 8
99 0.5 6.123233996e-17 0.5 6.123233996e-17 1
100 1.224646799e-16 1 1.224646799e-16
101 -- Raw Matrix nxm =6x6 nb none zero coef. 8
102 -- Raw Matrix nxm =6x6 nb none zero coef. 6
103 D = # Sparse Matrix (Morse)
104 # first line: n m (is symmetic) nbcoef
105 # after for each nonzero coefficient: i j a_ij where (i,j) \in {1,...,n}x{1,...,m}
106 6 6 1 6
107 1 1 0
(continues on next page)
Warning: Due to Fortran indices starting at one, the output of a diagonal matrix D is indexed from 1. but in
FreeFEM, the indices start from 0.
1 load "lapack"
2 load "fflapack"
3
4 // Matrix
5 int n = 5;
6 real[int, int] A(n, n), A1(n, n), B(n,n);
7 for (int i = 0; i < n; ++i)
8 for (int j = 0; j < n; ++j)
9 A(i, j) = (i == j) ? n+1 : 1;
10 cout << A << endl;
11
12 // Inversion (lapack)
13 A1 = A^-1; //def in "lapack"
14 cout << A1 << endl;
15
16 B = 0;
17 for (int i = 0; i < n; ++i)
18 for (int j = 0; j < n; ++j)
19 for (int k = 0; k < n; ++k)
20 B(i, j) += A(i,k)*A1(k,j);
21 cout << B << endl;
22
23 // Inversion (fflapack)
24 inv(A1); //def in "fflapack"
25 cout << A1 << endl;
1 5 5
2 6 1 1 1 1
3 1 6 1 1 1
4 1 1 6 1 1
5 1 1 1 6 1
6 1 1 1 1 6
7
8 5 5
9 0.18 -0.02 -0.02 -0.02 -0.02
10 -0.02 0.18 -0.02 -0.02 -0.02
(continues on next page)
15 5 5
16 1 1.040834086e-17 1.040834086e-17 1.734723476e-17 2.775557562e-17
17 3.469446952e-18 1 -1.734723476e-17 1.734723476e-17 2.775557562e-17
18 2.428612866e-17 -3.122502257e-17 1 1.734723476e-17 2.775557562e-17
19 2.081668171e-17 -6.938893904e-17 -3.469446952e-17 1 0
20 2.775557562e-17 -4.163336342e-17 -2.775557562e-17 0 1
21
22 5 5
23 6 1 1 1 1
24 1 6 1 1 1
25 1 1 6 1 1
26 1 1 1 6 1
27 1 1 1 1 6
Tip: To compile lapack.cpp and fflapack.cpp, you must have the lapack library on your system and compile
the plugin with the command:
6.7.9 FE array
1 // Mesh
2 mesh Th = square(20, 20, [2*x, 2*y]);
3
4 // Fespace
5 fespace Vh(Th, P1);
6 Vh u, v, f;
7
8 // Problem
9 problem Poisson (u, v)
10 = int2d(Th)(
11 dx(u)*dx(v)
12 + dy(u)*dy(v)
13 )
14 + int2d(Th)(
15 - f*v
16 )
17 + on(1, 2, 3, 4, u=0)
18 ;
19
34 // Plot
35 for (int i = 0; i < 3; i++)
36 plot(uu[i], wait=true);
6.7.10 Loop
16 matrix A = a;
17 string[string] ss; //a map
18 ss["1"] = 1;
19 ss["2"] = 2;
20 ss["3"] = 5;
21 for [i, bi : ss]
22 bi = i + 6 + "-dddd";
23 cout << "ss = " << ss << endl;
24
25 int[string] si;
26 si[1] = 2;
27 si[50] = 1;
28 for [i, vi : si]{
29 cout << " i " << setw(3) << i << " " << setw(10) << vi << endl;
30 vi = atoi(i)*2;
31 }
(continues on next page)
1 0 1
2 1 2
3 2 3
4 3 4
5 4 5
6 5 6
7 6 7
8 7 8
9 8 9
10 9 10
11 b = 10
12 1 2 3 4 5
13 6 7 8 9 10
14
15 a = 10 10
16 0.5 0.3333333333 0.25 0.2 0 0 0 0 0 0
17 0.3333333333 0.25 0.2 0 0 0 0 0 0 0
18 0.25 0.2 0 0 0 0 0 0 0 0
19 0.2 0 0 0 0 0 0 0 0 0
20 0 0 0 0 0 0 0 0 0 0
21 0 0 0 0 0 0 0 0 0 0
22 0 0 0 0 0 0 0 0 0 0
23 0 0 0 0 0 0 0 0 0 0
24 0 0 0 0 0 0 0 0 0 0
25 0 0 0 0 0 0 0 0 0 0
26
27 ss = 1 1
28 2 2
29 3 5
30
31 i 1 2
32 i 50 1
33 si = 1 2
34 50 100
35
36 0 0 0.5
37 0 1 0.333333
38 0 2 0.25
39 0 3 0.2
40 1 0 0.333333
41 1 1 0.25
42 1 2 0.2
(continues on next page)
6.7.12 I/O
1 int i;
2 cout << "std-out" << endl;
3 cout << " enter i = ?";
4 cin >> i;
5
6 {
7 ofstream f("toto.txt");
8 f << i << "hello world'\n";
9 } //close the file f because the variable f is delete
10
11 {
12 ifstream f("toto.txt");
13 f >> i;
14 }
15
16 {
17 ofstream f("toto.txt", append);
18 //to append to the existing file "toto.txt"
19 f << i << "hello world'\n";
20 } //close the file f because the variable f is delete
21
1 int where;
2 real[int] f = [0, 1, 2, 3, 4, 5];
3 real[int] g(6);
4
5 {
6 ofstream file("f.txt", binary);
7 file.precision(16);
8 file << f << endl;
9 where = file.tellp();
10 file << 0.1 ;
11
37 {
38 real xx;
39 ifstream file("f.txt", binary);
40 cout << "Where " << file.seekg << endl;
41 file.seekg(where);
42 file >> xx;
43 cout << " xx = " << xx << " good ? " << file.good() << endl;
44 assert(xx == 0.1);
45 skipcomment(file) >> xx;
46 assert(xx == 0.2);
47 file.seekg(0); //rewind
48 cout << "Where " << file.tellg() << " " << file.good() << endl;
49 file >> g;
50 }
1 include "getARGV.idp"
2
6.7.15 Macro
8 xxx
9
13 toto({real i = 0; int j = 0; cout << i << " " << j << endl;})
14
30 // Macro concatenation
31 mesh Th = square(2, 2);
32 fespace Vh(Th, P1);
33 Vh Ux=x, Uy=y;
34
44 //NewMacro - EndMacro
45 NewMacro grad(u) [dx(u), dy(u)] EndMacro
46 cout << int2d(Th)(grad(Ux)' * grad(Uy)) << endl;
47
48 // IFMACRO - ENDIFMACRO
49 macro AA CAS1 //
50
51 IFMACRO(AA,CAS1 )
52 cout << "AA = " << Stringification(AA) << endl;
53 macro CASE file1.edp//
54 ENDIFMACRO
55 IFMACRO(AA, CAS2)
56 macro CASE file2.edp//
57 ENDIFMACRO
58
61 IFMACRO(CASE)
62 include Stringification(CASE)
63 ENDIFMACRO
64
65 // FILE - LINE
66 cout << "In " << FILE << ", line " << LINE << endl;
1 AA = CAS1
2 CASE = file1.edp
3 This is the file 1
4 In Macro.edp, line 59
1 real a;
2 try{
3 a = 1./0.;
4 }
5 catch (...) //all exceptions can be caught
6 {
7 cout << "Catch an ExecError" << endl;
8 a = 0.;
9 }
1 1/0 : d d d
2 current line = 3
3 Exec error : Div by 0
(continues on next page)
1 // Parameters
2 int nn = 5;
3 func f = 1; //right hand side function
4 func g = 0; //boundary condition function
5
6 // Mesh
7 mesh Th = square(nn, nn);
8
9 // Fespace
10 fespace Vh(Th, P1);
11 Vh uh, vh;
12
13 // Problem
14 real cpu = clock();
15 problem laplace (uh, vh, solver=Cholesky, tolpivot=1e-6)
16 = int2d(Th)(
17 dx(uh)*dx(vh)
18 + dy(uh)*dy(vh)
19 )
20 + int2d(Th)(
21 - f*vh
22 )
23 ;
24
25 try{
26 cout << "Try Cholesky" << endl;
27
28 // Solve
29 laplace;
30
31 // Plot
32 plot(uh);
33
34 // Display
35 cout << "laplacian Cholesky " << nn << ", x_" << nn << " : " << -cpu+clock() << " s,␣
˓→max = " << uh[].max << endl;
36 }
37 catch(...) { //catch all error
38 cout << " Catch cholesky PB " << endl;
39 }
1 Try Cholesky
2 ERREUR choleskypivot (35)= -6.43929e-15 < 1e-06
(continues on next page)
727
FreeFEM Documentation, Release 4.8
[HORGAN2004] HORGAN, Cornelius O. and SACCOMANDI, Giuseppe. Constitutive models for compressible non-
linearly elastic materials with limiting chain extensibility. Journal of Elasticity, 2004, vol. 77, no 2,
p. 123-138.
[LEHOUCQ1998] LEHOUCQ, Richard B., SORENSEN, Danny C., and YANG, Chao. ARPACK users’ guide: solu-
tion of large-scale eigenvalue problems with implicitly restarted Arnoldi methods. Siam, 1998.
[NECAS2017] NECAS, Jindrich and HLAVÁCEK, Ivan. Mathematical theory of elastic and elasto-plastic bodies: an
introduction. Elsevier, 2017.
[OHTSUKA2000] OHTSUKA, K. Theoretical and Numerical analysis of energy release rate in 2D fracture. INFOR-
MATION, 2000, vol. 3, p. 303-315.
[TABATA1994] TABATA, M. Numerical solutions of partial differential equations II. Iwanami Applied Math, 1994.
[LUCQUIN1998] PIRONNEAU, O. and LUCQUIN-DESREUX, B. Introduction to scientific computing. Wiley, 1998.
[WILMOTT1995] WILMOTT, Paul, HOWISON, Sam and DEWYNNE, Jeff. A student introduction to mathematical
finance. 1995.
[ACHDOU2005] ACHDOU, Yves and PIRONNEAU, Olivier. Computational methods for option pricing. Siam, 2005.
[TEMAM1977] TEMAM, Roger. Navier-Stokes equations: theory and numerical analysis. 1977.
[ROBERTS1993] ROBERTS, J. E. and THOMAS, J. M. Mixed and Hybrid Methods, Handbook of Numerical Ana-
ysis, Vol. II. North-Holland, 1993, vol. 183, p. 184.
[GLOWINSKI1979] GLOWINSKI, R. and PIRONNEAU, O. On numerical methods for the Stokes problem. In:
Energy methods in finite element analysis.(A79-53076 24-39) Chichester, Sussex, England, Wiley-
Interscience, 1979, p. 243-264., 1979, p. 243-264.
[GLOWINSKI1985] GLOWINSKI, Roland and ODEN, J. Tinsley. Numerical methods for nonlinear variational prob-
lems. Journal of Applied Mechanics, 1985, vol. 52, p. 739.
[GLOWINSKI2003] GLOWINSKI, Roland. Finite element methods for incompressible viscous flow. Handbook of
numerical analysis, 2003, vol. 9, p. 3-1176.
[ITO2003] ITO, Kazufumi and KUNISCH, Karl. Semi–smooth Newton methods for variational inequalities of the
first kind. ESAIM: Mathematical Modelling and Numerical Analysis, 2003, vol. 37, no 1, p. 41-62.
[HINTERMULLER2002] HINTERMÜLLER, Michael, ITO, Kazufumi, et KUNISCH, Karl. The primal-dual active
set strategy as a semismooth Newton method. SIAM Journal on Optimization, 2002, vol. 13, no 3, p.
865-888.
[OXBORROW2007] OXBORROW, Mark. Traceable 2-D finite-element simulation of the whispering-gallery modes
of axisymmetric electromagnetic resonators. IEEE Transactions on Microwave Theory and Techniques,
2007, vol. 55, no 6, p. 1209-1218.
[GRUDININ2012] GRUDININ, Ivan S. and YU, Nan. Finite-element modeling of coupled optical microdisk res-
onators for displacement sensing. JOSA B, 2012, vol. 29, no 11, p. 3010-3014.
[ERN2006] ERN, A. and GUERMOND, J. L. Discontinuous Galerkin methods for Friedrichs’ symmetric systems.
I. General theory. SIAM J. Numer. Anal.
[BERNADOU1980] BERNADOU, Michel, BOISSERIE, Jean-Marie and HASSAN, Kamal. Sur l’implémentation
des éléments finis de Hsieh-Clough-Tocher complet et réduit. 1980. Thèse de doctorat. INRIA.
[BERNARDI1985] BERNARDI, Christine and RAUGEL, Genevieve. Analysis of some finite elements for the Stokes
problem. Mathematics of Computation, 1985, p. 71-79.
[THOMASSET2012] THOMASSET, François. Implementation of finite element methods for Navier-Stokes equa-
tions. Springer Science & Business Media, 2012.
728 Bibliography
FreeFEM Documentation, Release 4.8
[CROUZEIX1984] CROUZEIX, Michel and MIGNOT, Alain L. Analyse numérique des équations différentielles.
Masson, 1984.
[TAYLOR2005] TAYLOR, Mark A., WINGATE, Beth A. and BOS, Len P. Several new quadrature formulas for
polynomial integration in the triangle. arXiv preprint math/0501496, 2005.
[CHOW1997] CHOW, Edmond and SAAD, Yousef. Parallel Approximate Inverse Preconditioners. In : PPSC. 1997.
Bibliography 729
INDEX
mesh, 109
meshL, 109
730