Skip to content

Commit cd646aa

Browse files
committed
SUPG, stokes tutorials. old tutorials adapted to current core API
1 parent c78ad3d commit cd646aa

12 files changed

+338
-91
lines changed

index.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Welcome to the fdaPDE 2.0's alpha-testing!
44
.. toctree::
55
:maxdepth: 2
66

7-
news
87
tutorials/tut_1
98
tutorials/tut_2
10-
tutorials/tut_3
9+
tutorials/tut_5
10+
tutorials/tut_4

news.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ Introduction of several core API changes, which significantly alter the usage of
203203
0x0000000000001139 <+41>: cvttsd2si %xmm0,%eax // return computed sum
204204
0x000000000000113d <+45>: ret
205205
End of assembler dump.
206-
206+
207207
While fdaPDE was completely able to avoid any for loop at run-time (as it perfectly knows how to compute the for-loop at compile time), Eigen cannot, and must execute code (therefore, waste time) to produce a result. You will further notice the advantages of such data-types when involved in the much more involved math expressions.
208208

209209
Below a summary of the API exposed for constexpr dense linear algebra at the time of this update:

tutorials/[email protected]:1724767011

-5
This file was deleted.

tutorials/stokes_velocity.png

1.55 MB
Loading

tutorials/stokes_x_pressure.png

214 KB
Loading

tutorials/stokes_y_pressure.png

330 KB
Loading

tutorials/supg_no_stab_solution.png

535 KB
Loading

tutorials/supg_stab_solution.png

142 KB
Loading

tutorials/tut_1.rst

+31-28
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,22 @@ As always, fdaPDE requires the weak formulation of the differential problem abov
4646
4747
\int_{\Omega} \nabla u^{k+1} \cdot \nabla v + \alpha (1-u^k) u^{k+1} v - \int_{\Omega} \alpha u^k u^{k+1} v = \int_{\Omega} f - \alpha (u^k)^2 v \quad \forall v \in V_h
4848
49-
which we iteratively solve for :math:`u^{k+1}` until convergence. Follows a step by step description of the program which enables us to find a solution to the considered problem in less than 50 lines of code.
49+
which we iteratively solve for :math:`u^{k+1}` until convergence.
5050

51-
First we load the geometry (currently generated from some third party triangulator), enabling the cell caching. As we are going to iterate several times over the mesh is convinient to compute the cells once to obtain a fast re-access to the mesh.
51+
Implementation
52+
--------------
53+
54+
The first step in any finite element code is the definition of the problem geometry. As we are going to iterate several times over the mesh is convinient to compute the cells once and cache them to obtain a fast re-access to the mesh. This option is enabled by activating the cell caching.
5255

5356
.. code-block:: cpp
5457
55-
Triangulation<2, 2> unit_square = read_mesh<2, 2>("unit_square", cache_cells);
58+
Triangulation<2, 2> unit_square = Triangulation<2, 2>::UnitSquare(60, cache_cells);
5659
5760
Once we have a discretization of the domain :math:`\Omega`, we can instatiate a (linear) finite element space on it togheter with trial and test functions.
5861

5962
.. code-block:: cpp
6063
61-
FiniteElementSpace Vh(unit_square, P1); // P1 denotes the space of linear finite elements
64+
FeSpace Vh(unit_square, P1<1>); // piecewise linear continuous scalar finite elements
6265
TrialFunction u(Vh);
6366
TestFunction v(Vh);
6467
@@ -85,7 +88,7 @@ We then define the forcing term as a plain :code:`ScalarField` togheter with the
8588

