LCOV - code coverage report
Current view: top level - src/utils - ParameterMesh.C (source / functions) Hit Total Coverage
Test: idaholab/moose optimization: #31730 (e8b711) with base e0c998 Lines: 160 180 88.9 %
Date: 2025-10-29 16:52:42 Functions: 18 18 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "LineSegment.h"
      11             : #include "MooseError.h"
      12             : #include "ParameterMesh.h"
      13             : 
      14             : #include "libmesh/cell_hex8.h"
      15             : #include "libmesh/cell_hex.h"
      16             : #include "libmesh/edge_edge2.h"
      17             : #include "libmesh/enum_elem_type.h"
      18             : #include "libmesh/enum_point_locator_type.h"
      19             : #include "libmesh/int_range.h"
      20             : #include "libmesh/dof_map.h"
      21             : 
      22             : #include "libmesh/elem.h"
      23             : #include "libmesh/face_quad.h"
      24             : #include "libmesh/face_quad4.h"
      25             : #include "libmesh/fe_compute_data.h"
      26             : #include "libmesh/fe_interface.h"
      27             : #include "libmesh/id_types.h"
      28             : #include "libmesh/int_range.h"
      29             : #include "libmesh/libmesh_common.h"
      30             : #include "libmesh/numeric_vector.h"
      31             : #include "libmesh/explicit_system.h"
      32             : #include "libmesh/plane.h"
      33             : #include "libmesh/enum_to_string.h"
      34             : #include <memory>
      35             : #include "libmesh/quadrature_gauss.h"
      36             : #include "libmesh/fe_base.h"
      37             : 
      38             : using namespace libMesh;
      39             : 
      40         546 : ParameterMesh::ParameterMesh(const FEType & param_type,
      41             :                              const std::string & exodus_mesh,
      42             :                              const bool find_closest,
      43         546 :                              const unsigned int kdtree_candidates)
      44         546 :   : _communicator(MPI_COMM_SELF),
      45         546 :     _mesh(_communicator),
      46         546 :     _find_closest(find_closest),
      47         546 :     _kdtree_candidates(kdtree_candidates),
      48         546 :     _param_var_id(0),
      49         546 :     _dof_map(nullptr),
      50         546 :     _fe_type(param_type)
      51             : {
      52             :   _mesh.allow_renumbering(false);
      53         546 :   _mesh.prepare_for_use();
      54        1092 :   _exodusII_io = std::make_unique<ExodusII_IO>(_mesh);
      55         546 :   _exodusII_io->read(exodus_mesh);
      56         546 :   _mesh.read(exodus_mesh);
      57             :   // Create system to store parameter values
      58        1092 :   _eq = std::make_unique<EquationSystems>(_mesh);
      59         546 :   _sys = &_eq->add_system<ExplicitSystem>("_parameter_mesh_sys");
      60         546 :   _sys->add_variable("_parameter_mesh_var", param_type);
      61             : 
      62             :   // Create point locator
      63        1092 :   _point_locator = PointLocatorBase::build(TREE_LOCAL_ELEMENTS, _mesh);
      64         546 :   _point_locator->enable_out_of_mesh_mode();
      65             : 
      66             :   // Initialize the equations systems
      67         546 :   _eq->init();
      68             : 
      69             :   // getting number of parameter dofs for size() function
      70         546 :   const unsigned short int var_id = _sys->variable_number("_parameter_mesh_var");
      71             :   std::set<dof_id_type> var_indices;
      72         546 :   _sys->local_dof_indices(var_id, var_indices);
      73         546 :   _param_dofs = var_indices.size();
      74             : 
      75         546 :   if (_find_closest)
      76             :   {
      77      147264 :     for (const auto & elem : _mesh.element_ptr_range())
      78       73542 :       if (elem->default_order() != FIRST)
      79          92 :         mooseError("Closet point projection currently does not support second order elements.");
      80             :   }
      81             : 
      82             :   // Initialize node-based KDTree optimization
      83         544 :   _mesh_nodes.clear();
      84             :   _node_to_elements.clear();
      85             : 
      86             :   // Extract all node coordinates
      87       80952 :   for (const auto & node : _mesh.node_ptr_range())
      88       40476 :     _mesh_nodes.push_back(*node);
      89             : 
      90             :   // Build node-to-elements connectivity map
      91      163276 :   for (const auto & elem : _mesh.element_ptr_range())
      92             :   {
      93      492234 :     for (const auto n : make_range(elem->n_nodes()))
      94             :     {
      95      330046 :       dof_id_type node_id = elem->node_id(n);
      96      330046 :       _node_to_elements[node_id].insert(elem);
      97             :     }
      98         544 :   }
      99             : 
     100             :   // Create KDTree from node coordinates
     101         544 :   if (!_mesh_nodes.empty())
     102        1088 :     _node_kdtree = std::make_unique<KDTree>(_mesh_nodes, 10);
     103             :   // Update cached values for gradient computations
     104         544 :   const_cast<unsigned short int &>(_param_var_id) = var_id;
     105         544 :   const_cast<const DofMap *&>(_dof_map) = &_sys->get_dof_map();
     106         544 :   const_cast<FEType &>(_fe_type) = _dof_map->variable_type(_param_var_id);
     107         544 : }
     108             : 
     109             : void
     110    12974412 : ParameterMesh::getIndexAndWeight(const Point & pt,
     111             :                                  std::vector<dof_id_type> & dof_indices,
     112             :                                  std::vector<Real> & weights) const
     113             : {
     114    12974412 :   Point test_point = (_find_closest ? projectToMesh(pt) : pt);
     115             : 
     116    12974412 :   const Elem * elem = (*_point_locator)(test_point);
     117    12974412 :   if (!elem)
     118           0 :     mooseError("No element was found to contain point ", test_point);
     119             : 
     120             :   // Get the dof_indices for our element
     121             :   // variable id is hard coded to _param_var_id
     122             :   // this is probably the only variable in the ParameterMesh system used by ParameterMeshFunction
     123    12974412 :   _dof_map->dof_indices(elem, dof_indices, _param_var_id);
     124             : 
     125             :   // Map the physical co-ordinates to the reference co-ordinates
     126    12974412 :   Point coor = FEMap::inverse_map(elem->dim(), elem, test_point);
     127             :   // get the shape function value via the FEInterface
     128    12974412 :   FEComputeData fe_data(*_eq, coor);
     129    12974412 :   FEInterface::compute_data(elem->dim(), _fe_type, elem, fe_data);
     130             :   // Set weights to the value of the shape functions
     131    12974412 :   weights = fe_data.shape;
     132             : 
     133    12974412 :   if (dof_indices.size() != weights.size())
     134           0 :     mooseError("Internal error: weights and DoF indices do not have the same size.");
     135    12974412 : }
     136             : 
     137             : void
     138       16200 : ParameterMesh::getIndexAndWeight(const Point & pt,
     139             :                                  std::vector<dof_id_type> & dof_indices,
     140             :                                  std::vector<RealGradient> & weights) const
     141             : {
     142       16200 :   if (!_sys->has_variable("_parameter_mesh_var"))
     143           0 :     mooseError("Internal error: System being read does not contain _parameter_mesh_var.");
     144       16200 :   Point test_point = (_find_closest ? projectToMesh(pt) : pt);
     145             :   // Locate the element the point is in
     146       16200 :   const Elem * elem = (*_point_locator)(test_point);
     147             : 
     148             :   // Get the dof_indices for our element
     149             :   // variable id is hard coded to _param_var_id
     150             :   // this is probably the only variable in the ParameterMesh system used by ParameterMeshFunction
     151       16200 :   _dof_map->dof_indices(elem, dof_indices, _param_var_id);
     152             : 
     153             :   // Map the physical co-ordinates to the reference co-ordinates
     154       16200 :   Point coor = FEMap::inverse_map(elem->dim(), elem, test_point);
     155             :   // get the shape function value via the FEInterface
     156       16200 :   FEComputeData fe_data(*_eq, coor);
     157       16200 :   fe_data.enable_derivative();
     158       16200 :   FEInterface::compute_data(elem->dim(), _fe_type, elem, fe_data);
     159             :   // Set weights to the value of the shape functions
     160       16200 :   weights = fe_data.dshape;
     161             : 
     162       16200 :   if (dof_indices.size() != weights.size())
     163           0 :     mooseError("Internal error: weights and DoF indices do not have the same size.");
     164       16200 : }
     165             : 
     166             : Point
     167       41730 : ParameterMesh::projectToMesh(const Point & p) const
     168             : {
     169             :   // quick path: p already inside an element
     170       41730 :   if ((*_point_locator)(p))
     171        1800 :     return p;
     172             : 
     173             :   // Lambda to find closest point from elements using squared distance for efficiency
     174       39930 :   auto findClosestElement = [&p, this](const auto & elements) -> Point
     175             :   {
     176             :     Real best_d2 = std::numeric_limits<Real>::max();
     177       39930 :     Point best_point = p;
     178             : 
     179      793866 :     for (const auto * elem : elements)
     180             :     {
     181      753936 :       Point trial = closestPoint(*elem, p);
     182      753936 :       Real d2 = (trial - p).norm_sq();
     183      753936 :       if (d2 < best_d2)
     184             :       {
     185             :         best_d2 = d2;
     186      146059 :         best_point = trial;
     187             :       }
     188             :     }
     189             : 
     190       39930 :     if (best_d2 == std::numeric_limits<Real>::max())
     191           0 :       mooseError("project_to_mesh failed - no candidate elements.");
     192       39930 :     return best_point;
     193       39930 :   };
     194             : 
     195             :   // Use KDTree optimization if available
     196       39930 :   if (_node_kdtree && !_mesh_nodes.empty())
     197             :   {
     198             :     // Find K nearest nodes using KDTree
     199             :     std::vector<std::size_t> nearest_node_indices;
     200       39930 :     _node_kdtree->neighborSearch(p, _kdtree_candidates, nearest_node_indices);
     201             : 
     202             :     // Collect all elements connected to these nodes
     203             :     std::set<const Elem *> candidate_elements;
     204      239580 :     for (auto node_idx : nearest_node_indices)
     205             :     {
     206             :       // Get the actual node from the mesh using the index
     207      199650 :       if (node_idx < _mesh.n_nodes())
     208             :       {
     209      199650 :         const Node * node = _mesh.node_ptr(node_idx);
     210      199650 :         dof_id_type node_id = node->id();
     211             :         auto it = _node_to_elements.find(node_id);
     212      199650 :         if (it != _node_to_elements.end())
     213             :         {
     214             :           const auto & connected_elems = it->second;
     215      199650 :           candidate_elements.insert(connected_elems.begin(), connected_elems.end());
     216             :         }
     217             :       }
     218             :     }
     219             : 
     220             :     // Convert set to vector for consistent type
     221             :     std::vector<const Elem *> candidate_vector(candidate_elements.begin(),
     222       39930 :                                                candidate_elements.end());
     223       39930 :     return findClosestElement(candidate_vector);
     224       79860 :   }
     225             :   else
     226             :   {
     227             :     // Fallback to original O(n) method if KDTree not available
     228             :     std::vector<const Elem *> all_elements;
     229           0 :     for (const auto & elem : _mesh.element_ptr_range())
     230           0 :       all_elements.push_back(elem);
     231             : 
     232           0 :     return findClosestElement(all_elements);
     233           0 :   }
     234             : }
     235             : 
     236             : Point
     237    11197791 : ParameterMesh::closestPoint(const Elem & elem, const Point & p) const
     238             : {
     239             :   mooseAssert(!elem.contains_point(p),
     240             :               "Points inside of elements shouldn't need to find closestPoint.");
     241             : 
     242             :   // Lambda to find closest point from range without storing temporary vectors
     243     3101880 :   auto findClosest = [&p](auto range, auto point_func) -> Point
     244             :   {
     245             :     Real min_distance = std::numeric_limits<Real>::max();
     246     3101880 :     Point min_point = p;
     247             : 
     248    13545735 :     for (const auto & item : range)
     249             :     {
     250    10443855 :       Point candidate = point_func(item);
     251    10443855 :       Real distance = (candidate - p).norm();
     252    10443855 :       if (distance < min_distance)
     253             :       {
     254             :         min_distance = distance;
     255     4158198 :         min_point = candidate;
     256             :       }
     257             :     }
     258     3101880 :     return min_point;
     259    11197791 :   };
     260             : 
     261    11197791 :   switch (elem.type())
     262             :   {
     263             :     case EDGE2:
     264             :     {
     265     8056704 :       LineSegment ls(*(elem.node_ptr(0)), *(elem.node_ptr(1)));
     266     8056704 :       return ls.closest_point(p);
     267             :     }
     268             : 
     269             :     case TRI3:
     270             :     {
     271     2126376 :       Point a = *(elem.node_ptr(0));
     272     2126376 :       Point b = *(elem.node_ptr(1));
     273     2126376 :       Point c = *(elem.node_ptr(2));
     274     2126376 :       Plane pl(a, b, c);
     275     2126376 :       Point trial = pl.closest_point(p);
     276     2126376 :       if (elem.contains_point(trial))
     277       28899 :         return trial;
     278             : 
     279     2097477 :       return findClosest(make_range(elem.n_edges()),
     280     6292431 :                          [&](dof_id_type i) { return closestPoint(*elem.build_edge_ptr(i), p); });
     281     2126376 :     }
     282             :     case QUAD4:
     283             :     {
     284      448800 :       Point a = *(elem.node_ptr(0));
     285      448800 :       Point b = *(elem.node_ptr(1));
     286      448800 :       Point c = *(elem.node_ptr(2));
     287      448800 :       Point d = *(elem.node_ptr(3));
     288      448800 :       Plane pl1(a, b, c);
     289      448800 :       Plane pl2(b, c, d);
     290      448800 :       Point trial1 = pl1.closest_point(p);
     291      448800 :       Point trial2 = pl2.closest_point(p);
     292      448800 :       if (!trial1.absolute_fuzzy_equals(trial2, TOLERANCE * TOLERANCE))
     293           0 :         mooseError("Quad4 element is not coplanar");
     294             : 
     295      448800 :       if (elem.contains_point(trial1))
     296       10308 :         return trial1;
     297             : 
     298      438492 :       return findClosest(make_range(elem.n_edges()),
     299     1753968 :                          [&](dof_id_type i) { return closestPoint(*elem.build_edge_ptr(i), p); });
     300      448800 :     }
     301             : 
     302      565911 :     default:
     303             :     {
     304      565911 :       if (elem.dim() == 3)
     305             :       {
     306      565911 :         return findClosest(make_range(elem.n_sides()),
     307     2397456 :                            [&](dof_id_type i) { return closestPoint(*elem.build_side_ptr(i), p); });
     308             :       }
     309             :       else
     310             :       {
     311           0 :         mooseError("Unsupported element type ",
     312           0 :                    Utility::enum_to_string(elem.type()),
     313             :                    " for projection of parameter mesh.");
     314             :       }
     315             :     }
     316             :   }
     317             : }
     318             : 
     319             : template <typename T>
     320             : T
     321        3204 : ParameterMesh::computeRegularizationLoop(const std::vector<Real> & parameter_values,
     322             :                                          RegularizationType reg_type) const
     323             : {
     324        3204 :   if (parameter_values.size() != _param_dofs)
     325           0 :     mooseError("Parameter values size (",
     326           0 :                parameter_values.size(),
     327             :                ") does not match mesh DOFs (",
     328           0 :                _param_dofs,
     329             :                ")");
     330             : 
     331             :   T result;
     332             :   if constexpr (std::is_same_v<T, Real>)
     333             :     result = 0.0;
     334             :   else if constexpr (std::is_same_v<T, std::vector<Real>>)
     335        1602 :     result.resize(_param_dofs, 0.0);
     336             : 
     337             :   // Iterate over all elements in the mesh
     338     1355292 :   for (const auto & elem : _mesh.element_ptr_range())
     339             :   {
     340             :     // Get DOF indices for this element
     341             :     std::vector<dof_id_type> dof_indices;
     342      448560 :     _dof_map->dof_indices(elem, dof_indices, _param_var_id);
     343             : 
     344             :     // Get quadrature rule for this element
     345      448560 :     const unsigned int dim = elem->dim();
     346      448560 :     QGauss qrule(dim, _fe_type.default_quadrature_order());
     347             : 
     348             :     // Create finite element objects
     349      448560 :     std::unique_ptr<FEBase> fe(FEBase::build(dim, _fe_type));
     350      448560 :     fe->attach_quadrature_rule(&qrule);
     351             : 
     352             :     // Request shape functions and derivatives before reinit
     353             :     const std::vector<Real> & JxW = fe->get_JxW();
     354             :     const std::vector<std::vector<Real>> & phi = fe->get_phi();
     355             :     const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
     356             : 
     357             :     // Reinitialize for current element
     358      448560 :     fe->reinit(elem);
     359             : 
     360     2242800 :     for (const auto qp : make_range(qrule.n_points()))
     361             :     {
     362             :       if constexpr (std::is_same_v<T, Real>)
     363      897120 :         result +=
     364      897120 :             computeRegularizationQp(parameter_values, phi, dphi, qp, dof_indices, JxW, reg_type);
     365             :       else if constexpr (std::is_same_v<T, std::vector<Real>>)
     366      897120 :         computeRegularizationGradientQp(
     367             :             parameter_values, phi, dphi, qp, dof_indices, JxW, reg_type, result);
     368             :     }
     369             :   }
     370             : 
     371        3204 :   return result;
     372           0 : }
     373             : 
     374             : Real
     375        1602 : ParameterMesh::computeRegularizationObjective(const std::vector<Real> & parameter_values,
     376             :                                               RegularizationType reg_type) const
     377             : {
     378        1602 :   return computeRegularizationLoop<Real>(parameter_values, reg_type);
     379             : }
     380             : 
     381             : std::vector<Real>
     382        1602 : ParameterMesh::computeRegularizationGradient(const std::vector<Real> & parameter_values,
     383             :                                              RegularizationType reg_type) const
     384             : {
     385        1602 :   return computeRegularizationLoop<std::vector<Real>>(parameter_values, reg_type);
     386             : }
     387             : 
     388             : Real
     389      897120 : ParameterMesh::computeRegularizationQp(const std::vector<Real> & parameter_values,
     390             :                                        const std::vector<std::vector<Real>> & /*phi*/,
     391             :                                        const std::vector<std::vector<RealGradient>> & dphi,
     392             :                                        const unsigned int qp,
     393             :                                        const std::vector<dof_id_type> & dof_indices,
     394             :                                        const std::vector<Real> & JxW,
     395             :                                        RegularizationType reg_type) const
     396             : {
     397             :   Real objective_contribution = 0.0;
     398             : 
     399             :   // Switch on regularization type
     400      897120 :   switch (reg_type)
     401             :   {
     402             :     case RegularizationType::L2_GRADIENT:
     403             :     {
     404             :       // Compute parameter gradient at this quadrature point
     405             :       RealGradient param_grad;
     406     4485600 :       for (const auto i : index_range(dof_indices))
     407     3588480 :         param_grad += parameter_values[dof_indices[i]] * dphi[i][qp];
     408             : 
     409             :       // Add L2 norm squared of gradient for regularization
     410      897120 :       objective_contribution = param_grad.norm_sq() * JxW[qp];
     411             :       break;
     412             :     }
     413           0 :     default:
     414           0 :       mooseError("Unknown Regularization Type");
     415             :   }
     416             : 
     417      897120 :   return objective_contribution;
     418             : }
     419             : 
     420             : void
     421      897120 : ParameterMesh::computeRegularizationGradientQp(const std::vector<Real> & parameter_values,
     422             :                                                const std::vector<std::vector<Real>> & /*phi*/,
     423             :                                                const std::vector<std::vector<RealGradient>> & dphi,
     424             :                                                const unsigned int qp,
     425             :                                                const std::vector<dof_id_type> & dof_indices,
     426             :                                                const std::vector<Real> & JxW,
     427             :                                                RegularizationType reg_type,
     428             :                                                std::vector<Real> & gradient) const
     429             : {
     430             :   // Switch on regularization type
     431      897120 :   switch (reg_type)
     432             :   {
     433             :     case RegularizationType::L2_GRADIENT:
     434             :     {
     435             :       // Compute parameter gradient at this quadrature point
     436             :       RealGradient param_grad;
     437     4485600 :       for (const auto i : index_range(dof_indices))
     438     3588480 :         param_grad += parameter_values[dof_indices[i]] * dphi[i][qp];
     439             : 
     440             :       // Compute gradient contribution: 2 * grad(p) * dphi_j
     441     4485600 :       for (const auto j : index_range(dof_indices))
     442     3588480 :         gradient[dof_indices[j]] += 2.0 * param_grad * dphi[j][qp] * JxW[qp];
     443             :       break;
     444             :     }
     445             : 
     446           0 :     default:
     447           0 :       mooseError("Unknown Regularization Type");
     448             :   }
     449      897120 : }

Generated by: LCOV version 1.14