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 75 std::vector<unsigned int>
76 nodalQuadraturePointToSecondaryNodeMap(
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 ",
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;
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 ",
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;
257 if (_amg._secondary_node_to_nodal_normal.empty() ||
258 _amg._secondary_node_to_hh_nodal_tangents.empty())
259 mooseError(
"No entries found in the secondary node -> nodal geometry map.");
261 auto & problem = _app.feProblem();
262 auto & subproblem = _amg._on_displaced
263 ?
static_cast<SubProblem &
>(*problem.getDisplacedProblem())
264 : static_cast<SubProblem &>(problem);
265 auto & nodal_normals_es = subproblem.
es();
267 const std::string nodal_normals_sys_name =
"nodal_normals";
269 if (!_nodal_normals_system)
271 for (
const auto s :
make_range(nodal_normals_es.n_systems()))
272 if (!nodal_normals_es.get_system(s).is_initialized())
277 _nodal_normals_system =
278 &nodal_normals_es.template add_system<ExplicitSystem>(nodal_normals_sys_name);
279 _nnx_var_num = _nodal_normals_system->add_variable(
"nodal_normal_x",
FEType(
FIRST,
LAGRANGE)),
280 _nny_var_num = _nodal_normals_system->add_variable(
"nodal_normal_y",
FEType(
FIRST,
LAGRANGE));
281 _nnz_var_num = _nodal_normals_system->add_variable(
"nodal_normal_z",
FEType(
FIRST,
LAGRANGE));
296 nodal_normals_es.
reinit();
299 const DofMap & dof_map = _nodal_normals_system->get_dof_map();
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;
304 for (MeshBase::const_element_iterator el = _amg._mesh.elements_begin(),
305 end_el = _amg._mesh.elements_end();
309 const Elem * elem = *el;
312 dof_map.
dof_indices(elem, dof_indices_nnx, _nnx_var_num);
313 dof_map.
dof_indices(elem, dof_indices_nny, _nny_var_num);
314 dof_map.
dof_indices(elem, dof_indices_nnz, _nnz_var_num);
316 dof_map.
dof_indices(elem, dof_indices_t1x, _t1x_var_num);
317 dof_map.
dof_indices(elem, dof_indices_t1y, _t1y_var_num);
318 dof_map.
dof_indices(elem, dof_indices_t1z, _t1z_var_num);
320 dof_map.
dof_indices(elem, dof_indices_t2x, _t2x_var_num);
321 dof_map.
dof_indices(elem, dof_indices_t2y, _t2y_var_num);
322 dof_map.
dof_indices(elem, dof_indices_t2z, _t2z_var_num);
330 auto it = _amg._secondary_node_to_nodal_normal.find(elem->
node_ptr(n));
331 if (it != _amg._secondary_node_to_nodal_normal.end())
333 _nodal_normals_system->solution->set(dof_indices_nnx[n], it->second(0));
334 _nodal_normals_system->solution->set(dof_indices_nny[n], it->second(1));
335 _nodal_normals_system->solution->set(dof_indices_nnz[n], it->second(2));
338 auto it_tangent = _amg._secondary_node_to_hh_nodal_tangents.find(elem->
node_ptr(n));
339 if (it_tangent != _amg._secondary_node_to_hh_nodal_tangents.end())
341 _nodal_normals_system->solution->set(dof_indices_t1x[n], it_tangent->second[0](0));
342 _nodal_normals_system->solution->set(dof_indices_t1y[n], it_tangent->second[0](1));
343 _nodal_normals_system->solution->set(dof_indices_t1z[n], it_tangent->second[0](2));
345 _nodal_normals_system->solution->set(dof_indices_t2x[n], it_tangent->second[1](0));
346 _nodal_normals_system->solution->set(dof_indices_t2y[n], it_tangent->second[1](1));
347 _nodal_normals_system->solution->set(dof_indices_t2z[n], it_tangent->second[1](2));
354 _nodal_normals_system->solution->close();
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");
562 std::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;
578 std::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;
595 std::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;
684 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
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 =
828 new_node->set_unique_id(new_id + node_unique_id_offset);
833 const Point normal =
getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
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;
1255 for (MeshBase::const_element_iterator el =
_mesh.active_local_elements_begin(),
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 =
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)
1783 std::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;
2156 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
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();
2423 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
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)
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();
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)
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
2889 std::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);
virtual T maximum() const
std::set< SubdomainID > _primary_boundary_subdomain_ids
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
const Elem * getSecondaryLowerdElemFromSecondaryElem(dof_id_type secondary_elem_id) const
Return lower dimensional secondary element given its interior parent.
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
static const std::string name_param
The name of the parameter that contains the object name.
static const std::string app_param
The name of the parameter that contains the MooseApp.
SubdomainID secondary_subd_id
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sin(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tan
SubdomainID primary_subd_id
unsigned int get_node_index(const Node *node_ptr) const
void computeInactiveLMNodes()
Get list of secondary nodes that don't contribute to interaction with any primary element...
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...
const Elem * interior_parent() const
static const std::string type_param
The name of the parameter that contains the object type.
Real secondary_lower_min_volume
unsigned int _t2x_var_num
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.
void clear()
Clears the mortar segment mesh and accompanying data structures.
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
std::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
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...
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 ...
unsigned int _t2z_var_num
static constexpr Real TOLERANCE
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
void swap(std::vector< T > &data, const std::size_t idx0, const std::size_t idx1, const libMesh::Parallel::Communicator &comm)
Swap function for serial or distributed vector of data.
unsigned int which_side_am_i(const Elem *e) const
void outputMortarMesh()
Write the mortar segment mesh to exodus.
Real _newton_tolerance
Newton solve tolerance for node projections.
Real secondary_lower_max_volume
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
void output() override
Overload this function with the desired output activities.
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
MortarNodalGeometryOutput(const InputParameters ¶ms)
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const bool _distributed
Whether the mortar segment mesh is distributed.
Base class for MOOSE-based applications.
void buildMortarSegmentMesh()
Builds the mortar segment mesh once the secondary and primary node projections have been completed...
const Parallel::Communicator & comm() const
void msmStatistics()
Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally) ...
unsigned int _nnx_var_num
void buildCouplingInformation()
build the _mortar_interface_coupling data
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
Special adaptor that works with subdomains of the Mesh.
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
static const Real invalid_xi
virtual Real hmax() const
unsigned int _nny_var_num
std::string mortarInterfaceName() const
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
FEProblemBase & feProblem() const
unsigned int _nnz_var_num
void projectSecondaryNodes()
Project secondary nodes (find xi^(2) values) to the closest points on the primary surface...
auto max(const L &left, const R &right)
const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & secondariesToMortarSegments() const
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
This class is a container/interface for the objects involved in automatic generation of mortar spaces...
const bool _debug
Whether to print debug output.
MortarSegmentTriangulationMode
Statistics for one primary-secondary subdomain pair.
Based class for output objects.
const Elem * primary_elem
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
uint8_t processor_id_type
const bool _triangulate_triangles
Whether already-triangular clipped polygons should still be centroid-subdivided.
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
virtual libMesh::EquationSystems & es()=0
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.
TypeVector< Real > unit() const
void libmesh_ignore(const Args &...)
const dof_id_type n_nodes
Real secondary_lower_median_volume
void projectPrimaryNodes()
(Inverse) project primary nodes to the points on the secondary surface where they would have come fro...
unsigned int _t1z_var_num
std::size_t primary_lower_n_elems
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template cos(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(cos
const Node & node_ref(const unsigned int i) const
virtual unsigned int n_nodes() const=0
unsigned int _t2y_var_num
void buildNodeToElemMaps()
Once the secondary_requested_boundary_ids and primary_requested_boundary_ids containers have been fil...
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.
virtual void write_equation_systems(const std::string &fname, const EquationSystems &es, const std::set< std::string > *system_names=nullptr) override
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)...
void projectPrimaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
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.
An inteface for the _console for outputting to the Console object.
const MortarSegmentReferencePoints & mortarSegmentReferencePoints(const Elem &mortar_segment_elem) const
Return the parent-face reference coordinates for a mortar segment.
unsigned int _t1y_var_num
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::unique_ptr< InputParameters > _output_params
Storage for the input parameters used by the mortar nodal geometry output.
std::set< BoundaryID > _primary_requested_boundary_ids
The boundary ids corresponding to all the primary surfaces.
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...
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
std::unordered_map< const Elem *, MortarSegmentReferencePoints > _msm_elem_to_reference_points
Reference-coordinate data used only by the reference-interpolation mapping mode.
void computeNodalGeometry()
Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the E...
Real _xi_tolerance
Tolerance for checking projection xi values.
static InputParameters validParams()
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
void buildMortarSegmentMesh3d()
Builds the mortar segment mesh once the secondary and primary node projections have been completed...
const Elem * secondary_elem
Real primary_lower_max_volume
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...
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped 3D mortar polygons.
Provides a way for users to bail out of the current solve.
void set_unique_id(unique_id_type new_id)
virtual unsigned int n_vertices() const=0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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)...
Holds xi^(1), xi^(2), and other data for a given mortar segment.
Real primary_lower_min_volume
Generic class for solving transient nonlinear problems.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
subdomain_id_type subdomain_id() const
Parent-face reference coordinates associated with the vertices of one triangular mortar segment...
const bool _on_displaced
Whether this object is on the displaced mesh.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
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.
const Node * node_ptr(const unsigned int i) const
unsigned int spatial_dimension() const
virtual void write(const std::string &fname) override
std::vector< MsmSubdomainStats > computeMsmStatistics()
Computes mortar segment mesh statistics and returns one entry per subdomain pair. ...
const Mortar3DSubpatchPlane _mortar_3d_subpatch_plane
Method used to define the local projection planes for 3D secondary subpatches.
void initOutput()
initialize mortar-mesh based output
virtual Point master_point(const unsigned int i) const=0
void addOutput(std::shared_ptr< Output > output)
Adds an existing output object to the warehouse.
virtual Real volume() const
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...
IntRange< T > make_range(T beg, T end)
KOKKOS_INLINE_FUNCTION T sign(T x)
Returns the sign of a value.
unsigned int _t1x_var_num
Real primary_lower_median_volume
unsigned int mesh_dimension() const
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
virtual T minimum() const
std::size_t secondary_lower_n_elems
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments...
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
virtual const Point & point(const dof_id_type i) const=0
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...
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
SimpleRange< NeighborPtrIter > neighbor_ptr_range()
virtual unsigned int n_sub_elem() const=0
std::set< BoundaryID > _secondary_requested_boundary_ids
The boundary ids corresponding to all the secondary surfaces.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
virtual const Node * node_ptr(const dof_id_type i) const=0
std::unordered_set< const Node * > _inactive_local_lm_nodes
processor_id_type processor_id() const
virtual Order default_order() const=0
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::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
SearchParams SearchParameters
AutomaticMortarGeneration & _amg
The mortar generation object that we will query for nodal normal and tangent information.
void set_hdf5_writing(bool write_hdf5)
processor_id_type processor_id() const
void householderOrthogolization(const Point &normal, Point &tangent_one, Point &tangent_two) const
Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors...
virtual ElemType type() const=0
dof_id_type node_id(const unsigned int i) const
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.
static InputParameters validParams()
const Mortar3DQuadraturePointMapping _mortar_3d_qp_mapping
Method used to map 3D mortar segment quadrature points to primary and secondary faces.
MooseApp & _app
The Moose app.
const Point & point(const unsigned int i) const
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
auto index_range(const T &sizable)
void set_extra_integer(const unsigned int index, const dof_id_type value)
OutputWarehouse & getOutputWarehouse()
Get the OutputWarehouse objects.
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::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 ...
void computeInactiveLMElems()
Get list of secondary elems without any corresponding primary elements.
void set_union(T &data, const unsigned int root_id) const