23#include "libmesh/mesh_tools.h"
24#include "libmesh/explicit_system.h"
25#include "libmesh/numeric_vector.h"
26#include "libmesh/elem.h"
27#include "libmesh/node.h"
28#include "libmesh/dof_map.h"
29#include "libmesh/edge_edge2.h"
30#include "libmesh/edge_edge3.h"
31#include "libmesh/face_tri3.h"
32#include "libmesh/face_tri6.h"
33#include "libmesh/face_tri7.h"
34#include "libmesh/face_quad4.h"
35#include "libmesh/face_quad8.h"
36#include "libmesh/face_quad9.h"
37#include "libmesh/exodusII_io.h"
38#include "libmesh/quadrature_gauss.h"
39#include "libmesh/quadrature_nodal.h"
40#include "libmesh/distributed_mesh.h"
41#include "libmesh/replicated_mesh.h"
42#include "libmesh/enum_to_string.h"
43#include "libmesh/statistics.h"
44#include "libmesh/equation_systems.h"
46#include "metaphysicl/dualnumber.h"
60#if NANOFLANN_VERSION < 0x150
74std::vector<unsigned int>
75nodalQuadraturePointToSecondaryNodeMap(
const Elem & secondary_elem,
76 const std::vector<Point> & q_points)
78 const auto n_nodes = secondary_elem.n_nodes();
82 " points for secondary mortar element ",
85 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
86 ", but the element has ",
90 const auto invalid_node = std::numeric_limits<unsigned int>::max();
91 std::vector<unsigned int> qpoint_to_node(
n_nodes, invalid_node);
92 std::vector<bool> node_used(
n_nodes,
false);
94 const Real element_size = secondary_elem.hmax();
95 mooseAssert(element_size > 0,
96 "Secondary mortar element "
97 << secondary_elem.id() <<
" of type "
98 << libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type())
99 <<
" has a non-positive hmax and cannot be used for nodal quadrature point "
106 const Real matching_tol = 100 * TOLERANCE * element_size;
107 const Real matching_tol_sq = matching_tol * matching_tol;
112 for (
const auto qp : make_range(q_points.size()))
114 unsigned int closest_node = invalid_node;
115 Real closest_dist_sq = std::numeric_limits<Real>::max();
116 Real second_closest_dist_sq = std::numeric_limits<Real>::max();
118 for (
const auto n : make_range(
n_nodes))
123 const Real dist_sq = (q_points[qp] - secondary_elem.point(n)).norm_sq();
124 if (dist_sq < closest_dist_sq)
126 second_closest_dist_sq = closest_dist_sq;
127 closest_dist_sq = dist_sq;
130 else if (dist_sq < second_closest_dist_sq)
131 second_closest_dist_sq = dist_sq;
134 if (closest_node == invalid_node || closest_dist_sq > matching_tol_sq)
135 mooseError(
"Could not match nodal quadrature point ",
139 " to a node on secondary mortar element ",
142 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
143 ". The nearest unmatched node distance is ",
144 std::sqrt(closest_dist_sq),
145 ", which exceeds the tolerance ",
149 if (second_closest_dist_sq <= matching_tol_sq)
154 " does not map uniquely to secondary mortar element ",
157 libMesh::Utility::enum_to_string<ElemType>(secondary_elem.type()),
158 ". Two unmatched nodes are within the matching tolerance ",
162 qpoint_to_node[qp] = closest_node;
163 node_used[closest_node] =
true;
169 std::vector<unsigned int> node_to_qpoint(
n_nodes, invalid_node);
170 for (
const auto qp : make_range(q_points.size()))
172 const auto mapped_node = qpoint_to_node[qp];
173 mooseAssert(mapped_node != invalid_node && mapped_node <
n_nodes,
174 "Invalid secondary node mapping for nodal quadrature point " << qp <<
".");
175 mooseAssert(node_to_qpoint[mapped_node] == invalid_node,
176 "Secondary node " << mapped_node <<
" on mortar element " << secondary_elem.id()
177 <<
" was matched to both nodal quadrature point "
178 << node_to_qpoint[mapped_node] <<
" and " << qp <<
".");
179 node_to_qpoint[mapped_node] = qp;
182 unsigned int candidate_count = 0;
183 unsigned int candidate_node = invalid_node;
184 for (
const auto n : make_range(
n_nodes))
185 if ((q_points[qp] - secondary_elem.point(n)).norm_sq() <= matching_tol_sq)
191 mooseAssert(candidate_count == 1,
192 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.id()
193 <<
" has " << candidate_count
194 <<
" secondary node candidates within tolerance "
195 << matching_tol <<
".");
196 mooseAssert(candidate_node == mapped_node,
197 "Nodal quadrature point " << qp <<
" on mortar element " << secondary_elem.id()
198 <<
" was matched to node " << mapped_node
199 <<
", but the full candidate search found node "
200 << candidate_node <<
".");
203 for (
const auto n : make_range(
n_nodes))
205 mooseAssert(node_to_qpoint[n] != invalid_node,
206 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
207 <<
" was not matched to a nodal quadrature point.");
210 unsigned int candidate_count = 0;
211 unsigned int candidate_qp = invalid_node;
212 for (
const auto qp : make_range(q_points.size()))
213 if ((q_points[qp] - secondary_elem.point(n)).norm_sq() <= matching_tol_sq)
219 mooseAssert(candidate_count == 1,
220 "Secondary node " << n <<
" on mortar element " << secondary_elem.id() <<
" has "
222 <<
" nodal quadrature point candidates within tolerance "
223 << matching_tol <<
".");
224 mooseAssert(candidate_qp == node_to_qpoint[n],
225 "Secondary node " << n <<
" on mortar element " << secondary_elem.id()
226 <<
" was matched to nodal quadrature point " << node_to_qpoint[n]
227 <<
", but the full candidate search found point " << candidate_qp
232 return qpoint_to_node;
258 mooseError(
"No entries found in the secondary node -> nodal geometry map.");
261 auto & subproblem =
_amg.
_on_displaced ? cast_ref<SubProblem &>(*problem.getDisplacedProblem())
262 : cast_ref<SubProblem &>(problem);
263 auto & nodal_normals_es = subproblem.es();
265 const std::string nodal_normals_sys_name =
"nodal_normals";
269 for (
const auto s : make_range(nodal_normals_es.n_systems()))
270 if (!nodal_normals_es.get_system(s).is_initialized())
276 &nodal_normals_es.template add_system<ExplicitSystem>(nodal_normals_sys_name);
294 nodal_normals_es.reinit();
298 std::vector<dof_id_type> dof_indices_nnx, dof_indices_nny, dof_indices_nnz;
299 std::vector<dof_id_type> dof_indices_t1x, dof_indices_t1y, dof_indices_t1z;
300 std::vector<dof_id_type> dof_indices_t2x, dof_indices_t2y, dof_indices_t2z;
302 for (MeshBase::const_element_iterator el =
_amg.
_mesh.elements_begin(),
307 const Elem * elem = *el;
311 dof_map.dof_indices(elem, dof_indices_nny,
_nny_var_num);
312 dof_map.dof_indices(elem, dof_indices_nnz,
_nnz_var_num);
314 dof_map.dof_indices(elem, dof_indices_t1x,
_t1x_var_num);
315 dof_map.dof_indices(elem, dof_indices_t1y,
_t1y_var_num);
316 dof_map.dof_indices(elem, dof_indices_t1z,
_t1z_var_num);
318 dof_map.dof_indices(elem, dof_indices_t2x,
_t2x_var_num);
319 dof_map.dof_indices(elem, dof_indices_t2y,
_t2y_var_num);
320 dof_map.dof_indices(elem, dof_indices_t2z,
_t2z_var_num);
326 for (MooseIndex(elem->n_vertices()) n = 0; n < elem->n_vertices(); ++n)
354 std::set<std::string> sys_names = {nodal_normals_sys_name};
357 ExodusII_IO nodal_normals_writer(
_amg.
_mesh);
360 nodal_normals_writer.set_hdf5_writing(
false);
362 nodal_normals_writer.write_equation_systems(
363 "nodal_geometry_only.e", nodal_normals_es, &sys_names);
390 const std::pair<BoundaryID, BoundaryID> & boundary_key,
391 const std::pair<SubdomainID, SubdomainID> & subdomain_key,
395 const bool correct_edge_dropping,
396 const Real minimum_projection_angle,
397 const Mortar3DSubpatchPlane mortar_3d_subpatch_plane,
399 const bool triangulate_triangles,
400 const Mortar3DQuadraturePointMapping mortar_3d_qp_mapping)
405 _on_displaced(on_displaced),
410 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
411 _correct_edge_dropping(correct_edge_dropping),
412 _minimum_projection_angle(minimum_projection_angle),
413 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
414 _triangulation_mode(triangulation_mode),
415 _triangulate_triangles(triangulate_triangles),
416 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
427 std::make_unique<DistributedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
430 std::make_unique<ReplicatedMesh>(
_mesh.comm(),
_mesh.spatial_dimension());
440 string_vec[2 * i] = std::to_string(primary_bnd_id);
441 string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
443 string_vec.back() =
_on_displaced ?
"displaced" :
"undisplaced";
444 return MooseUtils::join(string_vec,
"_");
489 mooseError(
"Mortar segment reference points were requested for mortar segment element ",
490 mortar_segment_elem.id(),
491 ", but the reference-interpolation mapping mode is not enabled.");
495 mooseError(
"No reference-point record was found for mortar segment element ",
496 mortar_segment_elem.id(),
497 ". The mortar segment info and reference-point maps are not aligned.");
499 return reference_points_it->second;
507 "Must specify secondary and primary boundary ids before building node-to-elem maps.");
510 for (
const auto & secondary_elem :
511 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
517 for (
const auto & nd : secondary_elem->node_ref_range())
520 vec.push_back(secondary_elem);
525 for (
const auto & primary_elem :
526 as_range(
_mesh.active_elements_begin(),
_mesh.active_elements_end()))
532 for (
const auto & nd : primary_elem->node_ref_range())
535 vec.push_back(primary_elem);
543 std::vector<Point> nodal_normals(secondary_elem.n_nodes());
544 for (
const auto n : make_range(secondary_elem.n_nodes()))
547 return nodal_normals;
552 dof_id_type secondary_elem_id)
const
555 "Map should locate secondary element");
560std::map<unsigned int, unsigned int>
563 std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
564 const Elem *
const secondary_ip = lower_secondary_elem.interior_parent();
565 mooseAssert(secondary_ip,
"This should be non-null");
567 for (
const auto i : make_range(lower_secondary_elem.n_nodes()))
569 const auto & nd = lower_secondary_elem.node_ref(i);
570 secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
573 return secondary_ip_i_to_lower_secondary_i;
576std::map<unsigned int, unsigned int>
578 const Elem & lower_primary_elem,
579 const Elem & primary_elem,
582 std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
584 for (
const auto i : make_range(lower_primary_elem.n_nodes()))
586 const auto & nd = lower_primary_elem.node_ref(i);
587 primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
590 return primary_ip_i_to_lower_primary_i;
593std::array<MooseUtils::SemidynamicVector<Point, 9>, 2>
597 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
598 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
600 for (
const auto n : make_range(secondary_elem.n_nodes()))
602 const auto & tangent_vectors =
604 nodal_tangents_one.push_back(tangent_vectors[0]);
605 nodal_tangents_two.push_back(tangent_vectors[1]);
608 return {{nodal_tangents_one, nodal_tangents_two}};
613 const std::vector<Real> & oned_xi1_pts)
const
615 std::vector<Point> xi1_pts(oned_xi1_pts.size());
616 for (
const auto qp : index_range(oned_xi1_pts))
617 xi1_pts[qp] = oned_xi1_pts[qp];
624 const std::vector<Point> & xi1_pts)
const
626 const auto mortar_dim =
_mesh.mesh_dimension() - 1;
627 const auto num_qps = xi1_pts.size();
629 std::vector<Point> normals(num_qps);
631 for (
const auto n : make_range(secondary_elem.n_nodes()))
632 for (
const auto qp : make_range(num_qps))
638 secondary_elem.default_order(),
640 cast_ref<
const TypeVector<Real> &>(xi1_pts[qp]));
641 normals[qp] += phi * nodal_normals[n];
645 for (
auto & normal : normals)
656 dof_id_type local_id_index = 0;
657 std::size_t node_unique_id_offset = 0;
665 const auto primary_bnd_id = pr.first;
666 const auto secondary_bnd_id = pr.second;
667 const auto num_primary_nodes =
668 std::distance(
_mesh.bid_nodes_begin(primary_bnd_id),
_mesh.bid_nodes_end(primary_bnd_id));
669 const auto num_secondary_nodes = std::distance(
_mesh.bid_nodes_begin(secondary_bnd_id),
670 _mesh.bid_nodes_end(secondary_bnd_id));
671 mooseAssert(num_primary_nodes,
672 "There are no primary nodes on boundary ID "
673 << primary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
674 mooseAssert(num_secondary_nodes,
675 "There are no secondary nodes on boundary ID "
676 << secondary_bnd_id <<
". Does that bondary ID even exist on the mesh?");
678 node_unique_id_offset += num_primary_nodes + 2 * num_secondary_nodes;
682 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
683 end_el =
_mesh.active_elements_end();
687 const Elem * secondary_elem = *el;
693 std::vector<Node *> new_nodes;
694 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
697 secondary_elem->point(n), secondary_elem->node_id(n), secondary_elem->processor_id()));
698 Node *
const new_node = new_nodes.back();
699 new_node->set_unique_id(new_node->id() + node_unique_id_offset);
702 std::unique_ptr<Elem> new_elem;
703 if (secondary_elem->default_order() == SECOND)
704 new_elem = std::make_unique<Edge3>();
706 new_elem = std::make_unique<Edge2>();
708 new_elem->processor_id() = secondary_elem->processor_id();
709 new_elem->subdomain_id() = secondary_elem->subdomain_id();
710 new_elem->set_id(local_id_index++);
711 new_elem->set_unique_id(new_elem->id());
713 for (MooseIndex(new_elem->n_nodes()) n = 0; n < new_elem->n_nodes(); ++n)
714 new_elem->set_node(n, new_nodes[n]);
725 std::make_pair(secondary_elem->node_ptr(0), secondary_elem)),
727 std::make_pair(secondary_elem->node_ptr(1), secondary_elem));
729 bool new_container_node0_found =
731 new_container_node1_found =
734 const Elem * node0_primary_candidate =
nullptr;
735 const Elem * node1_primary_candidate =
nullptr;
737 if (new_container_node0_found)
739 const auto & xi2_primary_elem_pair = new_container_it0->second;
740 msinfo.
xi2_a = xi2_primary_elem_pair.first;
741 node0_primary_candidate = xi2_primary_elem_pair.second;
744 if (new_container_node1_found)
746 const auto & xi2_primary_elem_pair = new_container_it1->second;
747 msinfo.
xi2_b = xi2_primary_elem_pair.first;
748 node1_primary_candidate = xi2_primary_elem_pair.second;
755 if (node0_primary_candidate == node1_primary_candidate)
770 auto val = pr.second;
772 const Node * primary_node = std::get<1>(key);
773 Real xi1 = val.first;
774 const Elem * secondary_elem = val.second;
780 auto && order = secondary_elem->default_order();
784 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
789 Elem * current_mortar_segment =
nullptr;
792 for (
const auto & mortar_segment_candidate : mortar_segment_set)
798 catch (std::out_of_range &)
800 mooseError(
"MortarSegmentInfo not found for the mortar segment candidate");
802 if (info->xi1_a <= xi1 && xi1 <= info->xi1_b)
804 current_mortar_segment = mortar_segment_candidate;
810 if (current_mortar_segment ==
nullptr)
811 mooseError(
"Unable to find appropriate mortar segment during linear search!");
818 if (info->xi1_a == xi1 || xi1 == info->xi1_b)
823 "new_id must be the same on all processes");
824 Node *
const new_node =
826 new_node->set_unique_id(new_id + node_unique_id_offset);
831 const Point normal =
getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
835 this->_nodes_to_primary_elem_map.end())
836 mooseError(
"We should already have built this primary node to elem pair!");
837 const std::vector<const Elem *> & primary_node_neighbors =
841 if (primary_node_neighbors.size() == 0 || primary_node_neighbors.size() > 2)
842 mooseError(
"We must have either 1 or 2 primary side nodal neighbors, but we had ",
843 primary_node_neighbors.size());
850 const Elem * left_primary_elem = primary_node_neighbors[0];
851 const Elem * right_primary_elem =
852 (primary_node_neighbors.size() == 2) ? primary_node_neighbors[1] :
nullptr;
858 std::array<Real, 2> secondary_node_cps;
859 std::vector<Real> primary_node_cps(primary_node_neighbors.size());
862 for (
unsigned int nid = 0; nid < 2; ++nid)
863 secondary_node_cps[nid] = normal.cross(secondary_elem->point(nid) - new_pt)(2);
865 for (MooseIndex(primary_node_neighbors) mnn = 0; mnn < primary_node_neighbors.size(); ++mnn)
867 const Elem * primary_neigh = primary_node_neighbors[mnn];
868 Point opposite = (primary_neigh->node_ptr(0) == primary_node) ? primary_neigh->point(1)
869 : primary_neigh->point(0);
870 Point cp = normal.cross(opposite - new_pt);
871 primary_node_cps[mnn] = cp(2);
875 bool orientation1_valid =
false, orientation2_valid =
false;
877 if (primary_node_neighbors.size() == 2)
880 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.) &&
881 (secondary_node_cps[1] * primary_node_cps[1] > 0.);
883 orientation2_valid = (secondary_node_cps[0] * primary_node_cps[1] > 0.) &&
884 (secondary_node_cps[1] * primary_node_cps[0] > 0.);
886 else if (primary_node_neighbors.size() == 1)
889 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.);
890 orientation2_valid = (secondary_node_cps[1] * primary_node_cps[0] > 0.);
893 mooseError(
"Invalid primary node neighbors size ", primary_node_neighbors.size());
899 if (orientation1_valid && orientation2_valid)
901 "AutomaticMortarGeneration: Both orientations cannot simultaneously be valid.");
907 if (!orientation1_valid && !orientation2_valid)
910 "AutomaticMortarGeneration: Unable to determine valid secondary-primary orientation. "
911 "Consequently we will consider projection of the primary node invalid and not split the "
913 "This situation can indicate there are very oblique projections between primary (mortar) "
914 "and secondary (non-mortar) surfaces for a good problem set up. It can also mean your "
915 "time step is too large. This message is only printed once."));
920 std::unique_ptr<Elem> new_elem_left;
922 new_elem_left = std::make_unique<Edge3>();
924 new_elem_left = std::make_unique<Edge2>();
926 new_elem_left->processor_id() = current_mortar_segment->processor_id();
927 new_elem_left->subdomain_id() = current_mortar_segment->subdomain_id();
928 new_elem_left->set_id(local_id_index++);
929 new_elem_left->set_unique_id(new_elem_left->id());
930 new_elem_left->set_node(0, current_mortar_segment->node_ptr(0));
931 new_elem_left->set_node(1, new_node);
934 std::unique_ptr<Elem> new_elem_right;
936 new_elem_right = std::make_unique<Edge3>();
938 new_elem_right = std::make_unique<Edge2>();
940 new_elem_right->processor_id() = current_mortar_segment->processor_id();
941 new_elem_right->subdomain_id() = current_mortar_segment->subdomain_id();
942 new_elem_right->set_id(local_id_index++);
943 new_elem_right->set_unique_id(new_elem_right->id());
944 new_elem_right->set_node(0, new_node);
945 new_elem_right->set_node(1, current_mortar_segment->node_ptr(1));
950 Point left_interior_point(0);
951 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
954 Real current_left_interior_eta =
955 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
957 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
958 n < current_mortar_segment->n_nodes();
961 current_mortar_segment->point(n);
965 "new_id must be the same on all processes");
967 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
968 new_elem_left->set_node(2, new_interior_node_left);
969 new_interior_node_left->set_unique_id(new_interior_left_id + node_unique_id_offset);
972 Point right_interior_point(0);
973 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
975 Real current_right_interior_eta =
976 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
978 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
979 n < current_mortar_segment->n_nodes();
982 current_mortar_segment->point(n);
986 "new_id must be the same on all processes");
988 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
989 new_elem_right->set_node(2, new_interior_node_right);
990 new_interior_node_right->set_unique_id(new_interior_id_right + node_unique_id_offset);
994 if (orientation2_valid)
995 std::swap(left_primary_elem, right_primary_elem);
999 if (left_primary_elem)
1000 left_xi2 = (primary_node == left_primary_elem->node_ptr(0)) ? -1 : +1;
1001 if (right_primary_elem)
1002 right_xi2 = (primary_node == right_primary_elem->node_ptr(0)) ? -1 : +1;
1011 mooseError(
"MortarSegmentInfo not found for current_mortar_segment.");
1024 new_msinfo_left.
xi1_a = current_msinfo.
xi1_a;
1025 new_msinfo_left.
xi2_a = current_msinfo.
xi2_a;
1027 new_msinfo_left.
xi1_b = xi1;
1028 new_msinfo_left.
xi2_b = left_xi2;
1036 mortar_segment_set.insert(msm_new_elem);
1046 new_msinfo_right.
xi1_b = current_msinfo.
xi1_b;
1047 new_msinfo_right.
xi2_b = current_msinfo.
xi2_b;
1049 new_msinfo_right.
xi1_a = xi1;
1050 new_msinfo_right.
xi2_a = right_xi2;
1055 mortar_segment_set.insert(msm_new_elem);
1063 mortar_segment_set.erase(current_mortar_segment);
1079 Elem * primary_elem =
const_cast<Elem *
>(msinfo.
primary_elem);
1080 if (primary_elem ==
nullptr || abs(msinfo.
xi2_a) > 1.0 + TOLERANCE ||
1081 abs(msinfo.
xi2_b) > 1.0 + TOLERANCE)
1086 "We should have found the element");
1087 auto & msm_set = it->second;
1088 msm_set.erase(msm_elem);
1095 if (msm_set.empty())
1111 std::unordered_set<Node *> msm_connected_nodes;
1116 for (
auto & n : element->node_ref_range())
1117 msm_connected_nodes.insert(&n);
1120 if (!msm_connected_nodes.count(node))
1129 "All mortar segment elements should have valid "
1130 "primary element.");
1149 mortar_segment_mesh_writer.set_hdf5_writing(
false);
1151 std::array<std::string, 3> file_pieces = {
1154 "mortar_segment_mesh.e"};
1155 mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces,
"_"));
1161 const bool use_reference_interpolation =
1176 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1179 for (
const auto *
const el :
1180 _mesh.active_local_subdomain_elements_ptr_range(secondary_sub_id))
1181 local_secondary_sub_elems += el->n_sub_elem();
1182 for (
const auto *
const el :
_mesh.active_subdomain_elements_ptr_range(primary_sub_id))
1183 visible_primary_sub_elems += el->n_sub_elem();
1185 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1186 std::vector<dof_id_type> per_rank_bounds;
1187 _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
1188 dof_id_type start = 0;
1189 for (
const auto r : make_range(
_mesh.processor_id()))
1190 start += per_rank_bounds[r];
1197 dof_id_type next_elem_id = next_node_id;
1202 const auto primary_subd_id = pr.first;
1203 const auto secondary_subd_id = pr.second;
1208 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
1211 kd_tree.buildIndex();
1216 auto get_sub_elem_geometric_normal = [](
const std::vector<Point> & nodes)
1220 if (nodes.size() == 3)
1222 dxdxi = nodes[1] - nodes[0];
1223 dxdeta = nodes[2] - nodes[0];
1225 else if (nodes.size() == 4)
1229 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1230 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1233 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1234 "triangular and quadrilateral subpatches, but received ",
1238 Point geometric_normal = dxdxi.cross(dxdeta);
1239 const auto normal_norm = geometric_normal.norm();
1242 if (normal_norm <= TOLERANCE * dxdxi.norm() * dxdeta.norm())
1243 mooseError(
"GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1244 "degenerate subpatch.");
1246 geometric_normal /= normal_norm;
1247 return geometric_normal;
1253 for (MeshBase::const_element_iterator el =
_mesh.active_local_elements_begin(),
1254 end_el =
_mesh.active_local_elements_end();
1258 const Elem * secondary_side_elem = *el;
1260 const Real secondary_volume = secondary_side_elem->volume();
1263 if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1266 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1268 std::set<Elem *, CompareDofObjectsByID>{});
1269 libmesh_ignore(insertion_happened);
1270 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1272 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1273 secondary_side_elem->n_sub_elem());
1286 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1289 const auto sub_elem_nodes =
1295 std::vector<Point> nodes(sub_elem_nodes.size());
1298 for (
auto iv : make_range(sub_elem_nodes.size()))
1300 const auto n = sub_elem_nodes[iv];
1301 nodes[iv] = secondary_side_elem->point(n);
1302 center += secondary_side_elem->point(n);
1303 normal += nodal_normals[n];
1305 center /= sub_elem_nodes.size();
1306 normal = normal.unit();
1310 const Point averaged_normal = normal;
1311 normal = get_sub_elem_geometric_normal(nodes);
1312 if (normal * averaged_normal < 0)
1316 if (use_reference_interpolation)
1318 std::vector<Point> sub_elem_reference_points;
1319 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1320 for (
const auto node_index : sub_elem_nodes)
1321 sub_elem_reference_points.push_back(secondary_side_elem->master_point(node_index));
1323 mortar_segment_helper[sel] =
1324 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1325 std::move(sub_elem_reference_points),
1332 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1344 std::array<Real, 3> query_pt;
1346 switch (secondary_side_elem->type())
1350 center_point = mortar_segment_helper[0]->center();
1351 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1355 center_point = mortar_segment_helper[1]->center();
1356 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1359 center_point = mortar_segment_helper[4]->center();
1360 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1363 center_point = secondary_side_elem->point(8);
1364 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1368 "Face element type: ", secondary_side_elem->type(),
"not supported for 3D mortar");
1374 const std::size_t num_results = 3;
1377 std::vector<size_t> ret_index(num_results);
1378 std::vector<Real> out_dist_sqr(num_results);
1379 nanoflann::KNNResultSet<Real> result_set(num_results);
1380 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1384 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1388 bool primary_elem_found =
false;
1389 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1390 const bool use_geometric_subpatch_normals =
1394 const Real minimum_subpatch_normal_alignment =
1399 for (
auto r : make_range(result_set.size()))
1402 mooseAssert(abs((
_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1404 "Lower-dimensional element squared distance verification failed.");
1407 std::vector<const Elem *> & node_elems =
1411 for (
auto elem : node_elems)
1412 primary_elem_candidates.insert(elem);
1422 while (!primary_elem_candidates.empty())
1424 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1427 if (processed_primary_elems.count(primary_elem_candidate))
1429 primary_elem_candidates.erase(primary_elem_candidate);
1434 std::vector<Point> nodal_points;
1437 std::vector<std::vector<unsigned int>> elem_to_node_map;
1440 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1441 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1442 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1448 for (
auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1451 const auto sub_elem_nodes =
1455 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1456 for (
auto iv : make_range(sub_elem_nodes.size()))
1458 const auto n = sub_elem_nodes[iv];
1459 primary_sub_elem[iv] = primary_elem_candidate->point(n);
1461 Point primary_sub_elem_normal;
1462 if (use_geometric_subpatch_normals)
1463 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1465 std::vector<Point> sub_elem_reference_points;
1466 if (use_reference_interpolation)
1468 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1469 for (
const auto node_index : sub_elem_nodes)
1470 sub_elem_reference_points.push_back(primary_elem_candidate->master_point(node_index));
1474 for (
auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1479 if (use_geometric_subpatch_normals &&
1480 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1481 minimum_subpatch_normal_alignment)
1491 const auto segments_before_helper = elem_to_node_map.size();
1492 if (use_reference_interpolation)
1493 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1494 sub_elem_reference_points,
1497 elem_to_secondary_reference_points,
1498 elem_to_primary_reference_points,
1499 TOLERANCE * secondary_volume);
1501 mortar_segment_helper[s_el]->getMortarSegments(
1502 primary_sub_elem, nodal_points, elem_to_node_map);
1505 for (
auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1506 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1511 processed_primary_elems.insert(primary_elem_candidate);
1512 primary_elem_candidates.erase(primary_elem_candidate);
1515 if (!elem_to_node_map.empty())
1517 if (sub_elem_map.size() != elem_to_node_map.size())
1518 mooseError(
"The mortar segment subpatch map is not aligned with the mortar segment "
1519 "connectivity map.");
1520 if (use_reference_interpolation &&
1521 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1522 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1523 mooseError(
"The mortar segment reference-point maps are not aligned with the mortar "
1524 "segment connectivity map.");
1528 bool seed_breadth_first_search =
false;
1529 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(),
false);
1530 for (
const auto el : index_range(elem_to_node_map))
1532 const auto & node_map = elem_to_node_map[el];
1533 if (node_map.size() != 3)
1535 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1539 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1540 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1541 retained_mortar_segments[el] =
1542 0.5 * e1.cross(e2).norm() / secondary_volume >= TOLERANCE;
1543 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1546 if (seed_breadth_first_search)
1550 if (!primary_elem_found)
1552 primary_elem_found =
true;
1553 primary_elem_candidates.clear();
1557 for (
auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1560 if (neighbor ==
nullptr || neighbor->subdomain_id() != primary_subd_id)
1563 if (processed_primary_elems.count(neighbor))
1566 primary_elem_candidates.insert(neighbor);
1573 std::vector<Node *> new_nodes;
1576 std::vector<bool> retained_nodes(nodal_points.size(),
false);
1577 for (
const auto el : index_range(elem_to_node_map))
1578 if (retained_mortar_segments[el])
1579 for (
const auto node : elem_to_node_map[el])
1580 retained_nodes[node] =
true;
1582 new_nodes.resize(nodal_points.size(),
nullptr);
1583 for (
const auto node : index_range(nodal_points))
1584 if (retained_nodes[node])
1586 nodal_points[node], next_node_id++, secondary_side_elem->processor_id());
1589 for (
auto el : index_range(elem_to_node_map))
1591 if (!retained_mortar_segments[el])
1594 std::unique_ptr<Elem> new_elem;
1595 if (elem_to_node_map[el].size() == 3)
1596 new_elem = std::make_unique<Tri3>();
1598 mooseError(
"Active mortar segments only supports TRI elements, 3 nodes expected "
1600 elem_to_node_map[el].size(),
1603 new_elem->processor_id() = secondary_side_elem->processor_id();
1604 new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1605 new_elem->set_id(next_elem_id++);
1608 for (
auto i : index_range(elem_to_node_map[el]))
1609 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1612 if (new_elem->volume() / secondary_volume < TOLERANCE)
1618 msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1619 msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1630 if (use_reference_interpolation)
1633 elem_to_primary_reference_points[el]};
1638 secondary_to_msm_element_set.insert(msm_new_elem);
1649 if (use_geometric_subpatch_normals)
1653 if (secondary_to_msm_element_set.empty())
1655 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1656 " a corresponding primary element; this may be expected depending on"
1657 " problem geometry but may indicate a failure of the element search"
1661 for (
auto sel : make_range(secondary_side_elem->n_sub_elem()))
1662 if (mortar_segment_helper[sel]->remainder() == 1.0)
1664 mooseWarning(
"Some secondary elements on mortar interface were unable to identify"
1665 " a corresponding primary element; this may be expected depending on"
1666 " problem geometry but may indicate a failure of the element search"
1669 if (secondary_to_msm_element_set.empty())
1674 mooseAssert(!use_reference_interpolation ||
1676 "Mortar segment info and reference-point maps must remain aligned.");
1690 if (msm_el->type() != TRI3)
1691 msm_el->subdomain_id()++;
1697 if (msm_el->type() != TRI3)
1698 msm_el->subdomain_id()--;
1713 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1724 const Elem * secondary_elem = pr.second.secondary_elem;
1725 const Elem * primary_elem = pr.second.primary_elem;
1728 coupling_info[secondary_elem->processor_id()].emplace_back(
1729 secondary_elem->id(), secondary_elem->interior_parent()->id());
1730 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1734 secondary_elem->interior_parent()->id());
1737 coupling_info[secondary_elem->processor_id()].emplace_back(
1738 secondary_elem->id(), primary_elem->interior_parent()->id());
1739 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1743 primary_elem->interior_parent()->id());
1746 coupling_info[secondary_elem->processor_id()].emplace_back(secondary_elem->id(),
1747 primary_elem->id());
1748 if (secondary_elem->processor_id() !=
_mesh.processor_id())
1754 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1755 secondary_elem->interior_parent()->id(), secondary_elem->id());
1758 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1759 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1762 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1763 primary_elem->interior_parent()->id(), secondary_elem->id());
1766 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1767 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1771 auto action_functor =
1772 [
this](processor_id_type,
1773 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1775 for (
auto [i, j] : coupling_info)
1781std::vector<AutomaticMortarGeneration::MsmSubdomainStats>
1784 std::vector<MsmSubdomainStats> result;
1788 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1792 for (
const auto *
const secondary_el :
1793 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1795 secondary.push_back(secondary_el->volume());
1800 for (
const auto *
const msm_elem : it->second)
1802 msm.push_back(msm_elem->volume());
1806 if (msm_info.primary_elem)
1808 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1809 mooseError(
"Unhandled primary-secondary pairing when computing mortar segment "
1810 "statistics. This could happen if you have the same secondary "
1811 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1812 "primary subdomain IDs. Contact a MOOSE developer for help.");
1813 if (
const auto [it, inserted] =
1814 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1816 it->second = msm_info.primary_elem->volume();
1819 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1820 "Volumes should be consistent");
1825 _mesh.comm().set_union(primary_elems_to_volume);
1826 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1827 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1828 primary.reserve(primary_elems_to_volume.size());
1829 for (
const auto [_, volume] : primary_elems_to_volume)
1830 primary.push_back(volume);
1847 result.push_back(stats);
1852 primary_elems_to_volume.clear();
1863 if (
_mesh.processor_id() != 0)
1866 Moose::out <<
"Mortar Interface Statistics:" << std::endl;
1867 for (
const auto & stats : all_stats)
1869 std::vector<std::string> col_names = {
"mesh",
"n_elems",
"max",
"min",
"median"};
1870 std::vector<std::string> subds = {
"secondary_lower",
"primary_lower",
"mortar_segment"};
1871 std::vector<size_t> n_elems = {
1872 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1873 std::vector<Real> maxs = {
1874 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1875 std::vector<Real> mins = {
1876 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1877 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1878 stats.primary_lower_median_volume,
1879 stats.msm_median_volume};
1883 for (
auto i : index_range(subds))
1886 table.
addData<std::string>(col_names[0], subds[i]);
1887 table.
addData<
size_t>(col_names[1], n_elems[i]);
1888 table.
addData<Real>(col_names[2], maxs[i]);
1889 table.
addData<Real>(col_names[3], mins[i]);
1890 table.
addData<Real>(col_names[4], medians[i]);
1893 Moose::out <<
"secondary subdomain: " << stats.secondary_subd_id
1894 <<
" \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1911 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
1913 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1914 const auto my_pid =
_mesh.processor_id();
1917 std::unordered_set<dof_id_type> inactive_node_ids;
1919 std::unordered_map<const Elem *, Real> active_volume{};
1922 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(pr.second))
1923 active_volume[el] = 0.;
1931 active_volume[secondary_elem] += msm_elem->volume();
1937 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(pr.second))
1939 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1942 for (
auto n : make_range(el->n_nodes()))
1943 inactive_node_ids.insert(el->node_id(n));
1949 const auto secondary_subd_id = pr.second;
1952 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1955 const auto pid = el->processor_id();
1962 for (
const auto n : make_range(el->n_nodes()))
1964 const auto node_id = el->node_id(n);
1965 if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1966 proc_to_inactive_nodes_set[pid].insert(node_id);
1974 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1975 for (
const auto & proc_set : proc_to_inactive_nodes_set)
1976 proc_to_inactive_nodes_vector[proc_set.first].insert(
1977 proc_to_inactive_nodes_vector[proc_set.first].end(),
1978 proc_set.second.begin(),
1979 proc_set.second.end());
1982 auto action_functor = [
this, &inactive_node_ids](
const processor_id_type pid,
1983 const std::vector<dof_id_type> & sent_data)
1985 if (pid ==
_mesh.processor_id())
1986 mooseError(
"Should not be communicating with self.");
1987 for (
const auto pr : sent_data)
1988 inactive_node_ids.insert(pr);
1993 for (
const auto node_id : inactive_node_ids)
2006 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
2007 const auto my_pid =
_mesh.processor_id();
2010 std::unordered_set<dof_id_type> active_local_nodes;
2018 for (
auto n : make_range(secondary_elem->n_nodes()))
2019 active_local_nodes.insert(secondary_elem->node_id(n));
2025 const auto secondary_subd_id = pr.second;
2028 for (
const auto el :
_mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
2031 const auto pid = el->processor_id();
2038 for (
const auto n : make_range(el->n_nodes()))
2040 const auto node_id = el->node_id(n);
2041 if (active_local_nodes.find(node_id) != active_local_nodes.end())
2042 proc_to_active_nodes_set[pid].insert(node_id);
2050 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
2051 for (
const auto & proc_set : proc_to_active_nodes_set)
2053 proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
2054 for (
const auto node_id : proc_set.second)
2055 proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
2059 auto action_functor = [
this, &active_local_nodes](
const processor_id_type pid,
2060 const std::vector<dof_id_type> & sent_data)
2062 if (pid ==
_mesh.processor_id())
2063 mooseError(
"Should not be communicating with self.");
2064 active_local_nodes.insert(sent_data.begin(), sent_data.end());
2073 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2075 for (
const auto n : make_range(el->n_nodes()))
2076 if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
2085 std::unordered_set<const Elem *> active_local_elems;
2091 const Real tol = (
dim() == 3) ? 0.1 : TOLERANCE;
2093 std::unordered_map<const Elem *, Real> active_volume;
2102 active_volume[secondary_elem] += msm_elem->volume();
2113 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
2117 active_local_elems.insert(secondary_elem);
2123 for (
const auto el :
_mesh.active_local_subdomain_elements_ptr_range(
2125 if (active_local_elems.find(el) == active_local_elems.end())
2133 const auto dim =
_mesh.mesh_dimension();
2135 mooseAssert(
dim == 2 ||
dim == 3,
2136 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2137 "mortar constraints on 2D or 3D meshes.");
2146 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2154 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2155 end_el =
_mesh.active_elements_end();
2159 const Elem * secondary_elem = *el;
2167 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2168 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(
dim, nnx_fe_type));
2169 nnx_fe_face->attach_quadrature_rule(&qface);
2170 const auto & face_normals = nnx_fe_face->get_normals();
2171 const auto & face_points = nnx_fe_face->get_xyz();
2173 const auto & JxW = nnx_fe_face->get_JxW();
2177 const Elem * interior_parent = secondary_elem->interior_parent();
2178 mooseAssert(interior_parent,
2179 "No interior parent exists for element "
2180 << secondary_elem->id()
2181 <<
". There may be a problem with your sideset set-up.");
2189 auto s = interior_parent->which_side_am_i(secondary_elem);
2192 nnx_fe_face->reinit(interior_parent, s);
2197 const auto qpoint_to_secondary_node =
2198 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2200 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2201 "Face nodal geometry vectors must have the same size.");
2203 for (
const auto qp : make_range(face_points.size()))
2205 const auto n = qpoint_to_secondary_node[qp];
2206 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2207 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2211 for (
const auto & pr : node_to_normals_map)
2214 const auto & node_id = pr.first;
2215 const auto & normals_and_weights_vec = pr.second;
2218 for (
const auto & norm_and_weight : normals_and_weights_vec)
2219 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2220 nodal_normal = nodal_normal.unit();
2224 Point nodal_tangent_one;
2225 Point nodal_tangent_two;
2235 Point & nodal_tangent_one,
2236 Point & nodal_tangent_two)
const
2240 mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.norm(), 1),
2241 "The input nodal normal should have unity norm");
2243 const Real nx = nodal_normal(0);
2244 const Real ny = nodal_normal(1);
2245 const Real nz = nodal_normal(2);
2250 const Point h_vector(nx + 1.0, ny, nz);
2255 if (abs(h_vector(0)) < TOLERANCE)
2257 nodal_tangent_one(0) = 0;
2258 nodal_tangent_one(1) = 1;
2259 nodal_tangent_one(2) = 0;
2261 nodal_tangent_two(0) = 0;
2262 nodal_tangent_two(1) = 0;
2263 nodal_tangent_two(2) = -1;
2268 const Real h = h_vector.norm();
2270 nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
2271 nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
2272 nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2274 nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
2275 nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2276 nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
2292 const Node & secondary_node,
2293 const Node & primary_node,
2294 const std::vector<const Elem *> * secondary_node_neighbors,
2295 const std::vector<const Elem *> * primary_node_neighbors,
2296 const VectorValue<Real> & nodal_normal,
2297 const Elem & candidate_element,
2298 std::set<const Elem *> & rejected_elem_candidates)
2300 if (!secondary_node_neighbors)
2302 if (!primary_node_neighbors)
2305 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(),
false);
2333 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2335 for (
const auto nn : index_range(*secondary_node_neighbors))
2337 const Elem *
const secondary_neigh = (*secondary_node_neighbors)[nn];
2338 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2339 ? secondary_neigh->point(1)
2340 : secondary_neigh->point(0);
2341 const Point cp = nodal_normal.cross(opposite - secondary_node);
2342 secondary_node_neighbor_cps[nn] = cp(2);
2345 for (
const auto nn : index_range(*primary_node_neighbors))
2347 const Elem *
const primary_neigh = (*primary_node_neighbors)[nn];
2348 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2349 : primary_neigh->point(0);
2350 const Point cp = nodal_normal.cross(opposite - primary_node);
2351 primary_node_neighbor_cps[nn] = cp(2);
2355 bool found_match =
false;
2356 for (
const auto snn : index_range(*secondary_node_neighbors))
2357 for (
const auto mnn : index_range(*primary_node_neighbors))
2358 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2361 if (primary_elems_mapped[mnn])
2363 primary_elems_mapped[mnn] =
true;
2367 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2368 const auto secondary_key =
2369 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2370 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2375 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2377 const auto primary_key =
2378 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2379 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2387 rejected_elem_candidates.insert(&candidate_element);
2394 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2395 for (
const auto i : index_range(primary_elems_mapped))
2396 if (!primary_elems_mapped[i])
2399 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2400 std::make_pair(1,
nullptr));
2408 SubdomainID lower_dimensional_primary_subdomain_id,
2409 SubdomainID lower_dimensional_secondary_subdomain_id)
2416 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2419 kd_tree.buildIndex();
2421 for (MeshBase::const_element_iterator el =
_mesh.active_elements_begin(),
2422 end_el =
_mesh.active_elements_end();
2426 const Elem * secondary_side_elem = *el;
2429 if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2436 for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2439 const Node * secondary_node = secondary_side_elem->node_ptr(n);
2443 const std::vector<const Elem *> & secondary_node_neighbors =
2448 bool is_mapped =
true;
2449 for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2452 auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2468 std::array<Real, 3> query_pt = {
2469 {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2475 const std::size_t num_results = 3;
2478 std::vector<size_t> ret_index(num_results);
2479 std::vector<Real> out_dist_sqr(num_results);
2480 nanoflann::KNNResultSet<Real> result_set(num_results);
2481 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2485 bool projection_succeeded =
false;
2489 std::set<const Elem *> rejected_primary_elem_candidates;
2495 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2498 mooseAssert(abs((
_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2499 out_dist_sqr[r]) <= TOLERANCE,
2500 "Lower-dimensional element squared distance verification failed.");
2504 std::vector<const Elem *> & primary_elem_candidates =
2508 for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2510 const Elem * primary_elem_candidate = primary_elem_candidates[e];
2513 if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2517 const auto order = primary_elem_candidate->default_order();
2519 unsigned int current_iterate = 0, max_iterates = 10;
2524 VectorValue<DualNumber<Real>> x2(0);
2525 for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2526 n < primary_elem_candidate->n_nodes();
2530 const auto u = x2 - (*secondary_node);
2531 const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2536 if (F.derivatives())
2538 Real dxi2 = -F.value() / F.derivatives();
2546 current_iterate = max_iterates;
2547 }
while (++current_iterate < max_iterates);
2549 Real xi2 = xi2_dn.value();
2562 if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 *
_xi_tolerance) &&
2563 (abs((primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2573 const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2574 : primary_elem_candidate->node_ptr(1);
2575 const bool created_mortar_segment =
2578 &secondary_node_neighbors,
2581 *primary_elem_candidate,
2582 rejected_primary_elem_candidates);
2584 if (!created_mortar_segment)
2590 for (MooseIndex(secondary_node_neighbors) nn = 0;
2591 nn < secondary_node_neighbors.size();
2594 const Elem * neigh = secondary_node_neighbors[nn];
2595 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2597 const Node * neigh_node = neigh->node_ptr(nid);
2598 if (secondary_node == neigh_node)
2600 auto key = std::make_pair(neigh_node, neigh);
2601 auto val = std::make_pair(xi2, primary_elem_candidate);
2608 projection_succeeded =
true;
2613 rejected_primary_elem_candidates.insert(primary_elem_candidate);
2616 if (projection_succeeded)
2620 if (!projection_succeeded)
2624 _console <<
"Failed to find primary Elem into which secondary node "
2625 << cast_ref<const Point &>(*secondary_node) <<
", id '" << secondary_node->id()
2626 <<
"', projects onto\n"
2645 <<
" secondary nodes were successfully projected\n"
2662 SubdomainID lower_dimensional_primary_subdomain_id,
2663 SubdomainID lower_dimensional_secondary_subdomain_id)
2670 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
2673 kd_tree.buildIndex();
2675 std::unordered_set<dof_id_type> primary_nodes_visited;
2677 for (
const auto & primary_side_elem :
_mesh.active_element_ptr_range())
2680 if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2685 for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2689 const Node * primary_node = primary_side_elem->node_ptr(n);
2692 const std::vector<const Elem *> & primary_node_neighbors =
2700 std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2701 if (!primary_nodes_visited.insert(primary_node->id()).second ||
2706 Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2712 const size_t num_results = 3;
2715 std::vector<size_t> ret_index(num_results);
2716 std::vector<Real> out_dist_sqr(num_results);
2717 nanoflann::KNNResultSet<Real> result_set(num_results);
2718 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2722 bool projection_succeeded =
false;
2727 std::set<const Elem *> rejected_secondary_elem_candidates;
2731 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2734 mooseAssert(abs((
_mesh.point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2736 "Lower-dimensional element squared distance verification failed.");
2740 const std::vector<const Elem *> & secondary_elem_candidates =
2744 for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2746 const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2749 if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2752 std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2753 for (
const auto n : make_range(secondary_elem_candidate->n_nodes()))
2761 auto && order = secondary_elem_candidate->default_order();
2762 unsigned int current_iterate = 0, max_iterates = 10;
2764 VectorValue<DualNumber<Real>> normals(0);
2772 VectorValue<DualNumber<Real>> x1(0);
2773 for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2774 n < secondary_elem_candidate->n_nodes();
2778 x1 += phi * secondary_elem_candidate->point(n);
2779 normals += phi * nodal_normals[n];
2782 const auto u = x1 - (*primary_node);
2784 const auto F = u(0) * normals(1) - u(1) * normals(0);
2792 Real dxi1 = -F.value() / F.derivatives();
2797 }
while (++current_iterate < max_iterates);
2799 Real xi1 = xi1_dn.value();
2803 if ((current_iterate < max_iterates) && (abs(xi1) <= 1. +
_xi_tolerance) &&
2804 (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2818 const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2819 : secondary_elem_candidate->node_ref(1);
2820 bool created_mortar_segment =
false;
2827 &primary_node_neighbors,
2829 *secondary_elem_candidate,
2830 rejected_secondary_elem_candidates);
2832 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2834 if (!created_mortar_segment)
2850 const Elem * neigh = primary_node_neighbors[0];
2851 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2853 const Node * neigh_node = neigh->node_ptr(nid);
2854 if (primary_node == neigh_node)
2856 auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2857 auto val = std::make_pair(xi1, secondary_elem_candidate);
2863 projection_succeeded =
true;
2869 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2873 if (projection_succeeded)
2877 if (!projection_succeeded &&
_debug)
2879 _console <<
"\nFailed to find point from which primary node "
2880 << cast_ref<const Point &>(*primary_node) <<
" was projected." << std::endl
2887std::vector<AutomaticMortarGeneration::MortarFilterIter>
2894 const auto & secondary_elems = secondary_it->second;
2895 std::vector<MortarFilterIter> ret;
2896 ret.reserve(secondary_elems.size());
2898 for (
const auto i : index_range(secondary_elems))
2900 auto *
const secondary_elem = secondary_elems[i];
2906 mooseAssert(secondary_elem->active(),
2907 "We loop over active elements when building the mortar segment mesh, so we golly "
2908 "well hope this is active.");
2909 mooseAssert(!msm_it->second.empty(),
2910 "We should have removed all secondaries from this map if they do not have any "
2911 "mortar segments associated with them.");
2912 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
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 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
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)
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