13 #include "libmesh/fe_interface.h" 14 #include "libmesh/fe_map.h" 15 #include "libmesh/face_quad4.h" 16 #include "libmesh/face_tri3.h" 17 #include "libmesh/int_range.h" 18 #include "libmesh/node.h" 19 #include "libmesh/utility.h" 20 #if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI) 21 #include "libmesh/replicated_mesh.h" 22 #include "libmesh/mesh_triangle_interface.h" 23 #include "libmesh/poly2tri_triangulator.h" 36 #include <unordered_map> 44 constexpr
Real mortar_reference_mapping_tolerance = 1e-8;
50 if (!std::isfinite(
point(component)))
63 return (b(0) - a(0)) * (c(1) - a(1)) - (b(1) - a(1)) * (c(0) - a(0));
69 return 0.5 *
std::abs(orient2dHelper(a, b, c));
75 std::array<unsigned int, 2>
76 canonicalEdgeHelper(
const unsigned int a,
const unsigned int b)
85 std::array<unsigned int, 3>
86 makeCCWTriangleHelper(
const std::vector<Point> & nodes,
91 if (orient2dHelper(nodes[a], nodes[b], nodes[c]) >= 0)
99 const auto ax = a(0) - p(0);
100 const auto ay = a(1) - p(1);
101 const auto bx = b(0) - p(0);
102 const auto by = b(1) - p(1);
103 const auto cx = c(0) - p(0);
104 const auto cy = c(1) - p(1);
105 const Real det = (ax * ax + ay * ay) * (bx * cy - by * cx) -
106 (bx * bx + by * by) * (ax * cy - ay * cx) +
107 (cx * cx + cy * cy) * (ax * by - ay * bx);
108 const Real orientation = orient2dHelper(a, b, c);
113 performLocalDelaunayFlips(
const std::vector<Point> & poly_nodes,
114 const std::set<std::array<unsigned int, 2>> & constrained_edges,
115 std::vector<std::array<unsigned int, 3>> & triangles)
122 std::map<std::array<unsigned int, 2>, std::vector<unsigned int>> edge_to_triangles;
123 for (
const auto tri_index :
index_range(triangles))
125 const auto & tri = triangles[tri_index];
126 edge_to_triangles[canonicalEdgeHelper(tri[0], tri[1])].push_back(tri_index);
127 edge_to_triangles[canonicalEdgeHelper(tri[1], tri[2])].push_back(tri_index);
128 edge_to_triangles[canonicalEdgeHelper(tri[2], tri[0])].push_back(tri_index);
131 for (
const auto & [edge, owning_triangles] : edge_to_triangles)
133 if (owning_triangles.size() != 2 || constrained_edges.count(edge))
136 const auto first_tri_index = owning_triangles[0];
137 const auto second_tri_index = owning_triangles[1];
138 const auto & first_triangle = triangles[first_tri_index];
139 const auto & second_triangle = triangles[second_tri_index];
141 const auto a = edge[0];
142 const auto b = edge[1];
143 const auto first_opposite =
144 *std::find_if(first_triangle.begin(),
145 first_triangle.end(),
146 [a, b](
const unsigned int vertex) {
return vertex != a && vertex != b; });
147 const auto second_opposite =
148 *std::find_if(second_triangle.begin(),
149 second_triangle.end(),
150 [a, b](
const unsigned int vertex) {
return vertex != a && vertex != b; });
152 if (first_opposite == second_opposite)
156 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[a]);
158 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[b]);
162 if (!pointInCircumcircleHelper(poly_nodes[first_triangle[0]],
163 poly_nodes[first_triangle[1]],
164 poly_nodes[first_triangle[2]],
165 poly_nodes[second_opposite]))
168 triangles[first_tri_index] =
169 makeCCWTriangleHelper(poly_nodes, first_opposite, second_opposite, b);
170 triangles[second_tri_index] =
171 makeCCWTriangleHelper(poly_nodes, second_opposite, first_opposite, a);
178 #if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI) 180 triangulateConstrainedDelaunayPolygon(std::vector<Point> & poly_nodes,
182 const Real length_tol,
183 std::vector<std::vector<unsigned int>> & tri_map)
187 std::unordered_map<dof_id_type, unsigned int> node_id_to_local_index;
188 node_id_to_local_index.reserve(poly_nodes.size());
191 triangulation_mesh.add_point(poly_nodes[i], i);
193 triangulation_mesh.set_mesh_dimension(2);
195 #ifdef LIBMESH_HAVE_TRIANGLE 199 triangulator.set_refine_boundary_allowed(
false);
203 triangulator.elem_type() =
TRI3;
204 triangulator.set_interpolate_boundary_points(0);
205 triangulator.set_verify_hole_boundaries(
false);
206 triangulator.desired_area() = 0;
207 triangulator.minimum_angle() = 0;
208 triangulator.smooth_after_generating() =
false;
209 triangulator.quiet() =
true;
210 triangulator.segments.reserve(poly_nodes.size());
212 triangulator.segments.emplace_back(i, (i + 1) % poly_nodes.size());
214 triangulator.triangulate();
218 for (
const auto *
const node : triangulation_mesh.node_ptr_range())
219 if (!node_id_to_local_index.count(node->id()))
229 if (distance <= length_tol && distance < best_distance)
238 matched_index = cast_int<unsigned int>(poly_nodes.size());
239 poly_nodes.push_back(*node);
242 node_id_to_local_index.emplace(node->id(), matched_index);
245 std::vector<std::array<unsigned int, 3>> triangles;
246 triangles.reserve(triangulation_mesh.n_elem());
248 for (
const auto *
const elem : triangulation_mesh.active_element_ptr_range())
250 mooseAssert(elem->type() ==
TRI3,
251 "The delaunay mortar triangulation backend produced a non-TRI3 element: " 252 <<
static_cast<int>(elem->type()));
254 std::array<unsigned int, 3> local_triangle;
256 local_triangle[i] = libmesh_map_find(node_id_to_local_index, elem->node_id(i));
258 const Real orientation = orient2dHelper(poly_nodes[local_triangle[0]],
259 poly_nodes[local_triangle[1]],
260 poly_nodes[local_triangle[2]]);
261 if (
std::abs(orientation) <= 2. * area_tol)
265 std::swap(local_triangle[1], local_triangle[2]);
267 triangles.push_back(local_triangle);
270 std::set<std::array<unsigned int, 2>> constrained_edges;
272 constrained_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
274 performLocalDelaunayFlips(poly_nodes, constrained_edges, triangles);
276 std::set<std::array<unsigned int, 3>> seen_triangles;
277 for (
auto local_triangle : triangles)
279 auto canonical_triangle = local_triangle;
280 std::sort(canonical_triangle.begin(), canonical_triangle.end());
281 if (!seen_triangles.insert(canonical_triangle).second)
284 tri_map.push_back({local_triangle[0], local_triangle[1], local_triangle[2]});
292 const Point & center,
293 const Point & normal,
295 const bool triangulate_triangles)
297 std::move(secondary_nodes), {}, center, normal, triangulation_mode, triangulate_triangles)
302 std::vector<Point> secondary_reference_points,
303 const Point & center,
304 const Point & normal,
306 const bool triangulate_triangles)
310 _triangulation_mode(triangulation_mode),
311 _triangulate_triangles(triangulate_triangles),
312 _secondary_reference_points(
std::move(secondary_reference_points))
316 "Each projected secondary node needs one parent reference point.");
322 const Point e1 = secondary_nodes[0] - secondary_nodes[1];
323 const Point e2 = secondary_nodes[2] - secondary_nodes[1];
333 for (
const auto & node : secondary_nodes)
354 const Point dp = p2 - p1;
355 const Point dq = q2 - q1;
356 const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
357 const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
358 const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
359 const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
360 s = -alpha * (cp1q2 - cp1q1 - cq1q2);
377 const Point e1 = q2 - q1;
378 const Point e2 = pt - q1;
384 const bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) <
_area_tol;
399 const Point edg = q2 - q1;
400 const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
404 auto is_inside = [&edg, cp](
Point & pt,
Real tol)
405 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
407 bool all_outside =
true;
422 const Point e1 = primary_nodes[0] - primary_nodes[1];
423 const Point e2 = primary_nodes[2] - primary_nodes[1];
430 std::vector<Point> primary_poly;
431 const int n_verts = primary_nodes.size();
432 primary_poly.reserve(primary_nodes.size());
435 Point pt = (orient > 0) ? primary_nodes[n] -
_center : primary_nodes[n_verts - 1 - n] -
_center;
436 primary_poly.emplace_back(pt *
_u, pt *
_v, 0.);
461 if (clipped_poly.size() < 3)
463 clipped_poly.clear();
468 std::vector<Point> input_poly(clipped_poly);
469 clipped_poly.clear();
472 const Point & clip_pt1 = primary_poly[i];
473 const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
474 const Point edg = clip_pt2 - clip_pt1;
475 const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
483 auto is_inside = [&edg, cp](
const Point & pt,
Real tol)
484 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
490 const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
491 const Point prev_pt = input_poly[j];
494 const bool is_current_inside = is_inside(curr_pt,
_area_tol);
495 const bool is_previous_inside = is_inside(prev_pt,
_area_tol);
497 if (is_current_inside)
499 if (!is_previous_inside)
518 clipped_poly.push_back(intersect);
520 clipped_poly.push_back(curr_pt);
522 else if (is_previous_inside)
527 clipped_poly.push_back(intersect);
533 if (clipped_poly.size() < 3)
535 clipped_poly.clear();
540 std::vector<Point> cleaned_poly;
541 cleaned_poly.push_back(clipped_poly.back());
542 for (
auto i :
make_range(clipped_poly.size() - 1))
544 const Point prev_pt = cleaned_poly.back();
545 const Point curr_pt = clipped_poly[i];
549 cleaned_poly.push_back(curr_pt);
553 cleaned_poly.size() <= 8,
554 "Our distributed mesh numbering scheme assumes that we have at most 8 nodes resulting from " 555 "clipping the projection of the primary sub-element onto the secondary sub-element");
561 std::vector<std::vector<unsigned int>> & tri_map)
const 565 const auto polygon_centroid = [](
const std::vector<Point> & polygon_nodes)
568 Real double_area = 0;
571 const auto & a = polygon_nodes[i];
572 const auto & b = polygon_nodes[(i + 1) % polygon_nodes.size()];
573 const Real cross = a(0) * b(1) - b(0) * a(1);
574 double_area += cross;
575 centroid(0) += (a(0) + b(0)) * cross;
576 centroid(1) += (a(1) + b(1)) * cross;
579 if (
std::abs(double_area) <= TOLERANCE)
581 for (
const auto & node : polygon_nodes)
583 centroid /= polygon_nodes.size();
587 centroid /= (3. * double_area);
592 const auto append_triangle = [
this, &poly_nodes, &tri_map](
593 const unsigned int a,
const unsigned int b,
const unsigned int c)
595 if (triangleAreaHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) <=
_area_tol)
598 if (orient2dHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) >= 0)
599 tri_map.push_back({a, b, c});
601 tri_map.push_back({a, c, b});
606 const auto point_in_triangle =
609 const Real ab = orient2dHelper(a, b, p);
610 const Real bc = orient2dHelper(b, c, p);
611 const Real ca = orient2dHelper(c, a, p);
615 const auto min_triangle_angle = [](
const Point & a,
const Point & b,
const Point & c)
618 const auto angle_at =
619 [&clamp_cos](
const Point & vertex,
const Point & point_one,
const Point & point_two)
621 const Point edge_one = point_one - vertex;
622 const Point edge_two = point_two - vertex;
624 if (denom <= TOLERANCE)
626 return std::acos(clamp_cos((edge_one * edge_two) / denom));
629 return std::min({angle_at(a, b, c), angle_at(b, c, a), angle_at(c, a, b)});
632 const auto canonicalize_polygon = [
this, &poly_nodes]()
634 if (poly_nodes.size() < 3)
637 if (
area(poly_nodes) < 0)
638 std::reverse(poly_nodes.begin(), poly_nodes.end());
641 while (changed && poly_nodes.size() > 3)
646 const auto prev = (i + poly_nodes.size() - 1) % poly_nodes.size();
647 const auto next = (i + 1) % poly_nodes.size();
650 triangleAreaHelper(poly_nodes[prev], poly_nodes[i], poly_nodes[next]) <=
_area_tol)
652 poly_nodes.erase(poly_nodes.begin() + i);
659 if (poly_nodes.size() >= 3 &&
area(poly_nodes) < 0)
660 std::reverse(poly_nodes.begin(), poly_nodes.end());
663 const auto triangulate_with_ear_clipping =
664 [
this, &poly_nodes, &point_in_triangle, &min_triangle_angle](
665 const bool perform_delaunay_flips)
667 std::vector<std::array<unsigned int, 3>> triangles;
668 if (poly_nodes.size() < 3)
671 if (poly_nodes.size() == 3)
673 triangles.push_back(makeCCWTriangleHelper(poly_nodes, 0, 1, 2));
677 std::vector<unsigned int> remaining_vertices(poly_nodes.size());
678 std::iota(remaining_vertices.begin(), remaining_vertices.end(), 0);
680 while (remaining_vertices.size() > 3)
682 std::optional<std::size_t> best_position;
686 for (
const auto position :
index_range(remaining_vertices))
688 const auto prev_position =
689 (position + remaining_vertices.size() - 1) % remaining_vertices.size();
690 const auto next_position = (position + 1) % remaining_vertices.size();
691 const auto prev = remaining_vertices[prev_position];
692 const auto curr = remaining_vertices[position];
693 const auto next = remaining_vertices[next_position];
695 if (orient2dHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]) <=
_area_tol)
698 bool contains_other_vertex =
false;
699 for (
const auto other : remaining_vertices)
701 if (other == prev || other == curr || other == next)
704 if (point_in_triangle(
705 poly_nodes[other], poly_nodes[prev], poly_nodes[curr], poly_nodes[next]))
707 contains_other_vertex =
true;
712 if (contains_other_vertex)
715 const Real candidate_score =
716 min_triangle_angle(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
717 const Real candidate_area =
718 triangleAreaHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
719 if (!best_position || candidate_score > best_score + TOLERANCE ||
720 (
std::abs(candidate_score - best_score) <= TOLERANCE &&
723 best_position = position;
724 best_score = candidate_score;
725 best_area = candidate_area;
731 std::vector<std::array<unsigned int, 3>> best_fan;
735 for (
const auto root_position :
index_range(remaining_vertices))
737 std::vector<std::array<unsigned int, 3>> candidate_fan;
740 bool valid_fan =
true;
741 const auto root = remaining_vertices[root_position];
743 for (
unsigned int step = 1; step + 1 < remaining_vertices.size(); ++step)
745 const auto next_position = (root_position + step) % remaining_vertices.size();
746 const auto following_position = (root_position + step + 1) % remaining_vertices.size();
747 const auto vertex_one = remaining_vertices[next_position];
748 const auto vertex_two = remaining_vertices[following_position];
750 if (orient2dHelper(poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]) <=
757 candidate_fan.push_back(
758 makeCCWTriangleHelper(poly_nodes, root, vertex_one, vertex_two));
762 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
766 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
769 if (!valid_fan || candidate_fan.empty())
772 if (candidate_score > best_fan_score + TOLERANCE ||
773 (
std::abs(candidate_score - best_fan_score) <= TOLERANCE &&
774 candidate_area > best_fan_area +
_area_tol))
776 best_fan = std::move(candidate_fan);
777 best_fan_score = candidate_score;
778 best_fan_area = candidate_area;
782 if (best_fan.empty())
783 for (
unsigned int i = 1; i + 1 < remaining_vertices.size(); ++i)
784 best_fan.push_back(makeCCWTriangleHelper(poly_nodes,
785 remaining_vertices[0],
786 remaining_vertices[i],
787 remaining_vertices[i + 1]));
789 triangles.insert(triangles.end(), best_fan.begin(), best_fan.end());
793 const auto prev_position =
794 (*best_position + remaining_vertices.size() - 1) % remaining_vertices.size();
795 const auto next_position = (*best_position + 1) % remaining_vertices.size();
796 triangles.push_back(makeCCWTriangleHelper(poly_nodes,
797 remaining_vertices[prev_position],
798 remaining_vertices[*best_position],
799 remaining_vertices[next_position]));
800 remaining_vertices.erase(remaining_vertices.begin() + *best_position);
803 if (remaining_vertices.size() == 3)
804 triangles.push_back(makeCCWTriangleHelper(
805 poly_nodes, remaining_vertices[0], remaining_vertices[1], remaining_vertices[2]));
807 if (!perform_delaunay_flips)
810 std::set<std::array<unsigned int, 2>> boundary_edges;
812 boundary_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
814 performLocalDelaunayFlips(poly_nodes, boundary_edges, triangles);
818 const auto is_convex_polygon = [
this](
const std::vector<Point> & polygon_nodes)
820 if (polygon_nodes.size() <= 3)
825 const auto prev = (i + polygon_nodes.size() - 1) % polygon_nodes.size();
826 const auto next = (i + 1) % polygon_nodes.size();
827 if (orient2dHelper(polygon_nodes[prev], polygon_nodes[i], polygon_nodes[next]) <=
_area_tol)
835 if (poly_nodes.size() < 3)
836 mooseError(
"Can't triangulate poly with fewer than 3 nodes");
847 if (poly_nodes.size() == 3)
849 tri_map.push_back({0, 1, 2});
853 const unsigned int n_verts = poly_nodes.size();
855 for (
const auto & node : poly_nodes)
857 poly_center /= n_verts;
860 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
862 poly_nodes.push_back(poly_center);
866 canonicalize_polygon();
867 if (poly_nodes.size() < 3)
872 append_triangle(0, 1, 2);
879 !force_triangle_centroid_split)
881 const unsigned int n_verts = poly_nodes.size();
882 for (
unsigned int i = 1; i + 1 < n_verts; ++i)
883 append_triangle(0, i, i + 1);
888 !force_triangle_centroid_split)
890 #if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI) 894 mooseError(
"The 'delaunay' mortar triangulation mode requires libMesh TriangleInterface or " 895 "Poly2Tri support.");
900 !force_triangle_centroid_split)
902 for (
const auto & triangle : triangulate_with_ear_clipping(
true))
903 append_triangle(triangle[0], triangle[1], triangle[2]);
907 if (!force_triangle_centroid_split && !is_convex_polygon(poly_nodes))
909 for (
const auto & triangle : triangulate_with_ear_clipping(
true))
910 append_triangle(triangle[0], triangle[1], triangle[2]);
914 const unsigned int n_verts = poly_nodes.size();
915 const Point poly_center = polygon_centroid(poly_nodes);
917 bool added_triangle =
false;
919 if (triangleAreaHelper(poly_nodes[i], poly_nodes[(i + 1) % n_verts], poly_center) >
_area_tol)
921 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
922 added_triangle =
true;
926 poly_nodes.push_back(poly_center);
931 std::vector<Point> & nodes,
932 std::vector<std::vector<unsigned int>> & elem_to_nodes)
939 const std::vector<Point> & primary_nodes,
940 const std::vector<Point> & primary_reference_points,
941 std::vector<Point> & nodes,
942 std::vector<std::vector<unsigned int>> & elem_to_nodes,
943 std::vector<std::array<Point, 3>> & elem_to_secondary_reference_points,
944 std::vector<std::array<Point, 3>> & elem_to_primary_reference_points,
945 const Real minimum_segment_area)
948 elem_to_secondary_reference_points,
949 elem_to_primary_reference_points,
950 minimum_segment_area};
956 std::vector<Point> & nodes,
957 std::vector<std::vector<unsigned int>> & elem_to_nodes,
960 std::vector<Point> primary_poly;
961 std::vector<Point> primary_poly_reference_points;
963 if (reference_mapping)
966 mooseError(
"Reference-interpolation mortar segment generation requires one primary " 967 "reference point per primary sub-element node.");
969 mooseError(
"Reference-interpolation mortar segment generation requires one secondary " 970 "reference point per secondary sub-element node.");
973 mooseError(
"Reference-interpolation mortar segment outputs must be aligned before appending " 977 const Point e1 = primary_nodes[0] - primary_nodes[1];
978 const Point e2 = primary_nodes[2] - primary_nodes[1];
980 const auto n_verts = primary_nodes.size();
986 const auto primary_node_index = (orient > 0) ? n : n_verts - 1 - n;
987 primary_poly_reference_points.push_back(
994 std::vector<Point> clipped_poly =
996 if (clipped_poly.size() < 3)
1000 for (
const auto &
point : clipped_poly)
1002 mooseError(
"Clipped polygon not inside linearized secondary element");
1009 std::vector<std::vector<unsigned int>> tri_map;
1013 std::remove_if(tri_map.begin(),
1015 [&clipped_poly, reference_mapping](
const std::vector<unsigned int> & tri)
1017 mooseAssert(tri.size() == 3,
1018 "Mortar segment triangulation should only produce TRI3 maps.");
1019 return triangleAreaHelper(clipped_poly[tri[0]],
1020 clipped_poly[tri[1]],
1021 clipped_poly[tri[2]]) <
1025 if (tri_map.empty())
1028 std::vector<Point> secondary_node_reference_points;
1029 std::vector<Point> primary_node_reference_points;
1030 if (reference_mapping)
1032 secondary_node_reference_points.reserve(clipped_poly.size());
1033 primary_node_reference_points.reserve(clipped_poly.size());
1035 const auto recover_reference_point = [
this](
const Point & projected_point,
1036 const std::vector<Point> &
poly,
1037 const std::vector<Point> & reference_points,
1038 const char *
const parent_name,
1039 const std::size_t node_index)
1041 std::string failure_reason;
1042 const auto reference_point =
1044 if (!reference_point)
1047 " parent reference point for retained 3D mortar overlap vertex ",
1049 " at projected point ",
1053 ". Reference interpolation does not fall back to normal projection.");
1055 return *reference_point;
1058 for (
const auto node_index :
index_range(clipped_poly))
1060 const auto &
point = clipped_poly[node_index];
1061 secondary_node_reference_points.push_back(recover_reference_point(
1063 primary_node_reference_points.push_back(recover_reference_point(
1064 point, primary_poly, primary_poly_reference_points,
"primary", node_index));
1069 const auto offset = cast_int<unsigned int>(nodes.size());
1070 for (
const auto &
point : clipped_poly)
1073 for (
const auto & tri : tri_map)
1075 std::vector<unsigned int> shifted_tri;
1076 shifted_tri.reserve(tri.size());
1077 for (
const auto local_index : tri)
1078 shifted_tri.push_back(offset + local_index);
1079 elem_to_nodes.push_back(std::move(shifted_tri));
1081 if (reference_mapping)
1083 mooseAssert(tri.size() == 3,
"Mortar segment triangulation should only produce TRI3 maps.");
1084 std::array<Point, 3> elem_secondary_reference_points;
1085 std::array<Point, 3> elem_primary_reference_points;
1088 const auto local_node = tri[n];
1089 elem_secondary_reference_points[n] = secondary_node_reference_points[local_node];
1090 elem_primary_reference_points[n] = primary_node_reference_points[local_node];
1094 elem_secondary_reference_points);
1100 std::optional<Point>
1102 const std::vector<Point> &
poly,
1103 const std::vector<Point> & reference_points,
1104 std::string *
const failure_reason)
const 1106 mooseAssert(
poly.size() == reference_points.size(),
1107 "Projected point and reference point containers should be the same size.");
1110 failure_reason->clear();
1112 const auto fail = [failure_reason](
const std::string & reason) -> std::optional<Point>
1115 *failure_reason = reason;
1116 return std::nullopt;
1119 if (!isFinitePoint(
point))
1120 return fail(
"the projected target point contains a non-finite coordinate");
1124 if (!isFinitePoint(
poly[i]))
1125 return fail(
"projected polygon vertex " + std::to_string(i) +
1126 " contains a non-finite coordinate");
1127 if (!isFinitePoint(reference_points[i]))
1128 return fail(
"parent reference vertex " + std::to_string(i) +
1129 " contains a non-finite coordinate");
1132 if (
poly.size() != 3 &&
poly.size() != 4)
1133 return fail(
"reference point recovery only supports triangular and quadrilateral mortar " 1134 "sub-elements, but received " +
1135 std::to_string(
poly.size()) +
" vertices");
1139 for (
const auto & vertex :
poly)
1140 local_origin += vertex;
1141 local_origin /=
poly.size();
1143 Real local_scale = 0.;
1146 minimum_edge_length =
1151 const Real singular_tolerance = 100. * std::numeric_limits<Real>::epsilon();
1152 if (!std::isfinite(local_scale) || local_scale <= singular_tolerance)
1153 return fail(
"the projected polygon has a zero local length scale");
1154 if (!std::isfinite(minimum_edge_length) ||
1155 minimum_edge_length / local_scale <= singular_tolerance)
1156 return fail(
"the projected polygon has a zero-length edge relative to its local scale");
1158 std::vector<Point> normalized_poly;
1159 normalized_poly.reserve(
poly.size());
1160 for (
const auto & vertex :
poly)
1161 normalized_poly.push_back((vertex - local_origin) / local_scale);
1162 const Point normalized_point = (
point - local_origin) / local_scale;
1163 const Real reference_tolerance =
1164 std::max(mortar_reference_mapping_tolerance,
_area_tol / (minimum_edge_length * local_scale));
1165 std::array<Node, 4> element_nodes;
1167 const auto recover_with_libmesh = [&](
auto & element) -> std::optional<Point>
1171 element_nodes[i] = normalized_poly[i];
1172 element_nodes[i].set_id(i);
1173 element.set_node(i, &element_nodes[i]);
1176 if (!element.has_invertible_map(mortar_reference_mapping_tolerance))
1177 return fail(
"the projected sub-element map is degenerate or non-invertible");
1179 if (element.type() ==
QUAD4)
1180 for (
const auto corner :
make_range(element.n_vertices()))
1184 for (
const auto node :
make_range(element.n_nodes()))
1186 tangent_xi += FEInterface::shape_deriv(
1187 fe_type, 0, &element, node, 0, element.master_point(corner)) *
1188 element.point(node);
1189 tangent_eta += FEInterface::shape_deriv(
1190 fe_type, 0, &element, node, 1, element.master_point(corner)) *
1191 element.point(node);
1194 const Real corner_jacobian = tangent_xi.
cross(tangent_eta).norm();
1195 if (!std::isfinite(corner_jacobian) ||
1196 corner_jacobian <= mortar_reference_mapping_tolerance)
1197 return fail(
"the projected quadrilateral has a singular or ill-conditioned corner map");
1200 Point local_reference = FEMap::inverse_map(
1201 2, &element, normalized_point, mortar_reference_mapping_tolerance,
false,
false);
1202 if (!isFinitePoint(local_reference))
1203 return fail(
"libMesh inverse_map produced a non-finite reference point");
1205 const Real inverse_map_error =
1206 (FEMap::map(2, &element, local_reference) - normalized_point).
norm();
1207 if (!std::isfinite(inverse_map_error) || inverse_map_error > mortar_reference_mapping_tolerance)
1209 std::ostringstream reason;
1210 reason <<
"the normalized inverse-map error " << inverse_map_error <<
" exceeds " 1211 << mortar_reference_mapping_tolerance;
1212 return fail(reason.str());
1215 if (element.type() ==
TRI3)
1217 std::array<Real, 3> weights;
1219 weights[i] = FEInterface::shape(fe_type, &element, i, local_reference,
false);
1221 for (
auto &
weight : weights)
1223 const Real weight_sum = std::accumulate(weights.begin(), weights.end(), 0.);
1224 if (!std::isfinite(weight_sum) || weight_sum <= singular_tolerance)
1225 return fail(
"clamped triangle barycentric coordinates have a zero or non-finite sum");
1226 for (
auto &
weight : weights)
1230 const auto corrected_weight =
1231 std::distance(weights.begin(), std::max_element(weights.begin(), weights.end()));
1232 weights[corrected_weight] = 1.;
1234 if (i != static_cast<unsigned int>(corrected_weight))
1235 weights[corrected_weight] -= weights[i];
1237 local_reference =
Point();
1239 local_reference += weights[i] * element.master_point(i);
1243 local_reference(0) =
std::clamp(local_reference(0), -1., 1.);
1244 local_reference(1) =
std::clamp(local_reference(1), -1., 1.);
1245 local_reference(2) = 0.;
1248 if (!element.on_reference_element(local_reference, mortar_reference_mapping_tolerance))
1249 return fail(
"the clamped inverse-map result is outside the reference element");
1251 const Real round_trip_error =
1252 (FEMap::map(2, &element, local_reference) - normalized_point).
norm();
1253 if (!std::isfinite(round_trip_error) || round_trip_error > reference_tolerance)
1255 std::ostringstream reason;
1256 reason <<
"the normalized inverse-map round-trip error " << round_trip_error
1257 <<
" exceeds the clipping-consistent tolerance " << reference_tolerance;
1258 return fail(reason.str());
1261 Point parent_reference;
1264 FEInterface::shape(fe_type, &element, i, local_reference,
false) * reference_points[i];
1266 if (!isFinitePoint(parent_reference))
1267 return fail(
"reference interpolation produced a non-finite parent reference point");
1269 return parent_reference;
1272 if (
poly.size() == 3)
1275 return recover_with_libmesh(element);
1279 return recover_with_libmesh(element);
1287 poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
1288 nodes[i](1) * nodes[(i + 1) % nodes.size()](0);
std::vector< Point > clipProjectedPoly(const std::vector< Point > &primary_poly) const
Clip an already projected primary polygon against the secondary polygon.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
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.
Point _center
Geometric center of secondary element.
Real _area_tol
Tolerance times secondary area for dimensional consistency.
bool isInsideSecondary(const Point &pt) const
Check that a point is inside the secondary polygon (for verification only)
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
const unsigned int invalid_uint
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.
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.
const std::vector< Point > & primary_reference_points
Real _length_tol
Tolerance times secondary area for dimensional consistency.
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
static constexpr Real TOLERANCE
void swap(std::vector< T > &data, const std::size_t idx0, const std::size_t idx1, const libMesh::Parallel::Communicator &comm)
Swap function for serial or distributed vector of data.
static constexpr std::size_t dim
This is the dimension of all vector and tensor datastructures used in MOOSE.
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...
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
Real distance(const Point &p)
std::vector< std::array< Point, 3 > > & elem_to_primary_reference_points
std::vector< Point > projectPrimaryPoly(const std::vector< Point > &primary_nodes) const
Project a primary polygon into the helper plane while preserving the clipping orientation.
auto max(const L &left, const R &right)
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.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
MortarSegmentTriangulationMode
Real _tolerance
Tolerance for intersection and clipping.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on. ...
Real _secondary_area
Area of projected secondary element.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
std::vector< std::array< Point, 3 > > & elem_to_secondary_reference_points
Point _normal
Unit normal of the plane used to project and clip the linearized secondary subpatch.
bool isDisjoint(const std::vector< Point > &poly) const
Checks whether polygons are disjoint for an easy out.
const Point & center() const
Get center point of secondary element.
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped polygons.
Real area(const std::vector< Point > &nodes) const
Compute area of polygon.
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
Real minimum_segment_area
void getMortarSegmentsImpl(const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes, ReferenceMappingData *reference_mapping)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _triangulate_triangles
Whether already-triangular polygons should still be centroid-subdivided.
This class supports defining mortar segment mesh elements in 3D by projecting secondary and primary e...
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
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...
IntRange< T > make_range(T beg, T end)
std::vector< Point > _secondary_reference_points
Parent reference points corresponding to _secondary_poly.
Output containers and filtering data used while generating reference-coordinate mappings.
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.
auto min(const L &left, const R &right)
auto index_range(const T &sizable)
Point point(unsigned int i) const
Get 3D position of node of linearized secondary element.