23#include "libmesh/mesh_tools.h"
24#include "libmesh/explicit_system.h"
25#include "libmesh/numeric_vector.h"
26#include "libmesh/elem.h"
27#include "libmesh/node.h"
28#include "libmesh/dof_map.h"
29#include "libmesh/edge_edge2.h"
30#include "libmesh/edge_edge3.h"
31#include "libmesh/face_tri3.h"
32#include "libmesh/face_tri6.h"
33#include "libmesh/face_tri7.h"
34#include "libmesh/face_quad4.h"
35#include "libmesh/face_quad8.h"
36#include "libmesh/face_quad9.h"
37#include "libmesh/exodusII_io.h"
38#include "libmesh/quadrature_gauss.h"
39#include "libmesh/quadrature_nodal.h"
40#include "libmesh/distributed_mesh.h"
41#include "libmesh/replicated_mesh.h"
42#include "libmesh/enum_to_string.h"
43#include "libmesh/statistics.h"
44#include "libmesh/equation_systems.h"
46#include "metaphysicl/dualnumber.h"
61#if NANOFLANN_VERSION < 0x150
75std::vector<unsigned int>
76nodalQuadraturePointToSecondaryNodeMap(
const Elem & secondary_elem,
77 const std::vector<Point> & q_points)
83 " points for secondary mortar element ",
86 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.
type()),
87 ", but the element has ",
91 const auto invalid_node = std::numeric_limits<unsigned int>::max();
92 std::vector<unsigned int> qpoint_to_node(
n_nodes, invalid_node);
93 std::vector<bool> node_used(
n_nodes,
false);
95 const Real element_size = secondary_elem.
hmax();
96 mooseAssert(element_size > 0,
97 "Secondary mortar element "
98 << secondary_elem.
id() <<
" of type "
99 << libMesh::Utility::enum_to_string<ElemType>(secondary_elem.
type())
100 <<
" has a non-positive hmax and cannot be used for nodal quadrature point "
108 const Real matching_tol_sq = matching_tol * matching_tol;
113 for (
const auto qp :
make_range(q_points.size()))
115 unsigned int closest_node = invalid_node;
116 Real closest_dist_sq = std::numeric_limits<Real>::max();
117 Real second_closest_dist_sq = std::numeric_limits<Real>::max();
124 const Real dist_sq = (q_points[qp] - secondary_elem.
point(n)).norm_sq();
125 if (dist_sq < closest_dist_sq)
127 second_closest_dist_sq = closest_dist_sq;
128 closest_dist_sq = dist_sq;
131 else if (dist_sq < second_closest_dist_sq)
132 second_closest_dist_sq = dist_sq;
135 if (closest_node == invalid_node || closest_dist_sq > matching_tol_sq)
136 mooseError(
"Could not match nodal quadrature point ",
140 " to a node on secondary mortar element ",
143 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.
type()),
144 ". The nearest unmatched node distance is ",
145 std::sqrt(closest_dist_sq),
146 ", which exceeds the tolerance ",
150 if (second_closest_dist_sq <= matching_tol_sq)
155 " does not map uniquely to secondary mortar element ",
158 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.
type()),
159 ". Two unmatched nodes are within the matching tolerance ",
163 qpoint_to_node[qp] = closest_node;
164 node_used[closest_node] =
true;
170 std::vector<unsigned int> node_to_qpoint(
n_nodes, invalid_node);
171 for (
const auto qp :
make_range(q_points.size()))
173 const auto mapped_node = qpoint_to_node[qp];
174 mooseAssert(mapped_node != invalid_node && mapped_node <
n_nodes,
175 "Invalid secondary node mapping for nodal quadrature point " << qp <<
".");
176 mooseAssert(node_to_qpoint[mapped_node] == invalid_node,
177 "Secondary node " << mapped_node <<
" on mortar element " << secondary_elem.
id()
178 <<
" was matched to both nodal quadrature point "
179 << node_to_qpoint[mapped_node] <<
" and " << qp <<
".");
180 node_to_qpoint[mapped_node] = qp;
183 unsigned int candidate_count = 0;
184 unsigned int candidate_node = invalid_node;
186 if ((q_points[qp] - secondary_elem.
point(n)).norm_sq() <= matching_tol_sq)
192 mooseAssert(candidate_count == 1,
193 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.
id()
194 <<
" has " << candidate_count
195 <<
" secondary node candidates within tolerance "
196 << matching_tol <<
".");
197 mooseAssert(candidate_node == mapped_node,
198 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.
id()
199 <<
" was matched to node " << mapped_node
200 <<
", but the full candidate search found node "
201 << candidate_node <<
".");
206 mooseAssert(node_to_qpoint[n] != invalid_node,
207 "Secondary node " << n <<
" on mortar element " << secondary_elem.
id()
208 <<
" was not matched to a nodal quadrature point.");
211 unsigned int candidate_count = 0;
212 unsigned int candidate_qp = invalid_node;
213 for (
const auto qp :
make_range(q_points.size()))
214 if ((q_points[qp] - secondary_elem.
point(n)).norm_sq() <= matching_tol_sq)
220 mooseAssert(candidate_count == 1,
221 "Secondary node " << n <<
" on mortar element " << secondary_elem.
id() <<
" has "
223 <<
" nodal quadrature point candidates within tolerance "
224 << matching_tol <<
".");
225 mooseAssert(candidate_qp == node_to_qpoint[n],
226 "Secondary node " << n <<
" on mortar element " << secondary_elem.
id()
227 <<
" was matched to nodal quadrature point " << node_to_qpoint[n]
228 <<
", but the full candidate search found point " << candidate_qp
233 return qpoint_to_node;
259 mooseError(
"No entries found in the secondary node -> nodal geometry map.");
263 ?
static_cast<SubProblem &
>(*problem.getDisplacedProblem())
265 auto & nodal_normals_es = subproblem.
es();
267 const std::string nodal_normals_sys_name =
"nodal_normals";
271 for (
const auto s :
make_range(nodal_normals_es.n_systems()))
272 if (!nodal_normals_es.get_system(s).is_initialized())
278 &nodal_normals_es.template add_system<ExplicitSystem>(nodal_normals_sys_name);
296 nodal_normals_es.reinit();
300 std::vector<dof_id_type> dof_indices_nnx, dof_indices_nny, dof_indices_nnz;
301 std::vector<dof_id_type> dof_indices_t1x, dof_indices_t1y, dof_indices_t1z;
302 std::vector<dof_id_type> dof_indices_t2x, dof_indices_t2y, dof_indices_t2z;
309 const Elem * elem = *el;
356 std::set<std::string> sys_names = {nodal_normals_sys_name};
365 "nodal_geometry_only.e", nodal_normals_es, &sys_names);
392 const std::pair<BoundaryID, BoundaryID> & boundary_key,
393 const std::pair<SubdomainID, SubdomainID> & subdomain_key,
397 const bool correct_edge_dropping,
398 const Real minimum_projection_angle,
399 const Mortar3DSubpatchPlane mortar_3d_subpatch_plane,
401 const bool triangulate_triangles,
402 const Mortar3DQuadraturePointMapping mortar_3d_qp_mapping)
407 _on_displaced(on_displaced),
412 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
413 _correct_edge_dropping(correct_edge_dropping),
414 _minimum_projection_angle(minimum_projection_angle),
415 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
416 _triangulation_mode(triangulation_mode),
417 _triangulate_triangles(triangulate_triangles),
418 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
442 string_vec[2 * i] = std::to_string(primary_bnd_id);
443 string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
445 string_vec.back() =
_on_displaced ?
"displaced" :
"undisplaced";
446 return MooseUtils::join(string_vec,
"_");
491 mooseError(
"Mortar segment reference points were requested for mortar segment element ",
492 mortar_segment_elem.
id(),
493 ", but the reference-interpolation mapping mode is not enabled.");
497 mooseError(
"No reference-point record was found for mortar segment element ",
498 mortar_segment_elem.
id(),
499 ". The mortar segment info and reference-point maps are not aligned.");
501 return reference_points_it->second;
509 "Must specify secondary and primary boundary ids before building node-to-elem maps.");
512 for (
const auto & secondary_elem :
519 for (
const auto & nd : secondary_elem->node_ref_range())
522 vec.push_back(secondary_elem);
527 for (
const auto & primary_elem :
534 for (
const auto & nd : primary_elem->node_ref_range())
537 vec.push_back(primary_elem);
545 std::vector<Point> nodal_normals(secondary_elem.
n_nodes());
549 return nodal_normals;
557 "Map should locate secondary element");
562std::map<unsigned int, unsigned int>
565 std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
567 mooseAssert(secondary_ip,
"This should be non-null");
571 const auto & nd = lower_secondary_elem.
node_ref(i);
572 secondary_ip_i_to_lower_secondary_i[secondary_ip->
get_node_index(&nd)] = i;
575 return secondary_ip_i_to_lower_secondary_i;
578std::map<unsigned int, unsigned int>
580 const Elem & lower_primary_elem,
581 const Elem & primary_elem,
584 std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
588 const auto & nd = lower_primary_elem.
node_ref(i);
589 primary_ip_i_to_lower_primary_i[primary_elem.
get_node_index(&nd)] = i;
592 return primary_ip_i_to_lower_primary_i;
595std::array<MooseUtils::SemidynamicVector<Point, 9>, 2>
599 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
600 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
604 const auto & tangent_vectors =
606 nodal_tangents_one.push_back(tangent_vectors[0]);
607 nodal_tangents_two.push_back(tangent_vectors[1]);
610 return {{nodal_tangents_one, nodal_tangents_two}};
615 const std::vector<Real> & oned_xi1_pts)
const
617 std::vector<Point> xi1_pts(oned_xi1_pts.size());
619 xi1_pts[qp] = oned_xi1_pts[qp];
626 const std::vector<Point> & xi1_pts)
const
629 const auto num_qps = xi1_pts.size();
631 std::vector<Point> normals(num_qps);
643 normals[qp] += phi * nodal_normals[n];
647 for (
auto & normal : normals)
659 std::size_t node_unique_id_offset = 0;
667 const auto primary_bnd_id = pr.first;
668 const auto secondary_bnd_id = pr.second;
669 const auto num_primary_nodes =
670 std::distance(
_mesh.bid_nodes_begin(primary_bnd_id),
_mesh.bid_nodes_end(primary_bnd_id));
671 const auto num_secondary_nodes = std::distance(
_mesh.bid_nodes_begin(secondary_bnd_id),
672 _mesh.bid_nodes_end(secondary_bnd_id));
673 mooseAssert(num_primary_nodes,
674 "There are no primary nodes on boundary ID "
675 << primary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
676 mooseAssert(num_secondary_nodes,
677 "There are no secondary nodes on boundary ID "
678 << secondary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
680 node_unique_id_offset += num_primary_nodes + 2 * num_secondary_nodes;
685 end_el =
_mesh.active_elements_end();
689 const Elem * secondary_elem = *el;
695 std::vector<Node *> new_nodes;
696 for (MooseIndex(secondary_elem->
n_nodes()) n = 0; n < secondary_elem->
n_nodes(); ++n)
700 Node *
const new_node = new_nodes.back();
704 std::unique_ptr<Elem> new_elem;
706 new_elem = std::make_unique<Edge3>();
708 new_elem = std::make_unique<Edge2>();
710 new_elem->processor_id() = secondary_elem->
processor_id();
711 new_elem->subdomain_id() = secondary_elem->
subdomain_id();
712 new_elem->set_id(local_id_index++);
713 new_elem->set_unique_id(new_elem->id());
715 for (MooseIndex(new_elem->n_nodes()) n = 0; n < new_elem->n_nodes(); ++n)
716 new_elem->set_node(n, new_nodes[n]);
727 std::make_pair(secondary_elem->
node_ptr(0), secondary_elem)),
729 std::make_pair(secondary_elem->
node_ptr(1), secondary_elem));
731 bool new_container_node0_found =
733 new_container_node1_found =
736 const Elem * node0_primary_candidate =
nullptr;
737 const Elem * node1_primary_candidate =
nullptr;
739 if (new_container_node0_found)
741 const auto & xi2_primary_elem_pair = new_container_it0->second;
742 msinfo.
xi2_a = xi2_primary_elem_pair.first;
743 node0_primary_candidate = xi2_primary_elem_pair.second;
746 if (new_container_node1_found)
748 const auto & xi2_primary_elem_pair = new_container_it1->second;
749 msinfo.
xi2_b = xi2_primary_elem_pair.first;
750 node1_primary_candidate = xi2_primary_elem_pair.second;
757 if (node0_primary_candidate == node1_primary_candidate)
772 auto val = pr.second;
774 const Node * primary_node = std::get<1>(key);
775 Real xi1 = val.first;
776 const Elem * secondary_elem = val.second;
786 for (MooseIndex(secondary_elem->
n_nodes()) n = 0; n < secondary_elem->
n_nodes(); ++n)
791 Elem * current_mortar_segment =
nullptr;
794 for (
const auto & mortar_segment_candidate : mortar_segment_set)
800 catch (std::out_of_range &)
802 mooseError(
"MortarSegmentInfo not found for the mortar segment candidate");
804 if (info->xi1_a <= xi1 && xi1 <= info->xi1_b)
806 current_mortar_segment = mortar_segment_candidate;
812 if (current_mortar_segment ==
nullptr)
813 mooseError(
"Unable to find appropriate mortar segment during linear search!");
820 if (info->xi1_a == xi1 || xi1 == info->xi1_b)
825 "new_id must be the same on all processes");
826 Node *
const new_node =
833 const Point normal =
getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
837 this->_nodes_to_primary_elem_map.end())
838 mooseError(
"We should already have built this primary node to elem pair!");
839 const std::vector<const Elem *> & primary_node_neighbors =
843 if (primary_node_neighbors.size() == 0 || primary_node_neighbors.size() > 2)
844 mooseError(
"We must have either 1 or 2 primary side nodal neighbors, but we had ",
845 primary_node_neighbors.size());
852 const Elem * left_primary_elem = primary_node_neighbors[0];
853 const Elem * right_primary_elem =
854 (primary_node_neighbors.size() == 2) ? primary_node_neighbors[1] :
nullptr;
860 std::array<Real, 2> secondary_node_cps;
861 std::vector<Real> primary_node_cps(primary_node_neighbors.size());
864 for (
unsigned int nid = 0; nid < 2; ++nid)
865 secondary_node_cps[nid] = normal.
cross(secondary_elem->
point(nid) - new_pt)(2);
867 for (MooseIndex(primary_node_neighbors) mnn = 0; mnn < primary_node_neighbors.size(); ++mnn)
869 const Elem * primary_neigh = primary_node_neighbors[mnn];
870 Point opposite = (primary_neigh->
node_ptr(0) == primary_node) ? primary_neigh->
point(1)
871 : primary_neigh->
point(0);
873 primary_node_cps[mnn] = cp(2);
877 bool orientation1_valid =
false, orientation2_valid =
false;
879 if (primary_node_neighbors.size() == 2)
882 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.) &&
883 (secondary_node_cps[1] * primary_node_cps[1] > 0.);
885 orientation2_valid = (secondary_node_cps[0] * primary_node_cps[1] > 0.) &&
886 (secondary_node_cps[1] * primary_node_cps[0] > 0.);
888 else if (primary_node_neighbors.size() == 1)
891 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.);
892 orientation2_valid = (secondary_node_cps[1] * primary_node_cps[0] > 0.);
895 mooseError(
"Invalid primary node neighbors size ", primary_node_neighbors.size());
901 if (orientation1_valid && orientation2_valid)
903 "AutomaticMortarGeneration: Both orientations cannot simultaneously be valid.");
909 if (!orientation1_valid && !orientation2_valid)
912 "AutomaticMortarGeneration: Unable to determine valid secondary-primary orientation. "
913 "Consequently we will consider projection of the primary node invalid and not split the "
915 "This situation can indicate there are very oblique projections between primary (mortar) "
916 "and secondary (non-mortar) surfaces for a good problem set up. It can also mean your "
917 "time step is too large. This message is only printed once."));
922 std::unique_ptr<Elem> new_elem_left;
924 new_elem_left = std::make_unique<Edge3>();
926 new_elem_left = std::make_unique<Edge2>();
928 new_elem_left->processor_id() = current_mortar_segment->
processor_id();
929 new_elem_left->subdomain_id() = current_mortar_segment->
subdomain_id();
930 new_elem_left->set_id(local_id_index++);
931 new_elem_left->set_unique_id(new_elem_left->id());
932 new_elem_left->set_node(0, current_mortar_segment->
node_ptr(0));
933 new_elem_left->set_node(1, new_node);
936 std::unique_ptr<Elem> new_elem_right;
938 new_elem_right = std::make_unique<Edge3>();
940 new_elem_right = std::make_unique<Edge2>();
942 new_elem_right->processor_id() = current_mortar_segment->
processor_id();
943 new_elem_right->subdomain_id() = current_mortar_segment->
subdomain_id();
944 new_elem_right->set_id(local_id_index++);
945 new_elem_right->set_unique_id(new_elem_right->id());
946 new_elem_right->set_node(0, new_node);
947 new_elem_right->set_node(1, current_mortar_segment->
node_ptr(1));
952 Point left_interior_point(0);
953 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
956 Real current_left_interior_eta =
957 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
959 for (MooseIndex(current_mortar_segment->
n_nodes()) n = 0;
960 n < current_mortar_segment->
n_nodes();
963 current_mortar_segment->
point(n);
967 "new_id must be the same on all processes");
969 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
970 new_elem_left->set_node(2, new_interior_node_left);
971 new_interior_node_left->
set_unique_id(new_interior_left_id + node_unique_id_offset);
974 Point right_interior_point(0);
975 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
977 Real current_right_interior_eta =
978 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
980 for (MooseIndex(current_mortar_segment->
n_nodes()) n = 0;
981 n < current_mortar_segment->
n_nodes();
984 current_mortar_segment->
point(n);
988 "new_id must be the same on all processes");
990 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
991 new_elem_right->set_node(2, new_interior_node_right);
992 new_interior_node_right->
set_unique_id(new_interior_id_right + node_unique_id_offset);
996 if (orientation2_valid)
997 std::swap(left_primary_elem, right_primary_elem);
1001 if (left_primary_elem)
1002 left_xi2 = (primary_node == left_primary_elem->
node_ptr(0)) ? -1 : +1;
1003 if (right_primary_elem)
1004 right_xi2 = (primary_node == right_primary_elem->
node_ptr(0)) ? -1 : +1;
1013 mooseError(
"MortarSegmentInfo not found for current_mortar_segment.");
1026 new_msinfo_left.
xi1_a = current_msinfo.
xi1_a;
1027 new_msinfo_left.
xi2_a = current_msinfo.
xi2_a;
1029 new_msinfo_left.
xi1_b = xi1;
1030 new_msinfo_left.
xi2_b = left_xi2;
1038 mortar_segment_set.insert(msm_new_elem);
1048 new_msinfo_right.
xi1_b = current_msinfo.
xi1_b;
1049 new_msinfo_right.
xi2_b = current_msinfo.
xi2_b;
1051 new_msinfo_right.
xi1_a = xi1;
1052 new_msinfo_right.
xi2_a = right_xi2;
1057 mortar_segment_set.insert(msm_new_elem);
1065 mortar_segment_set.erase(current_mortar_segment);
1082 if (primary_elem ==
nullptr || abs(msinfo.
xi2_a) > 1.0 + TOLERANCE ||
1083 abs(msinfo.
xi2_b) > 1.0 + TOLERANCE)
1088 "We should have found the element");
1089 auto & msm_set = it->second;
1090 msm_set.erase(msm_elem);
1097 if (msm_set.empty())
1113 std::unordered_set<Node *> msm_connected_nodes;
1118 for (
auto & n : element->node_ref_range())
1119 msm_connected_nodes.insert(&n);
1122 if (!msm_connected_nodes.count(node))
1131 "All mortar segment elements should have valid "
1132 "primary element.");
1153 std::array<std::string, 3> file_pieces = {
1156 "mortar_segment_mesh.e"};
1157 mortar_segment_mesh_writer.
write(MooseUtils::join(file_pieces,
"_"));
1163 const bool use_reference_interpolation =
1178 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1181 for (
const auto *
const el :
1183 local_secondary_sub_elems += el->n_sub_elem();
1185 visible_primary_sub_elems += el->n_sub_elem();
1187 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1188 std::vector<dof_id_type> per_rank_bounds;
1192 start += per_rank_bounds[r];
1204 const auto primary_subd_id = pr.first;
1205 const auto secondary_subd_id = pr.second;
1210 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
1213 kd_tree.buildIndex();
1218 auto get_sub_elem_geometric_normal = [](
const std::vector<Point> & nodes)
1222 if (nodes.size() == 3)
1224 dxdxi = nodes[1] - nodes[0];
1225 dxdeta = nodes[2] - nodes[0];
1227 else if (nodes.size() == 4)
1231 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1232 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1235 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1236 "triangular and quadrilateral subpatches, but received ",
1240 Point geometric_normal = dxdxi.
cross(dxdeta);
1241 const auto normal_norm = geometric_normal.
norm();
1244 if (normal_norm <= TOLERANCE * dxdxi.
norm() * dxdeta.
norm())
1245 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1246 "degenerate subpatch.");
1248 geometric_normal /= normal_norm;
1249 return geometric_normal;
1256 end_el =
_mesh.active_local_elements_end();
1260 const Elem * secondary_side_elem = *el;
1262 const Real secondary_volume = secondary_side_elem->
volume();
1265 if (secondary_side_elem->
subdomain_id() != secondary_subd_id)
1268 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1270 std::set<Elem *, CompareDofObjectsByID>{});
1272 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1274 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1291 const auto sub_elem_nodes =
1297 std::vector<Point> nodes(sub_elem_nodes.size());
1300 for (
auto iv :
make_range(sub_elem_nodes.size()))
1302 const auto n = sub_elem_nodes[iv];
1303 nodes[iv] = secondary_side_elem->
point(n);
1305 normal += nodal_normals[n];
1307 center /= sub_elem_nodes.size();
1308 normal = normal.
unit();
1312 const Point averaged_normal = normal;
1313 normal = get_sub_elem_geometric_normal(nodes);
1314 if (normal * averaged_normal < 0)
1318 if (use_reference_interpolation)
1320 std::vector<Point> sub_elem_reference_points;
1321 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1322 for (
const auto node_index : sub_elem_nodes)
1323 sub_elem_reference_points.push_back(secondary_side_elem->
master_point(node_index));
1325 mortar_segment_helper[sel] =
1326 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1327 std::move(sub_elem_reference_points),
1334 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1346 std::array<Real, 3> query_pt;
1348 switch (secondary_side_elem->
type())
1352 center_point = mortar_segment_helper[0]->center();
1353 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1357 center_point = mortar_segment_helper[1]->center();
1358 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1361 center_point = mortar_segment_helper[4]->center();
1362 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1365 center_point = secondary_side_elem->
point(8);
1366 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1370 "Face element type: ", secondary_side_elem->
type(),
"not supported for 3D mortar");
1376 const std::size_t num_results = 3;
1379 std::vector<size_t> ret_index(num_results);
1380 std::vector<Real> out_dist_sqr(num_results);
1381 nanoflann::KNNResultSet<Real> result_set(num_results);
1382 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1386 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1390 bool primary_elem_found =
false;
1391 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1392 const bool use_geometric_subpatch_normals =
1396 const Real minimum_subpatch_normal_alignment =
1404 mooseAssert(abs((
_mesh.
point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1406 "Lower-dimensional element squared distance verification failed.");
1409 std::vector<const Elem *> & node_elems =
1413 for (
auto elem : node_elems)
1414 primary_elem_candidates.insert(elem);
1424 while (!primary_elem_candidates.empty())
1426 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1429 if (processed_primary_elems.count(primary_elem_candidate))
1431 primary_elem_candidates.erase(primary_elem_candidate);
1436 std::vector<Point> nodal_points;
1439 std::vector<std::vector<unsigned int>> elem_to_node_map;
1442 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1443 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1444 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1453 const auto sub_elem_nodes =
1457 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1458 for (
auto iv :
make_range(sub_elem_nodes.size()))
1460 const auto n = sub_elem_nodes[iv];
1461 primary_sub_elem[iv] = primary_elem_candidate->
point(n);
1463 Point primary_sub_elem_normal;
1464 if (use_geometric_subpatch_normals)
1465 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1467 std::vector<Point> sub_elem_reference_points;
1468 if (use_reference_interpolation)
1470 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1471 for (
const auto node_index : sub_elem_nodes)
1472 sub_elem_reference_points.push_back(primary_elem_candidate->
master_point(node_index));
1481 if (use_geometric_subpatch_normals &&
1482 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1483 minimum_subpatch_normal_alignment)
1493 const auto segments_before_helper = elem_to_node_map.size();
1494 if (use_reference_interpolation)
1495 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1496 sub_elem_reference_points,
1499 elem_to_secondary_reference_points,
1500 elem_to_primary_reference_points,
1501 TOLERANCE * secondary_volume);
1503 mortar_segment_helper[s_el]->getMortarSegments(
1504 primary_sub_elem, nodal_points, elem_to_node_map);
1507 for (
auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1508 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1513 processed_primary_elems.insert(primary_elem_candidate);
1514 primary_elem_candidates.erase(primary_elem_candidate);
1517 if (!elem_to_node_map.empty())
1519 if (sub_elem_map.size() != elem_to_node_map.size())
1520 mooseError(
"The mortar segment subpatch map is not aligned with the mortar segment "
1521 "connectivity map.");
1522 if (use_reference_interpolation &&
1523 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1524 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1525 mooseError(
"The mortar segment reference-point maps are not aligned with the mortar "
1526 "segment connectivity map.");
1530 bool seed_breadth_first_search =
false;
1531 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(),
false);
1532 for (
const auto el :
index_range(elem_to_node_map))
1534 const auto & node_map = elem_to_node_map[el];
1535 if (node_map.size() != 3)
1537 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1541 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1542 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1543 retained_mortar_segments[el] =
1544 0.5 * e1.
cross(e2).norm() / secondary_volume >= TOLERANCE;
1545 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1548 if (seed_breadth_first_search)
1552 if (!primary_elem_found)
1554 primary_elem_found =
true;
1555 primary_elem_candidates.clear();
1562 if (neighbor ==
nullptr || neighbor->subdomain_id() != primary_subd_id)
1565 if (processed_primary_elems.count(neighbor))
1568 primary_elem_candidates.insert(neighbor);
1575 std::vector<Node *> new_nodes;
1578 std::vector<bool> retained_nodes(nodal_points.size(),
false);
1579 for (
const auto el :
index_range(elem_to_node_map))
1580 if (retained_mortar_segments[el])
1581 for (
const auto node : elem_to_node_map[el])
1582 retained_nodes[node] =
true;
1584 new_nodes.resize(nodal_points.size(),
nullptr);
1586 if (retained_nodes[node])
1588 nodal_points[node], next_node_id++, secondary_side_elem->
processor_id());
1593 if (!retained_mortar_segments[el])
1596 std::unique_ptr<Elem> new_elem;
1597 if (elem_to_node_map[el].size() == 3)
1598 new_elem = std::make_unique<Tri3>();
1600 mooseError(
"Active mortar segments only supports TRI elements, 3 nodes expected "
1602 elem_to_node_map[el].size(),
1605 new_elem->processor_id() = secondary_side_elem->
processor_id();
1606 new_elem->subdomain_id() = secondary_side_elem->
subdomain_id();
1607 new_elem->set_id(next_elem_id++);
1611 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1614 if (new_elem->volume() / secondary_volume < TOLERANCE)
1632 if (use_reference_interpolation)
1635 elem_to_primary_reference_points[el]};
1640 secondary_to_msm_element_set.insert(msm_new_elem);
1651 if (use_geometric_subpatch_normals)
1655 if (secondary_to_msm_element_set.empty())
1657 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1658 " a corresponding primary element; this may be expected depending on"
1659 " problem geometry but may indicate a failure of the element search"
1664 if (mortar_segment_helper[sel]->remainder() == 1.0)
1666 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1667 " a corresponding primary element; this may be expected depending on"
1668 " problem geometry but may indicate a failure of the element search"
1671 if (secondary_to_msm_element_set.empty())
1676 mooseAssert(!use_reference_interpolation ||
1678 "Mortar segment info and reference-point maps must remain aligned.");
1692 if (msm_el->type() !=
TRI3)
1693 msm_el->subdomain_id()++;
1699 if (msm_el->type() !=
TRI3)
1700 msm_el->subdomain_id()--;
1715 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1726 const Elem * secondary_elem = pr.second.secondary_elem;
1727 const Elem * primary_elem = pr.second.primary_elem;
1730 coupling_info[secondary_elem->
processor_id()].emplace_back(
1739 coupling_info[secondary_elem->
processor_id()].emplace_back(
1748 coupling_info[secondary_elem->
processor_id()].emplace_back(secondary_elem->
id(),
1749 primary_elem->
id());
1773 auto action_functor =
1775 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1777 for (
auto [i, j] : coupling_info)
1783std::vector<AutomaticMortarGeneration::MsmSubdomainStats>
1786 std::vector<MsmSubdomainStats> result;
1790 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1794 for (
const auto *
const secondary_el :
1795 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1797 secondary.push_back(secondary_el->volume());
1802 for (
const auto *
const msm_elem : it->second)
1804 msm.push_back(msm_elem->volume());
1808 if (msm_info.primary_elem)
1810 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1811 mooseError(
"Unhandled primary-secondary pairing when computing mortar segment "
1812 "statistics. This could happen if you have the same secondary "
1813 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1814 "primary subdomain IDs. Contact a MOOSE developer for help.");
1815 if (
const auto [it, inserted] =
1816 primary_elems_to_volume.emplace(msm_info.primary_elem->id(),
Real{});
1818 it->second = msm_info.primary_elem->volume();
1821 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1822 "Volumes should be consistent");
1830 primary.reserve(primary_elems_to_volume.size());
1831 for (
const auto [_, volume] : primary_elems_to_volume)
1832 primary.push_back(volume);
1849 result.push_back(stats);
1854 primary_elems_to_volume.clear();
1868 Moose::out <<
"Mortar Interface Statistics:" << std::endl;
1869 for (
const auto & stats : all_stats)
1871 std::vector<std::string> col_names = {
"mesh",
"n_elems",
"max",
"min",
"median"};
1872 std::vector<std::string> subds = {
"secondary_lower",
"primary_lower",
"mortar_segment"};
1873 std::vector<size_t> n_elems = {
1874 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1875 std::vector<Real> maxs = {
1876 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1877 std::vector<Real> mins = {
1878 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1879 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1880 stats.primary_lower_median_volume,
1881 stats.msm_median_volume};
1888 table.
addData<std::string>(col_names[0], subds[i]);
1889 table.
addData<
size_t>(col_names[1], n_elems[i]);
1895 Moose::out <<
"secondary subdomain: " << stats.secondary_subd_id
1896 <<
" \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1913 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
1915 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1919 std::unordered_set<dof_id_type> inactive_node_ids;
1921 std::unordered_map<const Elem *, Real> active_volume{};
1925 active_volume[el] = 0.;
1933 active_volume[secondary_elem] += msm_elem->
volume();
1941 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1945 inactive_node_ids.insert(el->node_id(n));
1951 const auto secondary_subd_id = pr.second;
1957 const auto pid = el->processor_id();
1964 for (
const auto n :
make_range(el->n_nodes()))
1966 const auto node_id = el->node_id(n);
1967 if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1968 proc_to_inactive_nodes_set[pid].insert(node_id);
1976 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1977 for (
const auto & proc_set : proc_to_inactive_nodes_set)
1978 proc_to_inactive_nodes_vector[proc_set.first].insert(
1979 proc_to_inactive_nodes_vector[proc_set.first].end(),
1980 proc_set.second.begin(),
1981 proc_set.second.end());
1985 const std::vector<dof_id_type> & sent_data)
1988 mooseError(
"Should not be communicating with self.");
1989 for (
const auto pr : sent_data)
1990 inactive_node_ids.insert(pr);
1995 for (
const auto node_id : inactive_node_ids)
2008 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
2012 std::unordered_set<dof_id_type> active_local_nodes;
2021 active_local_nodes.insert(secondary_elem->
node_id(n));
2027 const auto secondary_subd_id = pr.second;
2040 for (
const auto n :
make_range(el->n_nodes()))
2042 const auto node_id = el->node_id(n);
2043 if (active_local_nodes.find(node_id) != active_local_nodes.end())
2044 proc_to_active_nodes_set[pid].insert(node_id);
2052 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
2053 for (
const auto & proc_set : proc_to_active_nodes_set)
2055 proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
2056 for (
const auto node_id : proc_set.second)
2057 proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
2062 const std::vector<dof_id_type> & sent_data)
2065 mooseError(
"Should not be communicating with self.");
2066 active_local_nodes.insert(sent_data.begin(), sent_data.end());
2077 for (
const auto n :
make_range(el->n_nodes()))
2078 if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
2087 std::unordered_set<const Elem *> active_local_elems;
2093 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
2095 std::unordered_map<const Elem *, Real> active_volume;
2104 active_volume[secondary_elem] += msm_elem->
volume();
2115 if (abs(active_volume[secondary_elem] / secondary_elem->
volume() - 1.0) > tol)
2119 active_local_elems.insert(secondary_elem);
2127 if (active_local_elems.find(el) == active_local_elems.end())
2137 mooseAssert(
dim == 2 ||
dim == 3,
2138 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2139 "mortar constraints on 2D or 3D meshes.");
2148 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2157 end_el =
_mesh.active_elements_end();
2161 const Elem * secondary_elem = *el;
2171 nnx_fe_face->attach_quadrature_rule(&qface);
2172 const auto & face_normals = nnx_fe_face->get_normals();
2173 const auto & face_points = nnx_fe_face->get_xyz();
2175 const auto & JxW = nnx_fe_face->get_JxW();
2180 mooseAssert(interior_parent,
2181 "No interior parent exists for element "
2182 << secondary_elem->
id()
2183 <<
". There may be a problem with your sideset set-up.");
2194 nnx_fe_face->reinit(interior_parent, s);
2199 const auto qpoint_to_secondary_node =
2200 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2202 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2203 "Face nodal geometry vectors must have the same size.");
2205 for (
const auto qp :
make_range(face_points.size()))
2207 const auto n = qpoint_to_secondary_node[qp];
2208 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->
node_id(n)];
2209 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2213 for (
const auto & pr : node_to_normals_map)
2216 const auto & node_id = pr.first;
2217 const auto & normals_and_weights_vec = pr.second;
2220 for (
const auto & norm_and_weight : normals_and_weights_vec)
2221 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2222 nodal_normal = nodal_normal.
unit();
2226 Point nodal_tangent_one;
2227 Point nodal_tangent_two;
2237 Point & nodal_tangent_one,
2238 Point & nodal_tangent_two)
const
2242 mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.
norm(), 1),
2243 "The input nodal normal should have unity norm");
2245 const Real nx = nodal_normal(0);
2246 const Real ny = nodal_normal(1);
2247 const Real nz = nodal_normal(2);
2252 const Point h_vector(nx + 1.0, ny, nz);
2257 if (abs(h_vector(0)) < TOLERANCE)
2259 nodal_tangent_one(0) = 0;
2260 nodal_tangent_one(1) = 1;
2261 nodal_tangent_one(2) = 0;
2263 nodal_tangent_two(0) = 0;
2264 nodal_tangent_two(1) = 0;
2265 nodal_tangent_two(2) = -1;
2272 nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
2273 nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
2274 nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2276 nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
2277 nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2278 nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
2294 const Node & secondary_node,
2295 const Node & primary_node,
2296 const std::vector<const Elem *> * secondary_node_neighbors,
2297 const std::vector<const Elem *> * primary_node_neighbors,
2299 const Elem & candidate_element,
2300 std::set<const Elem *> & rejected_elem_candidates)
2302 if (!secondary_node_neighbors)
2304 if (!primary_node_neighbors)
2307 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(),
false);
2335 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2337 for (
const auto nn :
index_range(*secondary_node_neighbors))
2339 const Elem *
const secondary_neigh = (*secondary_node_neighbors)[nn];
2340 const Point opposite = (secondary_neigh->
node_ptr(0) == &secondary_node)
2341 ? secondary_neigh->
point(1)
2342 : secondary_neigh->
point(0);
2343 const Point cp = nodal_normal.
cross(opposite - secondary_node);
2344 secondary_node_neighbor_cps[nn] = cp(2);
2347 for (
const auto nn :
index_range(*primary_node_neighbors))
2349 const Elem *
const primary_neigh = (*primary_node_neighbors)[nn];
2350 const Point opposite = (primary_neigh->
node_ptr(0) == &primary_node) ? primary_neigh->
point(1)
2351 : primary_neigh->
point(0);
2352 const Point cp = nodal_normal.
cross(opposite - primary_node);
2353 primary_node_neighbor_cps[nn] = cp(2);
2357 bool found_match =
false;
2358 for (
const auto snn :
index_range(*secondary_node_neighbors))
2359 for (
const auto mnn :
index_range(*primary_node_neighbors))
2360 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2363 if (primary_elems_mapped[mnn])
2365 primary_elems_mapped[mnn] =
true;
2369 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2370 const auto secondary_key =
2371 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2372 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2377 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2379 const auto primary_key =
2380 std::make_tuple(primary_node.
id(), &primary_node, (*primary_node_neighbors)[mnn]);
2381 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2389 rejected_elem_candidates.insert(&candidate_element);
2396 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2397 for (
const auto i :
index_range(primary_elems_mapped))
2398 if (!primary_elems_mapped[i])
2401 std::make_tuple(primary_node.
id(), &primary_node, (*primary_node_neighbors)[i]),
2402 std::make_pair(1,
nullptr));
2410 SubdomainID lower_dimensional_primary_subdomain_id,
2411 SubdomainID lower_dimensional_secondary_subdomain_id)
2418 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2421 kd_tree.buildIndex();
2424 end_el =
_mesh.active_elements_end();
2428 const Elem * secondary_side_elem = *el;
2431 if (secondary_side_elem->
subdomain_id() != lower_dimensional_secondary_subdomain_id)
2438 for (MooseIndex(secondary_side_elem->
n_vertices()) n = 0; n < secondary_side_elem->
n_vertices();
2441 const Node * secondary_node = secondary_side_elem->
node_ptr(n);
2445 const std::vector<const Elem *> & secondary_node_neighbors =
2450 bool is_mapped =
true;
2451 for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2454 auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2470 std::array<Real, 3> query_pt = {
2471 {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2477 const std::size_t num_results = 3;
2480 std::vector<size_t> ret_index(num_results);
2481 std::vector<Real> out_dist_sqr(num_results);
2482 nanoflann::KNNResultSet<Real> result_set(num_results);
2483 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2487 bool projection_succeeded =
false;
2491 std::set<const Elem *> rejected_primary_elem_candidates;
2497 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2500 mooseAssert(abs((
_mesh.
point(ret_index[r]) - *secondary_node).norm_sq() -
2501 out_dist_sqr[r]) <= TOLERANCE,
2502 "Lower-dimensional element squared distance verification failed.");
2506 std::vector<const Elem *> & primary_elem_candidates =
2510 for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2512 const Elem * primary_elem_candidate = primary_elem_candidates[e];
2515 if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2521 unsigned int current_iterate = 0, max_iterates = 10;
2527 for (MooseIndex(primary_elem_candidate->
n_nodes()) n = 0;
2528 n < primary_elem_candidate->
n_nodes();
2532 const auto u = x2 - (*secondary_node);
2533 const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2538 if (F.derivatives())
2540 Real dxi2 = -F.value() / F.derivatives();
2548 current_iterate = max_iterates;
2549 }
while (++current_iterate < max_iterates);
2551 Real xi2 = xi2_dn.value();
2564 if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 *
_xi_tolerance) &&
2565 (abs((primary_elem_candidate->
point(0) - primary_elem_candidate->
point(1)).unit() *
2575 const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->
node_ptr(0)
2576 : primary_elem_candidate->
node_ptr(1);
2577 const bool created_mortar_segment =
2580 &secondary_node_neighbors,
2583 *primary_elem_candidate,
2584 rejected_primary_elem_candidates);
2586 if (!created_mortar_segment)
2592 for (MooseIndex(secondary_node_neighbors) nn = 0;
2593 nn < secondary_node_neighbors.size();
2596 const Elem * neigh = secondary_node_neighbors[nn];
2600 if (secondary_node == neigh_node)
2602 auto key = std::make_pair(neigh_node, neigh);
2603 auto val = std::make_pair(xi2, primary_elem_candidate);
2610 projection_succeeded =
true;
2615 rejected_primary_elem_candidates.insert(primary_elem_candidate);
2618 if (projection_succeeded)
2622 if (!projection_succeeded)
2626 _console <<
"Failed to find primary Elem into which secondary node "
2627 <<
static_cast<const Point &
>(*secondary_node) <<
", id '"
2628 << secondary_node->
id() <<
"', projects onto\n"
2647 <<
" secondary nodes were successfully projected\n"
2664 SubdomainID lower_dimensional_primary_subdomain_id,
2665 SubdomainID lower_dimensional_secondary_subdomain_id)
2672 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2675 kd_tree.buildIndex();
2677 std::unordered_set<dof_id_type> primary_nodes_visited;
2679 for (
const auto & primary_side_elem :
_mesh.active_element_ptr_range())
2682 if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2687 for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2691 const Node * primary_node = primary_side_elem->node_ptr(n);
2694 const std::vector<const Elem *> & primary_node_neighbors =
2702 std::make_tuple(primary_node->
id(), primary_node, primary_node_neighbors[0]);
2703 if (!primary_nodes_visited.insert(primary_node->
id()).second ||
2708 Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2714 const size_t num_results = 3;
2717 std::vector<size_t> ret_index(num_results);
2718 std::vector<Real> out_dist_sqr(num_results);
2719 nanoflann::KNNResultSet<Real> result_set(num_results);
2720 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2724 bool projection_succeeded =
false;
2729 std::set<const Elem *> rejected_secondary_elem_candidates;
2733 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2736 mooseAssert(abs((
_mesh.
point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2738 "Lower-dimensional element squared distance verification failed.");
2742 const std::vector<const Elem *> & secondary_elem_candidates =
2746 for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2748 const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2751 if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2754 std::vector<Point> nodal_normals(secondary_elem_candidate->
n_nodes());
2764 unsigned int current_iterate = 0, max_iterates = 10;
2775 for (MooseIndex(secondary_elem_candidate->
n_nodes()) n = 0;
2776 n < secondary_elem_candidate->
n_nodes();
2780 x1 += phi * secondary_elem_candidate->
point(n);
2781 normals += phi * nodal_normals[n];
2784 const auto u = x1 - (*primary_node);
2786 const auto F = u(0) * normals(1) - u(1) * normals(0);
2794 Real dxi1 = -F.value() / F.derivatives();
2799 }
while (++current_iterate < max_iterates);
2801 Real xi1 = xi1_dn.value();
2805 if ((current_iterate < max_iterates) && (abs(xi1) <= 1. +
_xi_tolerance) &&
2806 (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2820 const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->
node_ref(0)
2821 : secondary_elem_candidate->
node_ref(1);
2822 bool created_mortar_segment =
false;
2829 &primary_node_neighbors,
2831 *secondary_elem_candidate,
2832 rejected_secondary_elem_candidates);
2834 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2836 if (!created_mortar_segment)
2852 const Elem * neigh = primary_node_neighbors[0];
2856 if (primary_node == neigh_node)
2858 auto key = std::make_tuple(neigh_node->
id(), neigh_node, neigh);
2859 auto val = std::make_pair(xi1, secondary_elem_candidate);
2865 projection_succeeded =
true;
2871 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2875 if (projection_succeeded)
2879 if (!projection_succeeded &&
_debug)
2881 _console <<
"\nFailed to find point from which primary node "
2882 <<
static_cast<const Point &
>(*primary_node) <<
" was projected." << std::endl
2889std::vector<AutomaticMortarGeneration::MortarFilterIter>
2896 const auto & secondary_elems = secondary_it->second;
2897 std::vector<MortarFilterIter> ret;
2898 ret.reserve(secondary_elems.size());
2902 auto *
const secondary_elem = secondary_elems[i];
2908 mooseAssert(secondary_elem->active(),
2909 "We loop over active elements when building the mortar segment mesh, so we golly "
2910 "well hope this is active.");
2911 mooseAssert(!msm_it->second.empty(),
2912 "We should have removed all secondaries from this map if they do not have any "
2913 "mortar segments associated with them.");
2914 ret.push_back(msm_it);
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
MortarSegmentTriangulationMode
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
This class is a container/interface for the objects involved in automatic generation of mortar spaces...
const Elem * getSecondaryLowerdElemFromSecondaryElem(dof_id_type secondary_elem_id) const
Return lower dimensional secondary element given its interior parent.
Real _newton_tolerance
Newton solve tolerance for node projections.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
const MortarSegmentReferencePoints & mortarSegmentReferencePoints(const Elem &mortar_segment_elem) const
Return the parent-face reference coordinates for a mortar segment.
std::unordered_map< const Elem *, unsigned int > _lower_elem_to_side_id
Keeps track of the mapping between lower-dimensional elements and the side_id of the interior_parent ...
std::unordered_map< const Elem *, MortarSegmentReferencePoints > _msm_elem_to_reference_points
Reference-coordinate data used only by the reference-interpolation mapping mode.
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_secondary_elem_map
Map from nodes to connected lower-dimensional elements on the secondary/primary subdomains.
std::set< BoundaryID > _secondary_requested_boundary_ids
The boundary ids corresponding to all the secondary surfaces.
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
Real _xi_tolerance
Tolerance for checking projection xi values.
void outputMortarMesh()
Write the mortar segment mesh to exodus.
void computeNodalGeometry()
Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the E...
const Mortar3DQuadraturePointMapping _mortar_3d_qp_mapping
Method used to map 3D mortar segment quadrature points to primary and secondary faces.
void msmStatistics()
Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally)
std::vector< std::pair< BoundaryID, BoundaryID > > _primary_secondary_boundary_id_pairs
A list of primary/secondary boundary id pairs corresponding to each side of the mortar interface.
std::unique_ptr< InputParameters > _output_params
Storage for the input parameters used by the mortar nodal geometry output.
void buildMortarSegmentMesh()
Builds the mortar segment mesh once the secondary and primary node projections have been completed.
std::unordered_set< const Node * > _inactive_local_lm_nodes
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
void projectSecondaryNodes()
Project secondary nodes (find xi^(2) values) to the closest points on the primary surface.
void buildMortarSegmentMesh3d()
Builds the mortar segment mesh once the secondary and primary node projections have been completed.
void computeInactiveLMElems()
Get list of secondary elems without any corresponding primary elements.
std::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
void buildCouplingInformation()
build the _mortar_interface_coupling data
const Mortar3DSubpatchPlane _mortar_3d_subpatch_plane
Method used to define the local projection planes for 3D secondary subpatches.
void householderOrthogolization(const Point &normal, Point &tangent_one, Point &tangent_two) const
Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors.
std::vector< Point > getNormals(const Elem &secondary_elem, const std::vector< Point > &xi1_pts) const
Compute the normals at given reference points on a secondary element.
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
void initOutput()
initialize mortar-mesh based output
std::unordered_map< const Node *, std::array< Point, 2 > > _secondary_node_to_hh_nodal_tangents
Container for storing the nodal tangent/binormal vectors associated with each secondary node (Househo...
bool processAlignedNodes(const Node &secondary_node, const Node &primary_node, const std::vector< const Elem * > *secondary_node_neighbors, const std::vector< const Elem * > *primary_node_neighbors, const VectorValue< Real > &nodal_normal, const Elem &candidate_element, std::set< const Elem * > &rejected_element_candidates)
Process aligned nodes.
void projectSecondaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function responsible for projecting secondary nodes onto primary elements for a single primary...
std::vector< MsmSubdomainStats > computeMsmStatistics()
Computes mortar segment mesh statistics and returns one entry per subdomain pair.
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
const bool _debug
Whether to print debug output.
std::vector< std::pair< SubdomainID, SubdomainID > > _primary_secondary_subdomain_id_pairs
A list of primary/secondary subdomain id pairs corresponding to each side of the mortar interface.
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
Compute on-the-fly mapping from secondary interior parent nodes to lower dimensional nodes.
void buildNodeToElemMaps()
Once the secondary_requested_boundary_ids and primary_requested_boundary_ids containers have been fil...
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
std::string mortarInterfaceName() const
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.
void clear()
Clears the mortar segment mesh and accompanying data structures.
MooseApp & _app
The Moose app.
std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > _mortar_interface_coupling
Used by the AugmentSparsityOnInterface functor to determine whether a given Elem is coupled to any ot...
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
Compute on-the-fly mapping from primary interior parent nodes to its corresponding lower dimensional ...
std::unordered_map< std::pair< const Node *, const Elem * >, std::pair< Real, const Elem * > > _secondary_node_and_elem_to_xi2_primary_elem
Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2),...
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
AutomaticMortarGeneration(MooseApp &app, MeshBase &mesh_in, const std::pair< BoundaryID, BoundaryID > &boundary_key, const std::pair< SubdomainID, SubdomainID > &subdomain_key, bool on_displaced, bool periodic, const bool debug, const bool correct_edge_dropping, const Real minimum_projection_angle, const Mortar3DSubpatchPlane mortar_3d_subpatch_plane, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles, const Mortar3DQuadraturePointMapping mortar_3d_qp_mapping=Mortar3DQuadraturePointMapping::NORMAL_PROJECTION)
Must be constructed with a reference to the Mesh we are generating mortar spaces for.
std::map< std::tuple< dof_id_type, const Node *, const Elem * >, std::pair< Real, const Elem * > > _primary_node_and_elem_to_xi1_secondary_elem
Same type of container, but for mapping (Primary Node ID, Primary Node, Primary Elem) -> (xi^(1),...
const bool _on_displaced
Whether this object is on the displaced mesh.
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...
const bool _distributed
Whether the mortar segment mesh is distributed.
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents(const Elem &secondary_elem) const
Compute the two nodal tangents, which are built on-the-fly.
std::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
std::unordered_map< dof_id_type, const Elem * > _secondary_element_to_secondary_lowerd_element
Map from full dimensional secondary element id to lower dimensional secondary element.
const bool _triangulate_triangles
Whether already-triangular clipped polygons should still be centroid-subdivided.
std::set< SubdomainID > _primary_boundary_subdomain_ids
void projectPrimaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped 3D mortar polygons.
std::set< BoundaryID > _primary_requested_boundary_ids
The boundary ids corresponding to all the primary surfaces.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > _secondary_elems_to_mortar_segments
We maintain a mapping from lower-dimensional secondary elements in the original mesh to (sets of) ele...
const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & secondariesToMortarSegments() const
void projectPrimaryNodes()
(Inverse) project primary nodes to the points on the secondary surface where they would have come fro...
void computeInactiveLMNodes()
Get list of secondary nodes that don't contribute to interaction with any primary element.
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments.
An inteface for the _console for outputting to the Console object.
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
Base class for MOOSE-based applications.
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
OutputWarehouse & getOutputWarehouse()
Get the OutputWarehouse objects.
FEProblemBase & feProblem() const
static const std::string name_param
The name of the parameter that contains the object name.
static const std::string type_param
The name of the parameter that contains the object type.
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
T getCheckedPointerParam(const std::string &name, const std::string &error_string="") const
Verifies that the requested parameter exists and is not NULL and returns it to the caller.
static const std::string app_param
The name of the parameter that contains the MooseApp.
Provides a way for users to bail out of the current solve.
MooseApp & _app
The MOOSE application this is associated with.
MortarNodalGeometryOutput(const InputParameters ¶ms)
unsigned int _t1z_var_num
unsigned int _t1x_var_num
unsigned int _t2z_var_num
unsigned int _nny_var_num
unsigned int _t2y_var_num
static InputParameters validParams()
AutomaticMortarGeneration & _amg
The mortar generation object that we will query for nodal normal and tangent information.
unsigned int _nnz_var_num
unsigned int _nnx_var_num
unsigned int _t2x_var_num
libMesh::System * _nodal_normals_system
Member variables for geometry debug output.
unsigned int _t1y_var_num
void output() override
Overload this function with the desired output activities.
Special adaptor that works with subdomains of the Mesh.
void addOutput(std::shared_ptr< Output > output)
Adds an existing output object to the warehouse.
Based class for output objects.
static InputParameters validParams()
Generic class for solving transient nonlinear problems.
virtual libMesh::EquationSystems & es()=0
void set_union(T &data, const unsigned int root_id) const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
processor_id_type processor_id() const
void set_unique_id(unique_id_type new_id)
void set_extra_integer(const unsigned int index, const dof_id_type value)
SimpleRange< NeighborPtrIter > neighbor_ptr_range()
virtual unsigned int n_vertices() const=0
const Point & point(const unsigned int i) const
const Node & node_ref(const unsigned int i) const
virtual unsigned int n_nodes() const=0
virtual Order default_order() const=0
subdomain_id_type subdomain_id() const
virtual unsigned int n_sub_elem() const=0
virtual Real hmax() const
const Node * node_ptr(const unsigned int i) const
unsigned int which_side_am_i(const Elem *e) const
virtual Real volume() const
unsigned int get_node_index(const Node *node_ptr) const
virtual ElemType type() const=0
virtual Point master_point(const unsigned int i) const=0
const Elem * interior_parent() const
dof_id_type node_id(const unsigned int i) const
virtual void write(const std::string &fname) override
virtual void write_equation_systems(const std::string &fname, const EquationSystems &es, const std::set< std::string > *system_names=nullptr) override
void set_hdf5_writing(bool write_hdf5)
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
virtual const Point & point(const dof_id_type i) const=0
virtual const Node * node_ptr(const dof_id_type i) const=0
unsigned int mesh_dimension() const
unsigned int spatial_dimension() const
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
unsigned int level ElemType type std::set< subdomain_id_type > ss processor_id_type pid unsigned int level std::set< subdomain_id_type > virtual ss SimpleRange< element_iterator > active_subdomain_elements_ptr_range(subdomain_id_type sid)=0
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
virtual T maximum() const
virtual T minimum() const
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
std::unique_ptr< NumericVector< Number > > solution
const DofMap & get_dof_map() const
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
TypeVector< Real > unit() const
std::vector< unsigned int > getMortarSubElementNodeIndices(const Elem &parent_elem, unsigned int sub_elem)
Return the node indices for a first-order sub-element of a parent face.
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
auto index_range(const T &sizable)
void libmesh_ignore(const Args &...)
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
uint8_t processor_id_type
IntRange< T > make_range(T beg, T end)
SearchParams SearchParameters
Statistics for one primary-secondary subdomain pair.
SubdomainID primary_subd_id
Real secondary_lower_median_volume
std::size_t primary_lower_n_elems
std::size_t secondary_lower_n_elems
Real secondary_lower_max_volume
SubdomainID secondary_subd_id
Real primary_lower_min_volume
Real primary_lower_max_volume
Real secondary_lower_min_volume
Real primary_lower_median_volume
Holds xi^(1), xi^(2), and other data for a given mortar segment.
const Elem * primary_elem
static const Real invalid_xi
const Elem * secondary_elem
Parent-face reference coordinates associated with the vertices of one triangular mortar segment.
const dof_id_type n_nodes