deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
smoothness_estimator.cc
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2018 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
15
17
19
21
23
32#include <deal.II/lac/vector.h>
33
35
36#include <algorithm>
37#include <cmath>
38#include <limits>
39#include <utility>
40
41
43
44
45namespace SmoothnessEstimator
46{
47 namespace
48 {
52 template <int dim, typename CoefficientType>
53 void
54 resize(Table<dim, CoefficientType> &coeff, const unsigned int N)
55 {
57 for (unsigned int d = 0; d < dim; ++d)
58 size[d] = N;
59 coeff.reinit(size);
60 }
61 } // namespace
62
63
64
65 namespace Legendre
66 {
67 namespace
68 {
83 template <int dim>
84 std::pair<bool, unsigned int>
85 index_sum_less_than_N(const TableIndices<dim> &ind, const unsigned int N)
86 {
87 unsigned int v = 0;
88 for (unsigned int i = 0; i < dim; ++i)
89 v += ind[i];
90
91 return std::make_pair((v < N), v);
92 }
93 } // namespace
94
95
96
97 template <int dim, int spacedim, typename VectorType>
98 void
100 const DoFHandler<dim, spacedim> &dof_handler,
101 const VectorType &solution,
102 Vector<float> &smoothness_indicators,
103 const VectorTools::NormType regression_strategy,
104 const double smallest_abs_coefficient,
105 const bool only_flagged_cells)
106 {
107 using number = typename VectorType::value_type;
108 using number_coeff =
110
111 smoothness_indicators.reinit(
112 dof_handler.get_triangulation().n_active_cells());
113
114 unsigned int n_modes;
115 Table<dim, number_coeff> expansion_coefficients;
116
117 Vector<number> local_dof_values;
118 std::vector<double> converted_indices;
119 std::pair<std::vector<unsigned int>, std::vector<double>> res;
120 for (const auto &cell : dof_handler.active_cell_iterators() |
122 {
123 if (!only_flagged_cells || cell->refine_flag_set() ||
124 cell->coarsen_flag_set())
125 {
126 n_modes = fe_legendre.get_n_coefficients_per_direction(
127 cell->active_fe_index());
128 resize(expansion_coefficients, n_modes);
129
130 local_dof_values.reinit(cell->get_fe().n_dofs_per_cell());
131 cell->get_dof_values(solution, local_dof_values);
132
133 fe_legendre.calculate(local_dof_values,
134 cell->active_fe_index(),
135 expansion_coefficients);
136
137 // We fit our exponential decay of expansion coefficients to the
138 // provided regression_strategy on each possible value of |k|.
139 // To this end, we use FESeries::process_coefficients() to
140 // rework coefficients into the desired format.
141 res = FESeries::process_coefficients<dim>(
142 expansion_coefficients,
143 [n_modes](const TableIndices<dim> &indices) {
144 return index_sum_less_than_N(indices, n_modes);
145 },
146 regression_strategy,
147 smallest_abs_coefficient);
148
149 Assert(res.first.size() == res.second.size(), ExcInternalError());
150
151 // Last, do the linear regression.
152 float regularity = std::numeric_limits<float>::infinity();
153 if (res.first.size() > 1)
154 {
155 // Prepare linear equation for the logarithmic least squares
156 // fit.
157 converted_indices.assign(res.first.begin(), res.first.end());
158
159 for (auto &residual_element : res.second)
160 residual_element = std::log(residual_element);
161
162 const std::pair<double, double> fit =
163 FESeries::linear_regression(converted_indices, res.second);
164 regularity = static_cast<float>(-fit.first);
165 }
166
167 smoothness_indicators(cell->active_cell_index()) = regularity;
168 }
169 else
170 smoothness_indicators(cell->active_cell_index()) =
171 numbers::signaling_nan<float>();
172 }
173 }
174
175
176
177 template <int dim, int spacedim, typename VectorType>
178 void
181 const DoFHandler<dim, spacedim> &dof_handler,
182 const VectorType &solution,
183 Vector<float> &smoothness_indicators,
184 const ComponentMask &coefficients_predicate,
185 const double smallest_abs_coefficient,
186 const bool only_flagged_cells)
187 {
188 Assert(smallest_abs_coefficient >= 0.,
189 ExcMessage("smallest_abs_coefficient should be non-negative."));
190
191 using number = typename VectorType::value_type;
192 using number_coeff =
194
195 smoothness_indicators.reinit(
196 dof_handler.get_triangulation().n_active_cells());
197
198 unsigned int n_modes;
199 Table<dim, number_coeff> expansion_coefficients;
200 Vector<number> local_dof_values;
201
202 // auxiliary vector to do linear regression
203 const unsigned int max_degree =
204 dof_handler.get_fe_collection().max_degree();
205
206 std::vector<double> x, y;
207 x.reserve(max_degree);
208 y.reserve(max_degree);
209
210 for (const auto &cell : dof_handler.active_cell_iterators() |
212 {
213 if (!only_flagged_cells || cell->refine_flag_set() ||
214 cell->coarsen_flag_set())
215 {
216 n_modes = fe_legendre.get_n_coefficients_per_direction(
217 cell->active_fe_index());
218 resize(expansion_coefficients, n_modes);
219
220 const unsigned int pe = cell->get_fe().degree;
221 Assert(pe > 0, ExcInternalError());
222
223 // since we use coefficients with indices [1,pe] in each
224 // direction, the number of coefficients we need to calculate is
225 // at least N=pe+1
226 AssertIndexRange(pe, n_modes);
227
228 local_dof_values.reinit(cell->get_fe().n_dofs_per_cell());
229 cell->get_dof_values(solution, local_dof_values);
230
231 fe_legendre.calculate(local_dof_values,
232 cell->active_fe_index(),
233 expansion_coefficients);
234
235 // choose the smallest decay of coefficients in each direction,
236 // i.e. the maximum decay slope k_v as in exp(-k_v)
237 double k_v = std::numeric_limits<double>::max();
238 for (unsigned int d = 0; d < dim; ++d)
239 {
240 x.resize(0);
241 y.resize(0);
242
243 // will use all non-zero coefficients allowed by the
244 // predicate function
245 for (unsigned int i = 0; i <= pe; ++i)
246 if (coefficients_predicate[i])
247 {
249 ind[d] = i;
250 const double coeff_abs =
251 std::abs(expansion_coefficients(ind));
252
253 if (coeff_abs > smallest_abs_coefficient)
254 {
255 x.push_back(i);
256 y.push_back(std::log(coeff_abs));
257 }
258 }
259
260 // in case we don't have enough non-zero coefficient to fit,
261 // skip this direction
262 if (x.size() < 2)
263 continue;
264
265 const std::pair<double, double> fit =
267
268 // decay corresponds to negative slope
269 // take the lesser negative slope along each direction
270 k_v = std::min(k_v, -fit.first);
271 }
272
273 smoothness_indicators(cell->active_cell_index()) =
274 static_cast<float>(k_v);
275 }
276 else
277 smoothness_indicators(cell->active_cell_index()) =
278 numbers::signaling_nan<float>();
279 }
280 }
281
282
283
284 template <int dim, int spacedim>
287 const unsigned int component)
288 {
289 // Default number of coefficients per direction.
290 //
291 // With a number of modes equal to the polynomial degree plus two for each
292 // finite element, the smoothness estimation algorithm tends to produce
293 // stable results.
294 std::vector<unsigned int> n_coefficients_per_direction;
295 n_coefficients_per_direction.reserve(fe_collection.size());
296 for (unsigned int i = 0; i < fe_collection.size(); ++i)
297 n_coefficients_per_direction.push_back(fe_collection[i].degree + 2);
298
299 // Default quadrature collection.
300 //
301 // We initialize a FESeries::Legendre expansion object object which will
302 // be used to calculate the expansion coefficients. In addition to the
303 // hp::FECollection, we need to provide quadrature rules hp::QCollection
304 // for integration on the reference cell.
305 // We will need to assemble the expansion matrices for each of the finite
306 // elements we deal with, i.e. the matrices F_k,j. We have to do that for
307 // each of the finite elements in use. To that end we need a quadrature
308 // rule. As a default, we use the same quadrature formula for each finite
309 // element, namely a Gauss formula that yields exact results for the
310 // highest order Legendre polynomial used.
311 //
312 // We start with the zeroth Legendre polynomial which is just a constant,
313 // so the highest Legendre polynomial will be of order (n_modes - 1).
314 hp::QCollection<dim> q_collection;
315 for (unsigned int i = 0; i < fe_collection.size(); ++i)
316 {
317 const QGauss<dim> quadrature(n_coefficients_per_direction[i]);
318 const QSorted<dim> quadrature_sorted(quadrature);
319 q_collection.push_back(quadrature_sorted);
320 }
321
322 return FESeries::Legendre<dim, spacedim>(n_coefficients_per_direction,
323 fe_collection,
324 q_collection,
325 component);
326 }
327 } // namespace Legendre
328
329
330
331 namespace Fourier
332 {
333 namespace
334 {
349 template <int dim>
350 std::pair<bool, unsigned int>
351 index_norm_greater_than_zero_and_less_than_N_squared(
352 const TableIndices<dim> &ind,
353 const unsigned int N)
354 {
355 unsigned int v = 0;
356 for (unsigned int i = 0; i < dim; ++i)
357 v += ind[i] * ind[i];
358
359 return std::make_pair((v > 0 && v < N * N), v);
360 }
361 } // namespace
362
363
364
365 template <int dim, int spacedim, typename VectorType>
366 void
368 const DoFHandler<dim, spacedim> &dof_handler,
369 const VectorType &solution,
370 Vector<float> &smoothness_indicators,
371 const VectorTools::NormType regression_strategy,
372 const double smallest_abs_coefficient,
373 const bool only_flagged_cells)
374 {
375 using number = typename VectorType::value_type;
376 using number_coeff =
378
379 smoothness_indicators.reinit(
380 dof_handler.get_triangulation().n_active_cells());
381
382 unsigned int n_modes;
383 Table<dim, number_coeff> expansion_coefficients;
384
385 Vector<number> local_dof_values;
386 std::vector<double> ln_k;
387 std::pair<std::vector<unsigned int>, std::vector<double>> res;
388 for (const auto &cell : dof_handler.active_cell_iterators() |
390 {
391 if (!only_flagged_cells || cell->refine_flag_set() ||
392 cell->coarsen_flag_set())
393 {
394 n_modes = fe_fourier.get_n_coefficients_per_direction(
395 cell->active_fe_index());
396 resize(expansion_coefficients, n_modes);
397
398 // Inside the loop, we first need to get the values of the local
399 // degrees of freedom and then need to compute the series
400 // expansion by multiplying this vector with the matrix @f${\cal
401 // F}@f$ corresponding to this finite element.
402 local_dof_values.reinit(cell->get_fe().n_dofs_per_cell());
403 cell->get_dof_values(solution, local_dof_values);
404
405 fe_fourier.calculate(local_dof_values,
406 cell->active_fe_index(),
407 expansion_coefficients);
408
409 // We fit our exponential decay of expansion coefficients to the
410 // provided regression_strategy on each possible value of |k|.
411 // To this end, we use FESeries::process_coefficients() to
412 // rework coefficients into the desired format.
413 res = FESeries::process_coefficients<dim>(
414 expansion_coefficients,
415 [n_modes](const TableIndices<dim> &indices) {
416 return index_norm_greater_than_zero_and_less_than_N_squared(
417 indices, n_modes);
418 },
419 regression_strategy,
420 smallest_abs_coefficient);
421
422 Assert(res.first.size() == res.second.size(), ExcInternalError());
423
424 // Last, do the linear regression.
425 float regularity = std::numeric_limits<float>::infinity();
426 if (res.first.size() > 1)
427 {
428 // Prepare linear equation for the logarithmic least squares
429 // fit.
430 //
431 // First, calculate ln(|k|).
432 //
433 // For Fourier expansion, this translates to
434 // ln(2*pi*sqrt(predicate)) = ln(2*pi) + 0.5*ln(predicate).
435 // Since we are just interested in the slope of a linear
436 // regression later, we omit the ln(2*pi) factor.
437 ln_k.resize(res.first.size());
438 for (unsigned int f = 0; f < res.first.size(); ++f)
439 ln_k[f] = 0.5 * std::log(static_cast<double>(res.first[f]));
440
441 // Second, calculate ln(U_k).
442 for (auto &residual_element : res.second)
443 residual_element = std::log(residual_element);
444
445 const std::pair<double, double> fit =
446 FESeries::linear_regression(ln_k, res.second);
447 // Compute regularity s = mu - dim/2
448 regularity = static_cast<float>(-fit.first) -
449 ((dim > 1) ? (.5 * dim) : 0);
450 }
451
452 // Store result in the vector of estimated values for each cell.
453 smoothness_indicators(cell->active_cell_index()) = regularity;
454 }
455 else
456 smoothness_indicators(cell->active_cell_index()) =
457 numbers::signaling_nan<float>();
458 }
459 }
460
461
462
463 template <int dim, int spacedim, typename VectorType>
464 void
467 const DoFHandler<dim, spacedim> &dof_handler,
468 const VectorType &solution,
469 Vector<float> &smoothness_indicators,
470 const ComponentMask &coefficients_predicate,
471 const double smallest_abs_coefficient,
472 const bool only_flagged_cells)
473 {
474 Assert(smallest_abs_coefficient >= 0.,
475 ExcMessage("smallest_abs_coefficient should be non-negative."));
476
477 using number = typename VectorType::value_type;
478 using number_coeff =
480
481 smoothness_indicators.reinit(
482 dof_handler.get_triangulation().n_active_cells());
483
484 unsigned int n_modes;
485 Table<dim, number_coeff> expansion_coefficients;
486 Vector<number> local_dof_values;
487
488 // auxiliary vector to do linear regression
489 const unsigned int max_degree =
490 dof_handler.get_fe_collection().max_degree();
491
492 std::vector<double> x, y;
493 x.reserve(max_degree);
494 y.reserve(max_degree);
495
496 for (const auto &cell : dof_handler.active_cell_iterators() |
498 {
499 if (!only_flagged_cells || cell->refine_flag_set() ||
500 cell->coarsen_flag_set())
501 {
502 n_modes = fe_fourier.get_n_coefficients_per_direction(
503 cell->active_fe_index());
504 resize(expansion_coefficients, n_modes);
505
506 const unsigned int pe = cell->get_fe().degree;
507 Assert(pe > 0, ExcInternalError());
508
509 // since we use coefficients with indices [1,pe] in each
510 // direction, the number of coefficients we need to calculate is
511 // at least N=pe+1
512 AssertIndexRange(pe, n_modes);
513
514 local_dof_values.reinit(cell->get_fe().n_dofs_per_cell());
515 cell->get_dof_values(solution, local_dof_values);
516
517 fe_fourier.calculate(local_dof_values,
518 cell->active_fe_index(),
519 expansion_coefficients);
520
521 // choose the smallest decay of coefficients in each direction,
522 // i.e. the maximum decay slope k_v as in exp(-k_v)
523 double k_v = std::numeric_limits<double>::max();
524 for (unsigned int d = 0; d < dim; ++d)
525 {
526 x.resize(0);
527 y.resize(0);
528
529 // will use all non-zero coefficients allowed by the
530 // predicate function
531 //
532 // skip i=0 because of logarithm
533 for (unsigned int i = 1; i <= pe; ++i)
534 if (coefficients_predicate[i])
535 {
537 ind[d] = i;
538 const double coeff_abs =
539 std::abs(expansion_coefficients(ind));
540
541 if (coeff_abs > smallest_abs_coefficient)
542 {
543 x.push_back(std::log(i));
544 y.push_back(std::log(coeff_abs));
545 }
546 }
547
548 // in case we don't have enough non-zero coefficient to fit,
549 // skip this direction
550 if (x.size() < 2)
551 continue;
552
553 const std::pair<double, double> fit =
555
556 // decay corresponds to negative slope
557 // take the lesser negative slope along each direction
558 k_v = std::min(k_v, -fit.first);
559 }
560
561 smoothness_indicators(cell->active_cell_index()) =
562 static_cast<float>(k_v);
563 }
564 else
565 smoothness_indicators(cell->active_cell_index()) =
566 numbers::signaling_nan<float>();
567 }
568 }
569
570
571
572 template <int dim, int spacedim>
575 const unsigned int component)
576 {
577 // Default number of coefficients per direction.
578 //
579 // Since we omit the zero-th mode in the Fourier decay strategy, make sure
580 // that we have at least two modes to work with per finite element. With a
581 // number of modes equal to the polynomial degree plus two for each finite
582 // element, the smoothness estimation algorithm tends to produce stable
583 // results.
584 std::vector<unsigned int> n_coefficients_per_direction;
585 n_coefficients_per_direction.reserve(fe_collection.size());
586 for (unsigned int i = 0; i < fe_collection.size(); ++i)
587 n_coefficients_per_direction.push_back(fe_collection[i].degree + 2);
588
589 // Default quadrature collection.
590 //
591 // We initialize a series expansion object object which will be used to
592 // calculate the expansion coefficients. In addition to the
593 // hp::FECollection, we need to provide quadrature rules hp::QCollection
594 // for integration on the reference cell.
595 // We will need to assemble the expansion matrices for each of the finite
596 // elements we deal with, i.e. the matrices F_k,j. We have to do that for
597 // each of the finite elements in use. To that end we need a quadrature
598 // rule. As a default, we use the same quadrature formula for each finite
599 // element, namely one that is obtained by iterating a 5-point Gauss
600 // formula as many times as the maximal exponent we use for the term
601 // exp(ikx). Since the first mode corresponds to k = 0, the maximal wave
602 // number is k = n_modes - 1.
603 const QGauss<1> base_quadrature(5);
604 hp::QCollection<dim> q_collection;
605 for (unsigned int i = 0; i < fe_collection.size(); ++i)
606 {
607 const QIterated<dim> quadrature(base_quadrature,
608 n_coefficients_per_direction[i] - 1);
609 const QSorted<dim> quadrature_sorted(quadrature);
610 q_collection.push_back(quadrature_sorted);
611 }
612
613 return FESeries::Fourier<dim, spacedim>(n_coefficients_per_direction,
614 fe_collection,
615 q_collection,
616 component);
617 }
618 } // namespace Fourier
619} // namespace SmoothnessEstimator
620
621
622// explicit instantiations
623#include "numerics/smoothness_estimator.inst"
624
const hp::FECollection< dim, spacedim > & get_fe_collection() const
const Triangulation< dim, spacedim > & get_triangulation() const
void calculate(const ::Vector< Number > &local_dof_values, const unsigned int cell_active_fe_index, Table< dim, CoefficientType > &fourier_coefficients)
typename std::complex< double > CoefficientType
Definition fe_series.h:91
unsigned int get_n_coefficients_per_direction(const unsigned int index) const
unsigned int get_n_coefficients_per_direction(const unsigned int index) const
void calculate(const ::Vector< Number > &local_dof_values, const unsigned int cell_active_fe_index, Table< dim, CoefficientType > &legendre_coefficients)
unsigned int n_active_cells() const
virtual void reinit(const size_type N, const bool omit_zeroing_entries=false)
unsigned int size() const
Definition collection.h:314
unsigned int max_degree() const
void push_back(const Quadrature< dim_in > &new_quadrature)
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
IteratorRange< active_cell_iterator > active_cell_iterators() const
#define Assert(cond, exc)
#define AssertIndexRange(index, range)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
std::size_t size
Definition mpi.cc:733
std::pair< double, double > linear_regression(const std::vector< double > &x, const std::vector< double > &y)
Definition fe_series.cc:27
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
FESeries::Fourier< dim, spacedim > default_fe_series(const hp::FECollection< dim, spacedim > &fe_collection, const unsigned int component=numbers::invalid_unsigned_int)
void coefficient_decay_per_direction(FESeries::Fourier< dim, spacedim > &fe_fourier, const DoFHandler< dim, spacedim > &dof_handler, const VectorType &solution, Vector< float > &smoothness_indicators, const ComponentMask &coefficients_predicate={}, const double smallest_abs_coefficient=1e-10, const bool only_flagged_cells=false)
void coefficient_decay(FESeries::Fourier< dim, spacedim > &fe_fourier, const DoFHandler< dim, spacedim > &dof_handler, const VectorType &solution, Vector< float > &smoothness_indicators, const VectorTools::NormType regression_strategy=VectorTools::Linfty_norm, const double smallest_abs_coefficient=1e-10, const bool only_flagged_cells=false)
FESeries::Legendre< dim, spacedim > default_fe_series(const hp::FECollection< dim, spacedim > &fe_collection, const unsigned int component=numbers::invalid_unsigned_int)
void coefficient_decay(FESeries::Legendre< dim, spacedim > &fe_legendre, const DoFHandler< dim, spacedim > &dof_handler, const VectorType &solution, Vector< float > &smoothness_indicators, const VectorTools::NormType regression_strategy=VectorTools::Linfty_norm, const double smallest_abs_coefficient=1e-10, const bool only_flagged_cells=false)
void coefficient_decay_per_direction(FESeries::Legendre< dim, spacedim > &fe_legendre, const DoFHandler< dim, spacedim > &dof_handler, const VectorType &solution, Vector< float > &smoothness_indicators, const ComponentMask &coefficients_predicate={}, const double smallest_abs_coefficient=1e-10, const bool only_flagged_cells=false)
::VectorizedArray< Number, width > log(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)