https://mooseframework.inl.gov
FindContactPoint.C
Go to the documentation of this file.
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 
52 void
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  search_succeeded = true;
65 
66  const Elem * primary_elem = p_info._elem;
67 
68  unsigned int dim = primary_elem->dim();
69 
70  const Elem * side = p_info._side;
71 
72  const std::vector<libMesh::Point> & phys_point = fe_side->get_xyz();
73 
74  const std::vector<RealGradient> & dxyz_dxi = fe_side->get_dxyzdxi();
75  const std::vector<RealGradient> & d2xyz_dxi2 = fe_side->get_d2xyzdxi2();
76  const std::vector<RealGradient> & d2xyz_dxieta = fe_side->get_d2xyzdxideta();
77 
78  const std::vector<RealGradient> & dxyz_deta = fe_side->get_dxyzdeta();
79  const std::vector<RealGradient> & d2xyz_deta2 = fe_side->get_d2xyzdeta2();
80  const std::vector<RealGradient> & d2xyz_detaxi = fe_side->get_d2xyzdxideta();
81 
82  if (dim == 1)
83  {
84  const Node * nearest_node = side->node_ptr(0);
85  p_info._closest_point = *nearest_node;
86  p_info._closest_point_ref =
87  primary_elem->master_point(primary_elem->get_node_index(nearest_node));
88  std::vector<libMesh::Point> elem_points = {p_info._closest_point_ref};
89 
90  const std::vector<RealGradient> & elem_dxyz_dxi = fe_elem->get_dxyzdxi();
91 
92  fe_elem->reinit(primary_elem, &elem_points);
93  fe_side->reinit(side, &elem_points);
94 
95  p_info._normal = elem_dxyz_dxi[0];
96  if (nearest_node->id() == primary_elem->node_id(0))
97  p_info._normal *= -1.0;
98  p_info._normal /= p_info._normal.norm();
99 
100  libMesh::Point from_secondary_to_closest = p_info._closest_point - secondary_point;
101  p_info._distance = from_secondary_to_closest * p_info._normal;
102  libMesh::Point tangential = from_secondary_to_closest - p_info._distance * p_info._normal;
103  p_info._tangential_distance = tangential.norm();
104  p_info._dxyzdxi = dxyz_dxi;
105  p_info._dxyzdeta = dxyz_deta;
106  p_info._d2xyzdxideta = d2xyz_dxieta;
107  p_info._side_phi = fe_side->get_phi();
108  p_info._side_grad_phi = fe_side->get_dphi();
109  contact_point_on_side = true;
110  return;
111  }
112 
113  libMesh::Point ref_point;
114 
115  if (start_with_centroid)
116  ref_point = FEMap::inverse_map(dim - 1, side, side->vertex_average(), TOLERANCE, false);
117  else
118  ref_point = p_info._closest_point_ref;
119 
120  std::vector<libMesh::Point> points = {ref_point};
121  fe_side->reinit(side, &points);
122  RealGradient d = secondary_point - phys_point[0];
123 
124  Real update_size = std::numeric_limits<Real>::max();
125 
126  // Least squares
127  for (unsigned int it = 0; it < 3 && update_size > TOLERANCE * 1e3; ++it)
128  {
129  DenseMatrix<Real> jac(dim - 1, dim - 1);
130  jac(0, 0) = -(dxyz_dxi[0] * dxyz_dxi[0]);
131 
132  if (dim - 1 == 2)
133  {
134  jac(1, 0) = -(dxyz_dxi[0] * dxyz_deta[0]);
135  jac(0, 1) = -(dxyz_deta[0] * dxyz_dxi[0]);
136  jac(1, 1) = -(dxyz_deta[0] * dxyz_deta[0]);
137  }
138 
139  DenseVector<Real> rhs(dim - 1);
140  rhs(0) = dxyz_dxi[0] * d;
141 
142  if (dim - 1 == 2)
143  rhs(1) = dxyz_deta[0] * d;
144 
145  DenseVector<Real> update(dim - 1);
146  jac.lu_solve(rhs, update);
147 
148  ref_point(0) -= update(0);
149 
150  if (dim - 1 == 2)
151  ref_point(1) -= update(1);
152 
153  points[0] = ref_point;
154  fe_side->reinit(side, &points);
155  d = secondary_point - phys_point[0];
156 
157  update_size = update.l2_norm();
158  }
159 
160  update_size = std::numeric_limits<Real>::max();
161 
162  unsigned nit = 0;
163 
164  // Newton Loop
165  const auto max_newton_its = 25;
166  const auto tolerance_newton = 1e3 * TOLERANCE * TOLERANCE;
167  for (; nit < max_newton_its && update_size > tolerance_newton; nit++)
168  {
169  d = secondary_point - phys_point[0];
170 
171  DenseMatrix<Real> jac(dim - 1, dim - 1);
172  jac(0, 0) = (d2xyz_dxi2[0] * d) - (dxyz_dxi[0] * dxyz_dxi[0]);
173 
174  if (dim - 1 == 2)
175  {
176  jac(1, 0) = (d2xyz_dxieta[0] * d) - (dxyz_dxi[0] * dxyz_deta[0]);
177 
178  jac(0, 1) = (d2xyz_detaxi[0] * d) - (dxyz_deta[0] * dxyz_dxi[0]);
179  jac(1, 1) = (d2xyz_deta2[0] * d) - (dxyz_deta[0] * dxyz_deta[0]);
180  }
181 
182  DenseVector<Real> rhs(dim - 1);
183  rhs(0) = -dxyz_dxi[0] * d;
184 
185  if (dim - 1 == 2)
186  rhs(1) = -dxyz_deta[0] * d;
187 
188  DenseVector<Real> update(dim - 1);
189  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  Real mult = 1;
194  while (true)
195  {
196  try
197  {
198  ref_point(0) += mult * update(0);
199 
200  if (dim - 1 == 2)
201  ref_point(1) += mult * update(1);
202 
203  points[0] = ref_point;
204  fe_side->reinit(side, &points);
205  d = secondary_point - phys_point[0];
206 
207  // we don't multiply by 'mult' because it is used for convergence
208  update_size = update.l2_norm();
209  break;
210  }
211  // libMesh might throw here if we hit a zero/negative Jacobian
212  catch (std::exception & e)
213  {
214  // Make sure this *is* just a bad mapping Jacobian
215  if (!strstr(e.what(), "Jacobian") && !strstr(e.what(), "det != 0"))
216  throw;
217 
218  ref_point(0) -= mult * update(0);
219  if (dim - 1 == 2)
220  ref_point(1) -= mult * update(1);
221 
222  mult *= 0.5;
223  if (mult < 1e-6)
224  {
225 #ifndef NDEBUG
226  mooseWarning("We could not solve for the contact point.", e.what());
227 #endif
228  update_size = update.l2_norm();
229  d = (secondary_point - phys_point[0]) * mult;
230  break;
231  }
232  }
233  }
234  // We failed the line search, make sure to trigger the error
235  if (mult < 1e-6)
236  {
237  nit = max_newton_its;
238  update_size = 1;
239  break;
240  }
241  }
242 
243  if (nit == max_newton_its && update_size > tolerance_newton)
244  {
245  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  return;
256  }
257 
258  p_info._closest_point_ref = ref_point;
259  p_info._closest_point = phys_point[0];
260  p_info._distance = d.norm();
261 
262  if (dim - 1 == 2)
263  {
264  p_info._normal = dxyz_dxi[0].cross(dxyz_deta[0]);
265  if (!MooseUtils::absoluteFuzzyEqual(p_info._normal.norm(), 0))
266  p_info._normal /= p_info._normal.norm();
267  }
268  else
269  {
270  const Node * const * elem_nodes = primary_elem->get_nodes();
271  const libMesh::Point in_plane_vector1 = *elem_nodes[1] - *elem_nodes[0];
272  const libMesh::Point in_plane_vector2 = *elem_nodes[2] - *elem_nodes[0];
273 
274  libMesh::Point out_of_plane_normal = in_plane_vector1.cross(in_plane_vector2);
275  out_of_plane_normal /= out_of_plane_normal.norm();
276 
277  p_info._normal = dxyz_dxi[0].cross(out_of_plane_normal);
278  if (std::fabs(p_info._normal.norm()) > 1e-15)
279  p_info._normal /= p_info._normal.norm();
280  }
281 
282  // If the point has not penetrated the face, make the distance negative
283  const Real dot(d * p_info._normal);
284  if (dot > 0.0)
285  p_info._distance = -p_info._distance;
286 
287  contact_point_on_side = side->on_reference_element(ref_point);
288 
289  p_info._tangential_distance = 0.0;
290 
291  if (!contact_point_on_side)
292  {
293  p_info._closest_point_on_face_ref = ref_point;
295 
296  points[0] = p_info._closest_point_on_face_ref;
297  fe_side->reinit(side, &points);
298  libMesh::Point closest_point_on_face(phys_point[0]);
299 
300  RealGradient off_face = closest_point_on_face - p_info._closest_point;
301  Real tangential_distance = off_face.norm();
302  p_info._tangential_distance = tangential_distance;
303  if (tangential_distance <= tangential_tolerance)
304  {
305  contact_point_on_side = true;
306  }
307  }
308 
309  const std::vector<std::vector<Real>> & phi = fe_side->get_phi();
310  const std::vector<std::vector<RealGradient>> & grad_phi = fe_side->get_dphi();
311 
312  points[0] = p_info._closest_point_ref;
313  fe_side->reinit(side, &points);
314 
315  p_info._side_phi = phi;
316  p_info._side_grad_phi = grad_phi;
317  p_info._dxyzdxi = dxyz_dxi;
318  p_info._dxyzdeta = dxyz_deta;
319  p_info._d2xyzdxideta = d2xyz_dxieta;
320 }
321 
322 void
324  const Elem * side,
325  std::vector<const Node *> & off_edge_nodes)
326 {
327  const ElemType t(side->type());
328  off_edge_nodes.clear();
329  Real & xi = p(0);
330  Real & eta = p(1);
331 
332  switch (t)
333  {
334  case EDGE2:
335  case EDGE3:
336  case EDGE4:
337  {
338  // The reference 1D element is [-1,1].
339  if (xi < -1.0)
340  {
341  xi = -1.0;
342  off_edge_nodes.push_back(side->node_ptr(0));
343  }
344  else if (xi > 1.0)
345  {
346  xi = 1.0;
347  off_edge_nodes.push_back(side->node_ptr(1));
348  }
349  break;
350  }
351 
352  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  if (xi <= 0.0 && eta <= 0.0)
360  {
361  xi = 0.0;
362  eta = 0.0;
363  off_edge_nodes.push_back(side->node_ptr(0));
364  }
365  else if (xi > 0.0 && xi < 1.0 && eta < 0.0)
366  {
367  eta = 0.0;
368  off_edge_nodes.push_back(side->node_ptr(0));
369  off_edge_nodes.push_back(side->node_ptr(1));
370  }
371  else if (eta > 0.0 && eta < 1.0 && xi < 0.0)
372  {
373  xi = 0.0;
374  off_edge_nodes.push_back(side->node_ptr(2));
375  off_edge_nodes.push_back(side->node_ptr(0));
376  }
377  else if (xi >= 1.0 && (eta - xi) <= -1.0)
378  {
379  xi = 1.0;
380  eta = 0.0;
381  off_edge_nodes.push_back(side->node_ptr(1));
382  }
383  else if (eta >= 1.0 && (eta - xi) >= 1.0)
384  {
385  xi = 0.0;
386  eta = 1.0;
387  off_edge_nodes.push_back(side->node_ptr(2));
388  }
389  else if ((xi + eta) > 1.0)
390  {
391  Real delta = (xi + eta - 1.0) / 2.0;
392  xi -= delta;
393  eta -= delta;
394  off_edge_nodes.push_back(side->node_ptr(1));
395  off_edge_nodes.push_back(side->node_ptr(2));
396  }
397  break;
398  }
399 
400  case QUAD4:
401  case QUAD8:
402  case QUAD9:
403  {
404  // The reference quadrilateral element is [-1,1]^2.
405  if (xi < -1.0)
406  {
407  xi = -1.0;
408  if (eta < -1.0)
409  {
410  eta = -1.0;
411  off_edge_nodes.push_back(side->node_ptr(0));
412  }
413  else if (eta > 1.0)
414  {
415  eta = 1.0;
416  off_edge_nodes.push_back(side->node_ptr(3));
417  }
418  else
419  {
420  off_edge_nodes.push_back(side->node_ptr(3));
421  off_edge_nodes.push_back(side->node_ptr(0));
422  }
423  }
424  else if (xi > 1.0)
425  {
426  xi = 1.0;
427  if (eta < -1.0)
428  {
429  eta = -1.0;
430  off_edge_nodes.push_back(side->node_ptr(1));
431  }
432  else if (eta > 1.0)
433  {
434  eta = 1.0;
435  off_edge_nodes.push_back(side->node_ptr(2));
436  }
437  else
438  {
439  off_edge_nodes.push_back(side->node_ptr(1));
440  off_edge_nodes.push_back(side->node_ptr(2));
441  }
442  }
443  else
444  {
445  if (eta < -1.0)
446  {
447  eta = -1.0;
448  off_edge_nodes.push_back(side->node_ptr(0));
449  off_edge_nodes.push_back(side->node_ptr(1));
450  }
451  else if (eta > 1.0)
452  {
453  eta = 1.0;
454  off_edge_nodes.push_back(side->node_ptr(2));
455  off_edge_nodes.push_back(side->node_ptr(3));
456  }
457  }
458  break;
459  }
460 
461  default:
462  {
463  mooseError("Unsupported face type: ", t);
464  break;
465  }
466  }
467 }
468 
469 } // namespace Moose
std::vector< RealGradient > _d2xyzdxideta
ElemType
auto norm() const
unsigned int get_node_index(const Node *node_ptr) const
RealVectorValue _normal
virtual_for_inffe const std::vector< RealGradient > & get_dxyzdeta() const
virtual_for_inffe const std::vector< RealGradient > & get_d2xyzdxi2() const
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
Data structure used to hold penetration information.
static constexpr Real TOLERANCE
static Point inverse_map(const unsigned int dim, const Elem *elem, const Point &p, const Real tolerance=TOLERANCE, const bool secure=true, const bool extra_checks=true)
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition: MooseError.h:345
Point _closest_point_on_face_ref
virtual bool on_reference_element(const Point &p, const Real eps=TOLERANCE) const=0
static constexpr std::size_t dim
This is the dimension of all vector and tensor datastructures used in MOOSE.
Definition: Moose.h:163
virtual_for_inffe const std::vector< RealGradient > & get_d2xyzdxideta() const
std::vector< std::vector< RealGradient > > _side_grad_phi
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
std::vector< std::vector< Real > > _side_phi
virtual_for_inffe const std::vector< RealGradient > & get_d2xyzdeta2() const
auto max(const L &left, const R &right)
const std::vector< std::vector< OutputGradient > > & get_dphi() const
dof_id_type id() const
Real l2_norm() const
const Elem * _elem
const Node *const * get_nodes() const
virtual void reinit(const Elem *elem, const std::vector< Point > *const pts=nullptr, const std::vector< Real > *const weights=nullptr)=0
void findContactPoint(PenetrationInfo &p_info, libMesh::FEBase *fe_elem, libMesh::FEBase *fe_side, libMesh::FEType &fe_side_type, const libMesh::Point &secondary_point, bool start_with_centroid, const Real tangential_tolerance, bool &contact_point_on_side, bool &search_succeeded)
Finds the closest point (called the contact point) on the primary_elem on side "side" to the secondar...
virtual_for_inffe const std::vector< Point > & get_xyz() const
std::vector< RealGradient > _dxyzdxi
const Elem * _side
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
std::vector< const Node * > _off_edge_nodes
void restrictPointToFace(libMesh::Point &p, const libMesh::Elem *side, std::vector< const libMesh::Node *> &off_edge_nodes)
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual unsigned short dim() const=0
const Node * node_ptr(const unsigned int i) const
virtual Point master_point(const unsigned int i) const=0
std::vector< RealGradient > _dxyzdeta
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
virtual_for_inffe const std::vector< RealGradient > & get_dxyzdxi() const
virtual ElemType type() const=0
dof_id_type node_id(const unsigned int i) const
Point vertex_average() const
const std::vector< std::vector< OutputShape > > & get_phi() const