13#include "libmesh/enum_elem_quality.h"
14#include "libmesh/fe_interface.h"
15#include "libmesh/fe_map.h"
16#include "libmesh/face_quad4.h"
17#include "libmesh/face_tri3.h"
18#include "libmesh/int_range.h"
19#include "libmesh/node.h"
20#include "libmesh/utility.h"
21#if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
22#include "libmesh/replicated_mesh.h"
23#include "libmesh/mesh_triangle_interface.h"
24#include "libmesh/poly2tri_triangulator.h"
37#include <unordered_map>
45constexpr Real mortar_reference_mapping_tolerance = 1e-8;
48validateProjectedQuadrilateral(
const std::vector<Point> & polygon,
const char *
const side)
50 if (polygon.size() != 4)
54 for (
const auto & point : polygon)
57 mooseException(
"The projected ", side,
" mortar quadrilateral contains a non-finite vertex.");
60 origin /= polygon.size();
63 for (
const auto & point : polygon)
67 mooseException(
"The projected ", side,
" mortar quadrilateral has a zero local length scale.");
69 std::array<Node, 4> nodes;
73 nodes[i] = (polygon[i] - origin) /
scale;
82 !std::isfinite(scaled_jacobian) || scaled_jacobian <= mortar_reference_mapping_tolerance)
83 mooseException(
"The projected ",
85 " mortar quadrilateral is folded, singular, or non-injective in the clipping "
96 return (b(0) - a(0)) * (c(1) - a(1)) - (b(1) - a(1)) * (c(0) - a(0));
102 return 0.5 * std::abs(orient2dHelper(a, b, c));
108std::array<unsigned int, 2>
109canonicalEdgeHelper(
const unsigned int a,
const unsigned int b)
111 return {{std::min(a, b), std::max(a, b)}};
118std::array<unsigned int, 3>
119makeCCWTriangleHelper(
const std::vector<Point> & nodes,
120 const unsigned int a,
121 const unsigned int b,
122 const unsigned int c)
124 if (orient2dHelper(nodes[a], nodes[b], nodes[c]) >= 0)
132 const auto ax = a(0) - p(0);
133 const auto ay = a(1) - p(1);
134 const auto bx = b(0) - p(0);
135 const auto by = b(1) - p(1);
136 const auto cx = c(0) - p(0);
137 const auto cy = c(1) - p(1);
138 const Real det = (ax * ax + ay * ay) * (bx * cy - by * cx) -
139 (bx * bx + by * by) * (ax * cy - ay * cx) +
140 (cx * cx + cy * cy) * (ax * by - ay * bx);
141 const Real orientation = orient2dHelper(a, b, c);
146performLocalDelaunayFlips(
const std::vector<Point> & poly_nodes,
147 const std::set<std::array<unsigned int, 2>> & constrained_edges,
148 std::vector<std::array<unsigned int, 3>> & triangles)
155 std::map<std::array<unsigned int, 2>, std::vector<unsigned int>> edge_to_triangles;
156 for (
const auto tri_index :
index_range(triangles))
158 const auto & tri = triangles[tri_index];
159 edge_to_triangles[canonicalEdgeHelper(tri[0], tri[1])].push_back(tri_index);
160 edge_to_triangles[canonicalEdgeHelper(tri[1], tri[2])].push_back(tri_index);
161 edge_to_triangles[canonicalEdgeHelper(tri[2], tri[0])].push_back(tri_index);
164 for (
const auto & [edge, owning_triangles] : edge_to_triangles)
166 if (owning_triangles.size() != 2 || constrained_edges.count(edge))
169 const auto first_tri_index = owning_triangles[0];
170 const auto second_tri_index = owning_triangles[1];
171 const auto & first_triangle = triangles[first_tri_index];
172 const auto & second_triangle = triangles[second_tri_index];
174 const auto a = edge[0];
175 const auto b = edge[1];
176 const auto first_opposite =
177 *std::find_if(first_triangle.begin(),
178 first_triangle.end(),
179 [a, b](
const unsigned int vertex) { return vertex != a && vertex != b; });
180 const auto second_opposite =
181 *std::find_if(second_triangle.begin(),
182 second_triangle.end(),
183 [a, b](
const unsigned int vertex) { return vertex != a && vertex != b; });
185 if (first_opposite == second_opposite)
189 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[a]);
191 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[b]);
195 if (!pointInCircumcircleHelper(poly_nodes[first_triangle[0]],
196 poly_nodes[first_triangle[1]],
197 poly_nodes[first_triangle[2]],
198 poly_nodes[second_opposite]))
201 triangles[first_tri_index] =
202 makeCCWTriangleHelper(poly_nodes, first_opposite, second_opposite, b);
203 triangles[second_tri_index] =
204 makeCCWTriangleHelper(poly_nodes, second_opposite, first_opposite, a);
211#if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
213triangulateConstrainedDelaunayPolygon(std::vector<Point> & poly_nodes,
215 const Real length_tol,
216 std::vector<std::vector<unsigned int>> & tri_map)
220 std::unordered_map<dof_id_type, unsigned int> node_id_to_local_index;
221 node_id_to_local_index.reserve(poly_nodes.size());
224 triangulation_mesh.add_point(poly_nodes[i], i);
226 triangulation_mesh.set_mesh_dimension(2);
228#ifdef LIBMESH_HAVE_TRIANGLE
232 triangulator.set_refine_boundary_allowed(
false);
235 triangulator.triangulation_type() = TriangulatorInterface::PSLG;
236 triangulator.elem_type() =
TRI3;
237 triangulator.set_interpolate_boundary_points(0);
238 triangulator.set_verify_hole_boundaries(
false);
239 triangulator.desired_area() = 0;
240 triangulator.minimum_angle() = 0;
241 triangulator.smooth_after_generating() =
false;
242 triangulator.quiet() =
true;
243 triangulator.segments.reserve(poly_nodes.size());
245 triangulator.segments.emplace_back(i, (i + 1) % poly_nodes.size());
247 triangulator.triangulate();
251 for (
const auto *
const node : triangulation_mesh.node_ptr_range())
252 if (!node_id_to_local_index.
count(node->id()))
257 Real best_distance = std::numeric_limits<Real>::max();
262 if (distance <= length_tol && distance < best_distance)
271 matched_index = cast_int<unsigned int>(poly_nodes.size());
272 poly_nodes.push_back(*node);
275 node_id_to_local_index.emplace(node->id(), matched_index);
278 std::vector<std::array<unsigned int, 3>> triangles;
279 triangles.reserve(triangulation_mesh.n_elem());
281 for (
const auto *
const elem : triangulation_mesh.active_element_ptr_range())
283 mooseAssert(elem->type() ==
TRI3,
284 "The delaunay mortar triangulation backend produced a non-TRI3 element: "
285 <<
static_cast<int>(elem->type()));
287 std::array<unsigned int, 3> local_triangle;
289 local_triangle[i] = libmesh_map_find(node_id_to_local_index, elem->node_id(i));
291 const Real orientation = orient2dHelper(poly_nodes[local_triangle[0]],
292 poly_nodes[local_triangle[1]],
293 poly_nodes[local_triangle[2]]);
294 if (std::abs(orientation) <= 2. * area_tol)
298 std::swap(local_triangle[1], local_triangle[2]);
300 triangles.push_back(local_triangle);
303 std::set<std::array<unsigned int, 2>> constrained_edges;
305 constrained_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
307 performLocalDelaunayFlips(poly_nodes, constrained_edges, triangles);
309 std::set<std::array<unsigned int, 3>> seen_triangles;
310 for (
auto local_triangle : triangles)
312 auto canonical_triangle = local_triangle;
313 std::sort(canonical_triangle.begin(), canonical_triangle.end());
314 if (!seen_triangles.insert(canonical_triangle).second)
317 tri_map.push_back({local_triangle[0], local_triangle[1], local_triangle[2]});
326 const Point & normal,
328 const bool triangulate_triangles)
330 std::move(secondary_nodes), {},
center, normal, triangulation_mode, triangulate_triangles)
335 std::vector<Point> secondary_reference_points,
337 const Point & normal,
339 const bool triangulate_triangles)
343 _triangulation_mode(triangulation_mode),
344 _triangulate_triangles(triangulate_triangles),
345 _secondary_reference_points(
std::move(secondary_reference_points))
349 "Each projected secondary node needs one parent reference point.");
355 const Point e1 = secondary_nodes[0] - secondary_nodes[1];
356 const Point e2 = secondary_nodes[2] - secondary_nodes[1];
366 for (
const auto & node : secondary_nodes)
390 const Point dp = p2 - p1;
391 const Point dq = q2 - q1;
392 const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
393 const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
394 const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
395 const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
396 s = -alpha * (cp1q2 - cp1q1 - cq1q2);
413 const Point e1 = q2 - q1;
414 const Point e2 = pt - q1;
420 const bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) <
_area_tol;
435 const Point edg = q2 - q1;
436 const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
440 auto is_inside = [&edg, cp](
Point & pt,
Real tol)
441 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
443 bool all_outside =
true;
458 const Point e1 = primary_nodes[0] - primary_nodes[1];
459 const Point e2 = primary_nodes[2] - primary_nodes[1];
466 std::vector<Point> primary_poly;
467 const int n_verts = primary_nodes.size();
468 primary_poly.reserve(primary_nodes.size());
471 Point pt = (orient > 0) ? primary_nodes[n] -
_center : primary_nodes[n_verts - 1 - n] -
_center;
472 primary_poly.emplace_back(pt *
_u, pt *
_v, 0.);
476 validateProjectedQuadrilateral(primary_poly,
"primary");
500 if (clipped_poly.size() < 3)
502 clipped_poly.clear();
507 std::vector<Point> input_poly(clipped_poly);
508 clipped_poly.clear();
511 const Point & clip_pt1 = primary_poly[i];
512 const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
513 const Point edg = clip_pt2 - clip_pt1;
514 const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
522 auto is_inside = [&edg, cp](
const Point & pt,
Real tol)
523 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
529 const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
530 const Point prev_pt = input_poly[j];
533 const bool is_current_inside = is_inside(curr_pt,
_area_tol);
534 const bool is_previous_inside = is_inside(prev_pt,
_area_tol);
536 if (is_current_inside)
538 if (!is_previous_inside)
557 clipped_poly.push_back(intersect);
559 clipped_poly.push_back(curr_pt);
561 else if (is_previous_inside)
566 clipped_poly.push_back(intersect);
572 if (clipped_poly.size() < 3)
574 clipped_poly.clear();
579 std::vector<Point> cleaned_poly;
580 cleaned_poly.push_back(clipped_poly.back());
581 for (
auto i :
make_range(clipped_poly.size() - 1))
583 const Point prev_pt = cleaned_poly.back();
584 const Point curr_pt = clipped_poly[i];
588 cleaned_poly.push_back(curr_pt);
592 cleaned_poly.size() <= 8,
593 "Our distributed mesh numbering scheme assumes that we have at most 8 nodes resulting from "
594 "clipping the projection of the primary sub-element onto the secondary sub-element");
600 std::vector<std::vector<unsigned int>> & tri_map)
const
604 const auto polygon_centroid = [](
const std::vector<Point> & polygon_nodes)
607 Real double_area = 0;
610 const auto & a = polygon_nodes[i];
611 const auto & b = polygon_nodes[(i + 1) % polygon_nodes.size()];
612 const Real cross = a(0) * b(1) - b(0) * a(1);
613 double_area += cross;
614 centroid(0) += (a(0) + b(0)) * cross;
615 centroid(1) += (a(1) + b(1)) * cross;
618 if (std::abs(double_area) <= TOLERANCE)
620 for (
const auto & node : polygon_nodes)
622 centroid /= polygon_nodes.size();
626 centroid /= (3. * double_area);
631 const auto append_triangle = [
this, &poly_nodes, &tri_map](
632 const unsigned int a,
const unsigned int b,
const unsigned int c)
634 if (triangleAreaHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) <=
_area_tol)
637 if (orient2dHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) >= 0)
638 tri_map.push_back({a, b, c});
640 tri_map.push_back({a, c, b});
645 const auto point_in_triangle =
648 const Real ab = orient2dHelper(a, b, p);
649 const Real bc = orient2dHelper(b, c, p);
650 const Real ca = orient2dHelper(c, a, p);
651 return ab >= -_area_tol && bc >= -_area_tol && ca >= -_area_tol;
654 const auto min_triangle_angle = [](
const Point & a,
const Point & b,
const Point & c)
656 const auto clamp_cos = [](
Real value) {
return std::max(-1., std::min(1., value)); };
657 const auto angle_at =
658 [&clamp_cos](
const Point & vertex,
const Point & point_one,
const Point & point_two)
660 const Point edge_one = point_one - vertex;
661 const Point edge_two = point_two - vertex;
665 return std::acos(clamp_cos((edge_one * edge_two) / denom));
668 return std::min({angle_at(a, b, c), angle_at(b, c, a), angle_at(c, a, b)});
671 const auto canonicalize_polygon = [
this, &poly_nodes]()
673 if (poly_nodes.size() < 3)
676 if (area(poly_nodes) < 0)
677 std::reverse(poly_nodes.begin(), poly_nodes.end());
680 while (changed && poly_nodes.size() > 3)
685 const auto prev = (i + poly_nodes.size() - 1) % poly_nodes.size();
686 const auto next = (i + 1) % poly_nodes.size();
687 if ((poly_nodes[i] - poly_nodes[prev]).norm() <= _length_tol ||
688 (poly_nodes[next] - poly_nodes[i]).
norm() <= _length_tol ||
689 triangleAreaHelper(poly_nodes[prev], poly_nodes[i], poly_nodes[next]) <= _area_tol)
691 poly_nodes.erase(poly_nodes.begin() + i);
698 if (poly_nodes.size() >= 3 && area(poly_nodes) < 0)
699 std::reverse(poly_nodes.begin(), poly_nodes.end());
702 const auto triangulate_with_ear_clipping =
703 [
this, &poly_nodes, &point_in_triangle, &min_triangle_angle](
704 const bool perform_delaunay_flips)
706 std::vector<std::array<unsigned int, 3>> triangles;
707 if (poly_nodes.size() < 3)
710 if (poly_nodes.size() == 3)
712 triangles.push_back(makeCCWTriangleHelper(poly_nodes, 0, 1, 2));
716 std::vector<unsigned int> remaining_vertices(poly_nodes.size());
717 std::iota(remaining_vertices.begin(), remaining_vertices.end(), 0);
719 while (remaining_vertices.size() > 3)
721 std::optional<std::size_t> best_position;
722 Real best_score = -std::numeric_limits<Real>::max();
723 Real best_area = -std::numeric_limits<Real>::max();
725 for (
const auto position :
index_range(remaining_vertices))
727 const auto prev_position =
728 (position + remaining_vertices.size() - 1) % remaining_vertices.size();
729 const auto next_position = (position + 1) % remaining_vertices.size();
730 const auto prev = remaining_vertices[prev_position];
731 const auto curr = remaining_vertices[position];
732 const auto next = remaining_vertices[next_position];
734 if (orient2dHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]) <= _area_tol)
737 bool contains_other_vertex =
false;
738 for (
const auto other : remaining_vertices)
740 if (other == prev || other == curr || other == next)
743 if (point_in_triangle(
744 poly_nodes[other], poly_nodes[prev], poly_nodes[curr], poly_nodes[next]))
746 contains_other_vertex =
true;
751 if (contains_other_vertex)
754 const Real candidate_score =
755 min_triangle_angle(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
756 const Real candidate_area =
757 triangleAreaHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
758 if (!best_position || candidate_score > best_score +
TOLERANCE ||
759 (std::abs(candidate_score - best_score) <=
TOLERANCE &&
760 candidate_area > best_area + _area_tol))
762 best_position = position;
763 best_score = candidate_score;
764 best_area = candidate_area;
770 std::vector<std::array<unsigned int, 3>> best_fan;
771 Real best_fan_score = -std::numeric_limits<Real>::max();
772 Real best_fan_area = -std::numeric_limits<Real>::max();
774 for (
const auto root_position :
index_range(remaining_vertices))
776 std::vector<std::array<unsigned int, 3>> candidate_fan;
777 Real candidate_score = std::numeric_limits<Real>::max();
778 Real candidate_area = std::numeric_limits<Real>::max();
779 bool valid_fan =
true;
780 const auto root = remaining_vertices[root_position];
782 for (
unsigned int step = 1; step + 1 < remaining_vertices.size(); ++step)
784 const auto next_position = (root_position + step) % remaining_vertices.size();
785 const auto following_position = (root_position + step + 1) % remaining_vertices.size();
786 const auto vertex_one = remaining_vertices[next_position];
787 const auto vertex_two = remaining_vertices[following_position];
789 if (orient2dHelper(poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]) <=
796 candidate_fan.push_back(
797 makeCCWTriangleHelper(poly_nodes, root, vertex_one, vertex_two));
799 std::min(candidate_score,
801 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
803 std::min(candidate_area,
805 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
808 if (!valid_fan || candidate_fan.empty())
811 if (candidate_score > best_fan_score +
TOLERANCE ||
812 (std::abs(candidate_score - best_fan_score) <=
TOLERANCE &&
813 candidate_area > best_fan_area + _area_tol))
815 best_fan = std::move(candidate_fan);
816 best_fan_score = candidate_score;
817 best_fan_area = candidate_area;
821 if (best_fan.empty())
822 for (
unsigned int i = 1; i + 1 < remaining_vertices.size(); ++i)
823 best_fan.push_back(makeCCWTriangleHelper(poly_nodes,
824 remaining_vertices[0],
825 remaining_vertices[i],
826 remaining_vertices[i + 1]));
828 triangles.insert(triangles.end(), best_fan.begin(), best_fan.end());
832 const auto prev_position =
833 (*best_position + remaining_vertices.size() - 1) % remaining_vertices.size();
834 const auto next_position = (*best_position + 1) % remaining_vertices.size();
835 triangles.push_back(makeCCWTriangleHelper(poly_nodes,
836 remaining_vertices[prev_position],
837 remaining_vertices[*best_position],
838 remaining_vertices[next_position]));
839 remaining_vertices.erase(remaining_vertices.begin() + *best_position);
842 if (remaining_vertices.size() == 3)
843 triangles.push_back(makeCCWTriangleHelper(
844 poly_nodes, remaining_vertices[0], remaining_vertices[1], remaining_vertices[2]));
846 if (!perform_delaunay_flips)
849 std::set<std::array<unsigned int, 2>> boundary_edges;
851 boundary_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
853 performLocalDelaunayFlips(poly_nodes, boundary_edges, triangles);
857 const auto is_convex_polygon = [
this](
const std::vector<Point> & polygon_nodes)
859 if (polygon_nodes.size() <= 3)
864 const auto prev = (i + polygon_nodes.size() - 1) % polygon_nodes.size();
865 const auto next = (i + 1) % polygon_nodes.size();
866 if (orient2dHelper(polygon_nodes[prev], polygon_nodes[i], polygon_nodes[next]) <= _area_tol)
874 if (poly_nodes.size() < 3)
875 mooseError(
"Can't triangulate poly with fewer than 3 nodes");
886 if (poly_nodes.size() == 3)
888 tri_map.push_back({0, 1, 2});
892 const unsigned int n_verts = poly_nodes.size();
894 for (
const auto & node : poly_nodes)
896 poly_center /= n_verts;
899 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
901 poly_nodes.push_back(poly_center);
905 canonicalize_polygon();
906 if (poly_nodes.size() < 3)
909 if (poly_nodes.size() == 3 && !_triangulate_triangles)
911 append_triangle(0, 1, 2);
915 const bool force_triangle_centroid_split = _triangulate_triangles && poly_nodes.size() == 3;
918 !force_triangle_centroid_split)
920 const unsigned int n_verts = poly_nodes.size();
921 for (
unsigned int i = 1; i + 1 < n_verts; ++i)
922 append_triangle(0, i, i + 1);
927 !force_triangle_centroid_split)
929#if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
930 triangulateConstrainedDelaunayPolygon(poly_nodes, _area_tol, _length_tol, tri_map);
933 mooseError(
"The 'delaunay' mortar triangulation mode requires libMesh TriangleInterface or "
934 "Poly2Tri support.");
939 !force_triangle_centroid_split)
941 for (
const auto & triangle : triangulate_with_ear_clipping(true))
942 append_triangle(triangle[0], triangle[1], triangle[2]);
946 if (!force_triangle_centroid_split && !is_convex_polygon(poly_nodes))
948 for (
const auto & triangle : triangulate_with_ear_clipping(true))
949 append_triangle(triangle[0], triangle[1], triangle[2]);
953 const unsigned int n_verts = poly_nodes.size();
954 const Point poly_center = polygon_centroid(poly_nodes);
956 bool added_triangle =
false;
958 if (triangleAreaHelper(poly_nodes[i], poly_nodes[(i + 1) % n_verts], poly_center) > _area_tol)
960 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
961 added_triangle =
true;
965 poly_nodes.push_back(poly_center);
970 std::vector<Point> & nodes,
971 std::vector<std::vector<unsigned int>> & elem_to_nodes)
978 const std::vector<Point> & primary_nodes,
979 const std::vector<Point> & primary_reference_points,
980 std::vector<Point> & nodes,
981 std::vector<std::vector<unsigned int>> & elem_to_nodes,
982 std::vector<std::array<Point, 3>> & elem_to_secondary_reference_points,
983 std::vector<std::array<Point, 3>> & elem_to_primary_reference_points,
984 const Real minimum_segment_area)
987 elem_to_secondary_reference_points,
988 elem_to_primary_reference_points,
989 minimum_segment_area};
995 std::vector<Point> & nodes,
996 std::vector<std::vector<unsigned int>> & elem_to_nodes,
999 std::vector<Point> primary_poly;
1000 std::vector<Point> primary_poly_reference_points;
1002 if (reference_mapping)
1005 mooseError(
"Reference-interpolation mortar segment generation requires one primary "
1006 "reference point per primary sub-element node.");
1008 mooseError(
"Reference-interpolation mortar segment generation requires one secondary "
1009 "reference point per secondary sub-element node.");
1012 mooseError(
"Reference-interpolation mortar segment outputs must be aligned before appending "
1016 const Point e1 = primary_nodes[0] - primary_nodes[1];
1017 const Point e2 = primary_nodes[2] - primary_nodes[1];
1019 const auto n_verts = primary_nodes.size();
1025 const auto primary_node_index = (orient > 0) ? n : n_verts - 1 - n;
1026 primary_poly_reference_points.push_back(
1033 std::vector<Point> clipped_poly =
1035 if (clipped_poly.size() < 3)
1039 for (
const auto &
point : clipped_poly)
1041 mooseError(
"Clipped polygon not inside linearized secondary element");
1048 std::vector<std::vector<unsigned int>> tri_map;
1052 std::remove_if(tri_map.begin(),
1054 [&clipped_poly, reference_mapping](
const std::vector<unsigned int> & tri)
1056 mooseAssert(tri.size() == 3,
1057 "Mortar segment triangulation should only produce TRI3 maps.");
1058 return triangleAreaHelper(clipped_poly[tri[0]],
1059 clipped_poly[tri[1]],
1060 clipped_poly[tri[2]]) <
1061 reference_mapping->minimum_segment_area;
1064 if (tri_map.empty())
1067 std::vector<Point> secondary_node_reference_points;
1068 std::vector<Point> primary_node_reference_points;
1069 if (reference_mapping)
1071 secondary_node_reference_points.reserve(clipped_poly.size());
1072 primary_node_reference_points.reserve(clipped_poly.size());
1074 const auto recover_reference_point = [
this](
const Point & projected_point,
1075 const std::vector<Point> & poly,
1076 const std::vector<Point> & reference_points,
1077 const char *
const parent_name,
1078 const std::size_t node_index)
1080 std::string failure_reason;
1081 const auto reference_point =
1082 referencePoint(projected_point, poly, reference_points, &failure_reason);
1083 if (!reference_point)
1086 " parent reference point for retained 3D mortar overlap vertex ",
1088 " at projected point ",
1092 ". Reference interpolation does not fall back to normal projection.");
1094 return *reference_point;
1097 for (
const auto node_index :
index_range(clipped_poly))
1099 const auto &
point = clipped_poly[node_index];
1100 secondary_node_reference_points.push_back(recover_reference_point(
1102 primary_node_reference_points.push_back(recover_reference_point(
1103 point, primary_poly, primary_poly_reference_points,
"primary", node_index));
1108 const auto offset = cast_int<unsigned int>(nodes.size());
1109 for (
const auto &
point : clipped_poly)
1112 for (
const auto & tri : tri_map)
1114 std::vector<unsigned int> shifted_tri;
1115 shifted_tri.reserve(tri.size());
1116 for (
const auto local_index : tri)
1117 shifted_tri.push_back(offset + local_index);
1118 elem_to_nodes.push_back(std::move(shifted_tri));
1120 if (reference_mapping)
1122 mooseAssert(tri.size() == 3,
"Mortar segment triangulation should only produce TRI3 maps.");
1123 std::array<Point, 3> elem_secondary_reference_points;
1124 std::array<Point, 3> elem_primary_reference_points;
1127 const auto local_node = tri[n];
1128 elem_secondary_reference_points[n] = secondary_node_reference_points[local_node];
1129 elem_primary_reference_points[n] = primary_node_reference_points[local_node];
1133 elem_secondary_reference_points);
1141 const std::vector<Point> & poly,
1142 const std::vector<Point> & reference_points,
1143 std::string *
const failure_reason)
const
1145 mooseAssert(poly.size() == reference_points.size(),
1146 "Projected point and reference point containers should be the same size.");
1149 failure_reason->clear();
1151 const auto fail = [failure_reason](
const std::string & reason) -> std::optional<Point>
1154 *failure_reason = reason;
1155 return std::nullopt;
1159 return fail(
"the projected target point contains a non-finite coordinate");
1164 return fail(
"projected polygon vertex " + std::to_string(i) +
1165 " contains a non-finite coordinate");
1167 return fail(
"parent reference vertex " + std::to_string(i) +
1168 " contains a non-finite coordinate");
1171 if (poly.size() != 3 && poly.size() != 4)
1172 return fail(
"reference point recovery only supports triangular and quadrilateral mortar "
1173 "sub-elements, but received " +
1174 std::to_string(poly.size()) +
" vertices");
1176 Real minimum_edge_length = std::numeric_limits<Real>::max();
1178 for (
const auto & vertex : poly)
1179 local_origin += vertex;
1180 local_origin /= poly.size();
1182 Real local_scale = 0.;
1185 minimum_edge_length =
1186 std::min(minimum_edge_length, (poly[(i + 1) % poly.size()] - poly[i]).norm());
1187 local_scale = std::max(local_scale, (poly[i] - local_origin).norm());
1190 const Real singular_tolerance = 100. * std::numeric_limits<Real>::epsilon();
1191 if (!std::isfinite(local_scale) || local_scale <= singular_tolerance)
1192 return fail(
"the projected polygon has a zero local length scale");
1193 if (!std::isfinite(minimum_edge_length) ||
1194 minimum_edge_length / local_scale <= singular_tolerance)
1195 return fail(
"the projected polygon has a zero-length edge relative to its local scale");
1197 std::vector<Point> normalized_poly;
1198 normalized_poly.reserve(poly.size());
1199 for (
const auto & vertex : poly)
1200 normalized_poly.push_back((vertex - local_origin) / local_scale);
1201 const Point normalized_point = (
point - local_origin) / local_scale;
1202 const Real reference_tolerance =
1203 std::max(mortar_reference_mapping_tolerance,
_area_tol / (minimum_edge_length * local_scale));
1204 std::array<Node, 4> element_nodes;
1206 const auto recover_with_libmesh = [&](
auto & element) -> std::optional<Point>
1210 element_nodes[i] = normalized_poly[i];
1211 element_nodes[i].set_id(i);
1212 element.
set_node(i, &element_nodes[i]);
1216 return fail(
"the projected sub-element map is degenerate or non-invertible");
1226 fe_type, 0, &element, node, 0, element.
master_point(corner)) *
1227 element.
point(node);
1229 fe_type, 0, &element, node, 1, element.
master_point(corner)) *
1230 element.
point(node);
1233 const Real corner_jacobian = tangent_xi.
cross(tangent_eta).norm();
1234 if (!std::isfinite(corner_jacobian) ||
1235 corner_jacobian <= mortar_reference_mapping_tolerance)
1236 return fail(
"the projected quadrilateral has a singular or ill-conditioned corner map");
1240 2, &element, normalized_point, mortar_reference_mapping_tolerance,
false,
false);
1242 return fail(
"libMesh inverse_map produced a non-finite reference point");
1244 const Real inverse_map_error =
1245 (
FEMap::map(2, &element, local_reference) - normalized_point).norm();
1246 if (!std::isfinite(inverse_map_error) || inverse_map_error > mortar_reference_mapping_tolerance)
1248 std::ostringstream reason;
1249 reason <<
"the normalized inverse-map error " << inverse_map_error <<
" exceeds "
1250 << mortar_reference_mapping_tolerance;
1251 return fail(reason.str());
1256 std::array<Real, 3> weights;
1260 for (
auto & weight : weights)
1261 weight = std::clamp(weight, 0., 1.);
1262 const Real weight_sum = std::accumulate(weights.begin(), weights.end(), 0.);
1263 if (!std::isfinite(weight_sum) || weight_sum <= singular_tolerance)
1264 return fail(
"clamped triangle barycentric coordinates have a zero or non-finite sum");
1265 for (
auto & weight : weights)
1266 weight /= weight_sum;
1269 const auto corrected_weight =
1270 std::distance(weights.begin(), std::max_element(weights.begin(), weights.end()));
1271 weights[corrected_weight] = 1.;
1273 if (i !=
static_cast<unsigned int>(corrected_weight))
1274 weights[corrected_weight] -= weights[i];
1276 local_reference =
Point();
1278 local_reference += weights[i] * element.
master_point(i);
1282 local_reference(0) = std::clamp(local_reference(0), -1., 1.);
1283 local_reference(1) = std::clamp(local_reference(1), -1., 1.);
1284 local_reference(2) = 0.;
1288 return fail(
"the clamped inverse-map result is outside the reference element");
1290 const Real round_trip_error =
1291 (
FEMap::map(2, &element, local_reference) - normalized_point).norm();
1292 if (!std::isfinite(round_trip_error) || round_trip_error > reference_tolerance)
1294 std::ostringstream reason;
1295 reason <<
"the normalized inverse-map round-trip error " << round_trip_error
1296 <<
" exceeds the clipping-consistent tolerance " << reference_tolerance;
1297 return fail(reason.str());
1300 Point parent_reference;
1303 FEInterface::shape(fe_type, &element, i, local_reference,
false) * reference_points[i];
1306 return fail(
"reference interpolation produced a non-finite parent reference point");
1308 return parent_reference;
1311 if (poly.size() == 3)
1314 return recover_with_libmesh(element);
1318 return recover_with_libmesh(element);
1326 poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
1327 nodes[i](1) * nodes[(i + 1) % nodes.size()](0);
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
MortarSegmentTriangulationMode
This class supports defining mortar segment mesh elements in 3D by projecting secondary and primary e...
std::vector< Point > clipPoly(const std::vector< Point > &primary_nodes) const
Clip secondary element (defined in instantiation) against given primary polygon result is a set of 2D...
Point point(unsigned int i) const
Get 3D position of node of linearized secondary element.
Real _area_tol
Tolerance times secondary area for dimensional consistency.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on.
Point _center
Geometric center of secondary element.
Real _secondary_area
Area of projected secondary element.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
MortarSegmentHelper(std::vector< Point > secondary_nodes, const Point ¢er, const Point &normal, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles)
Construct a helper that generates mortar segment geometry only.
Point getIntersection(const Point &p1, const Point &p2, const Point &q1, const Point &q2, Real &s) const
Computes the intersection between line segments defined by point pairs (p1,p2) and (q1,...
std::vector< Point > projectPrimaryPoly(const std::vector< Point > &primary_nodes) const
Project a primary polygon into the helper plane while preserving the clipping orientation.
std::vector< Point > clipProjectedPoly(const std::vector< Point > &primary_poly) const
Clip an already projected primary polygon against the secondary polygon.
bool isInsideSecondary(const Point &pt) const
Check that a point is inside the secondary polygon (for verification only)
void triangulatePoly(std::vector< Point > &poly_nodes, std::vector< std::vector< unsigned int > > &tri_map) const
Triangulate a polygon according to the configured mortar-segment triangulation mode.
Point _normal
Unit normal of the plane used to project and clip the linearized secondary subpatch.
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.
std::optional< Point > referencePoint(const Point &point, const std::vector< Point > &poly, const std::vector< Point > &reference_points, std::string *failure_reason=nullptr) const
Recover a parent-reference point from a projected sub-element map.
Real _length_tol
Tolerance times secondary area for dimensional consistency.
const Point & center() const
Get center point of secondary element.
Real area(const std::vector< Point > &nodes) const
Compute area of polygon.
bool isDisjoint(const std::vector< Point > &poly) const
Checks whether polygons are disjoint for an easy out.
void getMortarSegments(const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int > > &elem_to_nodes)
Get mortar segments generated by a secondary and primary element pair.
void getMortarSegmentsImpl(const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int > > &elem_to_nodes, ReferenceMappingData *reference_mapping)
Real _tolerance
Tolerance for intersection and clipping.
std::vector< Point > _secondary_reference_points
Parent reference points corresponding to _secondary_poly.
virtual Node *& set_node(const unsigned int i)
const Point & point(const unsigned int i) const
static Real shape(const unsigned int dim, const FEType &fe_t, const ElemType t, const unsigned int i, const Point &p)
static Real shape_deriv(const unsigned int dim, const FEType &fe_t, const ElemType t, const unsigned int i, const unsigned int j, const Point &p)
static Point map(const unsigned int dim, const Elem *elem, const Point &reference_point)
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)
virtual bool has_invertible_map(Real tol) const override
virtual ElemType type() const override
virtual unsigned int n_vertices() const override final
virtual Real quality(const ElemQuality q) const override
virtual bool on_reference_element(const Point &p, const Real eps=TOLERANCE) const override final
virtual unsigned int n_nodes() const override
virtual Point master_point(const unsigned int i) const override final
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
auto max(const L &left, const R &right)
bool isFinitePoint(const Point &point)
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
auto index_range(const T &sizable)
const unsigned int invalid_uint
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
Output containers and filtering data used while generating reference-coordinate mappings.
const std::vector< Point > & primary_reference_points
std::vector< std::array< Point, 3 > > & elem_to_secondary_reference_points
std::vector< std::array< Point, 3 > > & elem_to_primary_reference_points
Real minimum_segment_area
Real distance(const Point &p)