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"
47#include "metaphysicl/dualnumber.h"
61#if NANOFLANN_VERSION < 0x150
75std::vector<unsigned int>
76nodalQuadraturePointToSecondaryNodeMap(
const Elem & secondary_elem,
77 const std::vector<Point> & q_points)
79 const auto n_nodes = secondary_elem.n_nodes();
83 " points for secondary mortar element ",
86 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
87 ", but the element has ",
91 const auto invalid_node = std::numeric_limits<unsigned int>::max();
92 std::vector<unsigned int> qpoint_to_node(
n_nodes, invalid_node);
93 std::vector<bool> node_used(
n_nodes,
false);
95 const Real element_size = secondary_elem.hmax();
96 mooseAssert(element_size > 0,
97 "Secondary mortar element "
98 << secondary_elem.id() <<
" of type "
99 << libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type())
100 <<
" has a non-positive hmax and cannot be used for nodal quadrature point "
107 const Real matching_tol = 100 * TOLERANCE * element_size;
108 const Real matching_tol_sq = matching_tol * matching_tol;
113 for (
const auto qp : make_range(q_points.size()))
115 unsigned int closest_node = invalid_node;
116 Real closest_dist_sq = std::numeric_limits<Real>::max();
117 Real second_closest_dist_sq = std::numeric_limits<Real>::max();
119 for (
const auto n : make_range(
n_nodes))
124 const Real dist_sq = (q_points[qp] - secondary_elem.point(n)).norm_sq();
125 if (dist_sq < closest_dist_sq)
127 second_closest_dist_sq = closest_dist_sq;
128 closest_dist_sq = dist_sq;
131 else if (dist_sq < second_closest_dist_sq)
132 second_closest_dist_sq = dist_sq;
135 if (closest_node == invalid_node || closest_dist_sq > matching_tol_sq)
136 mooseError(
"Could not match nodal quadrature point ",
140 " to a node on secondary mortar element ",
143 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
144 ". The nearest unmatched node distance is ",
145 std::sqrt(closest_dist_sq),
146 ", which exceeds the tolerance ",
150 if (second_closest_dist_sq <= matching_tol_sq)
155 " does not map uniquely to secondary mortar element ",
158 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
159 ". Two unmatched nodes are within the matching tolerance ",
163 qpoint_to_node[qp] = closest_node;
164 node_used[closest_node] =
true;
170 std::vector<unsigned int> node_to_qpoint(
n_nodes, invalid_node);
171 for (
const auto qp : make_range(q_points.size()))
173 const auto mapped_node = qpoint_to_node[qp];
174 mooseAssert(mapped_node != invalid_node && mapped_node <
n_nodes,
175 "Invalid secondary node mapping for nodal quadrature point " << qp <<
".");
176 mooseAssert(node_to_qpoint[mapped_node] == invalid_node,
177 "Secondary node " << mapped_node <<
" on mortar element " << secondary_elem.id()
178 <<
" was matched to both nodal quadrature point "
179 << node_to_qpoint[mapped_node] <<
" and " << qp <<
".");
180 node_to_qpoint[mapped_node] = qp;
183 unsigned int candidate_count = 0;
184 unsigned int candidate_node = invalid_node;
185 for (
const auto n : make_range(
n_nodes))
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 <<
".");
204 for (
const auto n : make_range(
n_nodes))
206 mooseAssert(node_to_qpoint[n] != invalid_node,
207 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
208 <<
" was not matched to a nodal quadrature point.");
211 unsigned int candidate_count = 0;
212 unsigned int candidate_qp = invalid_node;
213 for (
const auto qp : make_range(q_points.size()))
214 if ((q_points[qp] - secondary_elem.point(n)).norm_sq() <= matching_tol_sq)
220 mooseAssert(candidate_count == 1,
221 "Secondary node " << n <<
" on mortar element " << secondary_elem.id() <<
" has "
223 <<
" nodal quadrature point candidates within tolerance "
224 << matching_tol <<
".");
225 mooseAssert(candidate_qp == node_to_qpoint[n],
226 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
227 <<
" was matched to nodal quadrature point " << node_to_qpoint[n]
228 <<
", but the full candidate search found point " << candidate_qp
233 return qpoint_to_node;
259 mooseError(
"No entries found in the secondary node -> nodal geometry map.");
262 auto & subproblem =
_amg.
_on_displaced ? cast_ref<SubProblem &>(*problem.getDisplacedProblem())
263 : cast_ref<SubProblem &>(problem);
264 auto & nodal_normals_es = subproblem.es();
266 const std::string nodal_normals_sys_name =
"nodal_normals";
270 for (
const auto s : make_range(nodal_normals_es.n_systems()))
271 if (!nodal_normals_es.get_system(s).is_initialized())
277 &nodal_normals_es.template add_system<ExplicitSystem>(nodal_normals_sys_name);
295 nodal_normals_es.reinit();
299 std::vector<dof_id_type> dof_indices_nnx, dof_indices_nny, dof_indices_nnz;
300 std::vector<dof_id_type> dof_indices_t1x, dof_indices_t1y, dof_indices_t1z;
301 std::vector<dof_id_type> dof_indices_t2x, dof_indices_t2y, dof_indices_t2z;
303 for (MeshBase::const_element_iterator el =
_amg.
_mesh.elements_begin(),
308 const Elem * elem = *el;
312 dof_map.dof_indices(elem, dof_indices_nny,
_nny_var_num);
313 dof_map.dof_indices(elem, dof_indices_nnz,
_nnz_var_num);
315 dof_map.dof_indices(elem, dof_indices_t1x,
_t1x_var_num);
316 dof_map.dof_indices(elem, dof_indices_t1y,
_t1y_var_num);
317 dof_map.dof_indices(elem, dof_indices_t1z,
_t1z_var_num);
319 dof_map.dof_indices(elem, dof_indices_t2x,
_t2x_var_num);
320 dof_map.dof_indices(elem, dof_indices_t2y,
_t2y_var_num);
321 dof_map.dof_indices(elem, dof_indices_t2z,
_t2z_var_num);
327 for (MooseIndex(elem->n_vertices()) n = 0; n < elem->n_vertices(); ++n)
355 std::set<std::string> sys_names = {nodal_normals_sys_name};
358 ExodusII_IO nodal_normals_writer(
_amg.
_mesh);
361 nodal_normals_writer.set_hdf5_writing(
false);
363 nodal_normals_writer.write_equation_systems(
364 "nodal_geometry_only.e", nodal_normals_es, &sys_names);
391 const std::pair<BoundaryID, BoundaryID> & boundary_key,
392 const std::pair<SubdomainID, SubdomainID> & subdomain_key,
396 const bool correct_edge_dropping,
397 const Real minimum_projection_angle,
398 const Mortar3DSubpatchPlane mortar_3d_subpatch_plane,
400 const bool triangulate_triangles,
401 const Mortar3DQuadraturePointMapping mortar_3d_qp_mapping)
406 _on_displaced(on_displaced),
411 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
412 _correct_edge_dropping(correct_edge_dropping),
413 _minimum_projection_angle(minimum_projection_angle),
414 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
415 _triangulation_mode(triangulation_mode),
416 _triangulate_triangles(triangulate_triangles),
417 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
428 std::make_unique<DistributedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
431 std::make_unique<ReplicatedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
441 string_vec[2 * i] = std::to_string(primary_bnd_id);
442 string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
444 string_vec.back() =
_on_displaced ?
"displaced" :
"undisplaced";
445 return MooseUtils::join(string_vec,
"_");
490 mooseError(
"Mortar segment reference points were requested for mortar segment element ",
491 mortar_segment_elem.id(),
492 ", but the reference-interpolation mapping mode is not enabled.");
496 mooseError(
"No reference-point record was found for mortar segment element ",
497 mortar_segment_elem.id(),
498 ". The mortar segment info and reference-point maps are not aligned.");
500 return reference_points_it->second;
508 "Must specify secondary and primary boundary ids before building node-to-elem maps.");
511 for (
const auto & secondary_elem :
512 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
518 for (
const auto & nd : secondary_elem->node_ref_range())
521 vec.push_back(secondary_elem);
526 for (
const auto & primary_elem :
527 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
533 for (
const auto & nd : primary_elem->node_ref_range())
536 vec.push_back(primary_elem);
544 std::vector<Point> nodal_normals(secondary_elem.n_nodes());
545 for (
const auto n : make_range(secondary_elem.n_nodes()))
548 return nodal_normals;
553 dof_id_type secondary_elem_id)
const
556 "Map should locate secondary element");
561std::map<unsigned int, unsigned int>
564 std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
565 const Elem *
const secondary_ip = lower_secondary_elem.interior_parent();
566 mooseAssert(secondary_ip,
"This should be non-null");
568 for (
const auto i : make_range(lower_secondary_elem.n_nodes()))
570 const auto & nd = lower_secondary_elem.node_ref(i);
571 secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
574 return secondary_ip_i_to_lower_secondary_i;
577std::map<unsigned int, unsigned int>
579 const Elem & lower_primary_elem,
580 const Elem & primary_elem,
583 std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
585 for (
const auto i : make_range(lower_primary_elem.n_nodes()))
587 const auto & nd = lower_primary_elem.node_ref(i);
588 primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
591 return primary_ip_i_to_lower_primary_i;
594std::array<MooseUtils::SemidynamicVector<Point, 9>, 2>
598 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
599 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
601 for (
const auto n : make_range(secondary_elem.n_nodes()))
603 const auto & tangent_vectors =
605 nodal_tangents_one.push_back(tangent_vectors[0]);
606 nodal_tangents_two.push_back(tangent_vectors[1]);
609 return {{nodal_tangents_one, nodal_tangents_two}};
614 const std::vector<Real> & oned_xi1_pts)
const
616 std::vector<Point> xi1_pts(oned_xi1_pts.size());
617 for (
const auto qp : index_range(oned_xi1_pts))
618 xi1_pts[qp] = oned_xi1_pts[qp];
625 const std::vector<Point> & xi1_pts)
const
627 const auto mortar_dim =
_mesh.mesh_dimension() - 1;
628 const auto num_qps = xi1_pts.size();
630 std::vector<Point> normals(num_qps);
632 for (
const auto n : make_range(secondary_elem.n_nodes()))
633 for (
const auto qp : make_range(num_qps))
639 secondary_elem.default_order(),
641 cast_ref<
const TypeVector<Real> &>(xi1_pts[qp]));
642 normals[qp] += phi * nodal_normals[n];
646 for (
auto & normal : normals)
657 dof_id_type local_id_index = 0;
658 std::size_t node_unique_id_offset = 0;
666 const auto primary_bnd_id = pr.first;
667 const auto secondary_bnd_id = pr.second;
668 const auto num_primary_nodes =
669 std::distance(
_mesh.bid_nodes_begin(primary_bnd_id),
_mesh.bid_nodes_end(primary_bnd_id));
670 const auto num_secondary_nodes = std::distance(
_mesh.bid_nodes_begin(secondary_bnd_id),
671 _mesh.bid_nodes_end(secondary_bnd_id));
672 mooseAssert(num_primary_nodes,
673 "There are no primary nodes on boundary ID "
674 << primary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
675 mooseAssert(num_secondary_nodes,
676 "There are no secondary nodes on boundary ID "
677 << secondary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
679 node_unique_id_offset += num_primary_nodes + 2 * num_secondary_nodes;
683 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
684 end_el =
_mesh.active_elements_end();
688 const Elem * secondary_elem = *el;
694 std::vector<Node *> new_nodes;
695 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
698 secondary_elem->point(n), secondary_elem->node_id(n), secondary_elem->processor_id()));
699 Node *
const new_node = new_nodes.back();
700 new_node->set_unique_id(new_node->id() + node_unique_id_offset);
703 std::unique_ptr<Elem> new_elem;
704 if (secondary_elem->default_order() == SECOND)
705 new_elem = std::make_unique<Edge3>();
707 new_elem = std::make_unique<Edge2>();
709 new_elem->processor_id() = secondary_elem->processor_id();
710 new_elem->subdomain_id() = secondary_elem->subdomain_id();
711 new_elem->set_id(local_id_index++);
712 new_elem->set_unique_id(new_elem->id());
714 for (MooseIndex(new_elem->n_nodes()) n = 0; n < new_elem->n_nodes(); ++n)
715 new_elem->set_node(n, new_nodes[n]);
726 std::make_pair(secondary_elem->node_ptr(0), secondary_elem)),
728 std::make_pair(secondary_elem->node_ptr(1), secondary_elem));
730 bool new_container_node0_found =
732 new_container_node1_found =
735 const Elem * node0_primary_candidate =
nullptr;
736 const Elem * node1_primary_candidate =
nullptr;
738 if (new_container_node0_found)
740 const auto & xi2_primary_elem_pair = new_container_it0->second;
741 msinfo.
xi2_a = xi2_primary_elem_pair.first;
742 node0_primary_candidate = xi2_primary_elem_pair.second;
745 if (new_container_node1_found)
747 const auto & xi2_primary_elem_pair = new_container_it1->second;
748 msinfo.
xi2_b = xi2_primary_elem_pair.first;
749 node1_primary_candidate = xi2_primary_elem_pair.second;
756 if (node0_primary_candidate == node1_primary_candidate)
771 auto val = pr.second;
773 const Node * primary_node = std::get<1>(key);
774 Real xi1 = val.first;
775 const Elem * secondary_elem = val.second;
781 auto && order = secondary_elem->default_order();
785 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
790 Elem * current_mortar_segment =
nullptr;
793 for (
const auto & mortar_segment_candidate : mortar_segment_set)
799 catch (std::out_of_range &)
801 mooseError(
"MortarSegmentInfo not found for the mortar segment candidate");
803 if (info->xi1_a <= xi1 && xi1 <= info->xi1_b)
805 current_mortar_segment = mortar_segment_candidate;
811 if (current_mortar_segment ==
nullptr)
812 mooseError(
"Unable to find appropriate mortar segment during linear search!");
819 if (info->xi1_a == xi1 || xi1 == info->xi1_b)
824 "new_id must be the same on all processes");
825 Node *
const new_node =
827 new_node->set_unique_id(new_id + node_unique_id_offset);
832 const Point normal =
getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
836 this->_nodes_to_primary_elem_map.end())
837 mooseError(
"We should already have built this primary node to elem pair!");
838 const std::vector<const Elem *> & primary_node_neighbors =
842 if (primary_node_neighbors.size() == 0 || primary_node_neighbors.size() > 2)
843 mooseError(
"We must have either 1 or 2 primary side nodal neighbors, but we had ",
844 primary_node_neighbors.size());
851 const Elem * left_primary_elem = primary_node_neighbors[0];
852 const Elem * right_primary_elem =
853 (primary_node_neighbors.size() == 2) ? primary_node_neighbors[1] :
nullptr;
859 std::array<Real, 2> secondary_node_cps;
860 std::vector<Real> primary_node_cps(primary_node_neighbors.size());
863 for (
unsigned int nid = 0; nid < 2; ++nid)
864 secondary_node_cps[nid] = normal.cross(secondary_elem->point(nid) - new_pt)(2);
866 for (MooseIndex(primary_node_neighbors) mnn = 0; mnn < primary_node_neighbors.size(); ++mnn)
868 const Elem * primary_neigh = primary_node_neighbors[mnn];
869 Point opposite = (primary_neigh->node_ptr(0) == primary_node) ? primary_neigh->point(1)
870 : primary_neigh->point(0);
871 Point cp = normal.cross(opposite - new_pt);
872 primary_node_cps[mnn] = cp(2);
876 bool orientation1_valid =
false, orientation2_valid =
false;
878 if (primary_node_neighbors.size() == 2)
881 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.) &&
882 (secondary_node_cps[1] * primary_node_cps[1] > 0.);
884 orientation2_valid = (secondary_node_cps[0] * primary_node_cps[1] > 0.) &&
885 (secondary_node_cps[1] * primary_node_cps[0] > 0.);
887 else if (primary_node_neighbors.size() == 1)
890 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.);
891 orientation2_valid = (secondary_node_cps[1] * primary_node_cps[0] > 0.);
894 mooseError(
"Invalid primary node neighbors size ", primary_node_neighbors.size());
900 if (orientation1_valid && orientation2_valid)
902 "AutomaticMortarGeneration: Both orientations cannot simultaneously be valid.");
908 if (!orientation1_valid && !orientation2_valid)
910 static const auto invalid_orientation_id =
912 "AutomaticMortarGeneration",
913 "Unable to determine valid secondary-primary orientation. Consequently we will "
914 "consider projection of the primary node invalid and not split the mortar segment. "
915 "This situation can indicate there are very oblique projections between primary "
916 "(mortar) and secondary (non-mortar) surfaces for a good problem set up. It can also "
917 "mean your time step is too large.",
924 std::unique_ptr<Elem> new_elem_left;
926 new_elem_left = std::make_unique<Edge3>();
928 new_elem_left = std::make_unique<Edge2>();
930 new_elem_left->processor_id() = current_mortar_segment->processor_id();
931 new_elem_left->subdomain_id() = current_mortar_segment->subdomain_id();
932 new_elem_left->set_id(local_id_index++);
933 new_elem_left->set_unique_id(new_elem_left->id());
934 new_elem_left->set_node(0, current_mortar_segment->node_ptr(0));
935 new_elem_left->set_node(1, new_node);
938 std::unique_ptr<Elem> new_elem_right;
940 new_elem_right = std::make_unique<Edge3>();
942 new_elem_right = std::make_unique<Edge2>();
944 new_elem_right->processor_id() = current_mortar_segment->processor_id();
945 new_elem_right->subdomain_id() = current_mortar_segment->subdomain_id();
946 new_elem_right->set_id(local_id_index++);
947 new_elem_right->set_unique_id(new_elem_right->id());
948 new_elem_right->set_node(0, new_node);
949 new_elem_right->set_node(1, current_mortar_segment->node_ptr(1));
954 Point left_interior_point(0);
955 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
958 Real current_left_interior_eta =
959 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
961 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
962 n < current_mortar_segment->n_nodes();
965 current_mortar_segment->point(n);
969 "new_id must be the same on all processes");
971 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
972 new_elem_left->set_node(2, new_interior_node_left);
973 new_interior_node_left->set_unique_id(new_interior_left_id + node_unique_id_offset);
976 Point right_interior_point(0);
977 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
979 Real current_right_interior_eta =
980 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
982 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
983 n < current_mortar_segment->n_nodes();
986 current_mortar_segment->point(n);
990 "new_id must be the same on all processes");
992 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
993 new_elem_right->set_node(2, new_interior_node_right);
994 new_interior_node_right->set_unique_id(new_interior_id_right + node_unique_id_offset);
998 if (orientation2_valid)
999 std::swap(left_primary_elem, right_primary_elem);
1003 if (left_primary_elem)
1004 left_xi2 = (primary_node == left_primary_elem->node_ptr(0)) ? -1 : +1;
1005 if (right_primary_elem)
1006 right_xi2 = (primary_node == right_primary_elem->node_ptr(0)) ? -1 : +1;
1015 mooseError(
"MortarSegmentInfo not found for current_mortar_segment.");
1028 new_msinfo_left.
xi1_a = current_msinfo.
xi1_a;
1029 new_msinfo_left.
xi2_a = current_msinfo.
xi2_a;
1031 new_msinfo_left.
xi1_b = xi1;
1032 new_msinfo_left.
xi2_b = left_xi2;
1040 mortar_segment_set.insert(msm_new_elem);
1050 new_msinfo_right.
xi1_b = current_msinfo.
xi1_b;
1051 new_msinfo_right.
xi2_b = current_msinfo.
xi2_b;
1053 new_msinfo_right.
xi1_a = xi1;
1054 new_msinfo_right.
xi2_a = right_xi2;
1059 mortar_segment_set.insert(msm_new_elem);
1067 mortar_segment_set.erase(current_mortar_segment);
1083 Elem * primary_elem =
const_cast<Elem *
>(msinfo.
primary_elem);
1084 if (primary_elem ==
nullptr || abs(msinfo.
xi2_a) > 1.0 + TOLERANCE ||
1085 abs(msinfo.
xi2_b) > 1.0 + TOLERANCE)
1090 "We should have found the element");
1091 auto & msm_set = it->second;
1092 msm_set.erase(msm_elem);
1099 if (msm_set.empty())
1115 std::unordered_set<Node *> msm_connected_nodes;
1120 for (
auto & n : element->node_ref_range())
1121 msm_connected_nodes.insert(&n);
1124 if (!msm_connected_nodes.count(node))
1133 "All mortar segment elements should have valid "
1134 "primary element.");
1153 mortar_segment_mesh_writer.set_hdf5_writing(
false);
1155 std::array<std::string, 3> file_pieces = {
1158 "mortar_segment_mesh.e"};
1159 mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces,
"_"));
1165 const bool use_reference_interpolation =
1180 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1183 for (
const auto *
const el :
1184 _mesh.active_local_subdomain_elements_ptr_range(secondary_sub_id))
1185 local_secondary_sub_elems += el->n_sub_elem();
1186 for (
const auto *
const el :
_mesh.active_subdomain_elements_ptr_range(primary_sub_id))
1187 visible_primary_sub_elems += el->n_sub_elem();
1189 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1190 std::vector<dof_id_type> per_rank_bounds;
1191 _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
1192 dof_id_type start = 0;
1193 for (
const auto r : make_range(
_mesh.processor_id()))
1194 start += per_rank_bounds[r];
1201 dof_id_type next_elem_id = next_node_id;
1206 const auto primary_subd_id = pr.first;
1207 const auto secondary_subd_id = pr.second;
1212 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
1215 kd_tree.buildIndex();
1220 auto get_sub_elem_geometric_normal = [](
const std::vector<Point> & nodes)
1224 if (nodes.size() == 3)
1226 dxdxi = nodes[1] - nodes[0];
1227 dxdeta = nodes[2] - nodes[0];
1229 else if (nodes.size() == 4)
1233 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1234 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1237 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1238 "triangular and quadrilateral subpatches, but received ",
1242 Point geometric_normal = dxdxi.cross(dxdeta);
1243 const auto normal_norm = geometric_normal.norm();
1246 if (normal_norm <= TOLERANCE * dxdxi.norm() * dxdeta.norm())
1247 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1248 "degenerate subpatch.");
1250 geometric_normal /= normal_norm;
1251 return geometric_normal;
1257 for (MeshBase::const_element_iterator el =
_mesh.active_local_elements_begin(),
1258 end_el =
_mesh.active_local_elements_end();
1262 const Elem * secondary_side_elem = *el;
1264 const Real secondary_volume = secondary_side_elem->volume();
1267 if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1270 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1272 std::set<Elem *, CompareDofObjectsByID>{});
1273 libmesh_ignore(insertion_happened);
1274 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1276 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1277 secondary_side_elem->n_sub_elem());
1290 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1293 const auto sub_elem_nodes =
1299 std::vector<Point> nodes(sub_elem_nodes.size());
1302 for (
auto iv : make_range(sub_elem_nodes.size()))
1304 const auto n = sub_elem_nodes[iv];
1305 nodes[iv] = secondary_side_elem->point(n);
1306 center += secondary_side_elem->point(n);
1307 normal += nodal_normals[n];
1309 center /= sub_elem_nodes.size();
1310 normal = normal.unit();
1314 const Point averaged_normal = normal;
1315 normal = get_sub_elem_geometric_normal(nodes);
1316 if (normal * averaged_normal < 0)
1320 if (use_reference_interpolation)
1322 std::vector<Point> sub_elem_reference_points;
1323 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1324 for (
const auto node_index : sub_elem_nodes)
1325 sub_elem_reference_points.push_back(secondary_side_elem->master_point(node_index));
1327 mortar_segment_helper[sel] =
1328 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1329 std::move(sub_elem_reference_points),
1336 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1348 std::array<Real, 3> query_pt;
1350 switch (secondary_side_elem->type())
1354 center_point = mortar_segment_helper[0]->center();
1355 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1359 center_point = mortar_segment_helper[1]->center();
1360 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1363 center_point = mortar_segment_helper[4]->center();
1364 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1367 center_point = secondary_side_elem->point(8);
1368 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1372 "Face element type: ", secondary_side_elem->type(),
"not supported for 3D mortar");
1378 const std::size_t num_results = 3;
1381 std::vector<size_t> ret_index(num_results);
1382 std::vector<Real> out_dist_sqr(num_results);
1383 nanoflann::KNNResultSet<Real> result_set(num_results);
1384 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1388 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1392 bool primary_elem_found =
false;
1393 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1394 const bool use_geometric_subpatch_normals =
1398 const Real minimum_subpatch_normal_alignment =
1403 for (
auto r : make_range(result_set.size()))
1406 mooseAssert(abs((
_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1408 "Lower-dimensional element squared distance verification failed.");
1411 std::vector<const Elem *> & node_elems =
1415 for (
auto elem : node_elems)
1416 primary_elem_candidates.insert(elem);
1426 while (!primary_elem_candidates.empty())
1428 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1431 if (processed_primary_elems.count(primary_elem_candidate))
1433 primary_elem_candidates.erase(primary_elem_candidate);
1438 std::vector<Point> nodal_points;
1441 std::vector<std::vector<unsigned int>> elem_to_node_map;
1444 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1445 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1446 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1452 for (
auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1455 const auto sub_elem_nodes =
1459 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1460 for (
auto iv : make_range(sub_elem_nodes.size()))
1462 const auto n = sub_elem_nodes[iv];
1463 primary_sub_elem[iv] = primary_elem_candidate->point(n);
1465 Point primary_sub_elem_normal;
1466 if (use_geometric_subpatch_normals)
1467 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1469 std::vector<Point> sub_elem_reference_points;
1470 if (use_reference_interpolation)
1472 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1473 for (
const auto node_index : sub_elem_nodes)
1474 sub_elem_reference_points.push_back(primary_elem_candidate->master_point(node_index));
1478 for (
auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1483 if (use_geometric_subpatch_normals &&
1484 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1485 minimum_subpatch_normal_alignment)
1495 const auto segments_before_helper = elem_to_node_map.size();
1496 if (use_reference_interpolation)
1497 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1498 sub_elem_reference_points,
1501 elem_to_secondary_reference_points,
1502 elem_to_primary_reference_points,
1503 TOLERANCE * secondary_volume);
1505 mortar_segment_helper[s_el]->getMortarSegments(
1506 primary_sub_elem, nodal_points, elem_to_node_map);
1509 for (
auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1510 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1515 processed_primary_elems.insert(primary_elem_candidate);
1516 primary_elem_candidates.erase(primary_elem_candidate);
1519 if (!elem_to_node_map.empty())
1521 if (sub_elem_map.size() != elem_to_node_map.size())
1522 mooseError(
"The mortar segment subpatch map is not aligned with the mortar segment "
1523 "connectivity map.");
1524 if (use_reference_interpolation &&
1525 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1526 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1527 mooseError(
"The mortar segment reference-point maps are not aligned with the mortar "
1528 "segment connectivity map.");
1532 bool seed_breadth_first_search =
false;
1533 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(),
false);
1534 for (
const auto el : index_range(elem_to_node_map))
1536 const auto & node_map = elem_to_node_map[el];
1537 if (node_map.size() != 3)
1539 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1543 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1544 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1545 retained_mortar_segments[el] =
1546 0.5 * e1.cross(e2).norm() / secondary_volume >= TOLERANCE;
1547 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1550 if (seed_breadth_first_search)
1554 if (!primary_elem_found)
1556 primary_elem_found =
true;
1557 primary_elem_candidates.clear();
1561 for (
auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1564 if (neighbor ==
nullptr || neighbor->subdomain_id() != primary_subd_id)
1567 if (processed_primary_elems.count(neighbor))
1570 primary_elem_candidates.insert(neighbor);
1577 std::vector<Node *> new_nodes;
1580 std::vector<bool> retained_nodes(nodal_points.size(),
false);
1581 for (
const auto el : index_range(elem_to_node_map))
1582 if (retained_mortar_segments[el])
1583 for (
const auto node : elem_to_node_map[el])
1584 retained_nodes[node] =
true;
1586 new_nodes.resize(nodal_points.size(),
nullptr);
1587 for (
const auto node : index_range(nodal_points))
1588 if (retained_nodes[node])
1590 nodal_points[node], next_node_id++, secondary_side_elem->processor_id());
1593 for (
auto el : index_range(elem_to_node_map))
1595 if (!retained_mortar_segments[el])
1598 std::unique_ptr<Elem> new_elem;
1599 if (elem_to_node_map[el].size() == 3)
1600 new_elem = std::make_unique<Tri3>();
1602 mooseError(
"Active mortar segments only supports TRI elements, 3 nodes expected "
1604 elem_to_node_map[el].size(),
1607 new_elem->processor_id() = secondary_side_elem->processor_id();
1608 new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1609 new_elem->set_id(next_elem_id++);
1612 for (
auto i : index_range(elem_to_node_map[el]))
1613 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1616 if (new_elem->volume() / secondary_volume < TOLERANCE)
1622 msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1623 msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1634 if (use_reference_interpolation)
1637 elem_to_primary_reference_points[el]};
1642 secondary_to_msm_element_set.insert(msm_new_elem);
1653 if (use_geometric_subpatch_normals)
1657 if (secondary_to_msm_element_set.empty())
1659 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1660 " a corresponding primary element; this may be expected depending on"
1661 " problem geometry but may indicate a failure of the element search"
1665 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1666 if (mortar_segment_helper[sel]->remainder() == 1.0)
1668 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1669 " a corresponding primary element; this may be expected depending on"
1670 " problem geometry but may indicate a failure of the element search"
1673 if (secondary_to_msm_element_set.empty())
1678 mooseAssert(!use_reference_interpolation ||
1680 "Mortar segment info and reference-point maps must remain aligned.");
1694 if (msm_el->type() != TRI3)
1695 msm_el->subdomain_id()++;
1701 if (msm_el->type() != TRI3)
1702 msm_el->subdomain_id()--;
1717 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1728 const Elem * secondary_elem = pr.second.secondary_elem;
1729 const Elem * primary_elem = pr.second.primary_elem;
1732 coupling_info[secondary_elem->processor_id()].emplace_back(
1733 secondary_elem->id(), secondary_elem->interior_parent()->id());
1734 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1738 secondary_elem->interior_parent()->id());
1741 coupling_info[secondary_elem->processor_id()].emplace_back(
1742 secondary_elem->id(), primary_elem->interior_parent()->id());
1743 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1747 primary_elem->interior_parent()->id());
1750 coupling_info[secondary_elem->processor_id()].emplace_back(secondary_elem->id(),
1751 primary_elem->id());
1752 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1758 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1759 secondary_elem->interior_parent()->id(), secondary_elem->id());
1762 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1763 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1766 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1767 primary_elem->interior_parent()->id(), secondary_elem->id());
1770 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1771 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1775 auto action_functor =
1776 [
this](processor_id_type,
1777 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1779 for (
auto [i, j] : coupling_info)
1785std::vector<AutomaticMortarGeneration::MsmSubdomainStats>
1788 std::vector<MsmSubdomainStats> result;
1792 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1796 for (
const auto *
const secondary_el :
1797 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1799 secondary.push_back(secondary_el->volume());
1804 for (
const auto *
const msm_elem : it->second)
1806 msm.push_back(msm_elem->volume());
1810 if (msm_info.primary_elem)
1812 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1813 mooseError(
"Unhandled primary-secondary pairing when computing mortar segment "
1814 "statistics. This could happen if you have the same secondary "
1815 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1816 "primary subdomain IDs. Contact a MOOSE developer for help.");
1817 if (
const auto [it, inserted] =
1818 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1820 it->second = msm_info.primary_elem->volume();
1823 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1824 "Volumes should be consistent");
1829 _mesh.comm().set_union(primary_elems_to_volume);
1830 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1831 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1832 primary.reserve(primary_elems_to_volume.size());
1833 for (
const auto [_, volume] : primary_elems_to_volume)
1834 primary.push_back(volume);
1851 result.push_back(stats);
1856 primary_elems_to_volume.clear();
1867 if (
_mesh.processor_id() != 0)
1870 Moose::out <<
"Mortar Interface Statistics:" << std::endl;
1871 for (
const auto & stats : all_stats)
1873 std::vector<std::string> col_names = {
"mesh",
"n_elems",
"max",
"min",
"median"};
1874 std::vector<std::string> subds = {
"secondary_lower",
"primary_lower",
"mortar_segment"};
1875 std::vector<size_t> n_elems = {
1876 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1877 std::vector<Real> maxs = {
1878 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1879 std::vector<Real> mins = {
1880 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1881 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1882 stats.primary_lower_median_volume,
1883 stats.msm_median_volume};
1887 for (
auto i : index_range(subds))
1890 table.
addData<std::string>(col_names[0], subds[i]);
1891 table.
addData<
size_t>(col_names[1], n_elems[i]);
1892 table.
addData<Real>(col_names[2], maxs[i]);
1893 table.
addData<Real>(col_names[3], mins[i]);
1894 table.
addData<Real>(col_names[4], medians[i]);
1897 Moose::out <<
"secondary subdomain: " << stats.secondary_subd_id
1898 <<
" \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1915 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
1917 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1918 const auto my_pid =
_mesh.processor_id();
1921 std::unordered_set<dof_id_type> inactive_node_ids;
1923 std::unordered_map<const Elem *, Real> active_volume{};
1926 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(pr.second))
1927 active_volume[el] = 0.;
1935 active_volume[secondary_elem] += msm_elem->volume();
1941 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(pr.second))
1943 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1946 for (
auto n : make_range(el->n_nodes()))
1947 inactive_node_ids.insert(el->node_id(n));
1953 const auto secondary_subd_id = pr.second;
1956 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1959 const auto pid = el->processor_id();
1966 for (
const auto n : make_range(el->n_nodes()))
1968 const auto node_id = el->node_id(n);
1969 if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1970 proc_to_inactive_nodes_set[pid].insert(node_id);
1978 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1979 for (
const auto & proc_set : proc_to_inactive_nodes_set)
1980 proc_to_inactive_nodes_vector[proc_set.first].insert(
1981 proc_to_inactive_nodes_vector[proc_set.first].end(),
1982 proc_set.second.begin(),
1983 proc_set.second.end());
1986 auto action_functor = [
this, &inactive_node_ids](
const processor_id_type pid,
1987 const std::vector<dof_id_type> & sent_data)
1989 if (pid ==
_mesh.processor_id())
1990 mooseError(
"Should not be communicating with self.");
1991 for (
const auto pr : sent_data)
1992 inactive_node_ids.insert(pr);
1997 for (
const auto node_id : inactive_node_ids)
2010 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
2011 const auto my_pid =
_mesh.processor_id();
2014 std::unordered_set<dof_id_type> active_local_nodes;
2022 for (
auto n : make_range(secondary_elem->n_nodes()))
2023 active_local_nodes.insert(secondary_elem->node_id(n));
2029 const auto secondary_subd_id = pr.second;
2032 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
2035 const auto pid = el->processor_id();
2042 for (
const auto n : make_range(el->n_nodes()))
2044 const auto node_id = el->node_id(n);
2045 if (active_local_nodes.find(node_id) != active_local_nodes.end())
2046 proc_to_active_nodes_set[pid].insert(node_id);
2054 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
2055 for (
const auto & proc_set : proc_to_active_nodes_set)
2057 proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
2058 for (
const auto node_id : proc_set.second)
2059 proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
2063 auto action_functor = [
this, &active_local_nodes](
const processor_id_type pid,
2064 const std::vector<dof_id_type> & sent_data)
2066 if (pid ==
_mesh.processor_id())
2067 mooseError(
"Should not be communicating with self.");
2068 active_local_nodes.insert(sent_data.begin(), sent_data.end());
2077 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2079 for (
const auto n : make_range(el->n_nodes()))
2080 if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
2089 std::unordered_set<const Elem *> active_local_elems;
2095 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
2097 std::unordered_map<const Elem *, Real> active_volume;
2106 active_volume[secondary_elem] += msm_elem->volume();
2117 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
2121 active_local_elems.insert(secondary_elem);
2127 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2129 if (active_local_elems.find(el) == active_local_elems.end())
2137 const auto dim =
_mesh.mesh_dimension();
2139 mooseAssert(
dim == 2 ||
dim == 3,
2140 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2141 "mortar constraints on 2D or 3D meshes.");
2150 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2158 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2159 end_el =
_mesh.active_elements_end();
2163 const Elem * secondary_elem = *el;
2171 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2172 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(
dim, nnx_fe_type));
2173 nnx_fe_face->attach_quadrature_rule(&qface);
2174 const auto & face_normals = nnx_fe_face->get_normals();
2175 const auto & face_points = nnx_fe_face->get_xyz();
2177 const auto & JxW = nnx_fe_face->get_JxW();
2181 const Elem * interior_parent = secondary_elem->interior_parent();
2182 mooseAssert(interior_parent,
2183 "No interior parent exists for element "
2184 << secondary_elem->id()
2185 <<
". There may be a problem with your sideset set-up.");
2193 auto s = interior_parent->which_side_am_i(secondary_elem);
2196 nnx_fe_face->reinit(interior_parent, s);
2201 const auto qpoint_to_secondary_node =
2202 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2204 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2205 "Face nodal geometry vectors must have the same size.");
2207 for (
const auto qp : make_range(face_points.size()))
2209 const auto n = qpoint_to_secondary_node[qp];
2210 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2211 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2215 for (
const auto & pr : node_to_normals_map)
2218 const auto & node_id = pr.first;
2219 const auto & normals_and_weights_vec = pr.second;
2222 for (
const auto & norm_and_weight : normals_and_weights_vec)
2223 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2224 nodal_normal = nodal_normal.unit();
2228 Point nodal_tangent_one;
2229 Point nodal_tangent_two;
2239 Point & nodal_tangent_one,
2240 Point & nodal_tangent_two)
const
2244 mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.norm(), 1),
2245 "The input nodal normal should have unity norm");
2247 const Real nx = nodal_normal(0);
2248 const Real ny = nodal_normal(1);
2249 const Real nz = nodal_normal(2);
2254 const Point h_vector(nx + 1.0, ny, nz);
2259 if (abs(h_vector(0)) < TOLERANCE)
2261 nodal_tangent_one(0) = 0;
2262 nodal_tangent_one(1) = 1;
2263 nodal_tangent_one(2) = 0;
2265 nodal_tangent_two(0) = 0;
2266 nodal_tangent_two(1) = 0;
2267 nodal_tangent_two(2) = -1;
2272 const Real h = h_vector.norm();
2274 nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
2275 nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
2276 nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2278 nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
2279 nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2280 nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
2296 const Node & secondary_node,
2297 const Node & primary_node,
2298 const std::vector<const Elem *> * secondary_node_neighbors,
2299 const std::vector<const Elem *> * primary_node_neighbors,
2300 const VectorValue<Real> & nodal_normal,
2301 const Elem & candidate_element,
2302 std::set<const Elem *> & rejected_elem_candidates)
2304 if (!secondary_node_neighbors)
2306 if (!primary_node_neighbors)
2309 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(),
false);
2337 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2339 for (
const auto nn : index_range(*secondary_node_neighbors))
2341 const Elem *
const secondary_neigh = (*secondary_node_neighbors)[nn];
2342 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2343 ? secondary_neigh->point(1)
2344 : secondary_neigh->point(0);
2345 const Point cp = nodal_normal.cross(opposite - secondary_node);
2346 secondary_node_neighbor_cps[nn] = cp(2);
2349 for (
const auto nn : index_range(*primary_node_neighbors))
2351 const Elem *
const primary_neigh = (*primary_node_neighbors)[nn];
2352 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2353 : primary_neigh->point(0);
2354 const Point cp = nodal_normal.cross(opposite - primary_node);
2355 primary_node_neighbor_cps[nn] = cp(2);
2359 bool found_match =
false;
2360 for (
const auto snn : index_range(*secondary_node_neighbors))
2361 for (
const auto mnn : index_range(*primary_node_neighbors))
2362 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2365 if (primary_elems_mapped[mnn])
2367 primary_elems_mapped[mnn] =
true;
2371 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2372 const auto secondary_key =
2373 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2374 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2379 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2381 const auto primary_key =
2382 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2383 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2391 rejected_elem_candidates.insert(&candidate_element);
2398 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2399 for (
const auto i : index_range(primary_elems_mapped))
2400 if (!primary_elems_mapped[i])
2403 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2404 std::make_pair(1,
nullptr));
2412 SubdomainID lower_dimensional_primary_subdomain_id,
2413 SubdomainID lower_dimensional_secondary_subdomain_id)
2420 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2423 kd_tree.buildIndex();
2425 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2426 end_el =
_mesh.active_elements_end();
2430 const Elem * secondary_side_elem = *el;
2433 if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2440 for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2443 const Node * secondary_node = secondary_side_elem->node_ptr(n);
2447 const std::vector<const Elem *> & secondary_node_neighbors =
2452 bool is_mapped =
true;
2453 for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2456 auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2472 std::array<Real, 3> query_pt = {
2473 {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2479 const std::size_t num_results = 3;
2482 std::vector<size_t> ret_index(num_results);
2483 std::vector<Real> out_dist_sqr(num_results);
2484 nanoflann::KNNResultSet<Real> result_set(num_results);
2485 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2489 bool projection_succeeded =
false;
2493 std::set<const Elem *> rejected_primary_elem_candidates;
2499 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2502 mooseAssert(abs((
_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2503 out_dist_sqr[r]) <= TOLERANCE,
2504 "Lower-dimensional element squared distance verification failed.");
2508 std::vector<const Elem *> & primary_elem_candidates =
2512 for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2514 const Elem * primary_elem_candidate = primary_elem_candidates[e];
2517 if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2521 const auto order = primary_elem_candidate->default_order();
2523 unsigned int current_iterate = 0, max_iterates = 10;
2528 VectorValue<DualNumber<Real>> x2(0);
2529 for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2530 n < primary_elem_candidate->n_nodes();
2534 const auto u = x2 - (*secondary_node);
2535 const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2540 if (F.derivatives())
2542 Real dxi2 = -F.value() / F.derivatives();
2550 current_iterate = max_iterates;
2551 }
while (++current_iterate < max_iterates);
2553 Real xi2 = xi2_dn.value();
2566 if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 *
_xi_tolerance) &&
2567 (abs((primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2577 const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2578 : primary_elem_candidate->node_ptr(1);
2579 const bool created_mortar_segment =
2582 &secondary_node_neighbors,
2585 *primary_elem_candidate,
2586 rejected_primary_elem_candidates);
2588 if (!created_mortar_segment)
2594 for (MooseIndex(secondary_node_neighbors) nn = 0;
2595 nn < secondary_node_neighbors.size();
2598 const Elem * neigh = secondary_node_neighbors[nn];
2599 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2601 const Node * neigh_node = neigh->node_ptr(nid);
2602 if (secondary_node == neigh_node)
2604 auto key = std::make_pair(neigh_node, neigh);
2605 auto val = std::make_pair(xi2, primary_elem_candidate);
2612 projection_succeeded =
true;
2617 rejected_primary_elem_candidates.insert(primary_elem_candidate);
2620 if (projection_succeeded)
2624 if (!projection_succeeded)
2628 _console <<
"Failed to find primary Elem into which secondary node "
2629 << cast_ref<const Point &>(*secondary_node) <<
", id '" << secondary_node->id()
2630 <<
"', projects onto\n"
2649 <<
" secondary nodes were successfully projected\n"
2666 SubdomainID lower_dimensional_primary_subdomain_id,
2667 SubdomainID lower_dimensional_secondary_subdomain_id)
2674 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2677 kd_tree.buildIndex();
2679 std::unordered_set<dof_id_type> primary_nodes_visited;
2681 for (
const auto & primary_side_elem :
_mesh.active_element_ptr_range())
2684 if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2689 for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2693 const Node * primary_node = primary_side_elem->node_ptr(n);
2696 const std::vector<const Elem *> & primary_node_neighbors =
2704 std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2705 if (!primary_nodes_visited.insert(primary_node->id()).second ||
2710 Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2716 const size_t num_results = 3;
2719 std::vector<size_t> ret_index(num_results);
2720 std::vector<Real> out_dist_sqr(num_results);
2721 nanoflann::KNNResultSet<Real> result_set(num_results);
2722 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2726 bool projection_succeeded =
false;
2731 std::set<const Elem *> rejected_secondary_elem_candidates;
2735 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2738 mooseAssert(abs((
_mesh.point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2740 "Lower-dimensional element squared distance verification failed.");
2744 const std::vector<const Elem *> & secondary_elem_candidates =
2748 for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2750 const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2753 if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2756 std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2757 for (
const auto n : make_range(secondary_elem_candidate->n_nodes()))
2765 auto && order = secondary_elem_candidate->default_order();
2766 unsigned int current_iterate = 0, max_iterates = 10;
2768 VectorValue<DualNumber<Real>> normals(0);
2776 VectorValue<DualNumber<Real>> x1(0);
2777 for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2778 n < secondary_elem_candidate->n_nodes();
2782 x1 += phi * secondary_elem_candidate->point(n);
2783 normals += phi * nodal_normals[n];
2786 const auto u = x1 - (*primary_node);
2788 const auto F = u(0) * normals(1) - u(1) * normals(0);
2796 Real dxi1 = -F.value() / F.derivatives();
2801 }
while (++current_iterate < max_iterates);
2803 Real xi1 = xi1_dn.value();
2807 if ((current_iterate < max_iterates) && (abs(xi1) <= 1. +
_xi_tolerance) &&
2808 (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2822 const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2823 : secondary_elem_candidate->node_ref(1);
2824 bool created_mortar_segment =
false;
2831 &primary_node_neighbors,
2833 *secondary_elem_candidate,
2834 rejected_secondary_elem_candidates);
2836 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2838 if (!created_mortar_segment)
2854 const Elem * neigh = primary_node_neighbors[0];
2855 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2857 const Node * neigh_node = neigh->node_ptr(nid);
2858 if (primary_node == neigh_node)
2860 auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2861 auto val = std::make_pair(xi1, secondary_elem_candidate);
2867 projection_succeeded =
true;
2873 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2877 if (projection_succeeded)
2881 if (!projection_succeeded &&
_debug)
2883 _console <<
"\nFailed to find point from which primary node "
2884 << cast_ref<const Point &>(*primary_node) <<
" was projected." << std::endl
2891std::vector<AutomaticMortarGeneration::MortarFilterIter>
2898 const auto & secondary_elems = secondary_it->second;
2899 std::vector<MortarFilterIter> ret;
2900 ret.reserve(secondary_elems.size());
2902 for (
const auto i : index_range(secondary_elems))
2904 auto *
const secondary_elem = secondary_elems[i];
2910 mooseAssert(secondary_elem->active(),
2911 "We loop over active elements when building the mortar segment mesh, so we golly "
2912 "well hope this is active.");
2913 mooseAssert(!msm_it->second.empty(),
2914 "We should have removed all secondaries from this map if they do not have any "
2915 "mortar segments associated with them.");
2916 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.
MortarSegmentTriangulationMode
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
This class is a container/interface for the objects involved in automatic generation of mortar spaces...
const Elem * getSecondaryLowerdElemFromSecondaryElem(dof_id_type secondary_elem_id) const
Return lower dimensional secondary element given its interior parent.
Real _newton_tolerance
Newton solve tolerance for node projections.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
const MortarSegmentReferencePoints & mortarSegmentReferencePoints(const Elem &mortar_segment_elem) const
Return the parent-face reference coordinates for a mortar segment.
std::unordered_map< const Elem *, unsigned int > _lower_elem_to_side_id
Keeps track of the mapping between lower-dimensional elements and the side_id of the interior_parent ...
std::unordered_map< const Elem *, MortarSegmentReferencePoints > _msm_elem_to_reference_points
Reference-coordinate data used only by the reference-interpolation mapping mode.
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_secondary_elem_map
Map from nodes to connected lower-dimensional elements on the secondary/primary subdomains.
std::set< BoundaryID > _secondary_requested_boundary_ids
The boundary ids corresponding to all the secondary surfaces.
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
Real _xi_tolerance
Tolerance for checking projection xi values.
void outputMortarMesh()
Write the mortar segment mesh to exodus.
void computeNodalGeometry()
Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the E...
const Mortar3DQuadraturePointMapping _mortar_3d_qp_mapping
Method used to map 3D mortar segment quadrature points to primary and secondary faces.
void msmStatistics()
Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally)
std::vector< std::pair< BoundaryID, BoundaryID > > _primary_secondary_boundary_id_pairs
A list of primary/secondary boundary id pairs corresponding to each side of the mortar interface.
std::unique_ptr< InputParameters > _output_params
Storage for the input parameters used by the mortar nodal geometry output.
void buildMortarSegmentMesh()
Builds the mortar segment mesh once the secondary and primary node projections have been completed.
std::unordered_set< const Node * > _inactive_local_lm_nodes
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
void projectSecondaryNodes()
Project secondary nodes (find xi^(2) values) to the closest points on the primary surface.
void buildMortarSegmentMesh3d()
Builds the mortar segment mesh once the secondary and primary node projections have been completed.
void computeInactiveLMElems()
Get list of secondary elems without any corresponding primary elements.
std::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
void buildCouplingInformation()
build the _mortar_interface_coupling data
const Mortar3DSubpatchPlane _mortar_3d_subpatch_plane
Method used to define the local projection planes for 3D secondary subpatches.
void householderOrthogolization(const Point &normal, Point &tangent_one, Point &tangent_two) const
Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors.
std::vector< Point > getNormals(const Elem &secondary_elem, const std::vector< Point > &xi1_pts) const
Compute the normals at given reference points on a secondary element.
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
void initOutput()
initialize mortar-mesh based output
std::unordered_map< const Node *, std::array< Point, 2 > > _secondary_node_to_hh_nodal_tangents
Container for storing the nodal tangent/binormal vectors associated with each secondary node (Househo...
bool processAlignedNodes(const Node &secondary_node, const Node &primary_node, const std::vector< const Elem * > *secondary_node_neighbors, const std::vector< const Elem * > *primary_node_neighbors, const VectorValue< Real > &nodal_normal, const Elem &candidate_element, std::set< const Elem * > &rejected_element_candidates)
Process aligned nodes.
void projectSecondaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function responsible for projecting secondary nodes onto primary elements for a single primary...
std::vector< MsmSubdomainStats > computeMsmStatistics()
Computes mortar segment mesh statistics and returns one entry per subdomain pair.
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
const bool _debug
Whether to print debug output.
std::vector< std::pair< SubdomainID, SubdomainID > > _primary_secondary_subdomain_id_pairs
A list of primary/secondary subdomain id pairs corresponding to each side of the mortar interface.
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
Compute on-the-fly mapping from secondary interior parent nodes to lower dimensional nodes.
void buildNodeToElemMaps()
Once the secondary_requested_boundary_ids and primary_requested_boundary_ids containers have been fil...
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
std::string mortarInterfaceName() const
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.
void clear()
Clears the mortar segment mesh and accompanying data structures.
MooseApp & _app
The Moose app.
std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > _mortar_interface_coupling
Used by the AugmentSparsityOnInterface functor to determine whether a given Elem is coupled to any ot...
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
Compute on-the-fly mapping from primary interior parent nodes to its corresponding lower dimensional ...
std::unordered_map< std::pair< const Node *, const Elem * >, std::pair< Real, const Elem * > > _secondary_node_and_elem_to_xi2_primary_elem
Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2),...
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
AutomaticMortarGeneration(MooseApp &app, MeshBase &mesh_in, const std::pair< BoundaryID, BoundaryID > &boundary_key, const std::pair< SubdomainID, SubdomainID > &subdomain_key, bool on_displaced, bool periodic, const bool debug, const bool correct_edge_dropping, const Real minimum_projection_angle, const Mortar3DSubpatchPlane mortar_3d_subpatch_plane, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles, const Mortar3DQuadraturePointMapping mortar_3d_qp_mapping=Mortar3DQuadraturePointMapping::NORMAL_PROJECTION)
Must be constructed with a reference to the Mesh we are generating mortar spaces for.
std::map< std::tuple< dof_id_type, const Node *, const Elem * >, std::pair< Real, const Elem * > > _primary_node_and_elem_to_xi1_secondary_elem
Same type of container, but for mapping (Primary Node ID, Primary Node, Primary Elem) -> (xi^(1),...
const bool _on_displaced
Whether this object is on the displaced mesh.
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...
const bool _distributed
Whether the mortar segment mesh is distributed.
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents(const Elem &secondary_elem) const
Compute the two nodal tangents, which are built on-the-fly.
std::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
std::unordered_map< dof_id_type, const Elem * > _secondary_element_to_secondary_lowerd_element
Map from full dimensional secondary element id to lower dimensional secondary element.
const bool _triangulate_triangles
Whether already-triangular clipped polygons should still be centroid-subdivided.
std::set< SubdomainID > _primary_boundary_subdomain_ids
void projectPrimaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped 3D mortar polygons.
std::set< BoundaryID > _primary_requested_boundary_ids
The boundary ids corresponding to all the primary surfaces.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > _secondary_elems_to_mortar_segments
We maintain a mapping from lower-dimensional secondary elements in the original mesh to (sets of) ele...
const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & secondariesToMortarSegments() const
void projectPrimaryNodes()
(Inverse) project primary nodes to the points on the secondary surface where they would have come fro...
void computeInactiveLMNodes()
Get list of secondary nodes that don't contribute to interaction with any primary element.
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments.
An inteface for the _console for outputting to the Console object.
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
Base class for MOOSE-based applications.
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
OutputWarehouse & getOutputWarehouse()
Get the OutputWarehouse objects.
FEProblemBase & feProblem() const
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
virtual T maximum() const
virtual T minimum() const
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
std::unique_ptr< NumericVector< Number > > solution
const DofMap & get_dof_map() const
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.
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
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