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