https://mooseframework.inl.gov
MortarUtils.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 #include "MortarUtils.h"
11 #include "MooseLagrangeHelpers.h"
12 
13 #include "libmesh/enum_to_string.h"
14 #include "libmesh/fe_interface.h"
15 #include "metaphysicl/dualnumberarray.h"
16 #include "Eigen/Dense"
17 
18 using MetaPhysicL::NumberArray;
19 
20 typedef DualNumber<Real, NumberArray<2, Real>> Dual2;
21 
22 namespace Moose
23 {
24 namespace Mortar
25 {
26 std::vector<unsigned int>
27 getMortarSubElementNodeIndices(const Elem & parent_elem, const unsigned int sub_elem)
28 {
29  if (sub_elem >= parent_elem.n_sub_elem())
30  mooseError("Invalid 3D mortar sub-element index ",
31  sub_elem,
32  " for parent element ",
33  parent_elem.id(),
34  " of type ",
35  libMesh::Utility::enum_to_string<ElemType>(parent_elem.type()),
36  ", which has ",
37  parent_elem.n_sub_elem(),
38  " sub-elements.");
39 
40  switch (parent_elem.type())
41  {
42  case TRI3:
43  return {0, 1, 2};
44  case QUAD4:
45  return {0, 1, 2, 3};
46  case TRI6:
47  case TRI7:
48  switch (sub_elem)
49  {
50  case 0:
51  return {0, 3, 5};
52  case 1:
53  return {3, 4, 5};
54  case 2:
55  return {3, 1, 4};
56  case 3:
57  return {5, 4, 2};
58  default:
59  mooseError("Invalid 3D mortar triangular sub-element index ", sub_elem, ".");
60  }
61  case QUAD8:
62  switch (sub_elem)
63  {
64  case 0:
65  return {0, 4, 7};
66  case 1:
67  return {4, 1, 5};
68  case 2:
69  return {5, 2, 6};
70  case 3:
71  return {7, 6, 3};
72  case 4:
73  return {4, 5, 6, 7};
74  default:
75  mooseError("Invalid 3D mortar QUAD8 sub-element index ", sub_elem, ".");
76  }
77  case QUAD9:
78  switch (sub_elem)
79  {
80  case 0:
81  return {0, 4, 8, 7};
82  case 1:
83  return {4, 1, 5, 8};
84  case 2:
85  return {8, 5, 2, 6};
86  case 3:
87  return {7, 8, 6, 3};
88  default:
89  mooseError("Invalid 3D mortar QUAD9 sub-element index ", sub_elem, ".");
90  }
91  default:
92  mooseError("Parent face element ",
93  parent_elem.id(),
94  " has unsupported type ",
95  libMesh::Utility::enum_to_string<ElemType>(parent_elem.type()),
96  " for 3D mortar sub-element topology.");
97  }
98 }
99 
100 void
101 mapQPoints3dFromReference(const Elem & mortar_segment_elem,
102  const MortarSegmentReferencePoints & reference_points,
103  const QBase & qrule_msm,
104  std::vector<Point> & secondary_q_pts,
105  std::vector<Point> & primary_q_pts)
106 {
107  mooseAssert(mortar_segment_elem.type() == TRI3,
108  "Reference interpolation expects triangular mortar segments.");
109  const FEType fe_type(FIRST, LAGRANGE);
110 
111  for (const auto qp : make_range(qrule_msm.n_points()))
112  {
113  Point secondary_qp;
114  Point primary_qp;
115 
116  for (const auto n : index_range(reference_points.secondary_reference_points))
117  {
118  const auto phi =
119  FEInterface::shape(fe_type, &mortar_segment_elem, n, qrule_msm.qp(qp), false);
120  secondary_qp += phi * reference_points.secondary_reference_points[n];
121  primary_qp += phi * reference_points.primary_reference_points[n];
122  }
123 
124  secondary_q_pts.push_back(secondary_qp);
125  primary_q_pts.push_back(primary_qp);
126  }
127 }
128 
129 void
130 projectQPoints3d(const Elem * const msm_elem,
131  const Elem * const primal_elem,
132  const unsigned int sub_elem_index,
133  const QBase & qrule_msm,
134  std::vector<Point> & q_pts)
135 {
136  const auto msm_elem_order = msm_elem->default_order();
137  const auto msm_elem_type = msm_elem->type();
138 
139  // Get normal to linearized element, could store and query but computation is easy
140  const Point e1 = msm_elem->point(0) - msm_elem->point(1);
141  const Point e2 = msm_elem->point(2) - msm_elem->point(1);
142  const Point normal = e2.cross(e1).unit();
143 
144  // Get sub-elem (for second order meshes, otherwise trivial)
145  const auto sub_elem = msm_elem->get_extra_integer(sub_elem_index);
146  const ElemType primal_type = primal_elem->type();
147 
148  // Transforms quadrature point from first order sub-elements (in case of second-order)
149  // to primal element
150  auto transform_qp = [primal_type, sub_elem](const Real nu, const Real xi)
151  {
152  switch (primal_type)
153  {
154  case TRI3:
155  return Point(nu, xi, 0);
156  case QUAD4:
157  return Point(nu, xi, 0);
158  case TRI6:
159  case TRI7:
160  switch (sub_elem)
161  {
162  case 0:
163  return Point(0.5 * nu, 0.5 * xi, 0);
164  case 1:
165  return Point(0.5 * (1 - xi), 0.5 * (nu + xi), 0);
166  case 2:
167  return Point(0.5 * (1 + nu), 0.5 * xi, 0);
168  case 3:
169  return Point(0.5 * nu, 0.5 * (1 + xi), 0);
170  default:
171  mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
172  }
173  case QUAD8:
174  switch (sub_elem)
175  {
176  case 0:
177  return Point(nu - 1, xi - 1, 0);
178  case 1:
179  return Point(nu + xi, xi - 1, 0);
180  case 2:
181  return Point(1 - xi, nu + xi, 0);
182  case 3:
183  return Point(nu - 1, nu + xi, 0);
184  case 4:
185  return Point(0.5 * (nu - xi), 0.5 * (nu + xi), 0);
186  default:
187  mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
188  }
189  case QUAD9:
190  switch (sub_elem)
191  {
192  case 0:
193  return Point(0.5 * (nu - 1), 0.5 * (xi - 1), 0);
194  case 1:
195  return Point(0.5 * (nu + 1), 0.5 * (xi - 1), 0);
196  case 2:
197  return Point(0.5 * (nu + 1), 0.5 * (xi + 1), 0);
198  case 3:
199  return Point(0.5 * (nu - 1), 0.5 * (xi + 1), 0);
200  default:
201  mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
202  }
203  default:
204  mooseError("transform_qp: Face element type: ",
205  libMesh::Utility::enum_to_string<ElemType>(primal_type),
206  " invalid for 3D mortar");
207  }
208  };
209 
210  auto sub_element_type = [primal_type, sub_elem]()
211  {
212  switch (primal_type)
213  {
214  case TRI3:
215  case TRI6:
216  case TRI7:
217  return TRI3;
218  case QUAD4:
219  case QUAD9:
220  return QUAD4;
221  case QUAD8:
222  switch (sub_elem)
223  {
224  case 0:
225  case 1:
226  case 2:
227  case 3:
228  return TRI3;
229  case 4:
230  return QUAD4;
231  default:
232  mooseError("sub_element_type: Invalid sub_elem: ", sub_elem);
233  }
234  default:
235  mooseError("sub_element_type: Face element type: ",
236  libMesh::Utility::enum_to_string<ElemType>(primal_type),
237  " invalid for 3D mortar");
238  }
239  };
240 
241  // Get sub-elem node indices
242  const auto sub_elem_node_indices = getMortarSubElementNodeIndices(*primal_elem, sub_elem);
243 
244  // Loop through quadrature points on msm_elem
245  for (auto qp : make_range(qrule_msm.n_points()))
246  {
247  // Get physical point on msm_elem to project
248  Point x0;
249  for (auto n : make_range(msm_elem->n_nodes()))
250  x0 += Moose::fe_lagrange_2D_shape(msm_elem_type,
251  msm_elem_order,
252  n,
253  static_cast<const TypeVector<Real> &>(qrule_msm.qp(qp))) *
254  msm_elem->point(n);
255 
256  // Use msm_elem quadrature point as initial guess
257  // (will be correct for aligned meshes)
258  Dual2 xi1{};
259  xi1.value() = qrule_msm.qp(qp)(0);
260  xi1.derivatives()[0] = 1.0;
261  Dual2 xi2{};
262  xi2.value() = qrule_msm.qp(qp)(1);
263  xi2.derivatives()[1] = 1.0;
264  VectorValue<Dual2> xi(xi1, xi2, 0);
265  unsigned int current_iterate = 0, max_iterates = 10;
266 
267  // Project qp from mortar segments to first order sub-elements (elements in case of first order
268  // geometry)
269  do
270  {
271  VectorValue<Dual2> x1;
272  for (auto n : make_range(sub_elem_node_indices.size()))
273  x1 += Moose::fe_lagrange_2D_shape(sub_element_type(), FIRST, n, xi) *
274  primal_elem->point(sub_elem_node_indices[n]);
275  auto u = x1 - x0;
276 
277  VectorValue<Dual2> F(u(1) * normal(2) - u(2) * normal(1),
278  u(2) * normal(0) - u(0) * normal(2),
279  u(0) * normal(1) - u(1) * normal(0));
280 
281  Real projection_tolerance(1e-10);
282 
283  // Normalize tolerance with quantities involved in the projection.
284  // Absolute projection tolerance is loosened for displacements larger than those on the order
285  // of one. Tightening the tolerance for displacements of smaller orders causes this tolerance
286  // to not be reached in a number of tests.
287  if (!u.is_zero() && u.norm().value() > 1.0)
288  projection_tolerance *= u.norm().value();
289 
290  if (MetaPhysicL::raw_value(F).norm() < projection_tolerance)
291  break;
292 
293  RealEigenMatrix J(3, 2);
294  J << F(0).derivatives()[0], F(0).derivatives()[1], F(1).derivatives()[0],
295  F(1).derivatives()[1], F(2).derivatives()[0], F(2).derivatives()[1];
296  RealEigenVector f(3);
297  f << F(0).value(), F(1).value(), F(2).value();
298  const RealEigenVector dxi = -J.colPivHouseholderQr().solve(f);
299 
300  xi(0) += dxi(0);
301  xi(1) += dxi(1);
302  } while (++current_iterate < max_iterates);
303 
304  if (current_iterate < max_iterates)
305  {
306  // Transfer quadrature point from sub-element to element and store
307  q_pts.push_back(transform_qp(xi(0).value(), xi(1).value()));
308 
309  // The following checks if quadrature point falls in correct domain.
310  // On small mortar segment elements with very distorted elements this can fail, instead of
311  // erroring simply truncate quadrature point, these points typically have very small
312  // contributions to integrals
313  auto & qp_back = q_pts.back();
314  if (primal_elem->type() == TRI3 || primal_elem->type() == TRI6 || primal_elem->type() == TRI7)
315  {
316  if (qp_back(0) < -TOLERANCE || qp_back(1) < -TOLERANCE ||
317  qp_back(0) + qp_back(1) > (1 + TOLERANCE))
318  mooseException("Quadrature point: ", qp_back, " out of bounds, truncating.");
319  }
320  else if (primal_elem->type() == QUAD4 || primal_elem->type() == QUAD8 ||
321  primal_elem->type() == QUAD9)
322  {
323  if (qp_back(0) < (-1 - TOLERANCE) || qp_back(0) > (1 + TOLERANCE) ||
324  qp_back(1) < (-1 - TOLERANCE) || qp_back(1) > (1 + TOLERANCE))
325  mooseException("Quadrature point: ", qp_back, " out of bounds, truncating");
326  }
327  }
328  else
329  {
330  mooseError("Newton iteration for mortar quadrature mapping msm element: ",
331  msm_elem->id(),
332  " to elem: ",
333  primal_elem->id(),
334  " didn't converge. MSM element volume: ",
335  msm_elem->volume());
336  }
337  }
338 }
339 }
340 }
LAGRANGE
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
ElemType
QUAD8
DualNumber< Real, NumberArray< 2, Real > > Dual2
Definition: MortarUtils.C:20
std::vector< unsigned int > getMortarSubElementNodeIndices(const Elem &parent_elem, unsigned int sub_elem)
Return the node indices for a first-order sub-element of a parent face.
Definition: MortarUtils.C:27
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
FIRST
auto raw_value(const Eigen::Map< T > &in)
Definition: EigenADReal.h:100
void mapQPoints3dFromReference(const Elem &mortar_segment_elem, const MortarSegmentReferencePoints &reference_points, const QBase &qrule_msm, std::vector< Point > &secondary_q_pts, std::vector< Point > &primary_q_pts)
3D mapping operator that interpolates stored parent reference points on each triangular mortar segmen...
Definition: MortarUtils.C:101
std::array< Point, 3 > secondary_reference_points
TRI3
QUAD4
std::array< Point, 3 > primary_reference_points
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
TRI6
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic > RealEigenMatrix
Definition: MooseTypes.h:151
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Parent-face reference coordinates associated with the vertices of one triangular mortar segment...
auto norm(const T &a)
IntRange< T > make_range(T beg, T end)
TRI7
QUAD9
Eigen::Matrix< Real, Eigen::Dynamic, 1 > RealEigenVector
Definition: MooseTypes.h:147
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
auto index_range(const T &sizable)
void projectQPoints3d(const Elem *msm_elem, const Elem *primal_elem, unsigned int sub_elem_index, const QBase &qrule_msm, std::vector< Point > &q_pts)
3D projection operator for mapping qpoints on mortar segments to secondary or primary elements ...
Definition: MortarUtils.C:130