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>
43constexpr Real mortar_reference_mapping_tolerance = 1e-8;
46validateProjectedQuadrilateral(
const std::vector<Point> & polygon,
const char *
const side)
48 if (polygon.size() != 4)
52 for (
const auto & point : polygon)
55 mooseException(
"The projected ", side,
" mortar quadrilateral contains a non-finite vertex.");
58 origin /= polygon.size();
61 for (
const auto & point : polygon)
65 mooseException(
"The projected ", side,
" mortar quadrilateral has a zero local length scale.");
67 std::array<Node, 4> nodes;
71 nodes[i] = (polygon[i] - origin) /
scale;
73 element.set_node(i, &nodes[i]);
79 if (!element.has_invertible_map(mortar_reference_mapping_tolerance) ||
80 !std::isfinite(scaled_jacobian) || scaled_jacobian <= mortar_reference_mapping_tolerance)
81 mooseException(
"The projected ",
83 " mortar quadrilateral is folded, singular, or non-injective in the clipping "
92orient2dHelper(
const Point & a,
const Point & b,
const Point & c)
94 return (b(0) - a(0)) * (c(1) - a(1)) - (b(1) - a(1)) * (c(0) - a(0));
98triangleAreaHelper(
const Point & a,
const Point & b,
const Point & c)
100 return 0.5 * std::abs(orient2dHelper(a, b, c));
106std::array<unsigned int, 2>
107canonicalEdgeHelper(
const unsigned int a,
const unsigned int b)
109 return {{std::min(a, b), std::max(a, b)}};
116std::array<unsigned int, 3>
117makeCCWTriangleHelper(
const std::vector<Point> & nodes,
118 const unsigned int a,
119 const unsigned int b,
120 const unsigned int c)
122 if (orient2dHelper(nodes[a], nodes[b], nodes[c]) >= 0)
128pointInCircumcircleHelper(
const Point & a,
const Point & b,
const Point & c,
const Point & p)
130 const auto ax = a(0) - p(0);
131 const auto ay = a(1) - p(1);
132 const auto bx = b(0) - p(0);
133 const auto by = b(1) - p(1);
134 const auto cx = c(0) - p(0);
135 const auto cy = c(1) - p(1);
136 const Real det = (ax * ax + ay * ay) * (bx * cy - by * cx) -
137 (bx * bx + by * by) * (ax * cy - ay * cx) +
138 (cx * cx + cy * cy) * (ax * by - ay * bx);
139 const Real orientation = orient2dHelper(a, b, c);
140 return orientation >= 0 ? det > TOLERANCE : det < -TOLERANCE;
144performLocalDelaunayFlips(
const std::vector<Point> & poly_nodes,
145 const std::set<std::array<unsigned int, 2>> & constrained_edges,
146 std::vector<std::array<unsigned int, 3>> & triangles)
153 std::map<std::array<unsigned int, 2>, std::vector<unsigned int>> edge_to_triangles;
154 for (
const auto tri_index :
index_range(triangles))
156 const auto & tri = triangles[tri_index];
157 edge_to_triangles[canonicalEdgeHelper(tri[0], tri[1])].push_back(tri_index);
158 edge_to_triangles[canonicalEdgeHelper(tri[1], tri[2])].push_back(tri_index);
159 edge_to_triangles[canonicalEdgeHelper(tri[2], tri[0])].push_back(tri_index);
162 for (
const auto & [edge, owning_triangles] : edge_to_triangles)
164 if (owning_triangles.size() != 2 || constrained_edges.count(edge))
167 const auto first_tri_index = owning_triangles[0];
168 const auto second_tri_index = owning_triangles[1];
169 const auto & first_triangle = triangles[first_tri_index];
170 const auto & second_triangle = triangles[second_tri_index];
172 const auto a = edge[0];
173 const auto b = edge[1];
174 const auto first_opposite =
175 *std::find_if(first_triangle.begin(),
176 first_triangle.end(),
177 [a, b](
const unsigned int vertex) { return vertex != a && vertex != b; });
178 const auto second_opposite =
179 *std::find_if(second_triangle.begin(),
180 second_triangle.end(),
181 [a, b](
const unsigned int vertex) { return vertex != a && vertex != b; });
183 if (first_opposite == second_opposite)
187 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[a]);
189 orient2dHelper(poly_nodes[first_opposite], poly_nodes[second_opposite], poly_nodes[b]);
190 if (side_a * side_b >= -TOLERANCE)
193 if (!pointInCircumcircleHelper(poly_nodes[first_triangle[0]],
194 poly_nodes[first_triangle[1]],
195 poly_nodes[first_triangle[2]],
196 poly_nodes[second_opposite]))
199 triangles[first_tri_index] =
200 makeCCWTriangleHelper(poly_nodes, first_opposite, second_opposite, b);
201 triangles[second_tri_index] =
202 makeCCWTriangleHelper(poly_nodes, second_opposite, first_opposite, a);
209#if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
211triangulateConstrainedDelaunayPolygon(std::vector<Point> & poly_nodes,
213 const Real length_tol,
214 std::vector<std::vector<unsigned int>> & tri_map)
217 ReplicatedMesh triangulation_mesh(comm_self, 2);
218 std::unordered_map<dof_id_type, unsigned int> node_id_to_local_index;
219 node_id_to_local_index.reserve(poly_nodes.size());
222 triangulation_mesh.add_point(poly_nodes[i], i);
224 triangulation_mesh.set_mesh_dimension(2);
226#ifdef LIBMESH_HAVE_TRIANGLE
227 TriangleInterface triangulator(triangulation_mesh);
229 Poly2TriTriangulator triangulator(triangulation_mesh);
230 triangulator.set_refine_boundary_allowed(
false);
233 triangulator.triangulation_type() = TriangulatorInterface::PSLG;
234 triangulator.elem_type() =
TRI3;
235 triangulator.set_interpolate_boundary_points(0);
236 triangulator.set_verify_hole_boundaries(
false);
237 triangulator.desired_area() = 0;
238 triangulator.minimum_angle() = 0;
239 triangulator.smooth_after_generating() =
false;
240 triangulator.quiet() =
true;
241 triangulator.segments.reserve(poly_nodes.size());
243 triangulator.segments.emplace_back(i, (i + 1) % poly_nodes.size());
245 triangulator.triangulate();
249 for (
const auto *
const node : triangulation_mesh.node_ptr_range())
250 if (!node_id_to_local_index.
count(node->id()))
255 Real best_distance = std::numeric_limits<Real>::max();
260 if (distance <= length_tol && distance < best_distance)
269 matched_index = cast_int<unsigned int>(poly_nodes.size());
270 poly_nodes.push_back(*node);
273 node_id_to_local_index.emplace(node->id(), matched_index);
276 std::vector<std::array<unsigned int, 3>> triangles;
277 triangles.reserve(triangulation_mesh.n_elem());
279 for (
const auto *
const elem : triangulation_mesh.active_element_ptr_range())
281 mooseAssert(elem->type() == TRI3,
282 "The delaunay mortar triangulation backend produced a non-TRI3 element: "
283 <<
static_cast<int>(elem->type()));
285 std::array<unsigned int, 3> local_triangle;
287 local_triangle[i] = libmesh_map_find(node_id_to_local_index, elem->node_id(i));
289 const Real orientation = orient2dHelper(poly_nodes[local_triangle[0]],
290 poly_nodes[local_triangle[1]],
291 poly_nodes[local_triangle[2]]);
292 if (std::abs(orientation) <= 2. * area_tol)
296 std::swap(local_triangle[1], local_triangle[2]);
298 triangles.push_back(local_triangle);
301 std::set<std::array<unsigned int, 2>> constrained_edges;
303 constrained_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
305 performLocalDelaunayFlips(poly_nodes, constrained_edges, triangles);
307 std::set<std::array<unsigned int, 3>> seen_triangles;
308 for (
auto local_triangle : triangles)
310 auto canonical_triangle = local_triangle;
311 std::sort(canonical_triangle.begin(), canonical_triangle.end());
312 if (!seen_triangles.insert(canonical_triangle).second)
315 tri_map.push_back({local_triangle[0], local_triangle[1], local_triangle[2]});
324 const Point & normal,
326 const bool triangulate_triangles)
328 std::move(secondary_nodes), {},
center, normal, triangulation_mode, triangulate_triangles)
333 std::vector<Point> secondary_reference_points,
335 const Point & normal,
337 const bool triangulate_triangles)
341 _triangulation_mode(triangulation_mode),
342 _triangulate_triangles(triangulate_triangles),
343 _secondary_reference_points(
std::move(secondary_reference_points))
347 "Each projected secondary node needs one parent reference point.");
353 const Point e1 = secondary_nodes[0] - secondary_nodes[1];
354 const Point e2 = secondary_nodes[2] - secondary_nodes[1];
355 const Real orient = e2.cross(e1) *
_normal;
364 for (
const auto & node : secondary_nodes)
386 const Point & p1,
const Point & p2,
const Point & q1,
const Point & q2, Real & s)
const
388 const Point dp = p2 - p1;
389 const Point dq = q2 - q1;
390 const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
391 const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
392 const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
393 const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
394 s = -alpha * (cp1q2 - cp1q1 - cq1q2);
411 const Point e1 = q2 - q1;
412 const Point e2 = pt - q1;
418 const bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) <
_area_tol;
433 const Point edg = q2 - q1;
434 const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
438 auto is_inside = [&edg, cp](Point & pt, Real tol)
439 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
441 bool all_outside =
true;
456 const Point e1 = primary_nodes[0] - primary_nodes[1];
457 const Point e2 = primary_nodes[2] - primary_nodes[1];
461 const Real orient = e2.cross(e1) *
_u.cross(
_v);
464 std::vector<Point> primary_poly;
465 const int n_verts = primary_nodes.size();
466 primary_poly.reserve(primary_nodes.size());
467 for (
auto n : index_range(primary_nodes))
469 Point pt = (orient > 0) ? primary_nodes[n] -
_center : primary_nodes[n_verts - 1 - n] -
_center;
470 primary_poly.emplace_back(pt *
_u, pt *
_v, 0.);
474 validateProjectedQuadrilateral(primary_poly,
"primary");
495 for (
auto i : index_range(primary_poly))
498 if (clipped_poly.size() < 3)
500 clipped_poly.clear();
505 std::vector<Point> input_poly(clipped_poly);
506 clipped_poly.clear();
509 const Point & clip_pt1 = primary_poly[i];
510 const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
511 const Point edg = clip_pt2 - clip_pt1;
512 const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
520 auto is_inside = [&edg, cp](
const Point & pt, Real tol)
521 {
return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
524 for (
auto j : index_range(input_poly))
527 const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
528 const Point prev_pt = input_poly[j];
531 const bool is_current_inside = is_inside(curr_pt,
_area_tol);
532 const bool is_previous_inside = is_inside(prev_pt,
_area_tol);
534 if (is_current_inside)
536 if (!is_previous_inside)
539 Point intersect =
getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
555 clipped_poly.push_back(intersect);
557 clipped_poly.push_back(curr_pt);
559 else if (is_previous_inside)
562 Point intersect =
getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
564 clipped_poly.push_back(intersect);
570 if (clipped_poly.size() < 3)
572 clipped_poly.clear();
577 std::vector<Point> cleaned_poly;
578 cleaned_poly.push_back(clipped_poly.back());
579 for (
auto i : make_range(clipped_poly.size() - 1))
581 const Point prev_pt = cleaned_poly.back();
582 const Point curr_pt = clipped_poly[i];
586 cleaned_poly.push_back(curr_pt);
590 cleaned_poly.size() <= 8,
591 "Our distributed mesh numbering scheme assumes that we have at most 8 nodes resulting from "
592 "clipping the projection of the primary sub-element onto the secondary sub-element");
598 std::vector<std::vector<unsigned int>> & tri_map)
const
602 const auto polygon_centroid = [](
const std::vector<Point> & polygon_nodes)
605 Real double_area = 0;
606 for (
const auto i : index_range(polygon_nodes))
608 const auto & a = polygon_nodes[i];
609 const auto & b = polygon_nodes[(i + 1) % polygon_nodes.size()];
610 const Real cross = a(0) * b(1) - b(0) * a(1);
611 double_area += cross;
612 centroid(0) += (a(0) + b(0)) * cross;
613 centroid(1) += (a(1) + b(1)) * cross;
616 if (std::abs(double_area) <= TOLERANCE)
618 for (
const auto & node : polygon_nodes)
620 centroid /= polygon_nodes.size();
624 centroid /= (3. * double_area);
629 const auto append_triangle = [
this, &poly_nodes, &tri_map](
630 const unsigned int a,
const unsigned int b,
const unsigned int c)
632 if (triangleAreaHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) <=
_area_tol)
635 if (orient2dHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) >= 0)
636 tri_map.push_back({a, b, c});
638 tri_map.push_back({a, c, b});
643 const auto point_in_triangle =
644 [
this](
const Point & p,
const Point & a,
const Point & b,
const Point & c)
646 const Real ab = orient2dHelper(a, b, p);
647 const Real bc = orient2dHelper(b, c, p);
648 const Real ca = orient2dHelper(c, a, p);
649 return ab >= -_area_tol && bc >= -_area_tol && ca >= -_area_tol;
652 const auto min_triangle_angle = [](
const Point & a,
const Point & b,
const Point & c)
654 const auto clamp_cos = [](
Real value) {
return std::max(-1., std::min(1., value)); };
655 const auto angle_at =
656 [&clamp_cos](
const Point & vertex,
const Point & point_one,
const Point & point_two)
658 const Point edge_one = point_one - vertex;
659 const Point edge_two = point_two - vertex;
660 const Real denom = edge_one.norm() * edge_two.norm();
661 if (denom <= TOLERANCE)
663 return std::acos(clamp_cos((edge_one * edge_two) / denom));
666 return std::min({angle_at(a, b, c), angle_at(b, c, a), angle_at(c, a, b)});
669 const auto canonicalize_polygon = [
this, &poly_nodes]()
671 if (poly_nodes.size() < 3)
674 if (area(poly_nodes) < 0)
675 std::reverse(poly_nodes.begin(), poly_nodes.end());
678 while (changed && poly_nodes.size() > 3)
683 const auto prev = (i + poly_nodes.size() - 1) % poly_nodes.size();
684 const auto next = (i + 1) % poly_nodes.size();
685 if ((poly_nodes[i] - poly_nodes[prev]).norm() <= _length_tol ||
686 (poly_nodes[next] - poly_nodes[i]).
norm() <= _length_tol ||
687 triangleAreaHelper(poly_nodes[prev], poly_nodes[i], poly_nodes[next]) <= _area_tol)
689 poly_nodes.erase(poly_nodes.begin() + i);
696 if (poly_nodes.size() >= 3 && area(poly_nodes) < 0)
697 std::reverse(poly_nodes.begin(), poly_nodes.end());
700 const auto triangulate_with_ear_clipping =
701 [
this, &poly_nodes, &point_in_triangle, &min_triangle_angle](
702 const bool perform_delaunay_flips)
704 std::vector<std::array<unsigned int, 3>> triangles;
705 if (poly_nodes.size() < 3)
708 if (poly_nodes.size() == 3)
710 triangles.push_back(makeCCWTriangleHelper(poly_nodes, 0, 1, 2));
714 std::vector<unsigned int> remaining_vertices(poly_nodes.size());
715 std::iota(remaining_vertices.begin(), remaining_vertices.end(), 0);
717 while (remaining_vertices.size() > 3)
719 std::optional<std::size_t> best_position;
720 Real best_score = -std::numeric_limits<Real>::max();
721 Real best_area = -std::numeric_limits<Real>::max();
723 for (
const auto position :
index_range(remaining_vertices))
725 const auto prev_position =
726 (position + remaining_vertices.size() - 1) % remaining_vertices.size();
727 const auto next_position = (position + 1) % remaining_vertices.size();
728 const auto prev = remaining_vertices[prev_position];
729 const auto curr = remaining_vertices[position];
730 const auto next = remaining_vertices[next_position];
732 if (orient2dHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]) <= _area_tol)
735 bool contains_other_vertex =
false;
736 for (
const auto other : remaining_vertices)
738 if (other == prev || other == curr || other == next)
741 if (point_in_triangle(
742 poly_nodes[other], poly_nodes[prev], poly_nodes[curr], poly_nodes[next]))
744 contains_other_vertex =
true;
749 if (contains_other_vertex)
752 const Real candidate_score =
753 min_triangle_angle(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
754 const Real candidate_area =
755 triangleAreaHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
756 if (!best_position || candidate_score > best_score + TOLERANCE ||
757 (std::abs(candidate_score - best_score) <= TOLERANCE &&
758 candidate_area > best_area + _area_tol))
760 best_position = position;
761 best_score = candidate_score;
762 best_area = candidate_area;
768 std::vector<std::array<unsigned int, 3>> best_fan;
769 Real best_fan_score = -std::numeric_limits<Real>::max();
770 Real best_fan_area = -std::numeric_limits<Real>::max();
772 for (
const auto root_position :
index_range(remaining_vertices))
774 std::vector<std::array<unsigned int, 3>> candidate_fan;
775 Real candidate_score = std::numeric_limits<Real>::max();
776 Real candidate_area = std::numeric_limits<Real>::max();
777 bool valid_fan =
true;
778 const auto root = remaining_vertices[root_position];
780 for (
unsigned int step = 1; step + 1 < remaining_vertices.size(); ++step)
782 const auto next_position = (root_position + step) % remaining_vertices.size();
783 const auto following_position = (root_position + step + 1) % remaining_vertices.size();
784 const auto vertex_one = remaining_vertices[next_position];
785 const auto vertex_two = remaining_vertices[following_position];
787 if (orient2dHelper(poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]) <=
794 candidate_fan.push_back(
795 makeCCWTriangleHelper(poly_nodes, root, vertex_one, vertex_two));
797 std::min(candidate_score,
799 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
801 std::min(candidate_area,
803 poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
806 if (!valid_fan || candidate_fan.empty())
809 if (candidate_score > best_fan_score + TOLERANCE ||
810 (std::abs(candidate_score - best_fan_score) <= TOLERANCE &&
811 candidate_area > best_fan_area + _area_tol))
813 best_fan = std::move(candidate_fan);
814 best_fan_score = candidate_score;
815 best_fan_area = candidate_area;
819 if (best_fan.empty())
820 for (
unsigned int i = 1; i + 1 < remaining_vertices.size(); ++i)
821 best_fan.push_back(makeCCWTriangleHelper(poly_nodes,
822 remaining_vertices[0],
823 remaining_vertices[i],
824 remaining_vertices[i + 1]));
826 triangles.insert(triangles.end(), best_fan.begin(), best_fan.end());
830 const auto prev_position =
831 (*best_position + remaining_vertices.size() - 1) % remaining_vertices.size();
832 const auto next_position = (*best_position + 1) % remaining_vertices.size();
833 triangles.push_back(makeCCWTriangleHelper(poly_nodes,
834 remaining_vertices[prev_position],
835 remaining_vertices[*best_position],
836 remaining_vertices[next_position]));
837 remaining_vertices.erase(remaining_vertices.begin() + *best_position);
840 if (remaining_vertices.size() == 3)
841 triangles.push_back(makeCCWTriangleHelper(
842 poly_nodes, remaining_vertices[0], remaining_vertices[1], remaining_vertices[2]));
844 if (!perform_delaunay_flips)
847 std::set<std::array<unsigned int, 2>> boundary_edges;
849 boundary_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
851 performLocalDelaunayFlips(poly_nodes, boundary_edges, triangles);
855 const auto is_convex_polygon = [
this](
const std::vector<Point> & polygon_nodes)
857 if (polygon_nodes.size() <= 3)
862 const auto prev = (i + polygon_nodes.size() - 1) % polygon_nodes.size();
863 const auto next = (i + 1) % polygon_nodes.size();
864 if (orient2dHelper(polygon_nodes[prev], polygon_nodes[i], polygon_nodes[next]) <= _area_tol)
872 if (poly_nodes.size() < 3)
873 mooseError(
"Can't triangulate poly with fewer than 3 nodes");
884 if (poly_nodes.size() == 3)
886 tri_map.push_back({0, 1, 2});
890 const unsigned int n_verts = poly_nodes.size();
892 for (
const auto & node : poly_nodes)
894 poly_center /= n_verts;
897 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
899 poly_nodes.push_back(poly_center);
903 canonicalize_polygon();
904 if (poly_nodes.size() < 3)
907 if (poly_nodes.size() == 3 && !_triangulate_triangles)
909 append_triangle(0, 1, 2);
913 const bool force_triangle_centroid_split = _triangulate_triangles && poly_nodes.size() == 3;
916 !force_triangle_centroid_split)
918 const unsigned int n_verts = poly_nodes.size();
919 for (
unsigned int i = 1; i + 1 < n_verts; ++i)
920 append_triangle(0, i, i + 1);
925 !force_triangle_centroid_split)
927#if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
928 triangulateConstrainedDelaunayPolygon(poly_nodes, _area_tol, _length_tol, tri_map);
931 mooseError(
"The 'delaunay' mortar triangulation mode requires libMesh TriangleInterface or "
932 "Poly2Tri support.");
937 !force_triangle_centroid_split)
939 for (
const auto & triangle : triangulate_with_ear_clipping(true))
940 append_triangle(triangle[0], triangle[1], triangle[2]);
944 if (!force_triangle_centroid_split && !is_convex_polygon(poly_nodes))
946 for (
const auto & triangle : triangulate_with_ear_clipping(true))
947 append_triangle(triangle[0], triangle[1], triangle[2]);
951 const unsigned int n_verts = poly_nodes.size();
952 const Point poly_center = polygon_centroid(poly_nodes);
954 bool added_triangle =
false;
956 if (triangleAreaHelper(poly_nodes[i], poly_nodes[(i + 1) % n_verts], poly_center) > _area_tol)
958 tri_map.push_back({i, (i + 1) % n_verts, n_verts});
959 added_triangle =
true;
963 poly_nodes.push_back(poly_center);
968 std::vector<Point> & nodes,
969 std::vector<std::vector<unsigned int>> & elem_to_nodes)
976 const std::vector<Point> & primary_nodes,
977 const std::vector<Point> & primary_reference_points,
978 std::vector<Point> & nodes,
979 std::vector<std::vector<unsigned int>> & elem_to_nodes,
980 std::vector<std::array<Point, 3>> & elem_to_secondary_reference_points,
981 std::vector<std::array<Point, 3>> & elem_to_primary_reference_points,
982 const Real minimum_segment_area)
985 elem_to_secondary_reference_points,
986 elem_to_primary_reference_points,
987 minimum_segment_area};
993 std::vector<Point> & nodes,
994 std::vector<std::vector<unsigned int>> & elem_to_nodes,
997 std::vector<Point> primary_poly;
998 std::vector<Point> primary_poly_reference_points;
1000 if (reference_mapping)
1003 mooseError(
"Reference-interpolation mortar segment generation requires one primary "
1004 "reference point per primary sub-element node.");
1006 mooseError(
"Reference-interpolation mortar segment generation requires one secondary "
1007 "reference point per secondary sub-element node.");
1010 mooseError(
"Reference-interpolation mortar segment outputs must be aligned before appending "
1014 const Point e1 = primary_nodes[0] - primary_nodes[1];
1015 const Point e2 = primary_nodes[2] - primary_nodes[1];
1016 const Real orient = e2.cross(e1) *
_u.cross(
_v);
1017 const auto n_verts = primary_nodes.size();
1021 for (
const auto n : index_range(primary_nodes))
1023 const auto primary_node_index = (orient > 0) ? n : n_verts - 1 - n;
1024 primary_poly_reference_points.push_back(
1031 std::vector<Point> clipped_poly =
1033 if (clipped_poly.size() < 3)
1037 for (
const auto &
point : clipped_poly)
1039 mooseError(
"Clipped polygon not inside linearized secondary element");
1046 std::vector<std::vector<unsigned int>> tri_map;
1050 std::remove_if(tri_map.begin(),
1052 [&clipped_poly, reference_mapping](
const std::vector<unsigned int> & tri)
1054 mooseAssert(tri.size() == 3,
1055 "Mortar segment triangulation should only produce TRI3 maps.");
1056 return triangleAreaHelper(clipped_poly[tri[0]],
1057 clipped_poly[tri[1]],
1058 clipped_poly[tri[2]]) <
1059 reference_mapping->minimum_segment_area;
1062 if (tri_map.empty())
1065 std::vector<Point> secondary_node_reference_points;
1066 std::vector<Point> primary_node_reference_points;
1067 if (reference_mapping)
1069 secondary_node_reference_points.reserve(clipped_poly.size());
1070 primary_node_reference_points.reserve(clipped_poly.size());
1072 const auto recover_reference_point = [
this](
const Point & projected_point,
1073 const std::vector<Point> & poly,
1074 const std::vector<Point> & reference_points,
1075 const char *
const parent_name,
1076 const std::size_t node_index)
1078 std::string failure_reason;
1079 const auto reference_point =
1080 referencePoint(projected_point, poly, reference_points, &failure_reason);
1081 if (!reference_point)
1084 " parent reference point for retained 3D mortar overlap vertex ",
1086 " at projected point ",
1090 ". Reference interpolation does not fall back to normal projection.");
1092 return *reference_point;
1095 for (
const auto node_index : index_range(clipped_poly))
1097 const auto &
point = clipped_poly[node_index];
1098 secondary_node_reference_points.push_back(recover_reference_point(
1100 primary_node_reference_points.push_back(recover_reference_point(
1101 point, primary_poly, primary_poly_reference_points,
"primary", node_index));
1106 const auto offset = cast_int<unsigned int>(nodes.size());
1107 for (
const auto &
point : clipped_poly)
1110 for (
const auto & tri : tri_map)
1112 std::vector<unsigned int> shifted_tri;
1113 shifted_tri.reserve(tri.size());
1114 for (
const auto local_index : tri)
1115 shifted_tri.push_back(offset + local_index);
1116 elem_to_nodes.push_back(std::move(shifted_tri));
1118 if (reference_mapping)
1120 mooseAssert(tri.size() == 3,
"Mortar segment triangulation should only produce TRI3 maps.");
1121 std::array<Point, 3> elem_secondary_reference_points;
1122 std::array<Point, 3> elem_primary_reference_points;
1123 for (
const auto n : index_range(tri))
1125 const auto local_node = tri[n];
1126 elem_secondary_reference_points[n] = secondary_node_reference_points[local_node];
1127 elem_primary_reference_points[n] = primary_node_reference_points[local_node];
1131 elem_secondary_reference_points);
1139 const std::vector<Point> & poly,
1140 const std::vector<Point> & reference_points,
1141 std::string *
const failure_reason)
const
1143 mooseAssert(poly.size() == reference_points.size(),
1144 "Projected point and reference point containers should be the same size.");
1147 failure_reason->clear();
1149 const auto fail = [failure_reason](
const std::string & reason) -> std::optional<Point>
1152 *failure_reason = reason;
1153 return std::nullopt;
1157 return fail(
"the projected target point contains a non-finite coordinate");
1159 for (
const auto i : index_range(poly))
1162 return fail(
"projected polygon vertex " + std::to_string(i) +
1163 " contains a non-finite coordinate");
1165 return fail(
"parent reference vertex " + std::to_string(i) +
1166 " contains a non-finite coordinate");
1169 if (poly.size() != 3 && poly.size() != 4)
1170 return fail(
"reference point recovery only supports triangular and quadrilateral mortar "
1171 "sub-elements, but received " +
1172 std::to_string(poly.size()) +
" vertices");
1174 Real minimum_edge_length = std::numeric_limits<Real>::max();
1176 for (
const auto & vertex : poly)
1177 local_origin += vertex;
1178 local_origin /= poly.size();
1180 Real local_scale = 0.;
1181 for (
const auto i : index_range(poly))
1183 minimum_edge_length =
1184 std::min(minimum_edge_length, (poly[(i + 1) % poly.size()] - poly[i]).norm());
1185 local_scale = std::max(local_scale, (poly[i] - local_origin).norm());
1188 const Real singular_tolerance = 100. * std::numeric_limits<Real>::epsilon();
1189 if (!std::isfinite(local_scale) || local_scale <= singular_tolerance)
1190 return fail(
"the projected polygon has a zero local length scale");
1191 if (!std::isfinite(minimum_edge_length) ||
1192 minimum_edge_length / local_scale <= singular_tolerance)
1193 return fail(
"the projected polygon has a zero-length edge relative to its local scale");
1195 std::vector<Point> normalized_poly;
1196 normalized_poly.reserve(poly.size());
1197 for (
const auto & vertex : poly)
1198 normalized_poly.push_back((vertex - local_origin) / local_scale);
1199 const Point normalized_point = (
point - local_origin) / local_scale;
1200 const Real reference_tolerance =
1201 std::max(mortar_reference_mapping_tolerance,
_area_tol / (minimum_edge_length * local_scale));
1202 std::array<Node, 4> element_nodes;
1203 const FEType fe_type(FIRST, LAGRANGE);
1204 const auto recover_with_libmesh = [&](
auto & element) -> std::optional<Point>
1206 for (
const auto i : index_range(normalized_poly))
1208 element_nodes[i] = normalized_poly[i];
1209 element_nodes[i].set_id(i);
1210 element.set_node(i, &element_nodes[i]);
1213 if (!element.has_invertible_map(mortar_reference_mapping_tolerance))
1214 return fail(
"the projected sub-element map is degenerate or non-invertible");
1216 if (element.type() == QUAD4)
1217 for (
const auto corner : make_range(element.n_vertices()))
1221 for (
const auto node : make_range(element.n_nodes()))
1223 tangent_xi += FEInterface::shape_deriv(
1224 fe_type, 0, &element, node, 0, element.master_point(corner)) *
1225 element.point(node);
1226 tangent_eta += FEInterface::shape_deriv(
1227 fe_type, 0, &element, node, 1, element.master_point(corner)) *
1228 element.point(node);
1231 const Real corner_jacobian = tangent_xi.cross(tangent_eta).norm();
1232 if (!std::isfinite(corner_jacobian) ||
1233 corner_jacobian <= mortar_reference_mapping_tolerance)
1234 return fail(
"the projected quadrilateral has a singular or ill-conditioned corner map");
1237 Point local_reference = FEMap::inverse_map(
1238 2, &element, normalized_point, mortar_reference_mapping_tolerance,
false,
false);
1240 return fail(
"libMesh inverse_map produced a non-finite reference point");
1242 const Real inverse_map_error =
1243 (FEMap::map(2, &element, local_reference) - normalized_point).norm();
1244 if (!std::isfinite(inverse_map_error) || inverse_map_error > mortar_reference_mapping_tolerance)
1246 std::ostringstream reason;
1247 reason <<
"the normalized inverse-map error " << inverse_map_error <<
" exceeds "
1248 << mortar_reference_mapping_tolerance;
1249 return fail(reason.str());
1252 if (element.type() == TRI3)
1254 std::array<Real, 3> weights;
1255 for (
const auto i : index_range(weights))
1256 weights[i] = FEInterface::shape(fe_type, &element, i, local_reference,
false);
1258 for (
auto & weight : weights)
1259 weight = std::clamp(weight, 0., 1.);
1260 const Real weight_sum = std::accumulate(weights.begin(), weights.end(), 0.);
1261 if (!std::isfinite(weight_sum) || weight_sum <= singular_tolerance)
1262 return fail(
"clamped triangle barycentric coordinates have a zero or non-finite sum");
1263 for (
auto & weight : weights)
1264 weight /= weight_sum;
1267 const auto corrected_weight =
1268 std::distance(weights.begin(), std::max_element(weights.begin(), weights.end()));
1269 weights[corrected_weight] = 1.;
1270 for (
const auto i : index_range(weights))
1271 if (i !=
static_cast<unsigned int>(corrected_weight))
1272 weights[corrected_weight] -= weights[i];
1274 local_reference = Point();
1275 for (
const auto i : index_range(weights))
1276 local_reference += weights[i] * element.master_point(i);
1280 local_reference(0) = std::clamp(local_reference(0), -1., 1.);
1281 local_reference(1) = std::clamp(local_reference(1), -1., 1.);
1282 local_reference(2) = 0.;
1285 if (!element.on_reference_element(local_reference, mortar_reference_mapping_tolerance))
1286 return fail(
"the clamped inverse-map result is outside the reference element");
1288 const Real round_trip_error =
1289 (FEMap::map(2, &element, local_reference) - normalized_point).norm();
1290 if (!std::isfinite(round_trip_error) || round_trip_error > reference_tolerance)
1292 std::ostringstream reason;
1293 reason <<
"the normalized inverse-map round-trip error " << round_trip_error
1294 <<
" exceeds the clipping-consistent tolerance " << reference_tolerance;
1295 return fail(reason.str());
1298 Point parent_reference;
1299 for (
const auto i : index_range(normalized_poly))
1301 FEInterface::shape(fe_type, &element, i, local_reference,
false) * reference_points[i];
1304 return fail(
"reference interpolation produced a non-finite parent reference point");
1306 return parent_reference;
1309 if (poly.size() == 3)
1312 return recover_with_libmesh(element);
1316 return recover_with_libmesh(element);
1323 for (
auto i : index_range(nodes))
1324 poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
1325 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
if(!dmm->_nl) SETERRQ(PETSC_COMM_WORLD
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.
auto max(const L &left, const R &right)
bool isFinitePoint(const Point &point)
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
auto index_range(const T &sizable)
const unsigned int invalid_uint
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)