LCOV - code coverage report
Current view: top level - src/geomsearch - FindContactPoint.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 219 227 96.5 %
Date: 2025-07-17 01:28:37 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     1504438 : 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     1504438 :   search_succeeded = true;
      62             : 
      63     1504438 :   const Elem * primary_elem = p_info._elem;
      64             : 
      65     1504438 :   unsigned int dim = primary_elem->dim();
      66             : 
      67     1504438 :   const Elem * side = p_info._side;
      68             : 
      69     1504438 :   const std::vector<libMesh::Point> & phys_point = fe_side->get_xyz();
      70             : 
      71     1504438 :   const std::vector<RealGradient> & dxyz_dxi = fe_side->get_dxyzdxi();
      72     1504438 :   const std::vector<RealGradient> & d2xyz_dxi2 = fe_side->get_d2xyzdxi2();
      73     1504438 :   const std::vector<RealGradient> & d2xyz_dxieta = fe_side->get_d2xyzdxideta();
      74             : 
      75     1504438 :   const std::vector<RealGradient> & dxyz_deta = fe_side->get_dxyzdeta();
      76     1504438 :   const std::vector<RealGradient> & d2xyz_deta2 = fe_side->get_d2xyzdeta2();
      77     1504438 :   const std::vector<RealGradient> & d2xyz_detaxi = fe_side->get_d2xyzdxideta();
      78             : 
      79     1504438 :   if (dim == 1)
      80             :   {
      81          21 :     const Node * nearest_node = side->node_ptr(0);
      82          21 :     p_info._closest_point = *nearest_node;
      83             :     p_info._closest_point_ref =
      84          21 :         primary_elem->master_point(primary_elem->get_node_index(nearest_node));
      85          21 :     std::vector<libMesh::Point> elem_points = {p_info._closest_point_ref};
      86             : 
      87          21 :     const std::vector<RealGradient> & elem_dxyz_dxi = fe_elem->get_dxyzdxi();
      88             : 
      89          21 :     fe_elem->reinit(primary_elem, &elem_points);
      90          21 :     fe_side->reinit(side, &elem_points);
      91             : 
      92          21 :     p_info._normal = elem_dxyz_dxi[0];
      93          21 :     if (nearest_node->id() == primary_elem->node_id(0))
      94          21 :       p_info._normal *= -1.0;
      95          21 :     p_info._normal /= p_info._normal.norm();
      96             : 
      97          21 :     libMesh::Point from_secondary_to_closest = p_info._closest_point - secondary_point;
      98          21 :     p_info._distance = from_secondary_to_closest * p_info._normal;
      99          21 :     libMesh::Point tangential = from_secondary_to_closest - p_info._distance * p_info._normal;
     100          21 :     p_info._tangential_distance = tangential.norm();
     101          21 :     p_info._dxyzdxi = dxyz_dxi;
     102          21 :     p_info._dxyzdeta = dxyz_deta;
     103          21 :     p_info._d2xyzdxideta = d2xyz_dxieta;
     104          21 :     p_info._side_phi = fe_side->get_phi();
     105          21 :     p_info._side_grad_phi = fe_side->get_dphi();
     106          21 :     contact_point_on_side = true;
     107          21 :     return;
     108          21 :   }
     109             : 
     110     1504417 :   libMesh::Point ref_point;
     111             : 
     112     1504417 :   if (start_with_centroid)
     113      852767 :     ref_point = FEMap::inverse_map(dim - 1, side, side->vertex_average(), TOLERANCE, false);
     114             :   else
     115      651650 :     ref_point = p_info._closest_point_ref;
     116             : 
     117     1504417 :   std::vector<libMesh::Point> points = {ref_point};
     118     1504417 :   fe_side->reinit(side, &points);
     119     1504417 :   RealGradient d = secondary_point - phys_point[0];
     120             : 
     121     1504417 :   Real update_size = std::numeric_limits<Real>::max();
     122             : 
     123             :   // Least squares
     124     4051286 :   for (unsigned int it = 0; it < 3 && update_size > TOLERANCE * 1e3; ++it)
     125             :   {
     126     2546869 :     DenseMatrix<Real> jac(dim - 1, dim - 1);
     127     2546869 :     jac(0, 0) = -(dxyz_dxi[0] * dxyz_dxi[0]);
     128             : 
     129     2546869 :     if (dim - 1 == 2)
     130             :     {
     131     2179829 :       jac(1, 0) = -(dxyz_dxi[0] * dxyz_deta[0]);
     132     2179829 :       jac(0, 1) = -(dxyz_deta[0] * dxyz_dxi[0]);
     133     2179829 :       jac(1, 1) = -(dxyz_deta[0] * dxyz_deta[0]);
     134             :     }
     135             : 
     136     2546869 :     DenseVector<Real> rhs(dim - 1);
     137     2546869 :     rhs(0) = dxyz_dxi[0] * d;
     138             : 
     139     2546869 :     if (dim - 1 == 2)
     140     2179829 :       rhs(1) = dxyz_deta[0] * d;
     141             : 
     142     2546869 :     DenseVector<Real> update(dim - 1);
     143     2546869 :     jac.lu_solve(rhs, update);
     144             : 
     145     2546869 :     ref_point(0) -= update(0);
     146             : 
     147     2546869 :     if (dim - 1 == 2)
     148     2179829 :       ref_point(1) -= update(1);
     149             : 
     150     2546869 :     points[0] = ref_point;
     151     2546869 :     fe_side->reinit(side, &points);
     152     2546869 :     d = secondary_point - phys_point[0];
     153             : 
     154     2546869 :     update_size = update.l2_norm();
     155     2546869 :   }
     156             : 
     157     1504417 :   update_size = std::numeric_limits<Real>::max();
     158             : 
     159     1504417 :   unsigned nit = 0;
     160             : 
     161             :   // Newton Loop
     162     1504417 :   const auto max_newton_its = 25;
     163     1504417 :   const auto tolerance_newton = 1e3 * TOLERANCE * TOLERANCE;
     164     3015981 :   for (; nit < max_newton_its && update_size > tolerance_newton; nit++)
     165             :   {
     166     1511564 :     d = secondary_point - phys_point[0];
     167             : 
     168     1511564 :     DenseMatrix<Real> jac(dim - 1, dim - 1);
     169     1511564 :     jac(0, 0) = (d2xyz_dxi2[0] * d) - (dxyz_dxi[0] * dxyz_dxi[0]);
     170             : 
     171     1511564 :     if (dim - 1 == 2)
     172             :     {
     173     1273811 :       jac(1, 0) = (d2xyz_dxieta[0] * d) - (dxyz_dxi[0] * dxyz_deta[0]);
     174             : 
     175     1273811 :       jac(0, 1) = (d2xyz_detaxi[0] * d) - (dxyz_deta[0] * dxyz_dxi[0]);
     176     1273811 :       jac(1, 1) = (d2xyz_deta2[0] * d) - (dxyz_deta[0] * dxyz_deta[0]);
     177             :     }
     178             : 
     179     1511564 :     DenseVector<Real> rhs(dim - 1);
     180     1511564 :     rhs(0) = -dxyz_dxi[0] * d;
     181             : 
     182     1511564 :     if (dim - 1 == 2)
     183     1273811 :       rhs(1) = -dxyz_deta[0] * d;
     184             : 
     185     1511564 :     DenseVector<Real> update(dim - 1);
     186     1511564 :     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     1511564 :     Real mult = 1;
     191             :     while (true)
     192             :     {
     193             :       try
     194             :       {
     195     1512467 :         ref_point(0) += mult * update(0);
     196             : 
     197     1512467 :         if (dim - 1 == 2)
     198     1274714 :           ref_point(1) += mult * update(1);
     199             : 
     200     1512467 :         points[0] = ref_point;
     201     1512467 :         fe_side->reinit(side, &points);
     202     1511564 :         d = secondary_point - phys_point[0];
     203             : 
     204             :         // we don't multiply by 'mult' because it is used for convergence
     205     1511564 :         update_size = update.l2_norm();
     206     1511564 :         break;
     207             :       }
     208         903 :       catch (libMesh::LogicError & e)
     209             :       {
     210         903 :         ref_point(0) -= mult * update(0);
     211         903 :         if (dim - 1 == 2)
     212         903 :           ref_point(1) -= mult * update(1);
     213             : 
     214         903 :         mult *= 0.5;
     215         903 :         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         903 :       }
     225         903 :     }
     226             :     // We failed the line search, make sure to trigger the error
     227     1511564 :     if (mult < 1e-6)
     228             :     {
     229           0 :       nit = max_newton_its;
     230           0 :       update_size = 1;
     231           0 :       break;
     232             :     }
     233     1511564 :   }
     234             : 
     235     1504417 :   if (nit == max_newton_its && update_size > tolerance_newton)
     236             :   {
     237          14 :     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          14 :     return;
     248             :   }
     249             : 
     250     1504403 :   p_info._closest_point_ref = ref_point;
     251     1504403 :   p_info._closest_point = phys_point[0];
     252     1504403 :   p_info._distance = d.norm();
     253             : 
     254     1504403 :   if (dim - 1 == 2)
     255             :   {
     256     1266650 :     p_info._normal = dxyz_dxi[0].cross(dxyz_deta[0]);
     257     1266650 :     if (!MooseUtils::absoluteFuzzyEqual(p_info._normal.norm(), 0))
     258     1266447 :       p_info._normal /= p_info._normal.norm();
     259             :   }
     260             :   else
     261             :   {
     262      237753 :     const Node * const * elem_nodes = primary_elem->get_nodes();
     263      237753 :     const libMesh::Point in_plane_vector1 = *elem_nodes[1] - *elem_nodes[0];
     264      237753 :     const libMesh::Point in_plane_vector2 = *elem_nodes[2] - *elem_nodes[0];
     265             : 
     266      237753 :     libMesh::Point out_of_plane_normal = in_plane_vector1.cross(in_plane_vector2);
     267      237753 :     out_of_plane_normal /= out_of_plane_normal.norm();
     268             : 
     269      237753 :     p_info._normal = dxyz_dxi[0].cross(out_of_plane_normal);
     270      237753 :     if (std::fabs(p_info._normal.norm()) > 1e-15)
     271      237753 :       p_info._normal /= p_info._normal.norm();
     272             :   }
     273             : 
     274             :   // If the point has not penetrated the face, make the distance negative
     275     1504403 :   const Real dot(d * p_info._normal);
     276     1504403 :   if (dot > 0.0)
     277     1082880 :     p_info._distance = -p_info._distance;
     278             : 
     279     1504403 :   contact_point_on_side = side->on_reference_element(ref_point);
     280             : 
     281     1504403 :   p_info._tangential_distance = 0.0;
     282             : 
     283     1504403 :   if (!contact_point_on_side)
     284             :   {
     285      895713 :     p_info._closest_point_on_face_ref = ref_point;
     286      895713 :     restrictPointToFace(p_info._closest_point_on_face_ref, side, p_info._off_edge_nodes);
     287             : 
     288      895713 :     points[0] = p_info._closest_point_on_face_ref;
     289      895713 :     fe_side->reinit(side, &points);
     290      895713 :     libMesh::Point closest_point_on_face(phys_point[0]);
     291             : 
     292      895713 :     RealGradient off_face = closest_point_on_face - p_info._closest_point;
     293      895713 :     Real tangential_distance = off_face.norm();
     294      895713 :     p_info._tangential_distance = tangential_distance;
     295      895713 :     if (tangential_distance <= tangential_tolerance)
     296             :     {
     297       96782 :       contact_point_on_side = true;
     298             :     }
     299             :   }
     300             : 
     301     1504403 :   const std::vector<std::vector<Real>> & phi = fe_side->get_phi();
     302     1504403 :   const std::vector<std::vector<RealGradient>> & grad_phi = fe_side->get_dphi();
     303             : 
     304     1504403 :   points[0] = p_info._closest_point_ref;
     305     1504403 :   fe_side->reinit(side, &points);
     306             : 
     307     1504403 :   p_info._side_phi = phi;
     308     1504403 :   p_info._side_grad_phi = grad_phi;
     309     1504403 :   p_info._dxyzdxi = dxyz_dxi;
     310     1504403 :   p_info._dxyzdeta = dxyz_deta;
     311     1504403 :   p_info._d2xyzdxideta = d2xyz_dxieta;
     312     1504417 : }
     313             : 
     314             : void
     315      895713 : restrictPointToFace(libMesh::Point & p,
     316             :                     const Elem * side,
     317             :                     std::vector<const Node *> & off_edge_nodes)
     318             : {
     319      895713 :   const ElemType t(side->type());
     320      895713 :   off_edge_nodes.clear();
     321      895713 :   Real & xi = p(0);
     322      895713 :   Real & eta = p(1);
     323             : 
     324      895713 :   switch (t)
     325             :   {
     326       73640 :     case EDGE2:
     327             :     case EDGE3:
     328             :     case EDGE4:
     329             :     {
     330             :       // The reference 1D element is [-1,1].
     331       73640 :       if (xi < -1.0)
     332             :       {
     333       44743 :         xi = -1.0;
     334       44743 :         off_edge_nodes.push_back(side->node_ptr(0));
     335             :       }
     336       28897 :       else if (xi > 1.0)
     337             :       {
     338       28897 :         xi = 1.0;
     339       28897 :         off_edge_nodes.push_back(side->node_ptr(1));
     340             :       }
     341       73640 :       break;
     342             :     }
     343             : 
     344        2245 :     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        2245 :       if (xi <= 0.0 && eta <= 0.0)
     352             :       {
     353         235 :         xi = 0.0;
     354         235 :         eta = 0.0;
     355         235 :         off_edge_nodes.push_back(side->node_ptr(0));
     356             :       }
     357        2010 :       else if (xi > 0.0 && xi < 1.0 && eta < 0.0)
     358             :       {
     359         283 :         eta = 0.0;
     360         283 :         off_edge_nodes.push_back(side->node_ptr(0));
     361         283 :         off_edge_nodes.push_back(side->node_ptr(1));
     362             :       }
     363        1727 :       else if (eta > 0.0 && eta < 1.0 && xi < 0.0)
     364             :       {
     365         254 :         xi = 0.0;
     366         254 :         off_edge_nodes.push_back(side->node_ptr(2));
     367         254 :         off_edge_nodes.push_back(side->node_ptr(0));
     368             :       }
     369        1473 :       else if (xi >= 1.0 && (eta - xi) <= -1.0)
     370             :       {
     371         785 :         xi = 1.0;
     372         785 :         eta = 0.0;
     373         785 :         off_edge_nodes.push_back(side->node_ptr(1));
     374             :       }
     375         688 :       else if (eta >= 1.0 && (eta - xi) >= 1.0)
     376             :       {
     377         328 :         xi = 0.0;
     378         328 :         eta = 1.0;
     379         328 :         off_edge_nodes.push_back(side->node_ptr(2));
     380             :       }
     381         360 :       else if ((xi + eta) > 1.0)
     382             :       {
     383         360 :         Real delta = (xi + eta - 1.0) / 2.0;
     384         360 :         xi -= delta;
     385         360 :         eta -= delta;
     386         360 :         off_edge_nodes.push_back(side->node_ptr(1));
     387         360 :         off_edge_nodes.push_back(side->node_ptr(2));
     388             :       }
     389        2245 :       break;
     390             :     }
     391             : 
     392      819828 :     case QUAD4:
     393             :     case QUAD8:
     394             :     case QUAD9:
     395             :     {
     396             :       // The reference quadrilateral element is [-1,1]^2.
     397      819828 :       if (xi < -1.0)
     398             :       {
     399      283059 :         xi = -1.0;
     400      283059 :         if (eta < -1.0)
     401             :         {
     402       68099 :           eta = -1.0;
     403       68099 :           off_edge_nodes.push_back(side->node_ptr(0));
     404             :         }
     405      214960 :         else if (eta > 1.0)
     406             :         {
     407       48755 :           eta = 1.0;
     408       48755 :           off_edge_nodes.push_back(side->node_ptr(3));
     409             :         }
     410             :         else
     411             :         {
     412      166205 :           off_edge_nodes.push_back(side->node_ptr(3));
     413      166205 :           off_edge_nodes.push_back(side->node_ptr(0));
     414             :         }
     415             :       }
     416      536769 :       else if (xi > 1.0)
     417             :       {
     418      384334 :         xi = 1.0;
     419      384334 :         if (eta < -1.0)
     420             :         {
     421       77218 :           eta = -1.0;
     422       77218 :           off_edge_nodes.push_back(side->node_ptr(1));
     423             :         }
     424      307116 :         else if (eta > 1.0)
     425             :         {
     426       73392 :           eta = 1.0;
     427       73392 :           off_edge_nodes.push_back(side->node_ptr(2));
     428             :         }
     429             :         else
     430             :         {
     431      233724 :           off_edge_nodes.push_back(side->node_ptr(1));
     432      233724 :           off_edge_nodes.push_back(side->node_ptr(2));
     433             :         }
     434             :       }
     435             :       else
     436             :       {
     437      152435 :         if (eta < -1.0)
     438             :         {
     439       99122 :           eta = -1.0;
     440       99122 :           off_edge_nodes.push_back(side->node_ptr(0));
     441       99122 :           off_edge_nodes.push_back(side->node_ptr(1));
     442             :         }
     443       53313 :         else if (eta > 1.0)
     444             :         {
     445       53313 :           eta = 1.0;
     446       53313 :           off_edge_nodes.push_back(side->node_ptr(2));
     447       53313 :           off_edge_nodes.push_back(side->node_ptr(3));
     448             :         }
     449             :       }
     450      819828 :       break;
     451             :     }
     452             : 
     453           0 :     default:
     454             :     {
     455           0 :       mooseError("Unsupported face type: ", t);
     456             :       break;
     457             :     }
     458             :   }
     459      895713 : }
     460             : 
     461             : } // namespace Moose

Generated by: LCOV version 1.14