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

Generated by: LCOV version 1.14