https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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"
12#include "MooseUtils.h"
13
14#include "libmesh/enum_to_string.h"
15#include "libmesh/fe_interface.h"
16#include "metaphysicl/dualnumberarray.h"
17#include "Eigen/Dense"
18
19#include <algorithm>
20#include <array>
21#include <cmath>
22#include <string>
23#include <vector>
24
25using MetaPhysicL::NumberArray;
26
27typedef DualNumber<Real, NumberArray<2, Real>> Dual2;
28
29namespace Moose
30{
31namespace Mortar
32{
33namespace
34{
35// These cutoffs identify degeneracy in normalized coefficients and Jacobians above roundoff.
36constexpr Real coefficient_tolerance = 1e-14;
37constexpr Real jacobian_tolerance = 1e-12;
38// Root and residual tolerances preserve ten-digit normalized inverse consistency.
39constexpr Real root_tolerance = 1e-10;
40constexpr Real inverse_residual_tolerance = 1e-10;
41// This matches the reference-space tolerance used when clipping mortar segments.
42constexpr Real mortar_reference_tolerance = 1e-8;
43
44Real
45cross2D(const Point & first, const Point & second)
46{
47 return first(0) * second(1) - first(1) * second(0);
48}
49
50struct PolynomialRoots
51{
52 std::array<Real, 2> values = {};
53 unsigned int count = 0;
54};
55
56struct BilinearMap
57{
58 Point center;
59 Point xi;
60 Point eta;
61 Point mixed;
62 Real scale = 0;
63};
64}
65
66std::vector<unsigned int>
67getMortarSubElementNodeIndices(const Elem & parent_elem, const unsigned int sub_elem)
68{
69 if (sub_elem >= parent_elem.n_sub_elem())
70 mooseError("Invalid 3D mortar sub-element index ",
71 sub_elem,
72 " for parent element ",
73 parent_elem.id(),
74 " of type ",
75 libMesh::Utility::enum_to_string<ElemType>(parent_elem.type()),
76 ", which has ",
77 parent_elem.n_sub_elem(),
78 " sub-elements.");
79
80 switch (parent_elem.type())
81 {
82 case TRI3:
83 return {0, 1, 2};
84 case QUAD4:
85 return {0, 1, 2, 3};
86 case TRI6:
87 case TRI7:
88 switch (sub_elem)
89 {
90 case 0:
91 return {0, 3, 5};
92 case 1:
93 return {3, 4, 5};
94 case 2:
95 return {3, 1, 4};
96 case 3:
97 return {5, 4, 2};
98 default:
99 mooseError("Invalid 3D mortar triangular sub-element index ", sub_elem, ".");
100 }
101 case QUAD8:
102 switch (sub_elem)
103 {
104 case 0:
105 return {0, 4, 7};
106 case 1:
107 return {4, 1, 5};
108 case 2:
109 return {5, 2, 6};
110 case 3:
111 return {7, 6, 3};
112 case 4:
113 return {4, 5, 6, 7};
114 default:
115 mooseError("Invalid 3D mortar QUAD8 sub-element index ", sub_elem, ".");
116 }
117 case QUAD9:
118 switch (sub_elem)
119 {
120 case 0:
121 return {0, 4, 8, 7};
122 case 1:
123 return {4, 1, 5, 8};
124 case 2:
125 return {8, 5, 2, 6};
126 case 3:
127 return {7, 8, 6, 3};
128 default:
129 mooseError("Invalid 3D mortar QUAD9 sub-element index ", sub_elem, ".");
130 }
131 default:
132 mooseError("Parent face element ",
133 parent_elem.id(),
134 " has unsupported type ",
135 libMesh::Utility::enum_to_string<ElemType>(parent_elem.type()),
136 " for 3D mortar sub-element topology.");
137 }
138}
139
140namespace
141{
142ElemType
143subElementType(const ElemType parent_type, const unsigned int sub_elem)
144{
145 switch (parent_type)
146 {
147 case TRI3:
148 case TRI6:
149 case TRI7:
150 return TRI3;
151 case QUAD4:
152 case QUAD9:
153 return QUAD4;
154 case QUAD8:
155 return sub_elem == 4 ? QUAD4 : TRI3;
156 default:
157 mooseError("Unsupported parent face type ",
158 libMesh::Utility::enum_to_string<ElemType>(parent_type),
159 " for 3D mortar projection.");
160 }
161}
162
163Real
164quadrilateralReferenceViolation(const Point & point)
165{
166 return std::max({Real(0), -1 - point(0), point(0) - 1, -1 - point(1), point(1) - 1});
167}
168
169[[noreturn]] void
170projectionFailure(const Elem & msm_elem,
171 const Elem & parent_elem,
172 const unsigned int sub_elem,
173 const unsigned int qp,
174 const std::string & reason)
175{
176 mooseException("Unable to map 3D mortar quadrature point ",
177 qp,
178 " from mortar segment ",
179 msm_elem.id(),
180 " to subpatch ",
181 sub_elem,
182 " of parent element ",
183 parent_elem.id(),
184 " (",
185 libMesh::Utility::enum_to_string<ElemType>(parent_elem.type()),
186 "): ",
187 reason);
188}
189
190PolynomialRoots
191realPolynomialRoots(const Real quadratic, const Real linear, const Real constant)
192{
193 PolynomialRoots roots;
194 const Real scale = std::max({std::abs(quadratic), std::abs(linear), std::abs(constant)});
195 if (scale == 0)
196 return roots;
197
198 const Real a = quadratic / scale;
199 const Real b = linear / scale;
200 const Real c = constant / scale;
201 if (std::abs(a) <= coefficient_tolerance)
202 {
203 if (std::abs(b) <= coefficient_tolerance)
204 return roots;
205 roots.values[roots.count++] = -c / b;
206 return roots;
207 }
208
209 Real discriminant = b * b - 4 * a * c;
210 const Real discriminant_scale = b * b + std::abs(4 * a * c);
211 if (discriminant < -coefficient_tolerance * std::max(discriminant_scale, Real(1)))
212 return roots;
213 discriminant = std::max(discriminant, Real(0));
214
215 const Real sqrt_discriminant = std::sqrt(discriminant);
216 // Avoid cancellation in one root and recover the other through Vieta's relation.
217 const Real q = -0.5 * (b + std::copysign(sqrt_discriminant, b));
218 if (std::abs(q) <= coefficient_tolerance)
219 {
220 roots.values[roots.count++] = -b / (2 * a);
221 return roots;
222 }
223
224 const Real first_root = q / a;
225 const Real second_root = c / q;
226 roots.values[roots.count++] = first_root;
227 if (std::abs(first_root - second_root) <= root_tolerance)
228 return roots;
229 roots.values[roots.count++] = second_root;
230 return roots;
231}
232
233Point
234evaluateBilinear(const BilinearMap & map, const Point & target, const Real xi, const Real eta)
235{
236 return map.center - target + xi * map.xi + eta * map.eta + xi * eta * map.mixed;
237}
238
239template <std::size_t N>
240void
241projectToNormalizedPlane(const Elem & msm_elem,
242 const Elem & parent_elem,
243 const std::vector<unsigned int> & sub_elem_node_indices,
244 const Point & normal,
245 const Point & target,
246 const unsigned int sub_elem,
247 const unsigned int qp,
248 const char * const sub_elem_name,
249 std::array<Point, N> & projected_nodes,
250 Point & projected_target)
251{
252 mooseAssert(sub_elem_node_indices.size() == N, "Unexpected mortar subpatch node count.");
253
254 Point longest_projected_edge;
255 Real length_scale = 0;
256 for (const auto first : index_range(sub_elem_node_indices))
257 {
258 const auto second = (first + 1) % sub_elem_node_indices.size();
259 const Point edge = parent_elem.point(sub_elem_node_indices[second]) -
260 parent_elem.point(sub_elem_node_indices[first]);
261 const Point projected_edge = edge - (edge * normal) * normal;
262 if (projected_edge.norm() > length_scale)
263 {
264 length_scale = projected_edge.norm();
265 longest_projected_edge = projected_edge;
266 }
267 }
268
269 if (!std::isfinite(length_scale) || length_scale == 0)
270 projectionFailure(msm_elem,
271 parent_elem,
272 sub_elem,
273 qp,
274 std::string("the projected ") + sub_elem_name + " is singular");
275
276 const Point first_tangent = longest_projected_edge / length_scale;
277 const Point second_tangent = normal.cross(first_tangent).unit();
278 const Point origin = parent_elem.point(sub_elem_node_indices[0]);
279
280 for (const auto node : index_range(sub_elem_node_indices))
281 {
282 const Point offset = parent_elem.point(sub_elem_node_indices[node]) - origin;
283 projected_nodes[node] =
284 Point((offset * first_tangent) / length_scale, (offset * second_tangent) / length_scale);
285 }
286 const Point target_offset = target - origin;
287 projected_target = Point((target_offset * first_tangent) / length_scale,
288 (target_offset * second_tangent) / length_scale);
289}
290
291Point
292analyticalTriangleInverse(const Elem & msm_elem,
293 const Elem & parent_elem,
294 const std::vector<unsigned int> & sub_elem_node_indices,
295 const Point & normal,
296 const Point & target,
297 const unsigned int sub_elem,
298 const unsigned int qp)
299{
300 std::array<Point, 3> projected_nodes;
301 Point projected_target;
302 projectToNormalizedPlane(msm_elem,
303 parent_elem,
304 sub_elem_node_indices,
305 normal,
306 target,
307 sub_elem,
308 qp,
309 "TRI3",
310 projected_nodes,
311 projected_target);
312
313 const Point first_edge = projected_nodes[1] - projected_nodes[0];
314 const Point second_edge = projected_nodes[2] - projected_nodes[0];
315 const Real determinant = cross2D(first_edge, second_edge);
316 if (std::abs(determinant) <= jacobian_tolerance)
317 projectionFailure(msm_elem, parent_elem, sub_elem, qp, "the projected TRI3 is singular");
318
319 const Point right_hand_side = projected_target - projected_nodes[0];
320 const Real xi = cross2D(right_hand_side, second_edge) / determinant;
321 const Real eta = cross2D(first_edge, right_hand_side) / determinant;
322 if (!std::isfinite(xi) || !std::isfinite(eta))
323 projectionFailure(msm_elem, parent_elem, sub_elem, qp, "the TRI3 inverse is not finite");
324
325 const Point unsnapped_result(xi, eta);
326 const Point unsnapped_residual =
327 projected_nodes[0] + xi * first_edge + eta * second_edge - projected_target;
328 if (unsnapped_residual.norm() > inverse_residual_tolerance)
329 projectionFailure(
330 msm_elem, parent_elem, sub_elem, qp, "the TRI3 inverse does not satisfy the projection");
331
332 std::array<Real, 3> barycentric = {{1 - xi - eta, xi, eta}};
333 const Real violation = std::max({Real(0), -barycentric[0], -barycentric[1], -barycentric[2]});
334 if (violation == 0)
335 return unsnapped_result;
336 if (violation > mortar_reference_tolerance)
337 projectionFailure(
338 msm_elem, parent_elem, sub_elem, qp, "the TRI3 inverse is outside the subpatch");
339
340 // A clipping-sized violation is roundoff at an edge: clamp all barycentric coordinates and
341 // renormalize to preserve their partition of unity.
342 for (auto & coordinate : barycentric)
343 coordinate = std::clamp(coordinate, Real(0), Real(1));
344 const Real barycentric_sum = barycentric[0] + barycentric[1] + barycentric[2];
345 for (auto & coordinate : barycentric)
346 coordinate /= barycentric_sum;
347
348 const Point result(barycentric[1], barycentric[2]);
349 const Point snapped_residual =
350 projected_nodes[0] + result(0) * first_edge + result(1) * second_edge - projected_target;
351 if (snapped_residual.norm() > mortar_reference_tolerance)
352 projectionFailure(msm_elem,
353 parent_elem,
354 sub_elem,
355 qp,
356 "the snapped TRI3 inverse does not satisfy the projection");
357 return result;
358}
359
360// The four points are projected QUAD4 vertices in libMesh node order.
361BilinearMap
362prepareQuadrilateralMap(const std::array<Point, 4> & points,
363 const Elem & msm_elem,
364 const Elem & parent_elem,
365 const unsigned int sub_elem,
366 const unsigned int qp)
367{
368 BilinearMap map;
369 map.center = 0.25 * (points[0] + points[1] + points[2] + points[3]);
370 map.xi = 0.25 * (-points[0] + points[1] + points[2] - points[3]);
371 map.eta = 0.25 * (-points[0] - points[1] + points[2] + points[3]);
372 map.mixed = 0.25 * (points[0] - points[1] + points[2] - points[3]);
373 map.scale = std::max({map.xi.norm(), map.eta.norm(), map.mixed.norm()});
374 if (!std::isfinite(map.scale) || map.scale == 0)
375 projectionFailure(
376 msm_elem, parent_elem, sub_elem, qp, "the projected QUAD4 has invalid coefficients");
377
378 // The bilinear Jacobian is affine, so nonzero corner determinants with one sign exclude folding.
379 Real orientation = 0;
380 for (const auto xi : {-1.0, 1.0})
381 for (const auto eta : {-1.0, 1.0})
382 {
383 const Real determinant = cross2D(map.xi + eta * map.mixed, map.eta + xi * map.mixed);
384 if (std::abs(determinant) <= jacobian_tolerance)
385 projectionFailure(msm_elem, parent_elem, sub_elem, qp, "the projected QUAD4 is singular");
386 if (orientation == 0)
387 orientation = std::copysign(1.0, determinant);
388 else if (orientation * determinant < 0)
389 projectionFailure(msm_elem, parent_elem, sub_elem, qp, "the projected QUAD4 is folded");
390 }
391 return map;
392}
393
394Point
395inverseMapQuadrilateral(const BilinearMap & map,
396 const Point & target,
397 const Elem & msm_elem,
398 const Elem & parent_elem,
399 const unsigned int sub_elem,
400 const unsigned int qp)
401{
402 std::vector<Point> strict_candidates;
403 std::vector<Point> tolerance_candidates;
404
405 auto store_candidate = [](const Point & candidate, auto & candidates)
406 {
407 if (std::none_of(candidates.begin(),
408 candidates.end(),
409 [&candidate](const Point & existing)
410 { return (candidate - existing).norm() <= root_tolerance; }))
411 candidates.push_back(candidate);
412 };
413
414 auto add_candidate = [&](const Real xi, const Real eta)
415 {
416 Point candidate(xi, eta);
417 if (!MooseUtils::isFinitePoint(candidate))
418 return;
419
420 if (evaluateBilinear(map, target, xi, eta).norm() > inverse_residual_tolerance)
421 return;
422
423 const Real violation = quadrilateralReferenceViolation(candidate);
424 if (violation == 0)
425 {
426 store_candidate(candidate, strict_candidates);
427 return;
428 }
429 if (violation > mortar_reference_tolerance)
430 return;
431
432 // Only tolerance-sized exterior roots may be snapped, and the snapped point must still
433 // satisfy the normalized projection equation.
434 const Point unsnapped_candidate = candidate;
435 candidate(0) = std::clamp(candidate(0), Real(-1), Real(1));
436 candidate(1) = std::clamp(candidate(1), Real(-1), Real(1));
437 if (evaluateBilinear(map, target, candidate(0), candidate(1)).norm() >
438 mortar_reference_tolerance)
439 return;
440 store_candidate(unsnapped_candidate, tolerance_candidates);
441 };
442
443 // Enumerate both eliminations because one reconstruction direction can be singular at a root.
444 const auto xi_roots =
445 realPolynomialRoots(cross2D(map.xi, map.mixed),
446 cross2D(map.center - target, map.mixed) + cross2D(map.xi, map.eta),
447 cross2D(map.center - target, map.eta));
448 const Real direction_tolerance_sq =
449 coefficient_tolerance * coefficient_tolerance * map.scale * map.scale;
450 for (const auto root : make_range(xi_roots.count))
451 {
452 const Real xi = xi_roots.values[root];
453 const Point eta_direction = map.eta + xi * map.mixed;
454 const Real denominator = eta_direction.norm_sq();
455 if (denominator > direction_tolerance_sq)
456 add_candidate(xi, (((target - map.center) - xi * map.xi) * eta_direction) / denominator);
457 }
458
459 const auto eta_roots =
460 realPolynomialRoots(cross2D(map.eta, map.mixed),
461 cross2D(map.center - target, map.mixed) + cross2D(map.eta, map.xi),
462 cross2D(map.center - target, map.xi));
463 for (const auto root : make_range(eta_roots.count))
464 {
465 const Real eta = eta_roots.values[root];
466 const Point xi_direction = map.xi + eta * map.mixed;
467 const Real denominator = xi_direction.norm_sq();
468 if (denominator > direction_tolerance_sq)
469 add_candidate((((target - map.center) - eta * map.eta) * xi_direction) / denominator, eta);
470 }
471
472 // A true in-domain root takes precedence over a clipping-tolerance boundary candidate.
473 if (strict_candidates.size() == 1)
474 return strict_candidates[0];
475 if (strict_candidates.empty() && tolerance_candidates.size() == 1)
476 {
477 auto candidate = tolerance_candidates[0];
478 candidate(0) = std::clamp(candidate(0), Real(-1), Real(1));
479 candidate(1) = std::clamp(candidate(1), Real(-1), Real(1));
480 return candidate;
481 }
482
483 projectionFailure(msm_elem,
484 parent_elem,
485 sub_elem,
486 qp,
487 "the analytical fallback did not find one unique in-domain QUAD4 inverse");
488}
489
490Point
491analyticalQuadrilateralInverse(const Elem & msm_elem,
492 const Elem & parent_elem,
493 const std::vector<unsigned int> & sub_elem_node_indices,
494 const Point & normal,
495 const Point & target,
496 const unsigned int sub_elem,
497 const unsigned int qp)
498{
499 std::array<Point, 4> projected_nodes;
500 Point projected_target;
501 projectToNormalizedPlane(msm_elem,
502 parent_elem,
503 sub_elem_node_indices,
504 normal,
505 target,
506 sub_elem,
507 qp,
508 "QUAD4",
509 projected_nodes,
510 projected_target);
511
512 return inverseMapQuadrilateral(
513 prepareQuadrilateralMap(projected_nodes, msm_elem, parent_elem, sub_elem, qp),
514 projected_target,
515 msm_elem,
516 parent_elem,
517 sub_elem,
518 qp);
519}
520}
521
522void
523mapQPoints3dFromReference(const Elem & mortar_segment_elem,
524 const MortarSegmentReferencePoints & reference_points,
525 const QBase & qrule_msm,
526 std::vector<Point> & secondary_q_pts,
527 std::vector<Point> & primary_q_pts)
528{
529 mooseAssert(mortar_segment_elem.type() == TRI3,
530 "Reference interpolation expects triangular mortar segments.");
531 const FEType fe_type(FIRST, LAGRANGE);
532
533 for (const auto qp : make_range(qrule_msm.n_points()))
534 {
535 Point secondary_qp;
536 Point primary_qp;
537
538 for (const auto n : index_range(reference_points.secondary_reference_points))
539 {
540 const auto phi =
541 FEInterface::shape(fe_type, &mortar_segment_elem, n, qrule_msm.qp(qp), false);
542 secondary_qp += phi * reference_points.secondary_reference_points[n];
543 primary_qp += phi * reference_points.primary_reference_points[n];
544 }
545
546 secondary_q_pts.push_back(secondary_qp);
547 primary_q_pts.push_back(primary_qp);
548 }
549}
550
551void
552projectQPoints3d(const Elem * const msm_elem,
553 const Elem * const primal_elem,
554 const unsigned int sub_elem_index,
555 const QBase & qrule_msm,
556 std::vector<Point> & q_pts)
557{
558 const auto msm_elem_order = msm_elem->default_order();
559 const auto msm_elem_type = msm_elem->type();
560
561 // Get normal to linearized element, could store and query but computation is easy
562 const Point e1 = msm_elem->point(0) - msm_elem->point(1);
563 const Point e2 = msm_elem->point(2) - msm_elem->point(1);
564 const Point normal = e2.cross(e1).unit();
565
566 // Get sub-elem (for second order meshes, otherwise trivial)
567 const auto sub_elem = msm_elem->get_extra_integer(sub_elem_index);
568 const ElemType primal_type = primal_elem->type();
569 const ElemType sub_elem_type = subElementType(primal_type, sub_elem);
570
571 // Transforms quadrature point from first order sub-elements (in case of second-order)
572 // to primal element
573 auto transform_qp = [primal_type, sub_elem](const Real nu, const Real xi)
574 {
575 switch (primal_type)
576 {
577 case TRI3:
578 return Point(nu, xi, 0);
579 case QUAD4:
580 return Point(nu, xi, 0);
581 case TRI6:
582 case TRI7:
583 switch (sub_elem)
584 {
585 case 0:
586 return Point(0.5 * nu, 0.5 * xi, 0);
587 case 1:
588 return Point(0.5 * (1 - xi), 0.5 * (nu + xi), 0);
589 case 2:
590 return Point(0.5 * (1 + nu), 0.5 * xi, 0);
591 case 3:
592 return Point(0.5 * nu, 0.5 * (1 + xi), 0);
593 default:
594 mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
595 }
596 case QUAD8:
597 switch (sub_elem)
598 {
599 case 0:
600 return Point(nu - 1, xi - 1, 0);
601 case 1:
602 return Point(nu + xi, xi - 1, 0);
603 case 2:
604 return Point(1 - xi, nu + xi, 0);
605 case 3:
606 return Point(nu - 1, nu + xi, 0);
607 case 4:
608 return Point(0.5 * (nu - xi), 0.5 * (nu + xi), 0);
609 default:
610 mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
611 }
612 case QUAD9:
613 switch (sub_elem)
614 {
615 case 0:
616 return Point(0.5 * (nu - 1), 0.5 * (xi - 1), 0);
617 case 1:
618 return Point(0.5 * (nu + 1), 0.5 * (xi - 1), 0);
619 case 2:
620 return Point(0.5 * (nu + 1), 0.5 * (xi + 1), 0);
621 case 3:
622 return Point(0.5 * (nu - 1), 0.5 * (xi + 1), 0);
623 default:
624 mooseError("get_sub_elem_indices: Invalid sub_elem: ", sub_elem);
625 }
626 default:
627 mooseError("transform_qp: Face element type: ",
628 libMesh::Utility::enum_to_string<ElemType>(primal_type),
629 " invalid for 3D mortar");
630 }
631 };
632
633 // Get sub-elem node indices
634 const auto sub_elem_node_indices = getMortarSubElementNodeIndices(*primal_elem, sub_elem);
635
636 // Loop through quadrature points on msm_elem
637 for (auto qp : make_range(qrule_msm.n_points()))
638 {
639 // Get physical point on msm_elem to project
640 Point x0;
641 for (auto n : make_range(msm_elem->n_nodes()))
642 x0 += Moose::fe_lagrange_2D_shape(msm_elem_type,
643 msm_elem_order,
644 n,
645 static_cast<const TypeVector<Real> &>(qrule_msm.qp(qp))) *
646 msm_elem->point(n);
647
648 if (sub_elem_type == TRI3)
649 {
650 const Point sub_elem_point = analyticalTriangleInverse(
651 *msm_elem, *primal_elem, sub_elem_node_indices, normal, x0, sub_elem, qp);
652 const Point parent_point = transform_qp(sub_elem_point(0), sub_elem_point(1));
653 if (!MooseUtils::isFinitePoint(parent_point) ||
654 !primal_elem->on_reference_element(parent_point, mortar_reference_tolerance))
655 projectionFailure(*msm_elem,
656 *primal_elem,
657 sub_elem,
658 qp,
659 "the recovered TRI3 point is outside the parent face");
660 q_pts.push_back(parent_point);
661 continue;
662 }
663
664 // Use msm_elem quadrature point as initial guess
665 // (will be correct for aligned meshes)
666 Dual2 xi1{};
667 xi1.value() = qrule_msm.qp(qp)(0);
668 xi1.derivatives()[0] = 1.0;
669 Dual2 xi2{};
670 xi2.value() = qrule_msm.qp(qp)(1);
671 xi2.derivatives()[1] = 1.0;
672 VectorValue<Dual2> xi(xi1, xi2, 0);
673 unsigned int current_iterate = 0, max_iterates = 10;
674
675 // Project qp from mortar segments to first order sub-elements (elements in case of first order
676 // geometry)
677 do
678 {
679 VectorValue<Dual2> x1;
680 for (auto n : make_range(sub_elem_node_indices.size()))
681 x1 += Moose::fe_lagrange_2D_shape(sub_elem_type, FIRST, n, xi) *
682 primal_elem->point(sub_elem_node_indices[n]);
683 auto u = x1 - x0;
684
685 VectorValue<Dual2> F(u(1) * normal(2) - u(2) * normal(1),
686 u(2) * normal(0) - u(0) * normal(2),
687 u(0) * normal(1) - u(1) * normal(0));
688
689 Real projection_tolerance(1e-10);
690
691 // Normalize tolerance with quantities involved in the projection.
692 // Absolute projection tolerance is loosened for displacements larger than those on the order
693 // of one. Tightening the tolerance for displacements of smaller orders causes this tolerance
694 // to not be reached in a number of tests.
695 if (!u.is_zero() && u.norm().value() > 1.0)
696 projection_tolerance *= u.norm().value();
697
698 if (MetaPhysicL::raw_value(F).norm() < projection_tolerance)
699 break;
700
701 RealEigenMatrix J(3, 2);
702 J << F(0).derivatives()[0], F(0).derivatives()[1], F(1).derivatives()[0],
703 F(1).derivatives()[1], F(2).derivatives()[0], F(2).derivatives()[1];
704 RealEigenVector f(3);
705 f << F(0).value(), F(1).value(), F(2).value();
706 const RealEigenVector dxi = -J.colPivHouseholderQr().solve(f);
707
708 xi(0) += dxi(0);
709 xi(1) += dxi(1);
710 } while (++current_iterate < max_iterates);
711
712 const Point newton_sub_elem_point(xi(0).value(), xi(1).value());
713 const Point newton_parent_point =
714 transform_qp(newton_sub_elem_point(0), newton_sub_elem_point(1));
715 const bool newton_point_is_valid =
716 current_iterate < max_iterates && MooseUtils::isFinitePoint(newton_sub_elem_point) &&
717 MooseUtils::isFinitePoint(newton_parent_point) &&
718 quadrilateralReferenceViolation(newton_sub_elem_point) == 0 &&
719 primal_elem->on_reference_element(newton_parent_point, mortar_reference_tolerance);
720
721 if (newton_point_is_valid)
722 {
723 q_pts.push_back(newton_parent_point);
724 continue;
725 }
726
727 if (sub_elem_type == QUAD4)
728 {
729 // Newton can converge to the exterior root of a distorted bilinear QUAD.
730 const Point fallback_point = analyticalQuadrilateralInverse(
731 *msm_elem, *primal_elem, sub_elem_node_indices, normal, x0, sub_elem, qp);
732 const Point parent_point = transform_qp(fallback_point(0), fallback_point(1));
733 if (!MooseUtils::isFinitePoint(parent_point) ||
734 !primal_elem->on_reference_element(parent_point, mortar_reference_tolerance))
735 projectionFailure(*msm_elem,
736 *primal_elem,
737 sub_elem,
738 qp,
739 "the recovered point is outside the parent face");
740 q_pts.push_back(parent_point);
741 continue;
742 }
743
744 if (current_iterate == max_iterates)
745 mooseError("Newton iteration for mortar quadrature mapping msm element: ",
746 msm_elem->id(),
747 " to elem: ",
748 primal_elem->id(),
749 " didn't converge. MSM element volume: ",
750 msm_elem->volume());
751
752 projectionFailure(
753 *msm_elem, *primal_elem, sub_elem, qp, "the Newton result is outside the parent face");
754 }
755}
756}
757}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
Point eta
Definition MortarUtils.C:60
Real scale
Definition MortarUtils.C:62
unsigned int count
Definition MortarUtils.C:53
Point xi
Definition MortarUtils.C:59
Point center
Definition MortarUtils.C:58
DualNumber< Real, NumberArray< 2, Real > > Dual2
Definition MortarUtils.C:27
Point mixed
Definition MortarUtils.C:61
std::array< Real, 2 > values
Definition MortarUtils.C:52
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
Definition MathUtils.h:310
auto raw_value(const Eigen::Map< T > &in)
bool isFinitePoint(const Point &point)
Definition MooseUtils.C:60
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
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:67
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...
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
auto norm(const T &a)
auto index_range(const T &sizable)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
Parent-face reference coordinates associated with the vertices of one triangular mortar segment.
std::array< Point, 3 > primary_reference_points
std::array< Point, 3 > secondary_reference_points