libMesh
smoothness_estimator.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 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 
38 namespace libMesh
39 {
40 
41 
42 //-----------------------------------------------------------------
43 // SmoothnessEstimator implementations
45  _extra_order(1)
46 {
47 }
48 
49 
50 
51 std::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 
135 void 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.
213 for (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);
246  DenseVector<Number> Pu_h;
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
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
Order
defines an enum for polynomial orders.
Definition: enum_order.h:40
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition: dof_map.C:2229
void parallel_for(const Range &range, const Body &body)
Execute the provided function object in parallel on the specified range.
Definition: threads_none.h:73
unsigned int dim
The ErrorVector is a specialization of the StatisticsVector for error data computed on a finite eleme...
Definition: error_vector.h:50
static std::vector< Real > legepoly(const unsigned int dim, const Order order, const Point p, const unsigned int matsize)
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
const FEType & variable_type(const unsigned int c) const
Definition: dof_map.h:2230
void sum(T &r) const
MeshBase & mesh
int _extra_order
Extra order to use for quadrature rule.
const Parallel::Communicator & comm() const
OrderWrapper order
The approximation order of the element.
Definition: fe_type.h:215
The StoredRange class defines a contiguous, divisible set of objects.
Definition: stored_range.h:54
The libMesh namespace provides an interface to certain functionality in the library.
const Number zero
.
Definition: libmesh.h:304
const MeshBase & get_mesh() const
Definition: system.h:2358
This is the MeshBase class.
Definition: mesh_base.h:75
StoredRange< MeshBase::const_element_iterator, const Elem * > ConstElemRange
Definition: elem_range.h:34
Number current_solution(const dof_id_type global_dof_number) const
Definition: system.C:165
This class handles the numbering of degrees of freedom on a mesh.
Definition: dof_map.h:179
std::unique_ptr< QBase > default_quadrature_rule(const unsigned int dim, const int extraorder=0) const
Definition: fe_type.C:34
void operator()(const ConstElemRange &range) const
unsigned int n_vars
static Real compute_slope(int N, Real Sx, Real Sy, Real Sxx, Real Sxy)
Computes slop in a linear regression.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:96
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1593
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
auto norm(const T &a) -> decltype(std::abs(a))
Definition: tensor_tools.h:74
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...
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual void swap(NumericVector< T > &v)
Swaps the contents of this with v.
virtual unsigned int size() const override final
Definition: dense_vector.h:104
void reduce_smoothness(std::vector< ErrorVectorReal > &error_per_cell, const Parallel::Communicator &comm)
auto norm(const libMesh::TypeVector< T > &vector) -> decltype(std::norm(T()))
Definition: type_vector.h:1206
unsigned int n_vars() const
Definition: system.h:2430
const DofMap & get_dof_map() const
Definition: system.h:2374
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
uint8_t dof_id_type
Definition: id_types.h:67
spin_mutex spin_mtx
A convenient spin mutex object which can be used for obtaining locks.
Definition: threads.C:30