DUBeat  1.0.1
High-order discontinuous Galerkin methods and applications to cardiac electrophysiology
compute_errors_DG.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 COMPUTE_ERRORS_DG_HPP_
28 #define COMPUTE_ERRORS_DG_HPP_
29 
30 #include <deal.II/base/quadrature.h>
31 
32 #include <deal.II/dofs/dof_handler.h>
33 
34 #include <deal.II/fe/fe.h>
35 #include <deal.II/fe/mapping_q1_eulerian.h>
36 
37 #include <deal.II/lac/trilinos_vector.h>
38 
39 #include <time.h>
40 
41 #include <cmath>
42 #include <cstdlib>
43 #include <filesystem>
44 #include <fstream>
45 #include <iostream>
46 #include <string>
47 #include <vector>
48 
49 #include "DUBValues.hpp"
50 #include "dof_handler_DG.hpp"
51 
70 template <class basis>
72 {
73 public:
75  ComputeErrorsDG<basis>(const unsigned int degree,
76  const double stability_coeff,
77  const unsigned int local_dofs,
78  const DoFHandlerDG<basis> &dof_hand,
79  const unsigned int nref_,
80  const std::string &title)
81  : dof_handler(dof_hand)
82  , n_quad_points(static_cast<int>(std::pow(degree + 2, lifex::dim)))
83  , n_quad_points_face(static_cast<int>(std::pow(degree + 2, lifex::dim - 1)))
84  , dofs_per_cell(local_dofs)
85  , fe_degree(degree)
86  , nref(nref_)
87  , model_name(title)
88  , stability_coefficient(stability_coeff)
89  , basis_ptr(std::make_unique<basis>(degree))
90  , solution_name((char *)"u")
91  , errors({0, 0, 0, 0})
93  std::make_shared<DUBFEMHandler<basis>>(degree, dof_handler))
94  {}
95 
98 
101  default;
102 
105 
107  void
108  reinit(const lifex::LinAlg::MPI::Vector &sol_owned,
109  const lifex::LinAlg::MPI::Vector &sol_ex_owned,
110  const std::shared_ptr<dealii::Function<lifex::dim>> &u_ex_input,
111  const std::shared_ptr<dealii::Function<lifex::dim>> &grad_u_ex_input,
112  const char *solution_name_input);
113 
116  void
118  std::list<const char *> errors_defs = {"inf", "L2", "H1", "DG"});
119 
123  std::vector<double>
125  std::list<const char *> errors_defs = {"inf", "L2", "H1", "DG"}) const;
126 
128  void
129  update_datafile() const;
130 
132  void
133  initialize_datafile() const;
134 
135 private:
137  void
139 
141  void
143 
145  void
147 
149  void
151 
153  std::string
154  get_date() const;
155 
158 
161  const unsigned int n_quad_points;
162 
165  const unsigned int n_quad_points_face;
166 
168  const unsigned int dofs_per_cell;
169 
171  const double stability_coefficient;
172 
174  const unsigned int fe_degree;
175 
177  const unsigned int nref;
178 
180  const std::string model_name;
181 
183  const std::unique_ptr<basis> basis_ptr;
184 
186  lifex::LinAlg::MPI::Vector solution_owned;
187 
189  lifex::LinAlg::MPI::Vector solution_ex_owned;
190 
192  std::shared_ptr<dealii::Function<lifex::dim>> u_ex;
193 
195  std::shared_ptr<dealii::Function<lifex::dim>> grad_u_ex;
196 
200 
203  std::array<double, 4> errors;
204 
206  std::shared_ptr<DUBFEMHandler<basis>> dub_fem_values;
207 };
208 
209 template <class basis>
210 void
212  const lifex::LinAlg::MPI::Vector &sol_owned,
213  const lifex::LinAlg::MPI::Vector &sol_ex_owned,
214  const std::shared_ptr<dealii::Function<lifex::dim>> &u_ex_input,
215  const std::shared_ptr<dealii::Function<lifex::dim>> &grad_u_ex_input,
216  const char *solution_name_input)
217 {
218  solution_owned = sol_owned;
219  solution_ex_owned = sol_ex_owned;
220  u_ex = u_ex_input;
221  grad_u_ex = grad_u_ex_input;
222  solution_name = (char *)solution_name_input;
223 }
224 
225 template <class basis>
226 void
227 ComputeErrorsDG<basis>::compute_errors(std::list<const char *> errors_defs)
228 {
229  AssertThrow(u_ex != nullptr,
230  dealii::StandardExceptions::ExcMessage(
231  "No valid pointer to the exact solution."));
232 
233  AssertThrow(grad_u_ex != nullptr,
234  dealii::StandardExceptions::ExcMessage(
235  "No valid pointer to the gradient of the exact solution."));
236 
237  AssertThrow(solution_owned.size() == solution_ex_owned.size(),
238  dealii::StandardExceptions::ExcMessage(
239  "The exact solution vector and the approximate solution vector "
240  "must have the same length."));
241 
242  if (std::find(errors_defs.begin(), errors_defs.end(), "inf") !=
243  errors_defs.end())
244  {
245  this->compute_error_inf();
246  }
247 
248  // We need to respect the following order because the H1 semi error
249  // contributes to the DG error and the L2 error contributes to the H1 error.
250  if (std::find(errors_defs.begin(), errors_defs.end(), "DG") !=
251  errors_defs.end())
252  {
253  this->compute_error_L2();
254  this->compute_error_H1();
255  this->compute_error_DG();
256  }
257  else if (std::find(errors_defs.begin(), errors_defs.end(), "H1") !=
258  errors_defs.end())
259  {
260  this->compute_error_L2();
261  this->compute_error_H1();
262  }
263  else if (std::find(errors_defs.begin(), errors_defs.end(), "L2") !=
264  errors_defs.end())
265  {
266  this->compute_error_L2();
267  }
268 }
269 
270 template <class basis>
271 std::vector<double>
272 ComputeErrorsDG<basis>::output_errors(std::list<const char *> errors_defs) const
273 {
274  std::vector<double> output_errors = {};
275 
276  for (auto error_def = errors_defs.begin(); error_def != errors_defs.end();
277  error_def++)
278  {
279  AssertThrow(*error_def == "inf" || *error_def == "L2" ||
280  *error_def == "H1" || *error_def == "DG",
281  dealii::StandardExceptions::ExcMessage(
282  "Error definition must be inf, L2, H1 or DG."));
283 
284  if (*error_def == "inf")
285  output_errors.push_back(errors[0]);
286  else if (*error_def == "L2")
287  output_errors.push_back(errors[1]);
288  else if (*error_def == "H1")
289  output_errors.push_back(errors[2]);
290  else
291  output_errors.push_back(errors[3]);
292  }
293  return output_errors;
294 }
295 
296 template <class basis>
297 void
299 {
300  lifex::LinAlg::MPI::Vector difference = solution_owned;
301  difference -= solution_ex_owned;
302 
303  errors[0] = difference.linfty_norm();
304 }
305 
308 template <>
309 void
311 {
312  lifex::LinAlg::MPI::Vector solution_fem =
313  dub_fem_values->dubiner_to_fem(solution_owned);
314  lifex::LinAlg::MPI::Vector solution_ex_fem =
315  dub_fem_values->dubiner_to_fem(solution_ex_owned);
316 
317  lifex::LinAlg::MPI::Vector difference = solution_fem;
318  difference -= solution_ex_fem;
319 
320  errors[0] = difference.linfty_norm();
321 }
322 
323 template <class basis>
324 void
326 {
327  double error_L2 = 0;
328 
329  VolumeHandlerDG<lifex::dim> vol_handler(fe_degree);
330  std::vector<lifex::types::global_dof_index> dof_indices(dofs_per_cell);
331 
332  double local_approx;
333 
334  for (const auto &cell : dof_handler.active_cell_iterators())
335  {
336  vol_handler.reinit(cell);
337 
338  if (cell->is_locally_owned())
339  {
340  const dealii::Tensor<2, lifex::dim> BJinv =
341  vol_handler.get_jacobian_inverse();
342  const double det = 1 / determinant(BJinv);
343 
344  dof_indices = dof_handler.get_dof_indices(cell);
345 
346  // Computation of the L^2 norm error as the sum of the squared
347  // differences on the quadrature points.
348  for (unsigned int q = 0; q < n_quad_points; ++q)
349  {
350  local_approx = 0;
351 
352  // The following loop is necessary to evaluate the discretized
353  // solution on a quadrature point as a linear combination of the
354  // analytical basis functions evaluated on that point.
355  for (unsigned int i = 0; i < dofs_per_cell; ++i)
356  {
357  local_approx +=
358  basis_ptr->shape_value(i, vol_handler.quadrature_ref(q)) *
359  solution_owned[dof_indices[i]];
360  }
361 
362  error_L2 +=
363  pow(local_approx - u_ex->value(vol_handler.quadrature_real(q)),
364  2) *
365  det * vol_handler.quadrature_weight(q);
366  }
367  }
368  }
369 
370  errors[1] = sqrt(error_L2);
371 }
372 
373 template <class basis>
374 void
376 {
377  double error_semi_H1 = 0;
378  dealii::Tensor<1, lifex::dim> local_approx_gradient;
379  dealii::Tensor<1, lifex::dim> local_grad_exact;
380  dealii::Tensor<1, lifex::dim> pointwise_diff;
381 
382  VolumeHandlerDG<lifex::dim> vol_handler(fe_degree);
383  std::vector<lifex::types::global_dof_index> dof_indices(dofs_per_cell);
384 
385  for (const auto &cell : dof_handler.active_cell_iterators())
386  {
387  vol_handler.reinit(cell);
388 
389  if (cell->is_locally_owned())
390  {
391  const dealii::Tensor<2, lifex::dim> BJinv =
392  vol_handler.get_jacobian_inverse();
393  const double det = 1 / determinant(BJinv);
394 
395  dof_indices = dof_handler.get_dof_indices(cell);
396 
397  // Loop for the computation of the H1 semi-norm error, i.e. the L^2
398  // norm of the gradient of the error.
399  for (unsigned int q = 0; q < n_quad_points; ++q)
400  {
401  local_grad_exact = 0;
402  local_approx_gradient = 0;
403  pointwise_diff = 0;
404 
405  for (unsigned int j = 0; j < lifex::dim; ++j)
406  {
407  // Evaluation of the gradient of the exact solution on the
408  // quadrature point.
409  local_grad_exact[j] =
410  grad_u_ex->value(vol_handler.quadrature_real(q), j);
411 
412  // Evaluation of the gradient of the discretized solution on
413  // the quadrature point. Once again, we need to exploit the
414  // linearity of the discretized solution with respect to the
415  // analytical basis functions.
416  for (unsigned int i = 0; i < dofs_per_cell; ++i)
417  {
418  local_approx_gradient[j] +=
419  basis_ptr->shape_grad(
420  i, vol_handler.quadrature_ref(q))[j] *
421  solution_owned[dof_indices[i]];
422  }
423  }
424 
425  pointwise_diff =
426  local_grad_exact - (local_approx_gradient * BJinv);
427 
428  error_semi_H1 += pointwise_diff * pointwise_diff * det *
429  vol_handler.quadrature_weight(q);
430  }
431  }
432  }
433 
434  // The full H^1 norm error is composed by the sum of the L^2 norm and the H^1
435  // semi-norm.
436  errors[2] = sqrt(errors[1] * errors[1] + error_semi_H1);
437 }
438 
439 template <class basis>
440 void
442 {
443  double error_DG = 0;
444 
445  VolumeHandlerDG<lifex::dim> vol_handler(fe_degree);
446  std::vector<lifex::types::global_dof_index> dof_indices(dofs_per_cell);
447  FaceHandlerDG<lifex::dim> face_handler(fe_degree);
448  FaceHandlerDG<lifex::dim> face_handler_neigh(fe_degree);
449 
450  for (const auto &cell : dof_handler.active_cell_iterators())
451  {
452  vol_handler.reinit(cell);
453 
454  if (cell->is_locally_owned())
455  {
456  dof_indices = dof_handler.get_dof_indices(cell);
457 
458  for (const auto &edge : cell->face_indices())
459  {
460  face_handler.reinit(cell, edge);
461 
462  const double face_measure = face_handler.get_measure();
463  const double unit_face_measure = (4.0 - lifex::dim) / 2;
464  const double measure_ratio = face_measure / unit_face_measure;
465  const double h_local = (cell->measure()) / face_measure;
466 
467  lifex::LinAlg::MPI::Vector difference = solution_owned;
468  difference -= solution_ex_owned;
469 
470  const double local_stability_coeff =
471  (stability_coefficient * pow(fe_degree, 2)) / h_local;
472 
473  std::vector<lifex::types::global_dof_index> dof_indices_neigh(
474  dofs_per_cell);
475 
476  // Loop for the computation of the second component of the DG
477  // error, the one corresponding to the integral on the faces.
478  for (unsigned int q = 0; q < n_quad_points_face; ++q)
479  {
480  for (unsigned int i = 0; i < dofs_per_cell; ++i)
481  {
482  for (unsigned int j = 0; j < dofs_per_cell; ++j)
483  {
484  error_DG +=
485  difference[dof_indices[i]] *
486  difference[dof_indices[j]] * local_stability_coeff *
487  basis_ptr->shape_value(
488  i, face_handler.quadrature_ref(q)) *
489  basis_ptr->shape_value(
490  j, face_handler.quadrature_ref(q)) *
491  face_handler.quadrature_weight(q) * measure_ratio;
492 
493  if (!cell->at_boundary(edge))
494  {
495  const auto neighcell = cell->neighbor(edge);
496  const auto neighedge =
497  cell->neighbor_face_no(edge);
498  dof_indices_neigh =
499  dof_handler.get_dof_indices(neighcell);
500  face_handler_neigh.reinit(neighcell, neighedge);
501 
502  const unsigned int nq =
503  face_handler.corresponding_neigh_index(
504  q, face_handler_neigh);
505 
506  error_DG -=
507  difference[dof_indices[i]] *
508  difference[dof_indices_neigh[j]] *
509  local_stability_coeff *
510  basis_ptr->shape_value(
511  i, face_handler.quadrature_ref(q)) *
512  basis_ptr->shape_value(
513  j, face_handler_neigh.quadrature_ref(nq)) *
514  face_handler.quadrature_weight(q) *
515  measure_ratio;
516  }
517  }
518  }
519  }
520  }
521  }
522  }
523  double error_semi_H1 = errors[2] * errors[2] - errors[1] * errors[1];
524  // The full DG norm error is composed by the sum of the previous computed
525  // integral and the H^1 semi-norm.
526  errors[3] = sqrt(error_semi_H1 + error_DG);
527 }
528 
529 template <class basis>
530 void
532 {
533  std::ofstream outdata;
534  outdata.open("errors_" + model_name + "_" + std::to_string(lifex::dim) +
535  "D_" + solution_name + ".data");
536  if (!outdata)
537  {
538  std::cerr << "Error: file could not be opened" << std::endl;
539  exit(1);
540  }
541 
542  const std::string date = get_date();
543 
544  outdata << "nref" << '\t' << "l_inf" << '\t' << "l_2" << '\t' << "h_1" << '\t'
545  << "DG" << '\t' << "date" << std::endl;
546 
547  for (unsigned int i = 1; i <= 5; ++i)
548  outdata << i << '\t' << "x" << '\t' << "x" << '\t' << "x" << '\t' << "x"
549  << '\t' << date << std::endl;
550 
551  outdata.close();
552 }
553 
554 template <class basis>
555 std::string
557 {
558  char date[100];
559  time_t curr_time;
560  time(&curr_time);
561  tm curr_tm;
562  localtime_r(&curr_time, &curr_tm);
563  strftime(date, 100, "%D %T", &curr_tm);
564  return date;
565 }
566 
567 template <class basis>
568 void
570 {
571  const std::string filename = "errors_" + model_name + "_" +
572  std::to_string(lifex::dim) + "D_" +
573  solution_name + ".data";
574 
575  // If datafile does not exist, initialize it before.
576  if (!std::filesystem::exists(filename))
577  initialize_datafile();
578 
579  std::ifstream indata;
580  std::ofstream outdata;
581  indata.open(filename);
582  outdata.open("errors_tmp.data");
583 
584  if (!outdata || !indata)
585  {
586  std::cerr << "Error: file could not be opened" << std::endl;
587  exit(1);
588  }
589 
590  std::string line;
591  std::getline(indata, line);
592 
593  const char n_ref_c = '0' + nref;
594 
595  outdata << line << std::endl;
596 
597  const std::string date = get_date();
598 
599  for (unsigned int i = 0; i < 10; ++i)
600  {
601  std::getline(indata, line);
602 
603  if (line[0] == n_ref_c)
604  outdata << nref << '\t' << errors[0] << '\t' << errors[1] << '\t'
605  << errors[2] << '\t' << errors[3] << '\t' << date << std::endl;
606  else
607  outdata << line << std::endl;
608  }
609 
610  outdata.close();
611  indata.close();
612 
613  std::ifstream indata2("errors_tmp.data");
614  std::ofstream outdata2(filename);
615  outdata2 << indata2.rdbuf();
616  outdata2.close();
617  indata2.close();
618 
619  if (remove("errors_tmp.data") != 0)
620  {
621  std::cerr << "Error in deleting file" << std::endl;
622  exit(1);
623  }
624 }
625 
626 #endif /* ComputeErrorsDG_HPP_*/
Class to compute the errors between the numerical solution (solution_owned) and the exact solution (s...
const DoFHandlerDG< basis > & dof_handler
Dof handler object of the problem.
void initialize_datafile() const
Create the datafile for the errors.
void update_datafile() const
Update the datafile with the new errors.
void compute_error_inf()
Compute the error.
void compute_error_L2()
Compute the error.
const unsigned int n_quad_points_face
Number of quadrature points on the face element.
const std::unique_ptr< basis > basis_ptr
Pointer to the basis handler.
std::shared_ptr< dealii::Function< lifex::dim > > u_ex
Analytical exact solution.
std::shared_ptr< dealii::Function< lifex::dim > > grad_u_ex
Gradient of the analytical exact solution.
std::shared_ptr< DUBFEMHandler< basis > > dub_fem_values
Member to compute conversion to FEM basis, needed for L^inf error.
const std::string model_name
Name of the model.
lifex::LinAlg::MPI::Vector solution_owned
Computed solution.
const unsigned int n_quad_points
Number of quadrature points in the volume element.
const unsigned int nref
Number of refinements.
void compute_error_H1()
Compute the error.
char * solution_name
String that contains the solution name (to write on output files, default "u").
void compute_errors(std::list< const char * > errors_defs={"inf", "L2", "H1", "DG"})
Compute errors following the preferences in the list.
void reinit(const lifex::LinAlg::MPI::Vector &sol_owned, const lifex::LinAlg::MPI::Vector &sol_ex_owned, const std::shared_ptr< dealii::Function< lifex::dim >> &u_ex_input, const std::shared_ptr< dealii::Function< lifex::dim >> &grad_u_ex_input, const char *solution_name_input)
Reinitialization with new computed and exact solutions.
std::array< double, 4 > errors
Array that contains the current errors (in order, , , , ).
std::string get_date() const
Return the current date and time.
void compute_error_DG()
Compute the error.
lifex::LinAlg::MPI::Vector solution_ex_owned
Exact solution to compare with the numerical one.
const unsigned int fe_degree
Polynomial degree.
const double stability_coefficient
Stability coefficient, needed for the computation of the error.
std::vector< double > output_errors(std::list< const char * > errors_defs={"inf", "L2", "H1", "DG"}) const
Output of the errors following the preferences in the list.
const unsigned int dofs_per_cell
Number of degrees of freedom per cell.
Class used to discretize analytical solutions as linear combinations of Lagrangian or Dubiner basis.
Class to work with global and local degrees of freedom and their mapping.
dealii::Point< dim > quadrature_ref(const unsigned int q) const override
Return the -th spatial quadrature point position on the reference element.
double quadrature_weight(const unsigned int q) const override
Return the quadrature weight associated to the -th quadrature point.
double get_measure() const
Measure of face.
int corresponding_neigh_index(const unsigned int q, const FaceHandlerDG< dim > &FaceHandlerDG_neigh) const
To manually obtain the associated quadrature point index in the neighbor element on the shared face.
void reinit(const typename dealii::DoFHandler< dim >::active_cell_iterator &new_cell, const unsigned int new_edge)
Reinit objects on the current new_cell and new_edge.
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.
dealii::Tensor< 2, dim > get_jacobian_inverse() const
Inverse of the Jacobian of the reference-to-actual transformation.