LCOV - code coverage report
Current view: top level - src/error_estimation - smoothness_estimator.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4640 (c5f9f0) with base a68dd6 Lines: 141 176 80.1 %
Date: 2026-11-02 23:02:27 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      38             : namespace libMesh
      39             : {
      40             : 
      41             : 
      42             : //-----------------------------------------------------------------
      43             : // SmoothnessEstimator implementations
      44        2414 : SmoothnessEstimator::SmoothnessEstimator() :
      45        2414 :     _extra_order(1)
      46             : {
      47        2414 : }
      48             : 
      49             : 
      50             : 
      51       50176 : std::vector<Real> SmoothnessEstimator::legepoly(const unsigned int dim,
      52             :                                                 const Order order,
      53             :                                                 const Point p,
      54             :                                                 const unsigned int matsize)
      55             : {
      56        4186 :   std::vector<Real> psi;
      57       50176 :   psi.reserve(matsize);
      58             : 
      59             :   // Evaluate 1D Legendre polynomials at x, y, z up to order
      60       50176 :   const int npols = static_cast<int>(order) + 1;
      61             : 
      62       54362 :   std::vector<Real> Lx(npols, 0.), Ly(npols, 1.), Lz(npols, 1.);
      63       50176 :   const Real x = p(0);
      64       50176 :   Lx[0] = 1.;
      65       50176 :   if (npols > 1)
      66       50176 :     Lx[1] = x;
      67      124180 :   for (int n = 2; n < npols; ++n)
      68       92544 :     Lx[n] = ((2. * n - 1) * x * Lx[n - 1] - (n - 1) * Lx[n - 2]) / n;
      69             : 
      70       50176 :   if (dim > 1)
      71             :   {
      72       40320 :     const Real y = p(1);
      73       40320 :     Ly[0] = 1.;
      74       40320 :     if (npols > 1)
      75       40320 :       Ly[1] = y;
      76       80640 :     for (int n = 2; n < npols; ++n)
      77       50400 :       Ly[n] = ((2. * n - 1) * y * Ly[n - 1] - (n - 1) * Ly[n - 2]) / n;
      78             :   }
      79             : 
      80       41972 :   if (dim > 2)
      81             :   {
      82           0 :     const Real z = p(2);
      83           0 :     Lz[0] = 1.;
      84           0 :     if (npols > 1)
      85           0 :       Lz[1] = z;
      86           0 :     for (int n = 2; n < npols; ++n)
      87           0 :       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      224532 :   for (unsigned int poly_deg = 0; poly_deg <= static_cast<unsigned int>(order); poly_deg++)
      92             :   {
      93      174356 :     switch (dim)
      94             :     {
      95           0 :       case 3:
      96           0 :         for (int i = poly_deg; i >= 0; --i)
      97           0 :           for (int j = poly_deg - i; j >= 0; --j)
      98             :           {
      99           0 :             int k = poly_deg - i - j;
     100           0 :             psi.push_back(Lx[i] * Ly[j] * Lz[k]);
     101           0 :           }
     102           0 :         break;
     103             : 
     104      120960 :       case 2:
     105      362880 :         for (int i = poly_deg; i >= 0; --i)
     106             :         {
     107      241920 :           int j = poly_deg - i;
     108      282240 :           psi.push_back(Lx[i] * Ly[j]);
     109       20160 :         }
     110       10080 :         break;
     111             : 
     112       53396 :       case 1:
     113       57868 :         psi.push_back(Lx[poly_deg]);
     114        4472 :         break;
     115             : 
     116           0 :       default:
     117           0 :         libmesh_error_msg("Invalid dimension dim " << dim);
     118             :     }
     119             :   }
     120             : 
     121       54362 :   return psi;
     122             : }
     123             : 
     124        4166 : Real SmoothnessEstimator::compute_slope(int N, Real Sx, Real Sy, Real Sxx, Real Sxy)
     125             : {
     126        4166 :     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        4166 :     if (std::abs(denom) < std::numeric_limits<Real>::epsilon()) {
     130           0 :         return std::numeric_limits<Real>::max();
     131             :     }
     132        4166 :     return (N * Sxy - Sx * Sy) / denom;
     133             : }
     134             : 
     135        2414 : 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        2414 :   comm.sum(smoothness_per_cell);
     141        2414 : }
     142             : 
     143        2414 : void SmoothnessEstimator::estimate_smoothness (const System & system,
     144             :                                                   ErrorVector & smoothness_per_cell,
     145             :                                                   const NumericVector<Number> * solution_vector)
     146             : {
     147         204 :   LOG_SCOPE("estimate_smoothness()", "SmoothnessEstimator");
     148             : 
     149             :   // The current mesh
     150         136 :   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        2414 :   smoothness_per_cell.resize (mesh.max_elem_id());
     155          68 :   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        2414 :   if (solution_vector && solution_vector != system.solution.get())
     160             :     {
     161           0 :       NumericVector<Number> * newsol =
     162             :         const_cast<NumericVector<Number> *>(solution_vector);
     163           0 :       System & sys = const_cast<System &>(system);
     164           0 :       newsol->swap(*sys.solution);
     165           0 :       sys.update();
     166             :     }
     167             : 
     168             :   //------------------------------------------------------------
     169             :   // Iterate over all the active elements in the mesh
     170             :   // that live on this processor.
     171        2482 :   Threads::parallel_for (mesh.active_local_element_stored_range(),
     172        2346 :                          EstimateSmoothness(system,
     173             :                                        *this,
     174             :                                        smoothness_per_cell)
     175             :                          );
     176             : 
     177             :   // Each processor has now computed the smoothness for its local
     178             :   // elements, and smoothness_per_cell contains 0 for all the
     179             :   // non-local elements.  Summing the vector will provide the true
     180             :   // value for each element, local or remote
     181        2414 :   this->reduce_smoothness (smoothness_per_cell, system.comm());
     182             : 
     183             :   // If we used a non-standard solution before, now is the time to fix
     184             :   // the current_local_solution
     185        2414 :   if (solution_vector && solution_vector != system.solution.get())
     186             :     {
     187           0 :       NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector);
     188           0 :       System & sys = const_cast<System &>(system);
     189           0 :       newsol->swap(*sys.solution);
     190           0 :       sys.update();
     191             :     }
     192        2414 : }
     193             : 
     194             : 
     195        2414 : void SmoothnessEstimator::EstimateSmoothness::operator()(const ConstElemRange & range) const
     196             : {
     197             :   // The current mesh
     198        2414 :   const MeshBase & mesh = system.get_mesh();
     199             : 
     200             :   // The dimensionality of the mesh
     201        2414 :   const unsigned int dim = mesh.mesh_dimension();
     202             : 
     203             :   // The number of variables in the system
     204        2414 :   const unsigned int n_vars = system.n_vars();
     205             : 
     206             :   // The DofMap for this system
     207        2414 :   const DofMap & dof_map = system.get_dof_map();
     208             : 
     209             :   //------------------------------------------------------------
     210             :   // Iterate over all the elements in the range.
     211        6580 : for (const auto & elem : range)
     212             : {
     213        4166 :   const dof_id_type e_id = elem->id();
     214             :   // Loop over each variable
     215        8332 :   for (unsigned int var=0; var<n_vars; var++)
     216             :   {
     217         348 :     const FEType & fe_type = dof_map.variable_type(var);
     218        4166 :     const Order element_order = fe_type.order + elem->p_level();
     219             : 
     220        4514 :     std::unique_ptr<FEBase> fe(FEBase::build(dim, fe_type));
     221        4514 :     std::unique_ptr<QBase> qrule(fe_type.default_quadrature_rule(dim, smoothness_estimator._extra_order));
     222        4514 :     fe->attach_quadrature_rule(qrule.get());
     223             : 
     224        1044 :     const std::vector<Real> & JxW = fe->get_JxW();
     225        1044 :     const std::vector<Point> & q_point = fe->get_xyz();
     226         348 :     const std::vector<std::vector<Real>> * phi = &(fe->get_phi());
     227             : 
     228         696 :     std::vector<dof_id_type> dof_indices;
     229             : 
     230        4166 :     unsigned int matsize = element_order + 1;
     231        4166 :     if (dim > 1)
     232             :     {
     233        2520 :       matsize *= (element_order + 2);
     234        2520 :       matsize /= 2;
     235             :     }
     236        2796 :     if (dim > 2)
     237             :     {
     238           0 :       matsize *= (element_order + 3);
     239           0 :       matsize /= 3;
     240             :     }
     241             : 
     242        4862 :     DenseMatrix<Number> Kp(matsize, matsize);
     243        4166 :     DenseVector<Number> F;
     244        4166 :     DenseVector<Number> Pu_h;
     245             : 
     246        3818 :     F.resize(matsize);
     247        3818 :     Pu_h.resize(matsize);
     248             : 
     249             : 
     250        4166 :     fe->reinit(elem);
     251             : 
     252        4166 :     dof_map.dof_indices(elem, dof_indices, var);
     253         348 :     libmesh_assert_equal_to(dof_indices.size(), phi->size());
     254             : 
     255         696 :     const unsigned int n_dofs = cast_int<unsigned int>(dof_indices.size());
     256         348 :     const unsigned int n_qp   = qrule->n_points();
     257             : 
     258       54342 :     for (unsigned int qp = 0; qp < n_qp; qp++)
     259             :     {
     260       58548 :       std::vector<Real> psi = legepoly(dim, element_order, q_point[qp], matsize);
     261        8372 :       const unsigned int psi_size = cast_int<unsigned int>(psi.size());
     262             : 
     263      345492 :       for (unsigned int i = 0; i < matsize; i++)
     264     2065960 :         for (unsigned int j = 0; j < matsize; j++)
     265     2361316 :           Kp(i, j) += JxW[qp] * psi[i] * psi[j];
     266             : 
     267        4186 :       Number u_h = libMesh::zero;
     268      466452 :       for (unsigned int i = 0; i < n_dofs; i++)
     269      520412 :         u_h += (*phi)[i][qp] * system.current_solution(dof_indices[i]);
     270             : 
     271      345492 :       for (unsigned int i = 0; i < psi_size; i++)
     272      369212 :         F(i) += JxW[qp] * u_h * psi[i];
     273             :     }
     274             : 
     275        4166 :     Kp.lu_solve(F, Pu_h);
     276             : 
     277             :     // Generate index to total degree map. Total_degree_per_index[i] gives the degree ith basis function
     278         696 :     std::vector<unsigned int> total_degree_per_index;
     279             : 
     280       19936 :     for (unsigned int poly_deg = 0; poly_deg <= static_cast<unsigned int>(element_order); ++poly_deg)
     281             :     {
     282       15770 :       switch (dim)
     283             :       {
     284           0 :         case 3:
     285           0 :           for (int i = poly_deg; i >= 0; --i)
     286           0 :             for (int j = poly_deg - i; j >= 0; --j)
     287             :             {
     288           0 :               int k = poly_deg - i - j;
     289           0 :               total_degree_per_index.push_back(i + j + k);
     290           0 :             }
     291           0 :           break;
     292             : 
     293        7560 :         case 2:
     294       22680 :           for (int i = poly_deg; i >= 0; --i)
     295             :           {
     296        2520 :             int j = poly_deg - i;
     297       15120 :             total_degree_per_index.push_back(i + j);
     298        1260 :           }
     299         630 :           break;
     300             : 
     301        8210 :         case 1:
     302        8210 :           total_degree_per_index.push_back(poly_deg);
     303         688 :           break;
     304             : 
     305           0 :         default:
     306           0 :           libmesh_error_msg("Invalid dimension dim " << dim);
     307             :       }
     308             :     }
     309             : 
     310             :     // Group coefficients |c_k| by degree k
     311         696 :     std::map<unsigned int, std::vector<Number>> coeff_by_degree;
     312             : 
     313       27496 :     for (unsigned int i = 0; i < Pu_h.size(); ++i)
     314             :     {
     315       23330 :       unsigned int degree = total_degree_per_index[i];
     316             :       // Excluding the constant mode i.e, zeroth order coefficient as we use ln|order| later
     317             :       // Constant order term often dominates the scale and doesn't inform about regularity.
     318       23330 :       if (degree > 0)
     319       19164 :         coeff_by_degree[degree].push_back(Pu_h(i));
     320             :     }
     321             : 
     322             :     // Compute L2 norm of each group |c_k|
     323         696 :     std::vector<unsigned int> degrees;
     324         696 :     std::vector<double> norms;
     325             : 
     326       15770 :     for (const auto & pair : coeff_by_degree)
     327             :     {
     328       11604 :       unsigned int deg = pair.first;
     329         970 :       const std::vector<Number> & coeffs = pair.second;
     330             : 
     331       11604 :       double norm = 0.;
     332       30768 :       for (const auto & c : coeffs)
     333       20764 :         norm += std::norm(c);
     334             : 
     335       11604 :       norm = std::sqrt(norm);
     336             : 
     337       11604 :       degrees.push_back(deg);
     338       11604 :       norms.push_back(norm);
     339             :     }
     340             : 
     341             : 
     342             :     // Collect log |c_k| and log |k|
     343         696 :     std::vector<double> log_norms, log_degrees;
     344             : 
     345       16118 :     for (unsigned int i = 0; i < degrees.size(); ++i)
     346             :     {
     347             :       // filter tiny terms
     348       12574 :       if (norms[i] > 1e-12)
     349             :       {
     350       11604 :         log_degrees.push_back(std::log(static_cast<double>(degrees[i])));
     351       12574 :         log_norms.push_back(std::log(norms[i]));
     352             :       }
     353             :     }
     354             : 
     355             : 
     356             :     // Fit log-log line - we use least-squares fit
     357         348 :     double Sx = 0., Sy = 0., Sxx = 0., Sxy = 0.;
     358         696 :     const size_t N = log_degrees.size();
     359             : 
     360       15770 :     for (size_t i = 0; i < N; ++i)
     361             :     {
     362       11604 :       Sx += log_degrees[i];
     363       11604 :       Sy += log_norms[i];
     364       11604 :       Sxx += log_degrees[i] * log_degrees[i];
     365       11604 :       Sxy += log_degrees[i] * log_norms[i];
     366             :     }
     367             : 
     368        4166 :     const double regularity = -compute_slope(N, Sx, Sy, Sxx, Sxy);
     369             :     // const double intercept = (Sy - slope * Sx) / N;
     370             : 
     371         348 :     Threads::spin_mutex::scoped_lock acquire(Threads::spin_mtx);
     372        4514 :     smoothness_per_cell[e_id] = regularity;
     373        6940 :   } // end variables loop
     374             : 
     375             : } // end element loop
     376             : 
     377        2414 : } // End () operator definition
     378             : 
     379             : } // namespace libMesh

Generated by: LCOV version 1.14