24#include "libmesh/mesh_tools.h"
25#include "libmesh/explicit_system.h"
26#include "libmesh/numeric_vector.h"
27#include "libmesh/elem.h"
28#include "libmesh/node.h"
29#include "libmesh/dof_map.h"
30#include "libmesh/edge_edge2.h"
31#include "libmesh/edge_edge3.h"
32#include "libmesh/face_tri3.h"
33#include "libmesh/face_tri6.h"
34#include "libmesh/face_tri7.h"
35#include "libmesh/face_quad4.h"
36#include "libmesh/face_quad8.h"
37#include "libmesh/face_quad9.h"
38#include "libmesh/exodusII_io.h"
39#include "libmesh/quadrature_gauss.h"
40#include "libmesh/quadrature_nodal.h"
41#include "libmesh/distributed_mesh.h"
42#include "libmesh/replicated_mesh.h"
43#include "libmesh/enum_to_string.h"
44#include "libmesh/statistics.h"
45#include "libmesh/equation_systems.h"
46#include "libmesh/fe_map.h"
48#include "metaphysicl/dualnumber.h"
62#if NANOFLANN_VERSION < 0x150
76std::vector<unsigned int>
77nodalQuadraturePointToSecondaryNodeMap(
const Elem & secondary_elem,
78 const std::vector<Point> & q_points)
80 const auto n_nodes = secondary_elem.n_nodes();
84 " points for secondary mortar element ",
87 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
88 ", but the element has ",
92 const auto invalid_node = std::numeric_limits<unsigned int>::max();
93 std::vector<unsigned int> qpoint_to_node(
n_nodes, invalid_node);
94 std::vector<bool> node_used(
n_nodes,
false);
96 const Real element_size = secondary_elem.hmax();
97 mooseAssert(element_size > 0,
98 "Secondary mortar element "
99 << secondary_elem.id() <<
" of type "
100 << libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type())
101 <<
" has a non-positive hmax and cannot be used for nodal quadrature point "
108 const Real matching_tol = 100 * TOLERANCE * element_size;
109 const Real matching_tol_sq = matching_tol * matching_tol;
114 for (
const auto qp : make_range(q_points.size()))
116 unsigned int closest_node = invalid_node;
117 Real closest_dist_sq = std::numeric_limits<Real>::max();
118 Real second_closest_dist_sq = std::numeric_limits<Real>::max();
120 for (
const auto n : make_range(
n_nodes))
125 const Real dist_sq = (q_points[qp] - secondary_elem.point(n)).norm_sq();
126 if (dist_sq < closest_dist_sq)
128 second_closest_dist_sq = closest_dist_sq;
129 closest_dist_sq = dist_sq;
132 else if (dist_sq < second_closest_dist_sq)
133 second_closest_dist_sq = dist_sq;
136 if (closest_node == invalid_node || closest_dist_sq > matching_tol_sq)
137 mooseError(
"Could not match nodal quadrature point ",
141 " to a node on secondary mortar element ",
144 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
145 ". The nearest unmatched node distance is ",
146 std::sqrt(closest_dist_sq),
147 ", which exceeds the tolerance ",
151 if (second_closest_dist_sq <= matching_tol_sq)
156 " does not map uniquely to secondary mortar element ",
159 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
160 ". Two unmatched nodes are within the matching tolerance ",
164 qpoint_to_node[qp] = closest_node;
165 node_used[closest_node] =
true;
171 std::vector<unsigned int> node_to_qpoint(
n_nodes, invalid_node);
172 for (
const auto qp : make_range(q_points.size()))
174 const auto mapped_node = qpoint_to_node[qp];
175 mooseAssert(mapped_node != invalid_node && mapped_node <
n_nodes,
176 "Invalid secondary node mapping for nodal quadrature point " << qp <<
".");
177 mooseAssert(node_to_qpoint[mapped_node] == invalid_node,
178 "Secondary node " << mapped_node <<
" on mortar element " << secondary_elem.id()
179 <<
" was matched to both nodal quadrature point "
180 << node_to_qpoint[mapped_node] <<
" and " << qp <<
".");
181 node_to_qpoint[mapped_node] = qp;
184 unsigned int candidate_count = 0;
185 unsigned int candidate_node = invalid_node;
186 for (
const auto n : make_range(
n_nodes))
187 if ((q_points[qp] - secondary_elem.point(n)).norm_sq() <= matching_tol_sq)
193 mooseAssert(candidate_count == 1,
194 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.id()
195 <<
" has " << candidate_count
196 <<
" secondary node candidates within tolerance "
197 << matching_tol <<
".");
198 mooseAssert(candidate_node == mapped_node,
199 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.id()
200 <<
" was matched to node " << mapped_node
201 <<
", but the full candidate search found node "
202 << candidate_node <<
".");
205 for (
const auto n : make_range(
n_nodes))
207 mooseAssert(node_to_qpoint[n] != invalid_node,
208 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
209 <<
" was not matched to a nodal quadrature point.");
212 unsigned int candidate_count = 0;
213 unsigned int candidate_qp = invalid_node;
214 for (
const auto qp : make_range(q_points.size()))
215 if ((q_points[qp] - secondary_elem.point(n)).norm_sq() <= matching_tol_sq)
221 mooseAssert(candidate_count == 1,
222 "Secondary node " << n <<
" on mortar element " << secondary_elem.id() <<
" has "
224 <<
" nodal quadrature point candidates within tolerance "
225 << matching_tol <<
".");
226 mooseAssert(candidate_qp == node_to_qpoint[n],
227 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
228 <<
" was matched to nodal quadrature point " << node_to_qpoint[n]
229 <<
", but the full candidate search found point " << candidate_qp
234 return qpoint_to_node;
260 mooseError(
"No entries found in the secondary node -> nodal geometry map.");
263 auto & subproblem =
_amg.
_on_displaced ? cast_ref<SubProblem &>(*problem.getDisplacedProblem())
264 : cast_ref<SubProblem &>(problem);
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;
304 for (MeshBase::const_element_iterator el =
_amg.
_mesh.elements_begin(),
309 const Elem * elem = *el;
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);
328 for (MooseIndex(elem->n_vertices()) n = 0; n < elem->n_vertices(); ++n)
356 std::set<std::string> sys_names = {nodal_normals_sys_name};
359 ExodusII_IO nodal_normals_writer(
_amg.
_mesh);
362 nodal_normals_writer.set_hdf5_writing(
false);
364 nodal_normals_writer.write_equation_systems(
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)
429 std::make_unique<DistributedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
432 std::make_unique<ReplicatedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
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 :
513 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
519 for (
const auto & nd : secondary_elem->node_ref_range())
522 vec.push_back(secondary_elem);
527 for (
const auto & primary_elem :
528 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
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());
546 for (
const auto n : make_range(secondary_elem.n_nodes()))
549 return nodal_normals;
554 dof_id_type secondary_elem_id)
const
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;
566 const Elem *
const secondary_ip = lower_secondary_elem.interior_parent();
567 mooseAssert(secondary_ip,
"This should be non-null");
569 for (
const auto i : make_range(lower_secondary_elem.n_nodes()))
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;
586 for (
const auto i : make_range(lower_primary_elem.n_nodes()))
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);
602 for (
const auto n : make_range(secondary_elem.n_nodes()))
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());
618 for (
const auto qp : index_range(oned_xi1_pts))
619 xi1_pts[qp] = oned_xi1_pts[qp];
626 const std::vector<Point> & xi1_pts)
const
628 const auto mortar_dim =
_mesh.mesh_dimension() - 1;
629 const auto num_qps = xi1_pts.size();
631 std::vector<Point> normals(num_qps);
633 for (
const auto n : make_range(secondary_elem.n_nodes()))
634 for (
const auto qp : make_range(num_qps))
640 secondary_elem.default_order(),
642 cast_ref<
const TypeVector<Real> &>(xi1_pts[qp]));
643 normals[qp] += phi * nodal_normals[n];
647 for (
auto & normal : normals)
658 dof_id_type local_id_index = 0;
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)
699 secondary_elem->point(n), secondary_elem->node_id(n), secondary_elem->processor_id()));
700 Node *
const new_node = new_nodes.back();
701 new_node->set_unique_id(new_node->id() + node_unique_id_offset);
704 std::unique_ptr<Elem> new_elem;
705 if (secondary_elem->default_order() == SECOND)
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;
782 auto && order = secondary_elem->default_order();
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];
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);
872 Point cp = normal.cross(opposite - new_pt);
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)
911 static const auto invalid_orientation_id =
913 "AutomaticMortarGeneration",
914 "Unable to determine valid secondary-primary orientation. Consequently we will "
915 "consider projection of the primary node invalid and not split the mortar segment. "
916 "This situation can indicate there are very oblique projections between primary "
917 "(mortar) and secondary (non-mortar) surfaces for a good problem set up. It can also "
918 "mean your time step is too large.",
925 std::unique_ptr<Elem> new_elem_left;
927 new_elem_left = std::make_unique<Edge3>();
929 new_elem_left = std::make_unique<Edge2>();
931 new_elem_left->processor_id() = current_mortar_segment->processor_id();
932 new_elem_left->subdomain_id() = current_mortar_segment->subdomain_id();
933 new_elem_left->set_id(local_id_index++);
934 new_elem_left->set_unique_id(new_elem_left->id());
935 new_elem_left->set_node(0, current_mortar_segment->node_ptr(0));
936 new_elem_left->set_node(1, new_node);
939 std::unique_ptr<Elem> new_elem_right;
941 new_elem_right = std::make_unique<Edge3>();
943 new_elem_right = std::make_unique<Edge2>();
945 new_elem_right->processor_id() = current_mortar_segment->processor_id();
946 new_elem_right->subdomain_id() = current_mortar_segment->subdomain_id();
947 new_elem_right->set_id(local_id_index++);
948 new_elem_right->set_unique_id(new_elem_right->id());
949 new_elem_right->set_node(0, new_node);
950 new_elem_right->set_node(1, current_mortar_segment->node_ptr(1));
955 Point left_interior_point(0);
956 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
959 Real current_left_interior_eta =
960 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
962 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
963 n < current_mortar_segment->n_nodes();
966 current_mortar_segment->point(n);
970 "new_id must be the same on all processes");
972 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
973 new_elem_left->set_node(2, new_interior_node_left);
974 new_interior_node_left->set_unique_id(new_interior_left_id + node_unique_id_offset);
977 Point right_interior_point(0);
978 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
980 Real current_right_interior_eta =
981 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
983 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
984 n < current_mortar_segment->n_nodes();
987 current_mortar_segment->point(n);
991 "new_id must be the same on all processes");
993 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
994 new_elem_right->set_node(2, new_interior_node_right);
995 new_interior_node_right->set_unique_id(new_interior_id_right + node_unique_id_offset);
999 if (orientation2_valid)
1000 std::swap(left_primary_elem, right_primary_elem);
1004 if (left_primary_elem)
1005 left_xi2 = (primary_node == left_primary_elem->node_ptr(0)) ? -1 : +1;
1006 if (right_primary_elem)
1007 right_xi2 = (primary_node == right_primary_elem->node_ptr(0)) ? -1 : +1;
1016 mooseError(
"MortarSegmentInfo not found for current_mortar_segment.");
1029 new_msinfo_left.
xi1_a = current_msinfo.
xi1_a;
1030 new_msinfo_left.
xi2_a = current_msinfo.
xi2_a;
1032 new_msinfo_left.
xi1_b = xi1;
1033 new_msinfo_left.
xi2_b = left_xi2;
1041 mortar_segment_set.insert(msm_new_elem);
1051 new_msinfo_right.
xi1_b = current_msinfo.
xi1_b;
1052 new_msinfo_right.
xi2_b = current_msinfo.
xi2_b;
1054 new_msinfo_right.
xi1_a = xi1;
1055 new_msinfo_right.
xi2_a = right_xi2;
1060 mortar_segment_set.insert(msm_new_elem);
1068 mortar_segment_set.erase(current_mortar_segment);
1084 Elem * primary_elem =
const_cast<Elem *
>(msinfo.
primary_elem);
1085 if (primary_elem ==
nullptr || abs(msinfo.
xi2_a) > 1.0 + TOLERANCE ||
1086 abs(msinfo.
xi2_b) > 1.0 + TOLERANCE)
1091 "We should have found the element");
1092 auto & msm_set = it->second;
1093 msm_set.erase(msm_elem);
1100 if (msm_set.empty())
1116 std::unordered_set<Node *> msm_connected_nodes;
1121 for (
auto & n : element->node_ref_range())
1122 msm_connected_nodes.insert(&n);
1125 if (!msm_connected_nodes.count(node))
1134 "All mortar segment elements should have valid "
1135 "primary element.");
1154 mortar_segment_mesh_writer.set_hdf5_writing(
false);
1156 std::array<std::string, 3> file_pieces = {
1159 "mortar_segment_mesh.e"};
1160 mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces,
"_"));
1166 const bool use_reference_interpolation =
1181 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1184 for (
const auto *
const el :
1185 _mesh.active_local_subdomain_elements_ptr_range(secondary_sub_id))
1186 local_secondary_sub_elems += el->n_sub_elem();
1187 for (
const auto *
const el :
_mesh.active_subdomain_elements_ptr_range(primary_sub_id))
1188 visible_primary_sub_elems += el->n_sub_elem();
1190 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1191 std::vector<dof_id_type> per_rank_bounds;
1192 _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
1193 dof_id_type start = 0;
1194 for (
const auto r : make_range(
_mesh.processor_id()))
1195 start += per_rank_bounds[r];
1202 dof_id_type next_elem_id = next_node_id;
1207 const auto primary_subd_id = pr.first;
1208 const auto secondary_subd_id = pr.second;
1213 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
1216 kd_tree.buildIndex();
1221 auto get_sub_elem_geometric_normal = [](
const std::vector<Point> & nodes)
1225 if (nodes.size() == 3)
1227 dxdxi = nodes[1] - nodes[0];
1228 dxdeta = nodes[2] - nodes[0];
1230 else if (nodes.size() == 4)
1234 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1235 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1238 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1239 "triangular and quadrilateral subpatches, but received ",
1243 Point geometric_normal = dxdxi.cross(dxdeta);
1244 const auto normal_norm = geometric_normal.norm();
1247 if (normal_norm <= TOLERANCE * dxdxi.norm() * dxdeta.norm())
1248 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1249 "degenerate subpatch.");
1251 geometric_normal /= normal_norm;
1252 return geometric_normal;
1258 for (MeshBase::const_element_iterator el =
_mesh.active_local_elements_begin(),
1259 end_el =
_mesh.active_local_elements_end();
1263 const Elem * secondary_side_elem = *el;
1265 const Real secondary_volume = secondary_side_elem->volume();
1268 if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1271 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1273 std::set<Elem *, CompareDofObjectsByID>{});
1274 libmesh_ignore(insertion_happened);
1275 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1277 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1278 secondary_side_elem->n_sub_elem());
1291 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1294 const auto sub_elem_nodes =
1300 std::vector<Point> nodes(sub_elem_nodes.size());
1303 for (
auto iv : make_range(sub_elem_nodes.size()))
1305 const auto n = sub_elem_nodes[iv];
1306 nodes[iv] = secondary_side_elem->point(n);
1307 center += secondary_side_elem->point(n);
1308 normal += nodal_normals[n];
1310 center /= sub_elem_nodes.size();
1311 normal = normal.unit();
1315 const Point averaged_normal = normal;
1316 normal = get_sub_elem_geometric_normal(nodes);
1317 if (normal * averaged_normal < 0)
1321 if (use_reference_interpolation)
1323 std::vector<Point> sub_elem_reference_points;
1324 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1325 for (
const auto node_index : sub_elem_nodes)
1326 sub_elem_reference_points.push_back(secondary_side_elem->master_point(node_index));
1328 mortar_segment_helper[sel] =
1329 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1330 std::move(sub_elem_reference_points),
1337 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1349 std::array<Real, 3> query_pt;
1351 switch (secondary_side_elem->type())
1355 center_point = mortar_segment_helper[0]->center();
1356 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1360 center_point = mortar_segment_helper[1]->center();
1361 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1364 center_point = mortar_segment_helper[4]->center();
1365 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1368 center_point = secondary_side_elem->point(8);
1369 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1373 "Face element type: ", secondary_side_elem->type(),
"not supported for 3D mortar");
1379 const std::size_t num_results = 3;
1382 std::vector<size_t> ret_index(num_results);
1383 std::vector<Real> out_dist_sqr(num_results);
1384 nanoflann::KNNResultSet<Real> result_set(num_results);
1385 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1389 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1393 bool primary_elem_found =
false;
1394 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1395 const bool use_geometric_subpatch_normals =
1399 const Real minimum_subpatch_normal_alignment =
1404 for (
auto r : make_range(result_set.size()))
1407 mooseAssert(abs((
_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1409 "Lower-dimensional element squared distance verification failed.");
1412 std::vector<const Elem *> & node_elems =
1416 for (
auto elem : node_elems)
1417 primary_elem_candidates.insert(elem);
1427 while (!primary_elem_candidates.empty())
1429 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1432 if (processed_primary_elems.count(primary_elem_candidate))
1434 primary_elem_candidates.erase(primary_elem_candidate);
1439 std::vector<Point> nodal_points;
1442 std::vector<std::vector<unsigned int>> elem_to_node_map;
1445 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1446 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1447 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1453 for (
auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1456 const auto sub_elem_nodes =
1460 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1461 for (
auto iv : make_range(sub_elem_nodes.size()))
1463 const auto n = sub_elem_nodes[iv];
1464 primary_sub_elem[iv] = primary_elem_candidate->point(n);
1466 Point primary_sub_elem_normal;
1467 if (use_geometric_subpatch_normals)
1468 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1470 std::vector<Point> sub_elem_reference_points;
1471 if (use_reference_interpolation)
1473 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1474 for (
const auto node_index : sub_elem_nodes)
1475 sub_elem_reference_points.push_back(primary_elem_candidate->master_point(node_index));
1479 for (
auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1484 if (use_geometric_subpatch_normals &&
1485 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1486 minimum_subpatch_normal_alignment)
1496 const auto segments_before_helper = elem_to_node_map.size();
1497 if (use_reference_interpolation)
1498 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1499 sub_elem_reference_points,
1502 elem_to_secondary_reference_points,
1503 elem_to_primary_reference_points,
1504 TOLERANCE * secondary_volume);
1506 mortar_segment_helper[s_el]->getMortarSegments(
1507 primary_sub_elem, nodal_points, elem_to_node_map);
1510 for (
auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1511 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1516 processed_primary_elems.insert(primary_elem_candidate);
1517 primary_elem_candidates.erase(primary_elem_candidate);
1520 if (!elem_to_node_map.empty())
1522 if (sub_elem_map.size() != elem_to_node_map.size())
1523 mooseError(
"The mortar segment subpatch map is not aligned with the mortar segment "
1524 "connectivity map.");
1525 if (use_reference_interpolation &&
1526 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1527 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1528 mooseError(
"The mortar segment reference-point maps are not aligned with the mortar "
1529 "segment connectivity map.");
1533 bool seed_breadth_first_search =
false;
1534 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(),
false);
1535 for (
const auto el : index_range(elem_to_node_map))
1537 const auto & node_map = elem_to_node_map[el];
1538 if (node_map.size() != 3)
1540 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1544 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1545 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1546 retained_mortar_segments[el] =
1547 0.5 * e1.cross(e2).norm() / secondary_volume >= TOLERANCE;
1548 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1551 if (seed_breadth_first_search)
1555 if (!primary_elem_found)
1557 primary_elem_found =
true;
1558 primary_elem_candidates.clear();
1562 for (
auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1565 if (neighbor ==
nullptr || neighbor->subdomain_id() != primary_subd_id)
1568 if (processed_primary_elems.count(neighbor))
1571 primary_elem_candidates.insert(neighbor);
1578 std::vector<Node *> new_nodes;
1581 std::vector<bool> retained_nodes(nodal_points.size(),
false);
1582 for (
const auto el : index_range(elem_to_node_map))
1583 if (retained_mortar_segments[el])
1584 for (
const auto node : elem_to_node_map[el])
1585 retained_nodes[node] =
true;
1587 new_nodes.resize(nodal_points.size(),
nullptr);
1588 for (
const auto node : index_range(nodal_points))
1589 if (retained_nodes[node])
1591 nodal_points[node], next_node_id++, secondary_side_elem->processor_id());
1594 for (
auto el : index_range(elem_to_node_map))
1596 if (!retained_mortar_segments[el])
1599 std::unique_ptr<Elem> new_elem;
1600 if (elem_to_node_map[el].size() == 3)
1601 new_elem = std::make_unique<Tri3>();
1603 mooseError(
"Active mortar segments only supports TRI elements, 3 nodes expected "
1605 elem_to_node_map[el].size(),
1608 new_elem->processor_id() = secondary_side_elem->processor_id();
1609 new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1610 new_elem->set_id(next_elem_id++);
1613 for (
auto i : index_range(elem_to_node_map[el]))
1614 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1617 if (new_elem->volume() / secondary_volume < TOLERANCE)
1623 msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1624 msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1635 if (use_reference_interpolation)
1638 elem_to_primary_reference_points[el]};
1643 secondary_to_msm_element_set.insert(msm_new_elem);
1654 if (use_geometric_subpatch_normals)
1658 if (secondary_to_msm_element_set.empty())
1660 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1661 " a corresponding primary element; this may be expected depending on"
1662 " problem geometry but may indicate a failure of the element search"
1666 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1667 if (mortar_segment_helper[sel]->remainder() == 1.0)
1669 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1670 " a corresponding primary element; this may be expected depending on"
1671 " problem geometry but may indicate a failure of the element search"
1674 if (secondary_to_msm_element_set.empty())
1679 mooseAssert(!use_reference_interpolation ||
1681 "Mortar segment info and reference-point maps must remain aligned.");
1695 if (msm_el->type() != TRI3)
1696 msm_el->subdomain_id()++;
1702 if (msm_el->type() != TRI3)
1703 msm_el->subdomain_id()--;
1718 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1729 const Elem * secondary_elem = pr.second.secondary_elem;
1730 const Elem * primary_elem = pr.second.primary_elem;
1735 std::set<processor_id_type> secondary_face_coupling_processors{
1736 secondary_elem->processor_id(),
1737 secondary_elem->interior_parent()->processor_id(),
1738 primary_elem->interior_parent()->processor_id()};
1739 for (
const auto n : secondary_elem->node_index_range())
1740 secondary_face_coupling_processors.insert(secondary_elem->node_ptr(n)->processor_id());
1743 for (
const auto processor : secondary_face_coupling_processors)
1744 coupling_info[processor].emplace_back(secondary_elem->id(),
1745 secondary_elem->interior_parent()->id());
1748 for (
const auto processor : secondary_face_coupling_processors)
1749 coupling_info[processor].emplace_back(secondary_elem->id(),
1750 primary_elem->interior_parent()->id());
1753 for (
const auto processor : secondary_face_coupling_processors)
1754 coupling_info[processor].emplace_back(secondary_elem->id(), primary_elem->id());
1757 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1758 secondary_elem->interior_parent()->id(), secondary_elem->id());
1761 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1762 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1765 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1766 primary_elem->interior_parent()->id(), secondary_elem->id());
1769 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1770 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1774 for (
auto & [_, couplings] : coupling_info)
1776 std::sort(couplings.begin(), couplings.end());
1777 couplings.erase(std::unique(couplings.begin(), couplings.end()), couplings.end());
1781 auto action_functor =
1782 [
this](processor_id_type,
1783 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1785 for (
auto [i, j] : coupling_info)
1791std::vector<AutomaticMortarGeneration::MsmSubdomainStats>
1794 std::vector<MsmSubdomainStats> result;
1798 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1802 for (
const auto *
const secondary_el :
1803 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1805 secondary.push_back(secondary_el->volume());
1810 for (
const auto *
const msm_elem : it->second)
1812 msm.push_back(msm_elem->volume());
1816 if (msm_info.primary_elem)
1818 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1819 mooseError(
"Unhandled primary-secondary pairing when computing mortar segment "
1820 "statistics. This could happen if you have the same secondary "
1821 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1822 "primary subdomain IDs. Contact a MOOSE developer for help.");
1823 if (
const auto [it, inserted] =
1824 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1826 it->second = msm_info.primary_elem->volume();
1829 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1830 "Volumes should be consistent");
1835 _mesh.comm().set_union(primary_elems_to_volume);
1836 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1837 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1838 primary.reserve(primary_elems_to_volume.size());
1839 for (
const auto [_, volume] : primary_elems_to_volume)
1840 primary.push_back(volume);
1844 const auto max_volume = [](
const auto & volumes)
1845 {
return volumes.empty() ? Real(0) : volumes.maximum(); };
1846 const auto min_volume = [](
const auto & volumes)
1847 {
return volumes.empty() ? Real(0) : volumes.minimum(); };
1864 result.push_back(stats);
1869 primary_elems_to_volume.clear();
1880 if (
_mesh.processor_id() != 0)
1883 Moose::out <<
"Mortar Interface Statistics:" << std::endl;
1884 for (
const auto & stats : all_stats)
1886 std::vector<std::string> col_names = {
"mesh",
"n_elems",
"max",
"min",
"median"};
1887 std::vector<std::string> subds = {
"secondary_lower",
"primary_lower",
"mortar_segment"};
1888 std::vector<size_t> n_elems = {
1889 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1890 std::vector<Real> maxs = {
1891 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1892 std::vector<Real> mins = {
1893 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1894 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1895 stats.primary_lower_median_volume,
1896 stats.msm_median_volume};
1900 for (
auto i : index_range(subds))
1903 table.
addData<std::string>(col_names[0], subds[i]);
1904 table.
addData<
size_t>(col_names[1], n_elems[i]);
1905 table.
addData<Real>(col_names[2], maxs[i]);
1906 table.
addData<Real>(col_names[3], mins[i]);
1907 table.
addData<Real>(col_names[4], medians[i]);
1910 Moose::out <<
"secondary subdomain: " << stats.secondary_subd_id
1911 <<
" \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1928 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
1931 std::unordered_set<dof_id_type> inactive_node_ids;
1933 std::unordered_map<const Elem *, Real> active_volume{};
1936 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(pr.second))
1937 active_volume[el] = 0.;
1945 active_volume[secondary_elem] += msm_elem->volume();
1951 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(pr.second))
1953 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1956 for (
auto n : make_range(el->n_nodes()))
1957 inactive_node_ids.insert(el->node_id(n));
1963 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes;
1964 for (
const auto node_id : inactive_node_ids)
1965 proc_to_inactive_nodes[
_mesh.node_ptr(node_id)->processor_id()].push_back(node_id);
1968 auto action_functor = [
this](
const processor_id_type,
const std::vector<dof_id_type> & sent_data)
1970 for (
const auto node_id : sent_data)
1991 std::unordered_set<dof_id_type> active_local_nodes;
1999 for (
auto n : make_range(secondary_elem->n_nodes()))
2000 active_local_nodes.insert(secondary_elem->node_id(n));
2004 std::unordered_set<dof_id_type> local_nodes;
2006 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2008 for (
const auto n : make_range(el->n_nodes()))
2009 local_nodes.insert(el->node_id(n));
2012 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, unsigned char>>>
2014 for (
const auto node_id : local_nodes)
2015 proc_to_nodes[
_mesh.node_ptr(node_id)->processor_id()].emplace_back(
2016 node_id, active_local_nodes.count(node_id));
2018 std::unordered_set<dof_id_type> candidate_nodes, active_nodes;
2019 auto action_functor = [&candidate_nodes, &active_nodes](
2020 const processor_id_type,
2021 const std::vector<std::pair<dof_id_type, unsigned char>> & sent_data)
2023 for (
const auto & [node_id, active] : sent_data)
2025 candidate_nodes.insert(node_id);
2027 active_nodes.insert(node_id);
2034 for (
const auto node_id : candidate_nodes)
2035 if (!active_nodes.count(node_id))
2044 std::unordered_set<const Elem *> active_local_elems;
2050 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
2052 std::unordered_map<const Elem *, Real> active_volume;
2061 active_volume[secondary_elem] += msm_elem->volume();
2072 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
2076 active_local_elems.insert(secondary_elem);
2082 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2084 if (active_local_elems.find(el) == active_local_elems.end())
2092 const auto dim =
_mesh.mesh_dimension();
2094 mooseAssert(
dim == 2 ||
dim == 3,
2095 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2096 "mortar constraints on 2D or 3D meshes.");
2105 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2113 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2114 end_el =
_mesh.active_elements_end();
2118 const Elem * secondary_elem = *el;
2126 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2127 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(
dim, nnx_fe_type));
2128 nnx_fe_face->attach_quadrature_rule(&qface);
2129 const auto & face_normals = nnx_fe_face->get_normals();
2130 const auto & face_points = nnx_fe_face->get_xyz();
2132 const auto & JxW = nnx_fe_face->get_JxW();
2136 const Elem * interior_parent = secondary_elem->interior_parent();
2137 mooseAssert(interior_parent,
2138 "No interior parent exists for element "
2139 << secondary_elem->id()
2140 <<
". There may be a problem with your sideset set-up.");
2148 auto s = interior_parent->which_side_am_i(secondary_elem);
2151 nnx_fe_face->reinit(interior_parent, s);
2156 const auto qpoint_to_secondary_node =
2157 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2159 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2160 "Face nodal geometry vectors must have the same size.");
2162 for (
const auto qp : make_range(face_points.size()))
2164 const auto n = qpoint_to_secondary_node[qp];
2165 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2166 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2171 for (
const auto & pr : node_to_normals_map)
2174 const auto & node_id = pr.first;
2175 const auto & normals_and_weights_vec = pr.second;
2178 for (
const auto & norm_and_weight : normals_and_weights_vec)
2179 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2180 nodal_normal = nodal_normal.unit();
2190 const std::function<
ADPoint(
const Node &)> & coordinate,
2191 std::unordered_map<const Node *, ADRealVectorValue> & nodal_normals)
const
2193 const auto dim =
_mesh.mesh_dimension();
2194 mooseAssert(
dim == 2 ||
dim == 3,
"AD nodal normals are only valid for 2D or 3D mortar.");
2200 std::unordered_map<const Node *, ADRealVectorValue> weighted_area_vectors;
2201 std::unordered_map<const Node *, Real> weighted_area_magnitudes;
2204 std::map<Order, std::unique_ptr<FEBase>> fe_faces;
2208 for (
const Elem *
const secondary_elem :
2209 as_range(
_mesh.active_subdomain_elements_begin(secondary_subdomain_id),
2210 _mesh.active_subdomain_elements_end(secondary_subdomain_id)))
2212 const Elem *
const interior_parent = secondary_elem->interior_parent();
2213 mooseAssert(interior_parent,
2214 "No interior parent exists for element "
2215 << secondary_elem->id()
2216 <<
". There may be a problem with your sideset set-up.");
2218 const Real element_size = secondary_elem->hmax();
2219 if (element_size <= 0)
2220 mooseError(
"Cannot compute an AD nodal normal for secondary mortar element ",
2221 secondary_elem->id(),
2222 " because it has a non-positive characteristic length.");
2224 const auto side = interior_parent->which_side_am_i(secondary_elem);
2225 auto & fe_face = fe_faces[secondary_elem->default_order()];
2228 fe_face = FEBase::build(
dim, FEType(secondary_elem->default_order(), LAGRANGE));
2229 fe_face->attach_quadrature_rule(&qface);
2232 const auto & face_normals = fe_face->get_normals();
2233 const auto & face_points = fe_face->get_xyz();
2234 const auto & map_dpsidxi = fe_face->get_fe_map().get_dpsidxi();
2235 const auto *
const map_dpsideta =
dim == 3 ? &fe_face->get_fe_map().get_dpsideta() :
nullptr;
2237 fe_face->reinit(interior_parent, side);
2239 const auto qpoint_to_secondary_node =
2240 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2242 mooseAssert(face_normals.size() == face_points.size() &&
2243 qweights.size() == face_points.size(),
2244 "Face nodal geometry vectors and quadrature weights must have the same size.");
2247 auto parent_side_elem = interior_parent->build_side_ptr(side);
2248 const unsigned int n_mapping_shape_functions =
2250 parent_side_elem->default_order())
2252 parent_side_elem->default_order());
2253 if (map_dpsidxi.size() != n_mapping_shape_functions ||
2254 (map_dpsideta && map_dpsideta->size() != n_mapping_shape_functions))
2255 mooseError(
"The parent-side geometric map for secondary mortar element ",
2256 secondary_elem->id(),
2258 libMesh::Utility::enum_to_string<ElemType>(secondary_elem->type()),
2259 " does not have one mapping shape function per element node.");
2260 mooseAssert(n_mapping_shape_functions <= parent_side_elem->
n_nodes(),
2261 "The mapping basis must not index past the side element's nodes.");
2263 std::vector<ADPoint> side_points;
2264 side_points.reserve(n_mapping_shape_functions);
2265 for (
const auto side_node_index : make_range(n_mapping_shape_functions))
2266 side_points.push_back(coordinate(parent_side_elem->node_ref(side_node_index)));
2268 for (
const auto qp : index_range(face_points))
2270 const auto secondary_node_index = qpoint_to_secondary_node[qp];
2271 const Node *
const secondary_node = secondary_elem->node_ptr(secondary_node_index);
2275 for (
const auto side_node_index : make_range(n_mapping_shape_functions))
2277 tangent_xi.
add_scaled(side_points[side_node_index], map_dpsidxi[side_node_index][qp]);
2279 tangent_eta.
add_scaled(side_points[side_node_index],
2280 (*map_dpsideta)[side_node_index][qp]);
2284 : tangent_xi.
cross(tangent_eta);
2286 const Real degeneracy_tolerance =
2287 dim == 2 ? TOLERANCE * element_size : TOLERANCE * element_size * element_size;
2288 if (area <= degeneracy_tolerance)
2289 mooseError(
"Cannot compute an AD nodal normal on degenerate secondary mortar element ",
2290 secondary_elem->id(),
2296 const Real orientation =
2299 weighted_area_vectors[secondary_node] += sign * orientation * qweights[qp] * area_vector;
2300 weighted_area_magnitudes[secondary_node] += std::abs(qweights[qp]) * area;
2304 nodal_normals.clear();
2305 nodal_normals.reserve(weighted_area_vectors.size());
2306 for (
auto & [secondary_node, weighted_area_vector] : weighted_area_vectors)
2309 const Real weighted_area_magnitude = libmesh_map_find(weighted_area_magnitudes, secondary_node);
2310 if (weighted_area_vector_norm <= TOLERANCE * weighted_area_magnitude)
2311 mooseError(
"Cannot compute a normalized AD nodal normal for secondary node ",
2312 secondary_node->id(),
2313 " because its incident face contributions cancel to a near-zero weighted normal.");
2315 auto nodal_normal = weighted_area_vector.unit();
2320 "The stored and AD secondary nodal normals must use the same geometry state.");
2323 for (
const auto component : make_range(
Moose::dim))
2324 nodal_normal(component).value() = stored_normal(component);
2325 nodal_normals.emplace(secondary_node, std::move(nodal_normal));
2342 const Node & secondary_node,
2343 const Node & primary_node,
2344 const std::vector<const Elem *> * secondary_node_neighbors,
2345 const std::vector<const Elem *> * primary_node_neighbors,
2346 const VectorValue<Real> & nodal_normal,
2347 const Elem & candidate_element,
2348 std::set<const Elem *> & rejected_elem_candidates)
2350 if (!secondary_node_neighbors)
2352 if (!primary_node_neighbors)
2355 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(),
false);
2383 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2385 for (
const auto nn : index_range(*secondary_node_neighbors))
2387 const Elem *
const secondary_neigh = (*secondary_node_neighbors)[nn];
2388 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2389 ? secondary_neigh->point(1)
2390 : secondary_neigh->point(0);
2391 const Point cp = nodal_normal.cross(opposite - secondary_node);
2392 secondary_node_neighbor_cps[nn] = cp(2);
2395 for (
const auto nn : index_range(*primary_node_neighbors))
2397 const Elem *
const primary_neigh = (*primary_node_neighbors)[nn];
2398 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2399 : primary_neigh->point(0);
2400 const Point cp = nodal_normal.cross(opposite - primary_node);
2401 primary_node_neighbor_cps[nn] = cp(2);
2405 bool found_match =
false;
2406 for (
const auto snn : index_range(*secondary_node_neighbors))
2407 for (
const auto mnn : index_range(*primary_node_neighbors))
2408 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2411 if (primary_elems_mapped[mnn])
2413 primary_elems_mapped[mnn] =
true;
2417 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2418 const auto secondary_key =
2419 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2420 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2425 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2427 const auto primary_key =
2428 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2429 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2437 rejected_elem_candidates.insert(&candidate_element);
2444 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2445 for (
const auto i : index_range(primary_elems_mapped))
2446 if (!primary_elems_mapped[i])
2449 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2450 std::make_pair(1,
nullptr));
2458 SubdomainID lower_dimensional_primary_subdomain_id,
2459 SubdomainID lower_dimensional_secondary_subdomain_id)
2466 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2469 kd_tree.buildIndex();
2471 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2472 end_el =
_mesh.active_elements_end();
2476 const Elem * secondary_side_elem = *el;
2479 if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2486 for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2489 const Node * secondary_node = secondary_side_elem->node_ptr(n);
2493 const std::vector<const Elem *> & secondary_node_neighbors =
2498 bool is_mapped =
true;
2499 for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2502 auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2518 std::array<Real, 3> query_pt = {
2519 {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2525 const std::size_t num_results = 3;
2528 std::vector<size_t> ret_index(num_results);
2529 std::vector<Real> out_dist_sqr(num_results);
2530 nanoflann::KNNResultSet<Real> result_set(num_results);
2531 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2535 bool projection_succeeded =
false;
2539 std::set<const Elem *> rejected_primary_elem_candidates;
2545 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2548 mooseAssert(abs((
_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2549 out_dist_sqr[r]) <= TOLERANCE,
2550 "Lower-dimensional element squared distance verification failed.");
2554 std::vector<const Elem *> & primary_elem_candidates =
2558 for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2560 const Elem * primary_elem_candidate = primary_elem_candidates[e];
2563 if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2567 const auto order = primary_elem_candidate->default_order();
2569 unsigned int current_iterate = 0, max_iterates = 10;
2574 VectorValue<DualNumber<Real>> x2(0);
2575 for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2576 n < primary_elem_candidate->n_nodes();
2580 const auto u = x2 - (*secondary_node);
2581 const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2586 if (F.derivatives())
2588 Real dxi2 = -F.value() / F.derivatives();
2596 current_iterate = max_iterates;
2597 }
while (++current_iterate < max_iterates);
2599 Real xi2 = xi2_dn.value();
2612 if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 *
_xi_tolerance) &&
2613 (abs((primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2623 const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2624 : primary_elem_candidate->node_ptr(1);
2625 const bool created_mortar_segment =
2628 &secondary_node_neighbors,
2631 *primary_elem_candidate,
2632 rejected_primary_elem_candidates);
2634 if (!created_mortar_segment)
2640 for (MooseIndex(secondary_node_neighbors) nn = 0;
2641 nn < secondary_node_neighbors.size();
2644 const Elem * neigh = secondary_node_neighbors[nn];
2645 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2647 const Node * neigh_node = neigh->node_ptr(nid);
2648 if (secondary_node == neigh_node)
2650 auto key = std::make_pair(neigh_node, neigh);
2651 auto val = std::make_pair(xi2, primary_elem_candidate);
2658 projection_succeeded =
true;
2663 rejected_primary_elem_candidates.insert(primary_elem_candidate);
2666 if (projection_succeeded)
2670 if (!projection_succeeded)
2674 _console <<
"Failed to find primary Elem into which secondary node "
2675 << cast_ref<const Point &>(*secondary_node) <<
", id '" << secondary_node->id()
2676 <<
"', projects onto\n"
2695 <<
" secondary nodes were successfully projected\n"
2712 SubdomainID lower_dimensional_primary_subdomain_id,
2713 SubdomainID lower_dimensional_secondary_subdomain_id)
2720 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2723 kd_tree.buildIndex();
2725 std::unordered_set<dof_id_type> primary_nodes_visited;
2727 for (
const auto & primary_side_elem :
_mesh.active_element_ptr_range())
2730 if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2735 for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2739 const Node * primary_node = primary_side_elem->node_ptr(n);
2742 const std::vector<const Elem *> & primary_node_neighbors =
2750 std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2751 if (!primary_nodes_visited.insert(primary_node->id()).second ||
2756 Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2762 const size_t num_results = 3;
2765 std::vector<size_t> ret_index(num_results);
2766 std::vector<Real> out_dist_sqr(num_results);
2767 nanoflann::KNNResultSet<Real> result_set(num_results);
2768 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2772 bool projection_succeeded =
false;
2777 std::set<const Elem *> rejected_secondary_elem_candidates;
2781 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2784 mooseAssert(abs((
_mesh.point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2786 "Lower-dimensional element squared distance verification failed.");
2790 const std::vector<const Elem *> & secondary_elem_candidates =
2794 for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2796 const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2799 if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2802 std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2803 for (
const auto n : make_range(secondary_elem_candidate->n_nodes()))
2811 auto && order = secondary_elem_candidate->default_order();
2812 unsigned int current_iterate = 0, max_iterates = 10;
2814 VectorValue<DualNumber<Real>> normals(0);
2822 VectorValue<DualNumber<Real>> x1(0);
2823 for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2824 n < secondary_elem_candidate->n_nodes();
2828 x1 += phi * secondary_elem_candidate->point(n);
2829 normals += phi * nodal_normals[n];
2832 const auto u = x1 - (*primary_node);
2834 const auto F = u(0) * normals(1) - u(1) * normals(0);
2842 Real dxi1 = -F.value() / F.derivatives();
2847 }
while (++current_iterate < max_iterates);
2849 Real xi1 = xi1_dn.value();
2853 if ((current_iterate < max_iterates) && (abs(xi1) <= 1. +
_xi_tolerance) &&
2854 (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2868 const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2869 : secondary_elem_candidate->node_ref(1);
2870 bool created_mortar_segment =
false;
2877 &primary_node_neighbors,
2879 *secondary_elem_candidate,
2880 rejected_secondary_elem_candidates);
2882 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2884 if (!created_mortar_segment)
2900 const Elem * neigh = primary_node_neighbors[0];
2901 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2903 const Node * neigh_node = neigh->node_ptr(nid);
2904 if (primary_node == neigh_node)
2906 auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2907 auto val = std::make_pair(xi1, secondary_elem_candidate);
2913 projection_succeeded =
true;
2919 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2923 if (projection_succeeded)
2927 if (!projection_succeeded &&
_debug)
2929 _console <<
"\nFailed to find point from which primary node "
2930 << cast_ref<const Point &>(*primary_node) <<
" was projected." << std::endl
2937std::vector<AutomaticMortarGeneration::MortarFilterIter>
2944 const auto & secondary_elems = secondary_it->second;
2945 std::vector<MortarFilterIter> ret;
2946 ret.reserve(secondary_elems.size());
2948 for (
const auto i : index_range(secondary_elems))
2950 auto *
const secondary_elem = secondary_elems[i];
2956 mooseAssert(secondary_elem->active(),
2957 "We loop over active elements when building the mortar segment mesh, so we golly "
2958 "well hope this is active.");
2959 mooseAssert(!msm_it->second.empty(),
2960 "We should have removed all secondaries from this map if they do not have any "
2961 "mortar segments associated with them.");
2962 ret.push_back(msm_it);
subdomain_id_type SubdomainID
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.
libMesh::VectorValue< ADReal > ADRealVectorValue
AD Array typedefs.
libMesh::VectorValue< ADReal > ADPoint
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
List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)
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.
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().
void computeADNodalNormals(const std::function< ADPoint(const Node &)> &coordinate, std::unordered_map< const Node *, ADRealVectorValue > &nodal_normals) const
Build the normalized JxW-weighted secondary nodal normals from AD nodal coordinates.
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
SolutionInvalidity & solutionInvalidity()
Get the SolutionInvalidity for this app.
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()
void flagInvalidSolutionInternal(const InvalidSolutionID _invalid_solution_id)
Increments solution invalid occurrences for each solution id.
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
unsigned int n_dofs(const ElemType t, const Order o)
const std::vector< Real > & get_weights() 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< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
void add_scaled(const TypeVector< T2 > &, const T &)
InvalidSolutionID registerInvalidity(const std::string &object_type, const std::string &message, const bool warning)
Call to register an invalid calculation.
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.
std::array< Vector, 2 > householderTangents(const Vector &normal)
Construct the two tangent vectors used by mortar from a unit normal.
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
static constexpr std::size_t dim
This is the dimension of all vector and tensor datastructures used in MOOSE.
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)
SolutionInvalidityRegistry & getSolutionInvalidityRegistry()
Get the global SolutionInvalidityRegistry singleton.
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