LCOV - code coverage report
Current view: top level - src/geomsearch - FindContactPoint.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 419b9d Lines: 219 227 96.5 %
Date: 2025-08-08 20:01:16 Functions: 2 2 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             : // Moose
      11             : #include "DenseMatrix.h"
      12             : #include "FindContactPoint.h"
      13             : #include "LineSegment.h"
      14             : #include "PenetrationInfo.h"
      15             : 
      16             : // libMesh
      17             : #include "libmesh/fe_base.h"
      18             : #include "libmesh/boundary_info.h"
      19             : #include "libmesh/elem.h"
      20             : #include "libmesh/plane.h"
      21             : #include "libmesh/fe_interface.h"
      22             : #include "libmesh/dense_vector.h"
      23             : #include "libmesh/fe_base.h"
      24             : #include "libmesh/vector_value.h"
      25             : 
      26             : using namespace libMesh;
      27             : 
      28             : namespace Moose
      29             : {
      30             : 
      31             : /**
      32             :  * Finds the closest point (called the contact point) on the primary_elem on side "side" to the
      33             :  * secondary_point.
      34             :  *
      35             :  * @param p_info The penetration info object, contains primary_elem, side, various other information
      36             :  * @param fe_elem FE object for the element
      37             :  * @param fe_side FE object for the side
      38             :  * @param fe_side_type Unused; was used for a now-deprecated
      39             :  * inverse_map overload.
      40             :  * @param start_with_centroid if true, start inverse mapping procedure from element centroid
      41             :  * @param tangential_tolerance 'tangential' tolerance for determining whether a contact point on a
      42             :  * side
      43             :  * @param secondary_point The physical space coordinates of the secondary node
      44             :  * @param contact_point_on_side whether or not the contact_point actually lies on _that_ side of the
      45             :  * element.
      46             :  * @param search_succeeded whether or not the search for the contact point succeeded. If not it
      47             :  * should likely be discarded
      48             :  */
      49             : void
      50     1652879 : findContactPoint(PenetrationInfo & p_info,
      51             :                  FEBase * fe_elem,
      52             :                  FEBase * fe_side,
      53             :                  FEType & /* fe_side_type */,
      54             :                  const libMesh::Point & secondary_point,
      55             :                  bool start_with_centroid,
      56             :                  const Real tangential_tolerance,
      57             :                  bool & contact_point_on_side,
      58             :                  bool & search_succeeded)
      59             : {
      60             :   // Default to true and we'll switch on failures
      61     1652879 :   search_succeeded = true;
      62             : 
      63     1652879 :   const Elem * primary_elem = p_info._elem;
      64             : 
      65     1652879 :   unsigned int dim = primary_elem->dim();
      66             : 
      67     1652879 :   const Elem * side = p_info._side;
      68             : 
      69     1652879 :   const std::vector<libMesh::Point> & phys_point = fe_side->get_xyz();
      70             : 
      71     1652879 :   const std::vector<RealGradient> & dxyz_dxi = fe_side->get_dxyzdxi();
      72     1652879 :   const std::vector<RealGradient> & d2xyz_dxi2 = fe_side->get_d2xyzdxi2();
      73     1652879 :   const std::vector<RealGradient> & d2xyz_dxieta = fe_side->get_d2xyzdxideta();
      74             : 
      75     1652879 :   const std::vector<RealGradient> & dxyz_deta = fe_side->get_dxyzdeta();
      76     1652879 :   const std::vector<RealGradient> & d2xyz_deta2 = fe_side->get_d2xyzdeta2();
      77     1652879 :   const std::vector<RealGradient> & d2xyz_detaxi = fe_side->get_d2xyzdxideta();
      78             : 
      79     1652879 :   if (dim == 1)
      80             :   {
      81          23 :     const Node * nearest_node = side->node_ptr(0);
      82          23 :     p_info._closest_point = *nearest_node;
      83             :     p_info._closest_point_ref =
      84          23 :         primary_elem->master_point(primary_elem->get_node_index(nearest_node));
      85          23 :     std::vector<libMesh::Point> elem_points = {p_info._closest_point_ref};
      86             : 
      87          23 :     const std::vector<RealGradient> & elem_dxyz_dxi = fe_elem->get_dxyzdxi();
      88             : 
      89          23 :     fe_elem->reinit(primary_elem, &elem_points);
      90          23 :     fe_side->reinit(side, &elem_points);
      91             : 
      92          23 :     p_info._normal = elem_dxyz_dxi[0];
      93          23 :     if (nearest_node->id() == primary_elem->node_id(0))
      94          23 :       p_info._normal *= -1.0;
      95          23 :     p_info._normal /= p_info._normal.norm();
      96             : 
      97          23 :     libMesh::Point from_secondary_to_closest = p_info._closest_point - secondary_point;
      98          23 :     p_info._distance = from_secondary_to_closest * p_info._normal;
      99          23 :     libMesh::Point tangential = from_secondary_to_closest - p_info._distance * p_info._normal;
     100          23 :     p_info._tangential_distance = tangential.norm();
     101          23 :     p_info._dxyzdxi = dxyz_dxi;
     102          23 :     p_info._dxyzdeta = dxyz_deta;
     103          23 :     p_info._d2xyzdxideta = d2xyz_dxieta;
     104          23 :     p_info._side_phi = fe_side->get_phi();
     105          23 :     p_info._side_grad_phi = fe_side->get_dphi();
     106          23 :     contact_point_on_side = true;
     107          23 :     return;
     108          23 :   }
     109             : 
     110     1652856 :   libMesh::Point ref_point;
     111             : 
     112     1652856 :   if (start_with_centroid)
     113      937174 :     ref_point = FEMap::inverse_map(dim - 1, side, side->vertex_average(), TOLERANCE, false);
     114             :   else
     115      715682 :     ref_point = p_info._closest_point_ref;
     116             : 
     117     1652856 :   std::vector<libMesh::Point> points = {ref_point};
     118     1652856 :   fe_side->reinit(side, &points);
     119     1652856 :   RealGradient d = secondary_point - phys_point[0];
     120             : 
     121     1652856 :   Real update_size = std::numeric_limits<Real>::max();
     122             : 
     123             :   // Least squares
     124     4451874 :   for (unsigned int it = 0; it < 3 && update_size > TOLERANCE * 1e3; ++it)
     125             :   {
     126     2799018 :     DenseMatrix<Real> jac(dim - 1, dim - 1);
     127     2799018 :     jac(0, 0) = -(dxyz_dxi[0] * dxyz_dxi[0]);
     128             : 
     129     2799018 :     if (dim - 1 == 2)
     130             :     {
     131     2395713 :       jac(1, 0) = -(dxyz_dxi[0] * dxyz_deta[0]);
     132     2395713 :       jac(0, 1) = -(dxyz_deta[0] * dxyz_dxi[0]);
     133     2395713 :       jac(1, 1) = -(dxyz_deta[0] * dxyz_deta[0]);
     134             :     }
     135             : 
     136     2799018 :     DenseVector<Real> rhs(dim - 1);
     137     2799018 :     rhs(0) = dxyz_dxi[0] * d;
     138             : 
     139     2799018 :     if (dim - 1 == 2)
     140     2395713 :       rhs(1) = dxyz_deta[0] * d;
     141             : 
     142     2799018 :     DenseVector<Real> update(dim - 1);
     143     2799018 :     jac.lu_solve(rhs, update);
     144             : 
     145     2799018 :     ref_point(0) -= update(0);
     146             : 
     147     2799018 :     if (dim - 1 == 2)
     148     2395713 :       ref_point(1) -= update(1);
     149             : 
     150     2799018 :     points[0] = ref_point;
     151     2799018 :     fe_side->reinit(side, &points);
     152     2799018 :     d = secondary_point - phys_point[0];
     153             : 
     154     2799018 :     update_size = update.l2_norm();
     155     2799018 :   }
     156             : 
     157     1652856 :   update_size = std::numeric_limits<Real>::max();
     158             : 
     159     1652856 :   unsigned nit = 0;
     160             : 
     161             :   // Newton Loop
     162     1652856 :   const auto max_newton_its = 25;
     163     1652856 :   const auto tolerance_newton = 1e3 * TOLERANCE * TOLERANCE;
     164     3313880 :   for (; nit < max_newton_its && update_size > tolerance_newton; nit++)
     165             :   {
     166     1661024 :     d = secondary_point - phys_point[0];
     167             : 
     168     1661024 :     DenseMatrix<Real> jac(dim - 1, dim - 1);
     169     1661024 :     jac(0, 0) = (d2xyz_dxi2[0] * d) - (dxyz_dxi[0] * dxyz_dxi[0]);
     170             : 
     171     1661024 :     if (dim - 1 == 2)
     172             :     {
     173     1399649 :       jac(1, 0) = (d2xyz_dxieta[0] * d) - (dxyz_dxi[0] * dxyz_deta[0]);
     174             : 
     175     1399649 :       jac(0, 1) = (d2xyz_detaxi[0] * d) - (dxyz_deta[0] * dxyz_dxi[0]);
     176     1399649 :       jac(1, 1) = (d2xyz_deta2[0] * d) - (dxyz_deta[0] * dxyz_deta[0]);
     177             :     }
     178             : 
     179     1661024 :     DenseVector<Real> rhs(dim - 1);
     180     1661024 :     rhs(0) = -dxyz_dxi[0] * d;
     181             : 
     182     1661024 :     if (dim - 1 == 2)
     183     1399649 :       rhs(1) = -dxyz_deta[0] * d;
     184             : 
     185     1661024 :     DenseVector<Real> update(dim - 1);
     186     1661024 :     jac.lu_solve(rhs, update);
     187             : 
     188             :     // Improvised line search in case the update is too large and gets out of the element so bad
     189             :     // that we cannot reinit at the new point
     190     1661024 :     Real mult = 1;
     191             :     while (true)
     192             :     {
     193             :       try
     194             :       {
     195     1662056 :         ref_point(0) += mult * update(0);
     196             : 
     197     1662056 :         if (dim - 1 == 2)
     198     1400681 :           ref_point(1) += mult * update(1);
     199             : 
     200     1662056 :         points[0] = ref_point;
     201     1662056 :         fe_side->reinit(side, &points);
     202     1661024 :         d = secondary_point - phys_point[0];
     203             : 
     204             :         // we don't multiply by 'mult' because it is used for convergence
     205     1661024 :         update_size = update.l2_norm();
     206     1661024 :         break;
     207             :       }
     208        1032 :       catch (libMesh::LogicError & e)
     209             :       {
     210        1032 :         ref_point(0) -= mult * update(0);
     211        1032 :         if (dim - 1 == 2)
     212        1032 :           ref_point(1) -= mult * update(1);
     213             : 
     214        1032 :         mult *= 0.5;
     215        1032 :         if (mult < 1e-6)
     216             :         {
     217             : #ifndef NDEBUG
     218             :           mooseWarning("We could not solve for the contact point.", e.what());
     219             : #endif
     220           0 :           update_size = update.l2_norm();
     221           0 :           d = (secondary_point - phys_point[0]) * mult;
     222           0 :           break;
     223             :         }
     224        1032 :       }
     225        1032 :     }
     226             :     // We failed the line search, make sure to trigger the error
     227     1661024 :     if (mult < 1e-6)
     228             :     {
     229           0 :       nit = max_newton_its;
     230           0 :       update_size = 1;
     231           0 :       break;
     232             :     }
     233     1661024 :   }
     234             : 
     235     1652856 :   if (nit == max_newton_its && update_size > tolerance_newton)
     236             :   {
     237          16 :     search_succeeded = false;
     238             : #ifndef NDEBUG
     239             :     const auto initial_point =
     240             :         start_with_centroid ? side->vertex_average() : ref_point = p_info._closest_point_ref;
     241             :     Moose::err << "Warning!  Newton solve for contact point failed to converge!\nLast update "
     242             :                   "distance was: "
     243             :                << update_size << "\nInitial point guess:   " << initial_point
     244             :                << "\nLast considered point: " << phys_point[0]
     245             :                << "\nThis potential contact pair (face, point) will be discarded." << std::endl;
     246             : #endif
     247          16 :     return;
     248             :   }
     249             : 
     250     1652840 :   p_info._closest_point_ref = ref_point;
     251     1652840 :   p_info._closest_point = phys_point[0];
     252     1652840 :   p_info._distance = d.norm();
     253             : 
     254     1652840 :   if (dim - 1 == 2)
     255             :   {
     256     1391465 :     p_info._normal = dxyz_dxi[0].cross(dxyz_deta[0]);
     257     1391465 :     if (!MooseUtils::absoluteFuzzyEqual(p_info._normal.norm(), 0))
     258     1391233 :       p_info._normal /= p_info._normal.norm();
     259             :   }
     260             :   else
     261             :   {
     262      261375 :     const Node * const * elem_nodes = primary_elem->get_nodes();
     263      261375 :     const libMesh::Point in_plane_vector1 = *elem_nodes[1] - *elem_nodes[0];
     264      261375 :     const libMesh::Point in_plane_vector2 = *elem_nodes[2] - *elem_nodes[0];
     265             : 
     266      261375 :     libMesh::Point out_of_plane_normal = in_plane_vector1.cross(in_plane_vector2);
     267      261375 :     out_of_plane_normal /= out_of_plane_normal.norm();
     268             : 
     269      261375 :     p_info._normal = dxyz_dxi[0].cross(out_of_plane_normal);
     270      261375 :     if (std::fabs(p_info._normal.norm()) > 1e-15)
     271      261375 :       p_info._normal /= p_info._normal.norm();
     272             :   }
     273             : 
     274             :   // If the point has not penetrated the face, make the distance negative
     275     1652840 :   const Real dot(d * p_info._normal);
     276     1652840 :   if (dot > 0.0)
     277     1189175 :     p_info._distance = -p_info._distance;
     278             : 
     279     1652840 :   contact_point_on_side = side->on_reference_element(ref_point);
     280             : 
     281     1652840 :   p_info._tangential_distance = 0.0;
     282             : 
     283     1652840 :   if (!contact_point_on_side)
     284             :   {
     285      984211 :     p_info._closest_point_on_face_ref = ref_point;
     286      984211 :     restrictPointToFace(p_info._closest_point_on_face_ref, side, p_info._off_edge_nodes);
     287             : 
     288      984211 :     points[0] = p_info._closest_point_on_face_ref;
     289      984211 :     fe_side->reinit(side, &points);
     290      984211 :     libMesh::Point closest_point_on_face(phys_point[0]);
     291             : 
     292      984211 :     RealGradient off_face = closest_point_on_face - p_info._closest_point;
     293      984211 :     Real tangential_distance = off_face.norm();
     294      984211 :     p_info._tangential_distance = tangential_distance;
     295      984211 :     if (tangential_distance <= tangential_tolerance)
     296             :     {
     297      106400 :       contact_point_on_side = true;
     298             :     }
     299             :   }
     300             : 
     301     1652840 :   const std::vector<std::vector<Real>> & phi = fe_side->get_phi();
     302     1652840 :   const std::vector<std::vector<RealGradient>> & grad_phi = fe_side->get_dphi();
     303             : 
     304     1652840 :   points[0] = p_info._closest_point_ref;
     305     1652840 :   fe_side->reinit(side, &points);
     306             : 
     307     1652840 :   p_info._side_phi = phi;
     308     1652840 :   p_info._side_grad_phi = grad_phi;
     309     1652840 :   p_info._dxyzdxi = dxyz_dxi;
     310     1652840 :   p_info._dxyzdeta = dxyz_deta;
     311     1652840 :   p_info._d2xyzdxideta = d2xyz_dxieta;
     312     1652856 : }
     313             : 
     314             : void
     315      984211 : restrictPointToFace(libMesh::Point & p,
     316             :                     const Elem * side,
     317             :                     std::vector<const Node *> & off_edge_nodes)
     318             : {
     319      984211 :   const ElemType t(side->type());
     320      984211 :   off_edge_nodes.clear();
     321      984211 :   Real & xi = p(0);
     322      984211 :   Real & eta = p(1);
     323             : 
     324      984211 :   switch (t)
     325             :   {
     326       80825 :     case EDGE2:
     327             :     case EDGE3:
     328             :     case EDGE4:
     329             :     {
     330             :       // The reference 1D element is [-1,1].
     331       80825 :       if (xi < -1.0)
     332             :       {
     333       49150 :         xi = -1.0;
     334       49150 :         off_edge_nodes.push_back(side->node_ptr(0));
     335             :       }
     336       31675 :       else if (xi > 1.0)
     337             :       {
     338       31675 :         xi = 1.0;
     339       31675 :         off_edge_nodes.push_back(side->node_ptr(1));
     340             :       }
     341       80825 :       break;
     342             :     }
     343             : 
     344        2474 :     case TRI3:
     345             :     case TRI6:
     346             :     case TRI7:
     347             :     {
     348             :       // The reference triangle is isosceles
     349             :       // and is bound by xi=0, eta=0, and xi+eta=1.
     350             : 
     351        2474 :       if (xi <= 0.0 && eta <= 0.0)
     352             :       {
     353         260 :         xi = 0.0;
     354         260 :         eta = 0.0;
     355         260 :         off_edge_nodes.push_back(side->node_ptr(0));
     356             :       }
     357        2214 :       else if (xi > 0.0 && xi < 1.0 && eta < 0.0)
     358             :       {
     359         312 :         eta = 0.0;
     360         312 :         off_edge_nodes.push_back(side->node_ptr(0));
     361         312 :         off_edge_nodes.push_back(side->node_ptr(1));
     362             :       }
     363        1902 :       else if (eta > 0.0 && eta < 1.0 && xi < 0.0)
     364             :       {
     365         280 :         xi = 0.0;
     366         280 :         off_edge_nodes.push_back(side->node_ptr(2));
     367         280 :         off_edge_nodes.push_back(side->node_ptr(0));
     368             :       }
     369        1622 :       else if (xi >= 1.0 && (eta - xi) <= -1.0)
     370             :       {
     371         868 :         xi = 1.0;
     372         868 :         eta = 0.0;
     373         868 :         off_edge_nodes.push_back(side->node_ptr(1));
     374             :       }
     375         754 :       else if (eta >= 1.0 && (eta - xi) >= 1.0)
     376             :       {
     377         359 :         xi = 0.0;
     378         359 :         eta = 1.0;
     379         359 :         off_edge_nodes.push_back(side->node_ptr(2));
     380             :       }
     381         395 :       else if ((xi + eta) > 1.0)
     382             :       {
     383         395 :         Real delta = (xi + eta - 1.0) / 2.0;
     384         395 :         xi -= delta;
     385         395 :         eta -= delta;
     386         395 :         off_edge_nodes.push_back(side->node_ptr(1));
     387         395 :         off_edge_nodes.push_back(side->node_ptr(2));
     388             :       }
     389        2474 :       break;
     390             :     }
     391             : 
     392      900912 :     case QUAD4:
     393             :     case QUAD8:
     394             :     case QUAD9:
     395             :     {
     396             :       // The reference quadrilateral element is [-1,1]^2.
     397      900912 :       if (xi < -1.0)
     398             :       {
     399      311377 :         xi = -1.0;
     400      311377 :         if (eta < -1.0)
     401             :         {
     402       74657 :           eta = -1.0;
     403       74657 :           off_edge_nodes.push_back(side->node_ptr(0));
     404             :         }
     405      236720 :         else if (eta > 1.0)
     406             :         {
     407       53751 :           eta = 1.0;
     408       53751 :           off_edge_nodes.push_back(side->node_ptr(3));
     409             :         }
     410             :         else
     411             :         {
     412      182969 :           off_edge_nodes.push_back(side->node_ptr(3));
     413      182969 :           off_edge_nodes.push_back(side->node_ptr(0));
     414             :         }
     415             :       }
     416      589535 :       else if (xi > 1.0)
     417             :       {
     418      422259 :         xi = 1.0;
     419      422259 :         if (eta < -1.0)
     420             :         {
     421       84745 :           eta = -1.0;
     422       84745 :           off_edge_nodes.push_back(side->node_ptr(1));
     423             :         }
     424      337514 :         else if (eta > 1.0)
     425             :         {
     426       80643 :           eta = 1.0;
     427       80643 :           off_edge_nodes.push_back(side->node_ptr(2));
     428             :         }
     429             :         else
     430             :         {
     431      256871 :           off_edge_nodes.push_back(side->node_ptr(1));
     432      256871 :           off_edge_nodes.push_back(side->node_ptr(2));
     433             :         }
     434             :       }
     435             :       else
     436             :       {
     437      167276 :         if (eta < -1.0)
     438             :         {
     439      108629 :           eta = -1.0;
     440      108629 :           off_edge_nodes.push_back(side->node_ptr(0));
     441      108629 :           off_edge_nodes.push_back(side->node_ptr(1));
     442             :         }
     443       58647 :         else if (eta > 1.0)
     444             :         {
     445       58647 :           eta = 1.0;
     446       58647 :           off_edge_nodes.push_back(side->node_ptr(2));
     447       58647 :           off_edge_nodes.push_back(side->node_ptr(3));
     448             :         }
     449             :       }
     450      900912 :       break;
     451             :     }
     452             : 
     453           0 :     default:
     454             :     {
     455           0 :       mooseError("Unsupported face type: ", t);
     456             :       break;
     457             :     }
     458             :   }
     459      984211 : }
     460             : 
     461             : } // namespace Moose

Generated by: LCOV version 1.14