8689
.. code-block:: cpp
8790
88-
ScalarField<2, decltype([](const SVector<2>& p) {
91+
ScalarField<2, decltype([](const PointT& p) {
8992
return -9*std::pow(p[0], 4) - 12*p[0]*p[0]*p[1]*p[1] + 3*p[0]*p[0] +
9093
2*p[1]*p[1] - 4*std::pow(p[1], 4) - 10;
9194
})> f;
@@ -95,7 +98,7 @@ Observe that we explicitly require an higher order quadrature specifying the 6 p
9598

9699
.. code-block:: cpp
97100
98-
ScalarField<2, decltype([](const SVector<2>& p) { return 3 * p[0] * p[0] + 2 * p[1] * p[1]; })> g;
101+
ScalarField<2, decltype([](const PointT& p) { return 3 * p[0] * p[0] + 2 * p[1] * p[1]; })> g;
99102
DofHandler<2, 2>& dof_handler = Vh.dof_handler();
100103
dof_handler.set_dirichlet_constraint(/* on = */ BoundaryAll, /* data = */ g);
101104
@@ -105,12 +108,12 @@ We can now find an initial point for the Newton scheme. To this end, we solve th
105108

106109
.. code-block:: cpp
107110
108-
u_prev = DVector<double>::Zero(Vh.n_dofs()); // initial guess u = 0
109-
SpMatrix<double> A = a.assemble();
110-
DVector<double> v_ = F.assemble();
111+
u_prev = Eigen::Matrix<double, Dynamic, 1>::Zero(Vh.n_dofs()); // initial guess u = 0
112+
Eigen::SparseMatrix<double> A = a.assemble();
113+
Eigen::Matrix<double, Dynamic, 1> v_ = F.assemble();
111114
dof_handler.enforce_constraints(A, v_);
112115
// linear system solve A*u_prev = v_ using Cholesky factorization
113-
Eigen::SimplicialLLT<SpMatrix<double>> lin_solver(A);
116+
Eigen::SimplicialLLT<Eigen::SparseMatrix<double>> lin_solver(A);
114117
u_prev = lin_solver.solve(v_);
115118
116119
The code fragment above effectivelly assemble the discretization matrix :code:`A` for the bilinear form :math:`\int_{\Omega} \nabla u^0 \cdot \nabla v + u^0 v` togheter with the discretizing vector :code:`v_` of the forcing functional :math:`F`. Then, it sets the Dirichlet conditions at the boundary via the :code:`enforce_constaints` method of the :code:`dof_handler` object. Finally, observing that the bilinear form is SPD, solves the FEM linear system using a Cholesky factorization and sets :math:`u^0` to the solution of this linear system.
@@ -124,9 +127,9 @@ We can finally start looping until convergence, iteratively solving the recurren
124127
.. code-block:: cpp
125128
126129
while (err > 1e-7) {
127-
SpMatrix<double> A1 = a.assemble();
128-
SpMatrix<double> A2 = b.assemble();
129-
DVector<double> v = v_ + A2 * u_prev.coeff();
130+
Eigen::SparseMatrix<double> A1 = a.assemble();
131+
Eigen::SparseMatrix<double> A2 = b.assemble();
132+
Eigen::Matrix<double, Dynamic, 1> v = v_ + A2 * u_prev.coeff();
130133
A = A1 + A2;
131134
dof_handler.enforce_constraints(A, v);
132135
lin_solver.compute(A);
@@ -144,17 +147,17 @@ The code just assembles :code:`A1` and :code:`A2`, updates the right hand side :
144147
.. code-block:: cpp
145148
:linenos:
146149
147-
#include <fdaPDE/fields.h>
148-
#include <fdaPDE/geometry.h>
149150
#include <fdaPDE/finite_elements.h>
150-
151151
using namespace fdapde;
152152
153153
int main() {
154-
// import mesh, enable cell caching for fast re-cycling
155-
Triangulation<2, 2> unit_square = read_mesh<2, 2>("unit_square", cache_cells);
154+
// useful typedef and constants definition
155+
constexpr int local_dim = 2;
156+
using PointT = Eigen::Matrix<double, local_dim, 1>;
157+
158+
Triangulation<local_dim, local_dim> unit_square = Triangulation<2, 2>::UnitSquare(60, cache_cells);
156159

157-
FiniteElementSpace Vh(unit_square, P1);
160+
FeSpace Vh(unit_square, P1<1>);
158161
// create trial and test functions
159162
TrialFunction u(Vh);
160163
TestFunction v(Vh);
@@ -166,30 +169,30 @@ The code just assembles :code:`A1` and :code:`A2`, updates the right hand side :
166169
auto b = integral(unit_square)(-u_prev * u * v);
167170

168171
// define forcing functional
169-
ScalarField<2, decltype([](const SVector<2>& p) {
172+
ScalarField<2, decltype([](const PointT& p) {
170173
return -9*std::pow(p[0], 4) - 12*p[0]*p[0]*p[1]*p[1] + 3*p[0]*p[0] + 2*p[1]*p[1] - 4*std::pow(p[1], 4) - 10;
171174
})> f;
172175
auto F = integral(unit_square, QS2D6P)(f * v);
173176

174177
// define dirichlet data
175-
ScalarField<2, decltype([](const SVector<2>& p) { return 3 * p[0] * p[0] + 2 * p[1] * p[1]; })> g;
178+
ScalarField<2, decltype([](const PointT& p) { return 3 * p[0] * p[0] + 2 * p[1] * p[1]; })> g;
176179
DofHandler<2, 2>& dof_handler = Vh.dof_handler();
177180
dof_handler.set_dirichlet_constraint(/* on = */ BoundaryAll, /* data = */ g);
178181
179182
// Newton scheme initialization (solve linearized problem with initial guess u = 0)
180-
u_prev = DVector<double>::Zero(Vh.n_dofs()); // initial guess u = 0
181-
SpMatrix<double> A = a.assemble(); // this actually assembles dot(grad(u), grad(v)) + u * v
182-
DVector<double> v_ = F.assemble();
183+
u_prev = Eigen::Matrix<double, Dynamic, 1>::Zero(Vh.n_dofs()); // initial guess u = 0
184+
Eigen::SparseMatrix<double> A = a.assemble(); // this actually assembles dot(grad(u), grad(v)) + u * v
185+
Eigen::Matrix<double, Dynamic, 1> v_ = F.assemble();
183186
dof_handler.enforce_constraints(A, v_);
184187
// linear system solve A*u_prev = v_ using Cholesky factorization
185-
Eigen::SimplicialLLT<SpMatrix<double>> lin_solver(A);
188+
Eigen::SimplicialLLT<Eigen::SparseMatrix<double>> lin_solver(A);
186189
u_prev = lin_solver.solve(v_);
187190

188191
double err = std::numeric_limits<double>::max();
189192
while (err > 1e-7) {
190-
SpMatrix<double> A1 = a.assemble();
191-
SpMatrix<double> A2 = b.assemble();
192-
DVector<double> v = v_ + A2 * u_prev.coeff(); // update rhs
193+
Eigen::SparseMatrix<double> A1 = a.assemble();
194+
Eigen::SparseMatrix<double> A2 = b.assemble();
195+
Eigen::Matrix<double, Dynamic, 1> v = v_ + A2 * u_prev.coeff(); // update rhs
193196
A = A1 + A2;
194197
dof_handler.enforce_constraints(A, v);
195198
lin_solver.compute(A);

0 commit comments

Comments
 (0)