libMesh
Loading...
Searching...
No Matches
smoothness_estimator.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19// libmesh includes
20#include "libmesh/libmesh_common.h"
21#include "libmesh/smoothness_estimator.h"
22#include "libmesh/dof_map.h"
23#include "libmesh/fe_base.h"
24#include "libmesh/error_vector.h"
25#include "libmesh/libmesh_logging.h"
26#include "libmesh/elem.h"
27#include "libmesh/quadrature.h"
28#include "libmesh/system.h"
29#include "libmesh/mesh_base.h"
30#include "libmesh/numeric_vector.h"
31#include "libmesh/enum_to_string.h"
32
33// C++ includes
34#include <algorithm> // for std::fill
35#include <cstdlib> // *must* precede <cmath> for proper std:abs() on PGI, Sun Studio CC
36#include <cmath> // for std::sqrt std::pow std::abs
37
38namespace libMesh
39{
40
41
42//-----------------------------------------------------------------
43// SmoothnessEstimator implementations
45 _extra_order(1)
46{
47}
48
49
50
51std::vector<Real> SmoothnessEstimator::legepoly(const unsigned int dim,
52 const Order order,
53 const Point p,
54 const unsigned int matsize)
55{
56 std::vector<Real> psi;
57 psi.reserve(matsize);
58
59 // Evaluate 1D Legendre polynomials at x, y, z up to order
60 const int npols = static_cast<int>(order) + 1;
61
62 std::vector<Real> Lx(npols, 0.), Ly(npols, 1.), Lz(npols, 1.);
63 const Real x = p(0);
64 Lx[0] = 1.;
65 if (npols > 1)
66 Lx[1] = x;
67 for (int n = 2; n < npols; ++n)
68 Lx[n] = ((2. * n - 1) * x * Lx[n - 1] - (n - 1) * Lx[n - 2]) / n;
69
70 if (dim > 1)
71 {
72 const Real y = p(1);
73 Ly[0] = 1.;
74 if (npols > 1)
75 Ly[1] = y;
76 for (int n = 2; n < npols; ++n)
77 Ly[n] = ((2. * n - 1) * y * Ly[n - 1] - (n - 1) * Ly[n - 2]) / n;
78 }
79
80 if (dim > 2)
81 {
82 const Real z = p(2);
83 Lz[0] = 1.;
84 if (npols > 1)
85 Lz[1] = z;
86 for (int n = 2; n < npols; ++n)
87 Lz[n] = ((2. * n - 1) * z * Lz[n - 1] - (n - 1) * Lz[n - 2]) / n;
88 }
89
90 // Loop over total degree and build tensor-product Legendre basis
91 for (unsigned int poly_deg = 0; poly_deg <= static_cast<unsigned int>(order); poly_deg++)
92 {
93 switch (dim)
94 {
95 case 3:
96 for (int i = poly_deg; i >= 0; --i)
97 for (int j = poly_deg - i; j >= 0; --j)
98 {
99 int k = poly_deg - i - j;
100 psi.push_back(Lx[i] * Ly[j] * Lz[k]);
101 }
102 break;
103
104 case 2:
105 for (int i = poly_deg; i >= 0; --i)
106 {
107 int j = poly_deg - i;
108 psi.push_back(Lx[i] * Ly[j]);
109 }
110 break;
111
112 case 1:
113 psi.push_back(Lx[poly_deg]);
114 break;
115
116 default:
117 libmesh_error_msg("Invalid dimension dim " << dim);
118 }
119 }
120
121 return psi;
122}
123
125{
126 const Real denom = (N * Sxx - Sx * Sx);
127 // Triggers for first order polynomials when there only 1 point
128 // available at log |c_k| and log |k| space to fit.
129 if (std::abs(denom) < std::numeric_limits<Real>::epsilon()) {
130 return std::numeric_limits<Real>::max();
131 }
132 return (N * Sxy - Sx * Sy) / denom;
133}
134
135void SmoothnessEstimator::reduce_smoothness (std::vector<ErrorVectorReal> & smoothness_per_cell,
136 const Parallel::Communicator & comm)
137{
138 // Aggregates element-wise contributions computed
139 // in parallel across all processors
140 comm.sum(smoothness_per_cell);
141}
142
144 ErrorVector & smoothness_per_cell,
145 const NumericVector<Number> * solution_vector)
146{
147 LOG_SCOPE("estimate_smoothness()", "SmoothnessEstimator");
148
149 // The current mesh
150 const MeshBase & mesh = system.get_mesh();
151
152 // Resize the smoothness_per_cell vector to be
153 // the number of elements, initialize it to 0.
154 smoothness_per_cell.resize (mesh.max_elem_id());
155 std::fill (smoothness_per_cell.begin(), smoothness_per_cell.end(), 0.);
156
157 // Prepare current_local_solution to localize a non-standard
158 // solution vector if necessary
159 if (solution_vector && solution_vector != system.solution.get())
160 {
161 NumericVector<Number> * newsol =
162 const_cast<NumericVector<Number> *>(solution_vector);
163 System & sys = const_cast<System &>(system);
164 newsol->swap(*sys.solution);
165 sys.update();
166 }
167
168 //------------------------------------------------------------
169 // Iterate over all the active elements in the mesh
170 // that live on this processor.
171 Threads::parallel_for (ConstElemRange(mesh.active_local_elements_begin(),
172 mesh.active_local_elements_end(),
173 200),
174 EstimateSmoothness(system,
175 *this,
176 smoothness_per_cell)
177 );
178
179 // Each processor has now computed the smoothness for its local
180 // elements, and smoothness_per_cell contains 0 for all the
181 // non-local elements. Summing the vector will provide the true
182 // value for each element, local or remote
183 this->reduce_smoothness (smoothness_per_cell, system.comm());
184
185 // If we used a non-standard solution before, now is the time to fix
186 // the current_local_solution
187 if (solution_vector && solution_vector != system.solution.get())
188 {
189 NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector);
190 System & sys = const_cast<System &>(system);
191 newsol->swap(*sys.solution);
192 sys.update();
193 }
194}
195
196
198{
199 // The current mesh
200 const MeshBase & mesh = system.get_mesh();
201
202 // The dimensionality of the mesh
203 const unsigned int dim = mesh.mesh_dimension();
204
205 // The number of variables in the system
206 const unsigned int n_vars = system.n_vars();
207
208 // The DofMap for this system
209 const DofMap & dof_map = system.get_dof_map();
210
211 //------------------------------------------------------------
212 // Iterate over all the elements in the range.
213for (const auto & elem : range)
214{
215 const dof_id_type e_id = elem->id();
216 // Loop over each variable
217 for (unsigned int var=0; var<n_vars; var++)
218 {
219 const FEType & fe_type = dof_map.variable_type(var);
220 const Order element_order = fe_type.order + elem->p_level();
221
222 std::unique_ptr<FEBase> fe(FEBase::build(dim, fe_type));
223 std::unique_ptr<QBase> qrule(fe_type.default_quadrature_rule(dim, smoothness_estimator._extra_order));
224 fe->attach_quadrature_rule(qrule.get());
225
226 const std::vector<Real> & JxW = fe->get_JxW();
227 const std::vector<Point> & q_point = fe->get_xyz();
228 const std::vector<std::vector<Real>> * phi = &(fe->get_phi());
229
230 std::vector<dof_id_type> dof_indices;
231
232 unsigned int matsize = element_order + 1;
233 if (dim > 1)
234 {
235 matsize *= (element_order + 2);
236 matsize /= 2;
237 }
238 if (dim > 2)
239 {
240 matsize *= (element_order + 3);
241 matsize /= 3;
242 }
243
244 DenseMatrix<Number> Kp(matsize, matsize);
247
248 F.resize(matsize);
249 Pu_h.resize(matsize);
250
251
252 fe->reinit(elem);
253
254 dof_map.dof_indices(elem, dof_indices, var);
255 libmesh_assert_equal_to(dof_indices.size(), phi->size());
256
257 const unsigned int n_dofs = cast_int<unsigned int>(dof_indices.size());
258 const unsigned int n_qp = qrule->n_points();
259
260 for (unsigned int qp = 0; qp < n_qp; qp++)
261 {
262 std::vector<Real> psi = legepoly(dim, element_order, q_point[qp], matsize);
263 const unsigned int psi_size = cast_int<unsigned int>(psi.size());
264
265 for (unsigned int i = 0; i < matsize; i++)
266 for (unsigned int j = 0; j < matsize; j++)
267 Kp(i, j) += JxW[qp] * psi[i] * psi[j];
268
269 Number u_h = libMesh::zero;
270 for (unsigned int i = 0; i < n_dofs; i++)
271 u_h += (*phi)[i][qp] * system.current_solution(dof_indices[i]);
272
273 for (unsigned int i = 0; i < psi_size; i++)
274 F(i) += JxW[qp] * u_h * psi[i];
275 }
276
277 Kp.lu_solve(F, Pu_h);
278
279 // Generate index to total degree map. Total_degree_per_index[i] gives the degree ith basis function
280 std::vector<unsigned int> total_degree_per_index;
281
282 for (unsigned int poly_deg = 0; poly_deg <= static_cast<unsigned int>(element_order); ++poly_deg)
283 {
284 switch (dim)
285 {
286 case 3:
287 for (int i = poly_deg; i >= 0; --i)
288 for (int j = poly_deg - i; j >= 0; --j)
289 {
290 int k = poly_deg - i - j;
291 total_degree_per_index.push_back(i + j + k);
292 }
293 break;
294
295 case 2:
296 for (int i = poly_deg; i >= 0; --i)
297 {
298 int j = poly_deg - i;
299 total_degree_per_index.push_back(i + j);
300 }
301 break;
302
303 case 1:
304 total_degree_per_index.push_back(poly_deg);
305 break;
306
307 default:
308 libmesh_error_msg("Invalid dimension dim " << dim);
309 }
310 }
311
312 // Group coefficients |c_k| by degree k
313 std::map<unsigned int, std::vector<Number>> coeff_by_degree;
314
315 for (unsigned int i = 0; i < Pu_h.size(); ++i)
316 {
317 unsigned int degree = total_degree_per_index[i];
318 // Excluding the constant mode i.e, zeroth order coefficient as we use ln|order| later
319 // Constant order term often dominates the scale and doesn't inform about regularity.
320 if (degree > 0)
321 coeff_by_degree[degree].push_back(Pu_h(i));
322 }
323
324 // Compute L2 norm of each group |c_k|
325 std::vector<unsigned int> degrees;
326 std::vector<double> norms;
327
328 for (const auto & pair : coeff_by_degree)
329 {
330 unsigned int deg = pair.first;
331 const std::vector<Number> & coeffs = pair.second;
332
333 double norm = 0.;
334 for (const auto & c : coeffs)
335 norm += std::norm(c);
336
337 norm = std::sqrt(norm);
338
339 degrees.push_back(deg);
340 norms.push_back(norm);
341 }
342
343
344 // Collect log |c_k| and log |k|
345 std::vector<double> log_norms, log_degrees;
346
347 for (unsigned int i = 0; i < degrees.size(); ++i)
348 {
349 // filter tiny terms
350 if (norms[i] > 1e-12)
351 {
352 log_degrees.push_back(std::log(static_cast<double>(degrees[i])));
353 log_norms.push_back(std::log(norms[i]));
354 }
355 }
356
357
358 // Fit log-log line - we use least-squares fit
359 double Sx = 0., Sy = 0., Sxx = 0., Sxy = 0.;
360 const size_t N = log_degrees.size();
361
362 for (size_t i = 0; i < N; ++i)
363 {
364 Sx += log_degrees[i];
365 Sy += log_norms[i];
366 Sxx += log_degrees[i] * log_degrees[i];
367 Sxy += log_degrees[i] * log_norms[i];
368 }
369
370 const double regularity = -compute_slope(N, Sx, Sy, Sxx, Sxy);
371 // const double intercept = (Sy - slope * Sx) / N;
372
373 Threads::spin_mutex::scoped_lock acquire(Threads::spin_mtx);
374 smoothness_per_cell[e_id] = regularity;
375 } // end variables loop
376
377} // end element loop
378
379} // End () operator definition
380
381} // namespace libMesh
unsigned int n_vars
unsigned int dim
Defines a dense matrix for use in Finite Element-type computations.
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
Defines a dense vector for use in Finite Element-type computations.
void resize(const unsigned int n)
Resize the vector.
virtual unsigned int size() const override final
This class handles the numbering of degrees of freedom on a mesh.
Definition dof_map.h:181
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition dof_map.C:2201
const FEType & variable_type(const unsigned int i) const
Definition dof_map.h:2388
The ErrorVector is a specialization of the StatisticsVector for error data computed on a finite eleme...
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition fe_type.h:197
std::unique_ptr< QBase > default_quadrature_rule(const unsigned int dim, const int extraorder=0) const
Definition fe_type.C:34
OrderWrapper order
The approximation order of the element (at 0 p-refinement level).
Definition fe_type.h:203
This is the MeshBase class.
Definition mesh_base.h:81
unsigned int mesh_dimension() const
Definition mesh_base.C:430
Provides a uniform interface to vector storage schemes for different linear algebra libraries.
virtual void swap(NumericVector< T > &v)
Swaps the contents of this with v.
const Parallel::Communicator & comm() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
Class to compute the error contribution for a range of elements.
void operator()(const ConstElemRange &range) const
static std::vector< Real > legepoly(const unsigned int dim, const Order order, const Point p, const unsigned int matsize)
virtual void estimate_smoothness(const System &system, ErrorVector &smoothness_per_cell, const NumericVector< Number > *solution_vector=nullptr)
This function uses the Legendre expansion of solution to estimate coefficient decay to quantify the s...
static Real compute_slope(int N, Real Sx, Real Sy, Real Sxx, Real Sxy)
Computes slop in a linear regression.
int _extra_order
Extra order to use for quadrature rule.
void reduce_smoothness(std::vector< ErrorVectorReal > &error_per_cell, const Parallel::Communicator &comm)
The StoredRange class defines a contiguous, divisible set of objects.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
Number current_solution(const dof_id_type global_dof_number) const
Definition system.C:162
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition system.h:1655
unsigned int n_vars() const
Definition system.C:2674
const DofMap & get_dof_map() const
Definition system.h:2417
const MeshBase & get_mesh() const
Definition system.h:2401
MeshBase & mesh
spin_mutex spin_mtx
A convenient spin mutex object which can be used for obtaining locks.
Definition threads.C:30
void parallel_for(const Range &range, const Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided function object in parallel on the specified range.
The libMesh namespace provides an interface to certain functionality in the library.
StoredRange< MeshBase::const_element_iterator, const Elem * > ConstElemRange
Definition elem_range.h:34
const Number zero
.
Definition libmesh.h:297
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto norm(const libMesh::TypeVector< T > &vector) -> decltype(std::norm(T()))