LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
nested_cylinders.cc
1
6#define _USE_MATH_DEFINES
7#include <annulus_triag_mesh_builder.h>
8#include <build_system_matrix.h>
9#include <lf/assemble/dofhandler.h>
10#include <lf/io/gmsh_reader.h>
11#include <lf/io/vtk_writer.h>
12#include <lf/mesh/entity.h>
13#include <lf/mesh/hybrid2d/mesh_factory.h>
14#include <lf/mesh/utils/utils.h>
15#include <lf/quad/quad.h>
16#include <mesh_function_velocity.h>
17#include <norms.h>
18#include <piecewise_const_element_matrix_provider.h>
19#include <piecewise_const_element_vector_provider.h>
20#include <solution_to_mesh_data_set.h>
21
22#include <algorithm>
23#include <boost/program_options.hpp>
24#include <cstring>
25#include <filesystem>
26#include <iomanip>
27#include <iostream>
28#include <string>
29
30using lf::uscalfe::operator-;
31
44Eigen::VectorXd solveNestedCylindersZeroBC(
45 const std::shared_ptr<const lf::mesh::Mesh> &mesh,
46 const lf::assemble::DofHandler &dofh, double r, double R, double omega1,
47 double omega2, bool modified_penalty) {
48 // The volume forces are equal to zero everywhere
49 auto f = [](const Eigen::Vector2d & /*unused*/) {
50 return Eigen::Vector2d::Zero();
51 };
52 // Drive the inner and outer cylinder with omega1 and omega2
53 const double eps = 1e-10;
54 auto dirichlet_funct = [&](const lf::mesh::Entity &edge) -> Eigen::Vector2d {
55 const auto *const geom = edge.Geometry();
56 const auto vertices = geom->Global(edge.RefEl().NodeCoords());
57 if (vertices.col(0).norm() <= R + eps &&
58 vertices.col(0).norm() >= R - eps &&
59 vertices.col(1).norm() <= R + eps &&
60 vertices.col(1).norm() >= R - eps) {
61 return omega2 * R * (vertices.col(1) - vertices.col(0)).normalized();
62 }
63 if (vertices.col(0).norm() <= r + eps &&
64 vertices.col(0).norm() >= r - eps &&
65 vertices.col(1).norm() <= r + eps &&
66 vertices.col(1).norm() >= r - eps) {
67 return omega1 * r * (vertices.col(0) - vertices.col(1)).normalized();
68 }
69 return Eigen::Vector2d::Zero();
70 };
71
73 for (const auto *ep : mesh->Entities(1)) {
74 dirichlet(*ep) = dirichlet_funct(*ep);
75 }
76
77 // Asemble the LSE
78 const auto boundary = lf::mesh::utils::flagEntitiesOnBoundary(mesh);
79 // Assemble the Matrix
82 elem_mat_provider(100, boundary, modified_penalty);
83 lf::assemble::AssembleMatrixLocally(0, dofh, dofh, elem_mat_provider, A);
84 // Assemble the right hand side
85 Eigen::VectorXd rhs = Eigen::VectorXd::Zero(dofh.NumDofs());
87 elem_vec_provider(100, f, lf::quad::make_TriaQR_MidpointRule(), boundary,
88 dirichlet);
89 lf::assemble::AssembleVectorLocally(0, dofh, elem_vec_provider, rhs);
90
91 // Enforce the no-flow boundary conditions
92 auto selector = [&](lf::base::size_type idx) -> std::pair<bool, double> {
93 const auto &entity = dofh.Entity(idx);
94 if (entity.RefEl() == lf::base::RefElType::kPoint && boundary(entity)) {
95 return {true, 0};
96 }
97 return {false, 0};
98 };
100
101 // Solve the LSE using sparse LU
102 Eigen::SparseMatrix<double> As = A.makeSparse();
103 Eigen::SparseLU<Eigen::SparseMatrix<double>> solver;
104 solver.compute(As);
105 return solver.solve(rhs);
106}
107
121Eigen::VectorXd solveNestedCylindersNonzeroBC(
122 const std::shared_ptr<const lf::mesh::Mesh> &mesh,
123 const lf::assemble::DofHandler &dofh, double r, double R, double omega1,
124 double omega2, bool modified_penalty) {
125 // The volume forces are equal to zero everywhere
126 auto f = [](const Eigen::Vector2d & /*unused*/) {
127 return Eigen::Vector2d::Zero();
128 };
129 // Drive the inner and outer cylinder with omega1 and omega2
130 const double eps = 1e-10;
131 auto dirichlet_funct = [&](const lf::mesh::Entity &edge) -> Eigen::Vector2d {
132 const auto *const geom = edge.Geometry();
133 const auto vertices = geom->Global(edge.RefEl().NodeCoords());
134 if (vertices.col(0).norm() <= R + eps &&
135 vertices.col(0).norm() >= R - eps &&
136 vertices.col(1).norm() <= R + eps &&
137 vertices.col(1).norm() >= R - eps) {
138 return omega2 * R * (vertices.col(1) - vertices.col(0)).normalized();
139 }
140 if (vertices.col(0).norm() <= r + eps &&
141 vertices.col(0).norm() >= r - eps &&
142 vertices.col(1).norm() <= r + eps &&
143 vertices.col(1).norm() >= r - eps) {
144 return omega1 * r * (vertices.col(0) - vertices.col(1)).normalized();
145 }
146 return Eigen::Vector2d::Zero();
147 };
148
150 for (const auto *ep : mesh->Entities(1)) {
151 dirichlet(*ep) = dirichlet_funct(*ep);
152 }
153
154 // Asemble the LSE
155 const auto boundary = lf::mesh::utils::flagEntitiesOnBoundary(mesh);
156 // Assemble the Matrix
159 elem_mat_provider(100, boundary, modified_penalty);
160 lf::assemble::AssembleMatrixLocally(0, dofh, dofh, elem_mat_provider, A);
161 // Assemble the right hand side
162 Eigen::VectorXd rhs = Eigen::VectorXd::Zero(dofh.NumDofs());
164 elem_vec_provider(100, f, lf::quad::make_TriaQR_MidpointRule(), boundary,
165 dirichlet);
166 lf::assemble::AssembleVectorLocally(0, dofh, elem_vec_provider, rhs);
167
168 // Combine the basis functions on the inside boundary to a single one
169 const lf::base::size_type M_outer = 0;
170 const lf::base::size_type M_inner = 1;
171 // Create a mapping of DOF indices to remove the afterwards unused DOFs
172 std::vector<lf::base::size_type> dofmap(dofh.NumDofs());
173 lf::base::size_type idx = 2;
174 for (lf::base::size_type dof = 0; dof < dofh.NumDofs(); ++dof) {
175 const auto &entity = dofh.Entity(dof);
176 const auto *const geom = entity.Geometry();
177 if (entity.RefEl() == lf::base::RefElType::kPoint && boundary(entity)) {
178 if (geom->Global(entity.RefEl().NodeCoords()).norm() > R - eps) {
179 dofmap[dof] = M_outer;
180 } else {
181 dofmap[dof] = M_inner;
182 }
183 } else {
184 dofmap[dof] = idx++;
185 }
186 }
187 // Apply this mapping to the triplets of the matrix
188 std::for_each(A.triplets().begin(), A.triplets().end(),
189 [&](Eigen::Triplet<double> &trip) {
190 trip = Eigen::Triplet<double>(
191 dofmap[trip.row()], dofmap[trip.col()], trip.value());
192 });
193 // Apply the mapping to the right hand side vector
194 Eigen::VectorXd rhs_mapped = Eigen::VectorXd::Zero(idx);
195 for (lf::base::size_type dof = 0; dof < dofh.NumDofs(); ++dof) {
196 rhs_mapped[dofmap[dof]] += rhs[dof];
197 }
198
199 // Set the potential on the outer boundary to zero
200 auto selector = [&](lf::base::size_type idx) -> std::pair<bool, double> {
201 return {idx == M_outer, 0};
202 };
204
205 // Solve the LSE using sparse LU
206 Eigen::SparseMatrix<double> As_mapped = A.makeSparse().block(0, 0, idx, idx);
207 Eigen::SparseLU<Eigen::SparseMatrix<double>> solver;
208 solver.compute(As_mapped);
209 const Eigen::VectorXd sol_mapped = solver.solve(rhs_mapped);
210
211 // Apply the inverse mapping to recovr the basis function coefficients for the
212 // original basis functions
213 Eigen::VectorXd sol = Eigen::VectorXd::Zero(dofh.NumDofs());
214 for (lf::base::size_type dof = 0; dof < dofh.NumDofs(); ++dof) {
215 sol[dof] = sol_mapped[dofmap[dof]];
216 }
217
218 return sol;
219}
220
227template <typename... Args>
228static std::string concat(Args &&... args) {
229 std::ostringstream ss;
230 (ss << ... << args);
231 return ss.str();
232}
233
247int main(int argc, char *argv[]) {
248 const double r = 0.25;
249 const double R = 1;
250 const double omega1 = 0;
251 const double omega2 = 1;
252
253 // Parse the command line options
254 std::string mesh_selection;
255 boost::program_options::options_description desc{"Options"};
256 desc.add_options()("help,h", "Help Screen")(
257 "type", boost::program_options::value<std::string>(&mesh_selection),
258 "Type of mesh to use. Either 'builder', 'files' or 'irregular'");
259 boost::program_options::positional_options_description pos_desc;
260 pos_desc.add("type", 1);
261 boost::program_options::command_line_parser parser{argc, argv};
262 parser.options(desc).positional(pos_desc).allow_unregistered();
263 boost::program_options::parsed_options po = parser.run();
264 boost::program_options::variables_map vm;
265 boost::program_options::store(po, vm);
266 boost::program_options::notify(vm);
267 if (vm.count("help") != 0U) {
268 std::cout << desc << std::endl;
269 }
270
271 std::vector<std::shared_ptr<lf::mesh::Mesh>> meshes;
272 if (mesh_selection == "files") {
273 // Read the mesh from the gmsh file
274 std::filesystem::path meshpath = __FILE__;
275 meshpath = meshpath.parent_path();
276 for (int i = 0; i <= 4; ++i) {
277 const auto meshfile = meshpath / concat("annulus", std::setw(2),
278 std::setfill('0'), i, ".msh");
279 std::unique_ptr<lf::mesh::MeshFactory> factory =
280 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(2);
281 lf::io::GmshReader reader(std::move(factory), meshfile.string());
282 meshes.push_back(reader.mesh());
283 }
284 } else if (mesh_selection == "builder") {
285 // Build a sequence of meshes
286 for (unsigned i = 0U; i < 8U; ++i) {
287 const unsigned nx = 4U << i;
288 const double dx = 2 * M_PI * (r + R) / 2 / nx;
289 const unsigned ny = std::max(static_cast<unsigned>((R - r) / dx), 1U);
290
291 // Build the mesh
292 std::unique_ptr<lf::mesh::MeshFactory> factory =
293 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(2);
295 std::move(factory));
296 builder.setInnerRadius(r);
297 builder.setOuterRadius(R);
298 builder.setNumAngularCells(nx);
299 builder.setNumRadialCells(ny);
300 meshes.push_back(builder.Build());
301 }
302 } else if (mesh_selection == "irregular") {
303 std::filesystem::path meshpath = __FILE__;
304 const auto mesh_irregular_path =
305 meshpath.parent_path() / "annulus_irregular.msh";
306 const auto mesh_irregular_inverted_path =
307 meshpath.parent_path() / "annulus_irregular_inverted.msh";
308 std::unique_ptr<lf::mesh::MeshFactory> factory_irregular =
309 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(2);
310 std::unique_ptr<lf::mesh::MeshFactory> factory_irregular_inverted =
311 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(2);
312 lf::io::GmshReader reader_irregular(std::move(factory_irregular),
313 mesh_irregular_path.string());
314 lf::io::GmshReader reader_irregular_inverted(
315 std::move(factory_irregular_inverted),
316 mesh_irregular_inverted_path.string());
317 meshes.push_back(reader_irregular.mesh());
318 meshes.push_back(reader_irregular_inverted.mesh());
319 } else {
320 std::cout << desc << std::endl;
321 exit(1);
322 }
323
324 // Compute the analytic solution of the problem
325 const double C1 = 2 * (omega1 * r * r - omega2 * R * R) / (r * r - R * R);
326 const double C2 = ((omega1 - omega2) * r * r * R * R) / (R * R - r * r);
327 auto analytic_velocity = [&](const Eigen::Vector2d &x) -> Eigen::Vector2d {
328 const double radius = x.norm();
329 Eigen::Vector2d vec;
330 vec << x[1], -x[0];
331 vec.normalize();
332 return -(0.5 * C1 * radius + C2 / radius) * vec;
333 };
334 auto analytic_gradient = [&](const Eigen::Vector2d &x) -> Eigen::Matrix2d {
335 const double r2 = x.squaredNorm();
336 Eigen::Matrix2d g;
337 g << 2 * C2 * x[0] * x[1] / r2 / r2,
338 -C1 / 2 - (C2 * r2 - 2 * C2 * x[1] * x[1]) / r2 / r2,
339 C1 / 2 + (C2 * r2 - 2 * C2 * x[0] * x[0]) / r2 / r2,
340 -2 * C2 * x[0] * x[1] / r2 / r2;
341 return g;
342 };
343
344 // Solve the problem on each mesh and compute the error
345 for (const auto &mesh : meshes) {
347 mesh,
349 const auto fe_space =
350 std::make_shared<lf::uscalfe::FeSpaceLagrangeO1<double>>(mesh);
351 const Eigen::VectorXd solution_zero =
352 solveNestedCylindersZeroBC(mesh, dofh, r, R, omega1, omega2, false);
353 const Eigen::VectorXd solution_nonzero =
354 solveNestedCylindersNonzeroBC(mesh, dofh, r, R, omega1, omega2, false);
355 const Eigen::VectorXd solution_zero_modified =
356 solveNestedCylindersZeroBC(mesh, dofh, r, R, omega1, omega2, true);
357 const Eigen::VectorXd solution_nonzero_modified =
358 solveNestedCylindersNonzeroBC(mesh, dofh, r, R, omega1, omega2, true);
359 // Create mesh functions for the analytic and numerical solutions
360 const auto velocity_exact =
361 lf::mesh::utils::MeshFunctionGlobal(analytic_velocity);
362 const auto grad_exact =
363 lf::mesh::utils::MeshFunctionGlobal(analytic_gradient);
364 const auto velocity_zero =
366 double>(
367 fe_space, solution_zero);
368 const auto velocity_nonzero =
370 double>(
371 fe_space, solution_nonzero);
372 const auto velocity_zero_modified =
374 double>(
375 fe_space, solution_zero_modified);
376 const auto velocity_nonzero_modified =
378 double>(
379 fe_space, solution_nonzero_modified);
380 // Store the solution
381 lf::io::VtkWriter writer_zero(
382 mesh, concat("result_zero_", dofh.NumDofs(), ".vtk"));
383 lf::io::VtkWriter writer_nonzero(
384 mesh, concat("result_nonzero_", dofh.NumDofs(), ".vtk"));
385 writer_zero.WriteCellData("velocity", velocity_zero);
386 writer_zero.WriteCellData("velocity_modified", velocity_zero_modified);
387 writer_nonzero.WriteCellData("velocity", velocity_nonzero);
388 writer_nonzero.WriteCellData("velocity_modified",
389 velocity_nonzero_modified);
390 writer_zero.WriteCellData(
391 "analytic", lf::mesh::utils::MeshFunctionGlobal(analytic_velocity));
392 writer_nonzero.WriteCellData(
393 "analytic", lf::mesh::utils::MeshFunctionGlobal(analytic_velocity));
394 // Compute the difference between the numerical and the analytical solution
395 auto diff_velocity_zero = velocity_zero - velocity_exact;
396 auto diff_velocity_zero_modified = velocity_zero_modified - velocity_exact;
397 auto diff_velocity_nonzero = velocity_nonzero - velocity_exact;
398 auto diff_velocity_nonzero_modified =
399 velocity_nonzero_modified - velocity_exact;
400 auto diff_gradient_zero = -grad_exact;
401 auto diff_gradient_zero_modified = -grad_exact;
402 auto diff_gradient_nonzero = -grad_exact;
403 auto diff_gradient_nonzero_modified = -grad_exact;
404 const auto qr_provider = [](const lf::mesh::Entity &e) {
405 return lf::quad::make_QuadRule(e.RefEl(), 0);
406 };
408 mesh, diff_velocity_zero, qr_provider);
409 const double L2_nonzero = projects::ipdg_stokes::post_processing::L2norm(
410 mesh, diff_velocity_nonzero, qr_provider);
411 ;
413 mesh, diff_velocity_zero, diff_gradient_zero, qr_provider);
414 const double DG_nonzero = projects::ipdg_stokes::post_processing::DGnorm(
415 mesh, diff_velocity_nonzero, diff_gradient_nonzero, qr_provider);
416 const double L2_zero_modified =
418 mesh, diff_velocity_zero_modified, qr_provider);
419 const double L2_nonzero_modified =
421 mesh, diff_velocity_nonzero_modified, qr_provider);
422 const double DG_zero_modified =
424 mesh, diff_velocity_zero_modified, diff_gradient_zero_modified,
425 qr_provider);
426 const double DG_nonzero_modified =
428 mesh, diff_velocity_nonzero_modified,
429 diff_gradient_nonzero_modified, qr_provider);
430 std::cout << mesh->NumEntities(2) << ' ' << L2_zero << ' ' << DG_zero << ' '
431 << L2_nonzero << ' ' << DG_nonzero << ' ' << L2_zero_modified
432 << ' ' << DG_zero_modified << ' ' << L2_nonzero_modified << ' '
433 << DG_nonzero_modified << std::endl;
434 }
435
436 return 0;
437}
A temporary data structure for matrix in COO format.
Definition: coomatrix.h:52
A general (interface) class for DOF handling, see Lecture Document Paragraph 2.7.4....
Definition: dofhandler.h:109
virtual const lf::mesh::Entity & Entity(gdof_idx_t dofnum) const =0
retrieve unique entity at which a basis function is located
virtual size_type NumDofs() const =0
total number of dof's handled by the object
Dofhandler for uniform finite element spaces.
Definition: dofhandler.h:257
static constexpr RefEl kSegment()
Returns the (1-dimensional) reference segment.
Definition: ref_el.h:150
static constexpr RefEl kPoint()
Returns the (0-dimensional) reference point.
Definition: ref_el.h:141
Reads a Gmsh *.msh file into a mesh::MeshFactory and provides a link between mesh::Entity objects and...
Definition: gmsh_reader.h:70
Write a mesh along with mesh data into a vtk file.
Definition: vtk_writer.h:272
Interface class representing a topological entity in a cellular complex
Definition: entity.h:39
virtual const geometry::Geometry * Geometry() const =0
Describes the geometry of this entity.
A MeshDataSet that attaches data of type T to every entity of a mesh that has a specified codimension...
MeshFunction wrapper for a simple function of physical coordinates.
A mesh builder for disks with a hole in the middle.
A MeshFunction returning the velocity computed from the basis function coefficients of a vector poten...
void AssembleMatrixLocally(dim_t codim, const DofHandler &dof_handler_trial, const DofHandler &dof_handler_test, ENTITY_MATRIX_PROVIDER &entity_matrix_provider, TMPMATRIX &matrix)
Assembly function for standard assembly of finite element matrices.
Definition: assembler.h:113
void AssembleVectorLocally(dim_t codim, const DofHandler &dof_handler, ENTITY_VECTOR_PROVIDER &entity_vector_provider, VECTOR &resultvector)
entity-local assembly of (right-hand-side) vectors from element vectors
Definition: assembler.h:297
unsigned int size_type
general type for variables related to size of arrays
Definition: base.h:24
QuadRule make_TriaQR_MidpointRule()
midpoint quadrature rule for triangles
void FixFlaggedSolutionComponents(SELECTOR &&selectvals, COOMatrix< SCALAR > &A, RHSVECTOR &b)
enforce prescribed solution components
Definition: fix_dof.h:87
@ kPoint
Returns the (0-dimensional) reference point.
CodimMeshDataSet< bool > flagEntitiesOnBoundary(const std::shared_ptr< const Mesh > &mesh_p, lf::base::dim_t codim)
flag entities of a specific co-dimension located on the boundary
QuadRule make_QuadRule(base::RefEl ref_el, unsigned degree)
Returns a QuadRule object for the given Reference Element and Degree.
double DGnorm(const std::shared_ptr< const lf::mesh::Mesh > &mesh, const MF_F &f, const MF_GRAD &f_grad, const QR_SELECTOR &qr_selector)
Compute the DG-norm of a vector valued function over a mesh.
Definition: norms.h:68
double L2norm(const std::shared_ptr< const lf::mesh::Mesh > &mesh, const MF &f, const QR_SELECTOR &qr_selector)
Compute the -norm of a vector valued function over a mesh.
Definition: norms.h:40