DUBeat  1.0.1
High-order discontinuous Galerkin methods and applications to cardiac electrophysiology
DUB_FEM_handler.hpp
Go to the documentation of this file.
1 /********************************************************************************
2  Copyright (C) 2024 by the DUBeat authors.
3 
4  This file is part of DUBeat.
5 
6  DUBeat is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  DUBeat is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with DUBeat. If not, see <http://www.gnu.org/licenses/>.
18 ********************************************************************************/
19 
27 #ifndef DUBFEMHandler_HPP_
28 #define DUBFEMHandler_HPP_
29 
30 #include <deal.II/base/exceptions.h>
31 #include <deal.II/base/quadrature.h>
32 
33 #include <deal.II/dofs/dof_handler.h>
34 
35 #include <deal.II/fe/fe.h>
36 #include <deal.II/fe/mapping_q1_eulerian.h>
37 
38 #include <deal.II/lac/trilinos_vector.h>
39 
40 #include <cmath>
41 #include <filesystem>
42 #include <vector>
43 
44 #include "DUBValues.hpp"
45 #include "dof_handler_DG.hpp"
46 #include "source/geometry/mesh_handler.hpp"
47 #include "source/init.hpp"
48 #include "volume_handler_DG.hpp"
49 
59 template <class basis>
60 class DUBFEMHandler : public DUBValues<lifex::dim>
61 {
62 private:
65 
68  const unsigned int n_quad_points;
69 
70 public:
72  DUBFEMHandler<basis>(const unsigned int degree,
73  const DoFHandlerDG<basis> &dof_hand)
74  : DUBValues<lifex::dim>(degree)
75  , dof_handler(dof_hand)
76  , n_quad_points(static_cast<int>(std::pow(degree + 2, lifex::dim)))
77  {}
78 
81 
84 
87 
92  lifex::LinAlg::MPI::Vector
93  dubiner_to_fem(const lifex::LinAlg::MPI::Vector &dub_solution) const;
94 
99  lifex::LinAlg::MPI::Vector
100  dubiner_to_fem(const lifex::LinAlg::MPI::Vector &dub_solution,
101  const std::string &FEM_mesh_path,
102  const std::string &subsection,
103  const MPI_Comm &mpi_comm_,
104  const unsigned int degree_fem = 1,
105  const double scaling_factor = 1) const;
106 
109  lifex::LinAlg::MPI::Vector
110  fem_to_dubiner(const lifex::LinAlg::MPI::Vector &fem_solution) const;
111 
113  lifex::LinAlg::MPI::Vector
114  fem_to_dubiner(const lifex::LinAlg::MPI::Vector &fem_solution,
115  const std::string &FEM_mesh_path,
116  const std::string &subsection,
117  const MPI_Comm &mpi_comm_,
118  const unsigned int degree_fem = 1,
119  const double scaling_factor = 1) const;
120 
122  lifex::LinAlg::MPI::Vector
124  lifex::LinAlg::MPI::Vector dub_solution,
125  const std::shared_ptr<dealii::Function<lifex::dim>> &u_analytical) const;
126 
128  bool
129  is_in_unit_cell(const dealii::Point<lifex::dim> &unit_q,
130  const double tol = 0) const;
131 };
132 
133 template <class basis>
134 lifex::LinAlg::MPI::Vector
136  const lifex::LinAlg::MPI::Vector &dub_solution) const
137 {
138  lifex::LinAlg::MPI::Vector fem_solution;
139  fem_solution.reinit(dub_solution);
140 
141 
142  // Due to the current deal.II availabilities, FE spaces can be of order at
143  // most 2, see dof_handler_DG.hpp description. If the Dubiner space is of
144  // higher order, this method generates a FEM vector of order 2, i.e. the
145  // maximum possible. Hence, due to the possibility that DUB space and FEM
146  // space have different orders, we need to specify two different degrees,
147  // dof_handler, dofs_per_cell...
148  const unsigned int degree_fem =
149  (this->poly_degree < 3) ? this->poly_degree : 2;
150 
151  const dealii::FE_SimplexDGP<lifex::dim> fe_dg(degree_fem);
152  const std::vector<dealii::Point<lifex::dim>> support_points =
153  fe_dg.get_unit_support_points();
154  const unsigned int dofs_per_cell_fem = this->get_dofs_per_cell(degree_fem);
155 
156  // Generation of a new dof_handler for the FE evaluations.
157  dealii::DoFHandler<lifex::dim> dof_handler_fem(
158  dof_handler.get_triangulation());
159  dof_handler_fem.distribute_dofs(fe_dg);
160 
161  std::vector<unsigned int> dof_indices(this->dofs_per_cell);
162  std::vector<unsigned int> dof_indices_fem(dofs_per_cell_fem);
163 
164 
165  // To perform the conversion to FEM, we just need to evaluate the linear
166  // combination of Dubiner functions over the dof points.
167  for (const auto &cell : dof_handler_fem.active_cell_iterators())
168  {
169  dof_indices = dof_handler.get_dof_indices(cell);
170  cell->get_dof_indices(dof_indices_fem);
171 
172  for (unsigned int i = 0; i < dofs_per_cell_fem; ++i)
173  {
174  for (unsigned int j = 0; j < this->dofs_per_cell; ++j)
175  {
176  fem_solution[dof_indices_fem[i]] +=
177  dub_solution[dof_indices[j]] *
178  this->shape_value(j, support_points[i]);
179  }
180  }
181  }
182 
183  return fem_solution;
184 }
185 
186 
187 template <class basis>
188 lifex::LinAlg::MPI::Vector
190  const lifex::LinAlg::MPI::Vector &dub_solution,
191  const std::string &FEM_mesh_path,
192  const std::string &subsection,
193  const MPI_Comm &mpi_comm_,
194  const unsigned int degree_fem,
195  const double scaling_factor) const
196 {
197  // Creation of the new FEM evaluation mesh.
198  lifex::utils::MeshHandler triangulation_fem(subsection, mpi_comm_);
199  AssertThrow(std::filesystem::exists(FEM_mesh_path),
200  dealii::StandardExceptions::ExcMessage(
201  "This mesh file/directory does not exist."));
202  triangulation_fem.initialize_from_file(FEM_mesh_path, scaling_factor);
203  triangulation_fem.set_element_type(
204  lifex::utils::MeshHandler::ElementType::Tet);
205  triangulation_fem.create_mesh();
206 
207  const dealii::FE_SimplexDGP<lifex::dim> fe_dg(degree_fem);
208  const std::vector<dealii::Point<lifex::dim>> support_points =
209  fe_dg.get_unit_support_points();
210  const unsigned int dofs_per_cell_fem = this->get_dofs_per_cell(degree_fem);
211 
212  // Generation of the mapping.
213  const std::unique_ptr<dealii::MappingFE<lifex::dim>> mapping(
214  std::make_unique<dealii::MappingFE<lifex::dim>>(fe_dg));
215 
216  // Generation of a new dof_handler for the FE evaluations.
217  dealii::DoFHandler<lifex::dim> dof_handler_fem;
218  dof_handler_fem.reinit(triangulation_fem.get());
219  dof_handler_fem.distribute_dofs(fe_dg);
220 
221  // Initialization of the FEM evaluation vector.
222  lifex::LinAlg::MPI::Vector fem_solution;
223  dealii::IndexSet owned_dofs = dof_handler_fem.locally_owned_dofs();
224  fem_solution.reinit(owned_dofs, mpi_comm_);
225 
226  // Initialization of the dof_indices.
227  std::vector<unsigned int> dof_indices(this->dofs_per_cell);
228  std::vector<unsigned int> dof_indices_fem(dofs_per_cell_fem);
229 
230  // Tolerance.
231  const double tol = 1e-10;
232 
233  // To perform the conversion to FEM, we just need to evaluate the linear
234  // combination of Dubiner functions over the dof points.
235  for (const auto &cell_fem : dof_handler_fem.active_cell_iterators())
236  {
237  cell_fem->get_dof_indices(dof_indices_fem);
238  for (unsigned int i = 0; i < dofs_per_cell_fem; ++i)
239  {
240  dealii::Point<lifex::dim> real_support_point =
241  mapping->transform_unit_to_real_cell(cell_fem, support_points[i]);
242 
243  for (const auto &cell : dof_handler.active_cell_iterators())
244  {
245  // This condition needs to guarantee that neighbor elements do not
246  // both contribute to the FEM evaluation.
247  if (std::abs(fem_solution[dof_indices_fem[i]]) < tol)
248  {
249  dealii::Point<lifex::dim> unit_support_point_dub =
250  mapping->transform_real_to_unit_cell(cell,
251  real_support_point);
252  if (is_in_unit_cell(unit_support_point_dub, tol))
253  {
254  dof_indices = dof_handler.get_dof_indices(cell);
255  for (unsigned int j = 0; j < this->dofs_per_cell; ++j)
256  {
257  fem_solution[dof_indices_fem[i]] +=
258  (dub_solution[dof_indices[j]] *
259  this->shape_value(j, unit_support_point_dub));
260  }
261  }
262  }
263  }
264  }
265  }
266 
267  return fem_solution;
268 }
269 
270 template <class basis>
271 lifex::LinAlg::MPI::Vector
273  const lifex::LinAlg::MPI::Vector &fem_solution) const
274 {
275  const dealii::FE_SimplexDGP<lifex::dim> fe_dg(this->poly_degree);
276  VolumeHandlerDG<lifex::dim> vol_handler(this->poly_degree);
277 
278  lifex::LinAlg::MPI::Vector dub_solution;
279  dub_solution.reinit(fem_solution);
280 
281  std::vector<unsigned int> dof_indices(this->dofs_per_cell);
282  double eval_on_quad;
283 
284 
285  // To perform the conversion to Dubiner, we just need to perform a (numerical)
286  // L2 scalar product between the discretized solution and the Dubiner
287  // functions. More precisely, thanks to the L2-orthonormality of the Dubiner
288  // basis, the i-th coefficient w.r.t. the Dubiner basis is the L2 scalar
289  // product between the solution and the i-th Dubiner function.
290  for (const auto &cell : dof_handler.active_cell_iterators())
291  {
292  dof_indices = dof_handler.get_dof_indices(cell);
293  vol_handler.reinit(cell);
294 
295  for (unsigned int i = 0; i < this->dofs_per_cell; ++i)
296  {
297  for (unsigned int q = 0; q < n_quad_points; ++q)
298  {
299  eval_on_quad = 0;
300 
301  for (unsigned int j = 0; j < this->dofs_per_cell; ++j)
302  {
303  eval_on_quad +=
304  fem_solution[dof_indices[j]] *
305  fe_dg.shape_value(j, vol_handler.quadrature_ref(q));
306  }
307 
308  dub_solution[dof_indices[i]] +=
309  eval_on_quad *
310  this->shape_value(i, vol_handler.quadrature_ref(q)) *
311  vol_handler.quadrature_weight(q);
312  }
313  }
314  }
315 
316  return dub_solution;
317 }
318 
319 
320 template <class basis>
321 lifex::LinAlg::MPI::Vector
323  const lifex::LinAlg::MPI::Vector &fem_solution,
324  const std::string &FEM_mesh_path,
325  const std::string &subsection,
326  const MPI_Comm &mpi_comm_,
327  const unsigned int degree_fem,
328  const double scaling_factor) const
329 {
330  // Creation of the new FEM evaluation mesh.
331  lifex::utils::MeshHandler triangulation_fem(subsection, mpi_comm_);
332  AssertThrow(std::filesystem::exists(FEM_mesh_path),
333  dealii::StandardExceptions::ExcMessage(
334  "This mesh file/directory does not exist."));
335  triangulation_fem.initialize_from_file(FEM_mesh_path, scaling_factor);
336  triangulation_fem.set_element_type(
337  lifex::utils::MeshHandler::ElementType::Tet);
338  triangulation_fem.create_mesh();
339 
340  const dealii::FE_SimplexDGP<lifex::dim> fe_dg(degree_fem);
341  const std::vector<dealii::Point<lifex::dim>> support_points =
342  fe_dg.get_unit_support_points();
343  const unsigned int dofs_per_cell_fem = this->get_dofs_per_cell(degree_fem);
344 
345  // Generation of the mapping.
346  const std::unique_ptr<dealii::MappingFE<lifex::dim>> mapping(
347  std::make_unique<dealii::MappingFE<lifex::dim>>(fe_dg));
348 
349  // Generation of a new dof_handler for the FE evaluations.
350  dealii::DoFHandler<lifex::dim> dof_handler_fem;
351  dof_handler_fem.reinit(triangulation_fem.get());
352  dof_handler_fem.distribute_dofs(fe_dg);
353 
354  // Initialization of the dof_indices.
355  std::vector<unsigned int> dof_indices(this->dofs_per_cell);
356  std::vector<unsigned int> dof_indices_fem(dofs_per_cell_fem);
357 
358  // Tolerance.
359  const double tol = 1e-10;
360 
361  VolumeHandlerDG<lifex::dim> vol_handler(this->poly_degree);
362 
363  lifex::LinAlg::MPI::Vector dub_solution;
364  dealii::IndexSet owned_dofs = dof_handler.locally_owned_dofs();
365  dub_solution.reinit(owned_dofs, mpi_comm_);
366 
367  double eval_on_quad;
368 
369 
370  // To perform the conversion to Dubiner, we just need to perform a (numerical)
371  // L2 scalar product between the discretized solution and the Dubiner
372  // functions. More precisely, thanks to the L2-orthonormality of the Dubiner
373  // basis, the i-th coefficient w.r.t. the Dubiner basis is the L2 scalar
374  // product between the solution and the i-th Dubiner function.
375  for (const auto &cell : dof_handler.active_cell_iterators())
376  {
377  dof_indices = dof_handler.get_dof_indices(cell);
378  vol_handler.reinit(cell);
379 
380  for (unsigned int i = 0; i < this->dofs_per_cell; ++i)
381  {
382  for (unsigned int q = 0; q < n_quad_points; ++q)
383  {
384  dealii::Point<lifex::dim> real_q =
385  mapping->transform_unit_to_real_cell(
386  cell, vol_handler.quadrature_ref(q));
387 
388  for (const auto &cell_fem :
389  dof_handler_fem.active_cell_iterators())
390  {
391  // The following condition is to ensure that every Dubiner
392  // quadrature point belongs to only one FEM cell (and not the
393  // neighbor too).
394  if (std::abs(eval_on_quad) < tol)
395  {
396  cell_fem->get_dof_indices(dof_indices_fem);
397  dealii::Point<lifex::dim> unit_q =
398  mapping->transform_real_to_unit_cell(cell_fem, real_q);
399  if (is_in_unit_cell(unit_q, tol))
400  {
401  for (unsigned int j = 0; j < dofs_per_cell_fem; ++j)
402  {
403  eval_on_quad += fem_solution[dof_indices_fem[j]] *
404  fe_dg.shape_value(j, unit_q);
405  }
406 
407  dub_solution[dof_indices[i]] +=
408  eval_on_quad *
409  this->shape_value(i,
410  vol_handler.quadrature_ref(q)) *
411  vol_handler.quadrature_weight(q);
412  }
413  }
414  }
415  eval_on_quad = 0;
416  }
417  }
418  }
419 
420  return dub_solution;
421 }
422 
423 
424 template <class basis>
425 lifex::LinAlg::MPI::Vector
427  lifex::LinAlg::MPI::Vector dub_solution,
428  const std::shared_ptr<dealii::Function<lifex::dim>> &u_analytical) const
429 {
430  VolumeHandlerDG<lifex::dim> vol_handler(this->poly_degree);
431 
432  dealii::IndexSet owned_dofs = dof_handler.locally_owned_dofs();
433 
434  std::vector<unsigned int> dof_indices(this->dofs_per_cell);
435  double eval_on_quad;
436 
437 
438  // Here, we apply the same idea as in fem_to_dubiner. The only difference is
439  // that here we can directly evaluate the solution on the quadrature point
440  // since the solution is analytical.
441  for (const auto &cell : dof_handler.active_cell_iterators())
442  {
443  dof_indices = dof_handler.get_dof_indices(cell);
444  vol_handler.reinit(cell);
445 
446  for (unsigned int i = 0; i < this->dofs_per_cell; ++i)
447  {
448  dub_solution[dof_indices[i]] = 0;
449  for (unsigned int q = 0; q < n_quad_points; ++q)
450  {
451  eval_on_quad =
452  u_analytical->value(vol_handler.quadrature_real(q));
453 
454  dub_solution[dof_indices[i]] +=
455  eval_on_quad *
456  this->shape_value(i, vol_handler.quadrature_ref(q)) *
457  vol_handler.quadrature_weight(q);
458  }
459  }
460  }
461 
462  return dub_solution;
463 }
464 
465 
466 template <class basis>
467 bool
468 DUBFEMHandler<basis>::is_in_unit_cell(const dealii::Point<lifex::dim> &unit_q,
469  const double tol) const
470 {
471  if (lifex::dim == 2)
472  {
473  return (unit_q[0] + unit_q[1] < 1.0 + tol && unit_q[0] > -tol &&
474  unit_q[1] > -tol);
475  }
476  else if (lifex::dim == 3)
477  {
478  return (unit_q[0] + unit_q[1] + unit_q[2] < 1.0 + tol &&
479  unit_q[0] > -tol && unit_q[1] > -tol && unit_q[2] > -tol);
480  }
481 }
482 
483 #endif /* DUBFEMHandler_HPP_*/
Class used to discretize analytical solutions as linear combinations of Lagrangian or Dubiner basis.
const unsigned int n_quad_points
Number of quadrature points in the volume element.
lifex::LinAlg::MPI::Vector dubiner_to_fem(const lifex::LinAlg::MPI::Vector &dub_solution) const
Conversion of a discretized solution vector from Dubiner coefficients to FEM coefficients.
lifex::LinAlg::MPI::Vector fem_to_dubiner(const lifex::LinAlg::MPI::Vector &fem_solution) const
Conversion of a discretized solution vector from FEM coefficients to Dubiner coefficients.
const DoFHandlerDG< basis > & dof_handler
Dof handler object of the problem.
lifex::LinAlg::MPI::Vector analytical_to_dubiner(lifex::LinAlg::MPI::Vector dub_solution, const std::shared_ptr< dealii::Function< lifex::dim >> &u_analytical) const
Conversion of an analytical solution to a vector of Dubiner coefficients.
bool is_in_unit_cell(const dealii::Point< lifex::dim > &unit_q, const double tol=0) const
To check if a point is inside the reference cell, needed in other methods.
Class representing the Dubiner basis functions definitions.
Definition: DUBValues.hpp:50
const double tol
Default tolerance.
Definition: DUBValues.hpp:56
Class to work with global and local degrees of freedom and their mapping.
virtual double quadrature_weight(const unsigned int q) const
Return the quadrature weight associated to the -th quadrature point.
void reinit(const typename dealii::DoFHandler< dim >::active_cell_iterator &new_cell)
Reinit objects on the current new_cell.
virtual dealii::Point< dim > quadrature_ref(const unsigned int q) const
Return the -th spatial quadrature point position on the reference element.
virtual dealii::Point< dim > quadrature_real(const unsigned int q) const
Return the -th spatial quadrature point position on the actual element.