DUBeat 1.0.1
High-order discontinuous Galerkin methods and applications to cardiac electrophysiology
Loading...
Searching...
No Matches
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
70template <class basis>
72{
73public:
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
135private:
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
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
209template <class basis>
210void
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
225template <class basis>
226void
227ComputeErrorsDG<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
270template <class basis>
271std::vector<double>
272ComputeErrorsDG<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
296template <class basis>
297void
299{
300 lifex::LinAlg::MPI::Vector difference = solution_owned;
301 difference -= solution_ex_owned;
302
303 errors[0] = difference.linfty_norm();
304}
305
308template <>
309void
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
323template <class basis>
324void
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
373template <class basis>
374void
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
439template <class basis>
440void
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
529template <class basis>
530void
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
554template <class basis>
555std::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
567template <class basis>
568void
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.
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.
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.
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.
Class for the main operations on the faces of a discontinuous Galerkin element.
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.
Class for the main operations on a discontinuous Galerkin volume element.
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.