Consider the Poisson equation with homogeneous Dirichlet BC
The corresponding weak formulation is: Find
Let
We impose the boundary condition by setting
where
The corresponding discrete problem is: Find
Here,
Note that
But,
Let
Then we can rewrite the equation as: Find
Since
Now we obtained a set of equations
This can be rewritten as
where
For given finite element space
Using the basis representation, we obtain the corresponding linear system
where
FiniteElementCollection
(Par)Mesh
FiniteElementCollection
We use FiniteElementCollection
to represent a local finite element space.
Examples are: (
H1_FECollection
- Continuous DG_FECollection/L2_FECollection
- Discontinuous RT_FECollection
- ND_FECollection
- Code Example:
FiniteElementCollection *fec = new DG_FECollection(order, dim);
FiniteElementCollection *fec = new H1_FECollection(order, dim);
Mesh
class to represent meshes.Mesh
object can be created either from a fileMesh mesh("path/to/meshfile.mesh");
Mesh mesh = Mesh::MakeCartesian2D(nx, ny, Element::QUADRILATERAL); // Uniform 2D rectangular mesh
Mesh mesh = Mesh::MakeCartesian2D(nx, ny, Element::TRIANGLE);
mesh.UniformRefinement(); // Uniform refine
mesh.RefineByError(err, tol); // Refine where err > tol
mesh.Save("path/to/meshfile.mesh");
FiniteElementCollection
and Mesh
, we can define a finite element space FiniteElementSpace
class to store the informationconst int dim = 2;
const int order = 1;
const int nx = ny = 10;
FiniteElementCollection *fec = new H1_FECollection(order, dim);
Mesh mesh = Mesh::MakeCartesian2D(nx, ny, Element::QUADRILATERAL);
FiniteElementSpace fes(&mesh, fec); // P1-C0 Finite element space
FiniteElementCollection
, e.g., RT_FECollection
FiniteElementCollection
const int vdim = 2;
FiniteElementSpace vfes(&mesh, fec, vdim); // P1-C0 vector FE space
GridFunction u(&fes);
GridFunction
stores the primal vector, [c_1, c_2, ..., c_N]
associated with fes
).const int order = 2; // order of polynomial
const int nx = ny = 4; // number of elements in each axis
FiniteElementCollection *fec = new H1_FECollection(order, dim); // C0, Quadratic Polynomial
Mesh mesh = Mesh::MakeCartesian2D(nx, ny, Element::QUADRILATERAL); // Uniform quadrilateral mesh
const int dim = mesh.Dimension(); // spatial dimension, 2
// You can also define a vector FE space
const int vdim = 2; // vector dimension
FiniteElementSpace fespace(&mesh, fec, vdim); // [C0-Q2 space]^2
GridFunction u(&fespace);
BilinearForm
to represent such operatorBilinearForm a_h(&fespace);
MixedBilinearForm a_h(&fespace1, &fespace2);
a_h
.a_h.AddDomainIntegrator(new DiffusionIntegrator()); // a_h += (∇u, ∇v)
a_h.AddDomainIntegrator(new MassIntegrator()); // a_h += (u, v)
DiffusionIntegrator
and MassIntegrator
are BilinearFormIntegrator
fem/bilininteg.hpp
LinearForm
to represent such operatorLinearForm b_h(&fespace);
b_h
.b_h.AddDomainIntegrator(new DomainLFIntegrator(f)); // b_h += (f,v)
b_h.AddBoundaryIntegrator(new BoundaryLFIntegrator(g_N));// b_h += (g_N, v)_e
DomainLFIntegrator
and BoundaryLFIntegrator
are LinearFormIntegrator
fem/lininteg.hpp
R_T
, P_T
: Mapping between the global and local
R_T*A = A(J_T, J_T) = A_loc
B
, Q
: Mapping between basis and values at points.
B
can be function value, derivative value, etc.D
: Operation on quadrature values (dot products, weight, ...)
D(U, Ux, Uy, V, Vx, Vy) = Ux*Vx + Uy*Vy
void MassIntegrator::AssembleElementMatrix(
const FiniteElement &el, ElementTransformation &Trans, DenseMatrix &elmat)
{
// INPUT el: Local finite element: basis, dof, ...
// INPUT Trans: Local mesh information. e.g., element location, Jacobian
// OUTPUT elmat: resulting (ϕ_j, ϕ_i) where 0 ≤ i,j ≤ nd is local dof
int nd = el.GetDof(); // the number of basis related to current element
double w; // store weight
Vector shape; // store basis function values at an integration point
shape.SetSize(nd);
elmat.SetSize(nd); // set output size
// Determine integration rule in the location
const IntegrationRule *ir = IntRule ? IntRule : &GetRule(el, el, Trans);
elmat = 0.0; // initialize with 0
for (int i = 0; i < ir->GetNPoints(); i++) // for each integration point
{
// Get current integration point (x_i, w_i)
const IntegrationPoint &ip = ir->IntPoint(i);
Trans.SetIntPoint (&ip);
//
el.CalcPhysShape(Trans, shape);
// update weight based on the element jacobian and integration weight
w = Trans.Weight() * ip.weight;
// elmat = elmat + w * (shape * shape^T)
// Why? DIY
AddMult_a_VVt(w, shape, elmat);
}
} // Also see, DiffusionIntegrator, MixedVectorDivergenceOperator, ...
a_h.SetAssemblyLevel(AssemblyLevel::<LEVEL>)
FULL
(or LEGACY
)
ELEMENT
A_loc
. Ax
can be computed element-wisely,PARTIAL
NONE
f
when we define local linear formLinearForm b_h(&fespace);
b_h.AddDomainIntegrator(new DomainLFIntegrator(f)); // b_h += (f,v)
f
is a Coefficient
. More specifically, FunctionCoefficient
.Coefficient
represents a functionConstantCoefficient one_cf(1.0);
FunctionCoefficient f([](Vector &x){return sin(pi*x[0])*sin(pi*x[1]);});
FunctionCoefficient g([](Vector &x, double t){return exp(-t)*sin(pi*x[0]);});
GridFunctionCoefficient u_cf(&u_h); // Use GridFunction as a function
u_h.ProjectCoefficient(f); // interpolate given function f
b_h.AddDomainIntegrator(new DomainLFIntegrator(f)); // b_h += (f,v)
MFEM
.# Mesh faces/edges on the boundary, e.g. triangles (2)
boundary
8 # 8 boundaries
1 1 0 1 # Bottom (1), Segment, vertex 0 - vertex 1
1 1 1 2 # Borrom (1), Segment, vertex 1 - vertex 2
2 1 2 4 # Right (2), Segment, vertex 2 - vertex 4
2 1 4 8 # Right (2), Segment, vertex 4 - vertex 8
...
Mesh mesh(mesh_file);
// create an integer array of size, the number of boundary attributes
Array<int> ess_bdr(mesh.bdr_attributes.Max()); // in this case, 4
ess_bdr = 0; // initialize with 0
ess_bdr[0] = 1; // first boundary, bottom, is essential boundary
// stores all degrees of freedom on the Dirichlet Boundary
Array<int> ess_tdof_list;
fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); // Obtain related dofs
BilinearForm a_h
and LinarForm b_h
, we canOperatorPtr A;
Vector B, X;
a_h.FormLinearSystem(ess_tdof_list, u, b_h, A, X, B);
u
: GridFunction
, the solution with specified value on essential boundaryA
: OperatorPtr
or SparseMatrix
, the resulting matrix B
: Vector
, the resulting load vector X
: Vector
, the vector containing coefficient, MFEM
.UMFPackSolver
: General matrix from SuiteSparse
CG
: (Preconditioned) Conjugate gradient method for SPD systemCGSolver cg();
cg.SetOperator(*A);
cg.Mult(B, X);
GMRES
: (Preconditioned) generalized minimal residual method for general systemGMRESSolver gmres();
gmres.SetOperator(*A);
gmres.Mult(B, X);
Vector X
.GridFunction
by usinga_h.RecoverFEMSolution(X, b, u);
u.Save(solution.gf);
sout << "solution\n" << mesh << u; // plot solution
sout << "view 0 0\n"; // top view
sout << flush; // draw
ex1.cpp
and glvis
.glvis
in the background!ParaView
.make parallel
- Requires additional dependencies, see instruction.Par
before types. See, ex0p.cpp.Mpi::Init(); // Initialize MPI
H1_FECollection fec(order, dim);
ParMesh mesh(mesh_file); // Elements are distributed in each process
ParFiniteElementSpace fes(&mesh, &fec);
ParBilinearForm a_h(&fes);
ParLinearForm b_h(&fes);
ParGridFunction u_h(&fes);
a_h.AddDomainIntegrator(new DiffusionIntegrator());
ConstantCoefficient one_cf(1.0);
b_h.AddDomainIntegrator(new DomainLFIntegrator(one_cf));
...
mpirun -np <num_process> <file>
patchelf
brew install patchelf
git clone git@github.com:mfem/pymfem.git
cd pymfem
git checkout rpath-patch
python setup.py install
PyMFEM
needs swig
brew install swig
python -m pip install swig numpy scipy cmake
python setup.py install
PyMFEM
with parallelbrew install open-mpi
brew install swig
python -m pip install mpi4py swig numpy scipy cmake
python setup.py install --with-parallel
# FunctionCoefficient * On the other hand, `FunctionCoefficient` represents a function $\mathbb{R}^n\rightarrow\mathbb{R}$. ```cpp // 2D coordinates x=(x,y) ↦ sin(x)*sin(y) FunctionCoefficient f([](const Vector& x){return sin(x[0])*sin(x[1])}); ``` * For a vector-valued function, we use `VectorFunctionCoefficient` ```cpp // 2D coordinates x=(x,y) ↦ (sin(x), sin(y)) VectorFunctionCoefficient f(2, [](const Vector &x, Vector y){y[0] = sin(x[0]); y[1] = sin(x[1])}); ``` * When you want to compute its value at `x`, then use ```cpp Vector x(2); x = 0.5; // equivalent to x[0] = x[1] = 0.5; const double value = f.Eval(x); // if f:R^n → R Vector y(2); f.Eval(x, y); // if f:R^n → R^d ``` ---
# FormIntegrator * Now you have a bilinear form $a_h(\cdot,\cdot)$, `BilinearForm a` * But you did not specify its actual implementation $$ a_h(u, v) = \sum_{T\in\mathcal{T}_h}(∇ u, ∇ v)_T $$ * The local bilinear form $(∇ u, ∇ v)_T$ is called `BilinearFormIntegrator`. * This class should contains: * `AssembleElementMatrix`: if it is an element integrator, $(\cdot,\cdot)_T$ * `AssembleFaceMatrix`: if it is a face integrator, $(\cdot,\cdot)_F$ * ... see, <a href="https://docs.mfem.org/html/bilininteg_8hpp_source.html" target="_blank">`fem/bilininteg.hpp`</a> * Examples are: * `MassIntegrator`: $(u, v)_T$ * `DiffusionIntegrator`: $(∇ u, ∇ v)_T$ * `MixedVectorDivergenceIntegrator`: $(∇ ⋅ u, v)_T$ * `TransposeIntegrator`: convert $(f(u), g(v))$ to $(f(v), g(u))$ --- # FormIntegrator - continued * You can add `BilinearFormIntegrator` to `BilinearForm` ```cpp BilinearForm a_h(&fespace); a_h.AddDomainIntegrator(new DiffusionIntegrator); // a_h = ∑_T(∇ u, ∇ v)_T a_h.AddDomainIntegrator(new MassIntegrator); // a_h = ∑_T [ (∇ u, ∇ v)_T + (u, v)_T ] a_h.AddInteriorFaceIntegrator(new SomeFaceIntegrator); // a_h = ... + ∑_{F_i}(⋅⋅⋅, ⋅⋅⋅)_F a_h.AddBoundaryIntegrator(new SomeFaceIntegrator); // a_h = ... + ∑_{F_b}(⋅⋅⋅, ⋅⋅⋅)_F ``` * For a `LinearForm`, you can use `LinearFormIntegrator` * The most useful one is `DomainLFIntegrator` and `BoundaryLFIntegrator` ```cpp LinearForm b_h(&fespace); b_h.AddDomainIntegrator(new DomainLFIntegrator(f)); // b_h = ∑_T (f, v) b_h.AddBoundaryIntegrator(new BoundaryLFIntegrator(g, nbc_bdr)); // b_h = ... + ∑_{F_{b,N}}(g_N, v)_F ``` ---