https://mooseframework.inl.gov
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Public Attributes | Static Public Attributes | Private Member Functions | Private Attributes | Friends | List of all members
AutomaticMortarGeneration Class Reference

This class is a container/interface for the objects involved in automatic generation of mortar spaces. More...

#include <AutomaticMortarGeneration.h>

Inheritance diagram for AutomaticMortarGeneration:
[legend]

Classes

struct  MsmSubdomainStats
 Statistics for one primary-secondary subdomain pair. More...
 

Public Types

using MortarFilterIter = std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > >::const_iterator
 

Public Member Functions

 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.
 
void buildNodeToElemMaps ()
 Once the secondary_requested_boundary_ids and primary_requested_boundary_ids containers have been filled in, call this function to build node-to-Elem maps for the lower-dimensional elements.
 
void computeNodalGeometry ()
 Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the ExplicitSystem/NumericVector approach.
 
void projectSecondaryNodes ()
 Project secondary nodes (find xi^(2) values) to the closest points on the primary surface.
 
void projectPrimaryNodes ()
 (Inverse) project primary nodes to the points on the secondary surface where they would have come from (find (xi^(1) values)).
 
void buildMortarSegmentMesh ()
 Builds the mortar segment mesh once the secondary and primary node projections have been completed.
 
void buildMortarSegmentMesh3d ()
 Builds the mortar segment mesh once the secondary and primary node projections have been completed.
 
std::vector< MsmSubdomainStats > computeMsmStatistics ()
 Computes mortar segment mesh statistics and returns one entry per subdomain pair.
 
void msmStatistics ()
 Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally)
 
void clear ()
 Clears the mortar segment mesh and accompanying data structures.
 
void meshChanged ()
 Invalidates the cached MSM node/element ID starting offset so that the next call to buildMortarSegmentMesh3d() recomputes it via allgather.
 
bool onDisplaced () const
 returns whether this object is on the displaced mesh
 
std::vector< Point > getNodalNormals (const Elem &secondary_elem) const
 
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents (const Elem &secondary_elem) const
 Compute the two nodal tangents, which are built on-the-fly.
 
void computeADNodalNormals (const std::function< ADPoint(const Node &)> &coordinate, std::unordered_map< const Node *, ADRealVectorValue > &nodal_normals) const
 Build the normalized JxW-weighted secondary nodal normals from AD nodal coordinates.
 
std::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.
 
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 nodes.
 
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::vector< Point > getNormals (const Elem &secondary_elem, const std::vector< Real > &oned_xi1_pts) const
 Compute the normals at given reference points on a secondary element.
 
const Elem * getSecondaryLowerdElemFromSecondaryElem (dof_id_type secondary_elem_id) const
 Return lower dimensional secondary element given its interior parent.
 
void computeInactiveLMNodes ()
 Get list of secondary nodes that don't contribute to interaction with any primary element.
 
void computeIncorrectEdgeDroppingInactiveLMNodes ()
 Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching a partially or fully dropped element is dropped)
 
void computeInactiveLMElems ()
 Get list of secondary elems without any corresponding primary elements.
 
const std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > & mortarInterfaceCoupling () const
 
const std::pair< BoundaryID, BoundaryID > & primarySecondaryBoundaryIDPair () const
 
const MeshBase & mortarSegmentMesh () const
 
const std::unordered_map< const Elem *, MortarSegmentInfo > & mortarSegmentMeshElemToInfo () const
 
int dim () const
 
Mortar3DQuadraturePointMapping mortar3DQpMapping () const
 Return the 3D mortar quadrature-point mapping method.
 
const MortarSegmentReferencePoints & mortarSegmentReferencePoints (const Elem &mortar_segment_elem) const
 Return the parent-face reference coordinates for a mortar segment.
 
const std::unordered_set< const Node * > & getInactiveLMNodes () const
 
const std::unordered_set< const Elem * > & getInactiveLMElems () const
 
bool incorrectEdgeDropping () const
 
std::vector< MortarFilterIter > secondariesToMortarSegments (const Node &node) const
 
const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & secondariesToMortarSegments () const
 
const std::set< SubdomainID > & secondaryIPSubIDs () const
 
const std::set< SubdomainID > & primaryIPSubIDs () const
 
const std::unordered_map< dof_id_type, std::vector< const Elem * > > & nodesToSecondaryElem () const
 
void initOutput ()
 initialize mortar-mesh based output
 

Public Attributes

const ConsoleStream _console
 An instance of helper class to write streams to the Console objects.
 

Static Public Attributes

static const std::string system_name
 The name of the nodal normals system.
 

Private Member Functions

void outputMortarMesh ()
 Write the mortar segment mesh to exodus.
 
std::string mortarInterfaceName () const
 
void buildCouplingInformation ()
 build the _mortar_interface_coupling data
 
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/secondary pair.
 
void projectPrimaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
 
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.
 

Private Attributes

MooseApp & _app
 The Moose app.
 
MeshBase & _mesh
 Reference to the mesh stored in equation_systems.
 
std::set< BoundaryID > _secondary_requested_boundary_ids
 The boundary ids corresponding to all the secondary surfaces.
 
std::set< BoundaryID > _primary_requested_boundary_ids
 The boundary ids corresponding to all the primary surfaces.
 
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::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_secondary_elem_map
 Map from nodes to connected lower-dimensional elements on the secondary/primary subdomains.
 
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
 
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), primary Elem) pair.
 
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), Secondary Elem) where they are inverse-projected along the nodal normal direction.
 
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::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
 Map between Elems in the mortar segment mesh and their info structs.
 
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 which they are.
 
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::set< SubdomainID > _secondary_boundary_subdomain_ids
 The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids.
 
std::set< SubdomainID > _primary_boundary_subdomain_ids
 
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 others across the gap, and to explicitly set up the dependence between interior_parent() elements on the secondary side and their lower-dimensional sides which are on the interface.
 
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
 Container for storing the nodal normal vector associated with each secondary node.
 
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 (Householder approach).
 
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.
 
std::unordered_set< const Node * > _inactive_local_lm_nodes
 List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)
 
std::unordered_set< const Elem * > _inactive_local_lm_elems
 List of inactive lagrange multiplier nodes (for elemental variables)
 
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) elements in mortar_segment_mesh.
 
std::set< SubdomainID > _secondary_ip_sub_ids
 All the secondary interior parent subdomain IDs associated with the mortar mesh.
 
std::set< SubdomainID > _primary_ip_sub_ids
 All the primary interior parent subdomain IDs associated with the mortar mesh.
 
const bool _debug
 Whether to print debug output.
 
const bool _on_displaced
 Whether this object is on the displaced mesh.
 
const bool _periodic
 Whether this object will be generating a mortar segment mesh for periodic constraints.
 
const bool _distributed
 Whether the mortar segment mesh is distributed.
 
Real _newton_tolerance = 1e-12
 Newton solve tolerance for node projections.
 
Real _xi_tolerance = 1e-6
 Tolerance for checking projection xi values.
 
const bool _correct_edge_dropping
 Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are strongly set to 0.
 
const Real _minimum_projection_angle
 Parameter to control which angle (in degrees) is admissible for the creation of mortar segments.
 
const Mortar3DSubpatchPlane _mortar_3d_subpatch_plane
 Method used to define the local projection planes for 3D secondary subpatches.
 
const MortarSegmentTriangulationMode _triangulation_mode
 Triangulation mode used for clipped 3D mortar polygons.
 
const bool _triangulate_triangles
 Whether already-triangular clipped polygons should still be centroid-subdivided.
 
const Mortar3DQuadraturePointMapping _mortar_3d_qp_mapping
 Method used to map 3D mortar segment quadrature points to primary and secondary faces.
 
std::unordered_map< const Elem *, MortarSegmentReferencePoints > _msm_elem_to_reference_points
 Reference-coordinate data used only by the reference-interpolation mapping mode.
 
std::unique_ptr< InputParameters > _output_params
 Storage for the input parameters used by the mortar nodal geometry output.
 
std::optional< dof_id_type > _msm_node_id_start
 Cached per-rank starting ID for 3D MSM nodes/elements.
 
std::unordered_set< dof_id_type > _projected_secondary_nodes
 Debugging container for printing information about fraction of successful projections for secondary nodes.
 
std::unordered_set< dof_id_type > _failed_secondary_node_projections
 Secondary nodes that failed to project.
 

Friends

class MortarNodalGeometryOutput
 
class AugmentSparsityOnInterface
 

Detailed Description

This class is a container/interface for the objects involved in automatic generation of mortar spaces.

Definition at line 111 of file AutomaticMortarGeneration.h.

Member Typedef Documentation

◆ MortarFilterIter

using AutomaticMortarGeneration::MortarFilterIter = std::unordered_map<dof_id_type, std::set<Elem *, CompareDofObjectsByID> >::const_iterator

Definition at line 410 of file AutomaticMortarGeneration.h.

Constructor & Destructor Documentation

◆ AutomaticMortarGeneration()

AutomaticMortarGeneration::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.

Definition at line 389 of file AutomaticMortarGeneration.C.

404 _app(app),
405 _mesh(mesh_in),
406 _debug(debug),
407 _on_displaced(on_displaced),
408 _periodic(periodic),
409 // 3D mortar always builds the mortar segment mesh distributedly (each rank adds only its local
410 // secondary elements). For 2D, we ghost the entire mortar interface when displaced, so
411 // displaced meshes are always replicated; otherwise follow the parent mesh.
412 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
413 _correct_edge_dropping(correct_edge_dropping),
414 _minimum_projection_angle(minimum_projection_angle),
415 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
416 _triangulation_mode(triangulation_mode),
417 _triangulate_triangles(triangulate_triangles),
418 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
419{
420 _primary_secondary_boundary_id_pairs.push_back(boundary_key);
421 _primary_requested_boundary_ids.insert(boundary_key.first);
422 _secondary_requested_boundary_ids.insert(boundary_key.second);
423 _primary_secondary_subdomain_id_pairs.push_back(subdomain_key);
424 _primary_boundary_subdomain_ids.insert(subdomain_key.first);
425 _secondary_boundary_subdomain_ids.insert(subdomain_key.second);
426
427 if (_distributed)
429 std::make_unique<DistributedMesh>(_mesh.comm(), _mesh.spatial_dimension());
430 else
432 std::make_unique<ReplicatedMesh>(_mesh.comm(), _mesh.spatial_dimension());
433}
std::set< BoundaryID > _secondary_requested_boundary_ids
The boundary ids corresponding to all the secondary surfaces.
const Mortar3DQuadraturePointMapping _mortar_3d_qp_mapping
Method used to map 3D mortar segment quadrature points to primary and secondary faces.
std::vector< std::pair< BoundaryID, BoundaryID > > _primary_secondary_boundary_id_pairs
A list of primary/secondary boundary id pairs corresponding to each side of the mortar interface.
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
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...
const Mortar3DSubpatchPlane _mortar_3d_subpatch_plane
Method used to define the local projection planes for 3D secondary subpatches.
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::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
const bool _on_displaced
Whether this object is on the displaced mesh.
const bool _distributed
Whether the mortar segment mesh is distributed.
const bool _triangulate_triangles
Whether already-triangular clipped polygons should still be centroid-subdivided.
std::set< SubdomainID > _primary_boundary_subdomain_ids
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.
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.

Member Function Documentation

◆ buildCouplingInformation()

void AutomaticMortarGeneration::buildCouplingInformation ( )
private

build the _mortar_interface_coupling data

Definition at line 1716 of file AutomaticMortarGeneration.C.

1717{
1718 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1719 coupling_info;
1720
1721 // Loop over the msm_elem_to_info object and build a bi-directional
1722 // multimap from secondary elements to the primary Elems which they are
1723 // coupled to and vice-versa. This is used in the
1724 // AugmentSparsityOnInterface functor to determine whether a given
1725 // secondary Elem is coupled across the mortar interface to a primary
1726 // element.
1727 for (const auto & pr : _msm_elem_to_info)
1728 {
1729 const Elem * secondary_elem = pr.second.secondary_elem;
1730 const Elem * primary_elem = pr.second.primary_elem;
1731
1732 // Every process owning a row on a secondary node or either volume parent needs the complete
1733 // secondary face-star coupling, including when that process also owns the lower-dimensional
1734 // face.
1735 std::set<processor_id_type> secondary_face_coupling_processors{
1736 secondary_elem->processor_id(),
1737 secondary_elem->interior_parent()->processor_id(),
1738 primary_elem->interior_parent()->processor_id()};
1739 for (const auto n : secondary_elem->node_index_range())
1740 secondary_face_coupling_processors.insert(secondary_elem->node_ptr(n)->processor_id());
1741
1742 // LowerSecondary
1743 for (const auto processor : secondary_face_coupling_processors)
1744 coupling_info[processor].emplace_back(secondary_elem->id(),
1745 secondary_elem->interior_parent()->id());
1746
1747 // LowerPrimary
1748 for (const auto processor : secondary_face_coupling_processors)
1749 coupling_info[processor].emplace_back(secondary_elem->id(),
1750 primary_elem->interior_parent()->id());
1751
1752 // Lower-LowerDimensionalPrimary
1753 for (const auto processor : secondary_face_coupling_processors)
1754 coupling_info[processor].emplace_back(secondary_elem->id(), primary_elem->id());
1755
1756 // SecondaryLower
1757 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1758 secondary_elem->interior_parent()->id(), secondary_elem->id());
1759
1760 // SecondaryPrimary
1761 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1762 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1763
1764 // PrimaryLower
1765 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1766 primary_elem->interior_parent()->id(), secondary_elem->id());
1767
1768 // PrimarySecondary
1769 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1770 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1771 }
1772
1773 // A face pair may produce several mortar segments. Remove repeated entries before communication.
1774 for (auto & [_, couplings] : coupling_info)
1775 {
1776 std::sort(couplings.begin(), couplings.end());
1777 couplings.erase(std::unique(couplings.begin(), couplings.end()), couplings.end());
1778 }
1779
1780 // Push the coupling information
1781 auto action_functor =
1782 [this](processor_id_type,
1783 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1784 {
1785 for (auto [i, j] : coupling_info)
1786 _mortar_interface_coupling[i].insert(j);
1787 };
1788 TIMPI::push_parallel_vector_data(_mesh.comm(), coupling_info, action_functor);
1789}
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::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
uint8_t processor_id_type
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

◆ buildMortarSegmentMesh()

void AutomaticMortarGeneration::buildMortarSegmentMesh ( )

Builds the mortar segment mesh once the secondary and primary node projections have been completed.

Inputs:

  • mesh
  • primary_node_and_elem_to_xi1_secondary_elem
  • secondary_node_and_elem_to_xi2_primary_elem
  • nodes_to_primary_elem_map

Outputs:

  • mortar_segment_mesh
  • msm_elem_to_info

Defined in the file build_mortar_segment_mesh.C.

This was a change to how inactive LM DoFs are handled. Now mortar segment elements are not used in assembly if there is no corresponding primary element and inactive LM DoFs (those with no contribution to an active primary element) are zeroed.

Definition at line 654 of file AutomaticMortarGeneration.C.

655{
656 using std::abs;
657
658 dof_id_type local_id_index = 0;
659 std::size_t node_unique_id_offset = 0;
660
661 // Create an offset by the maximum number of mortar segment elements that can be created *plus*
662 // the number of lower-dimensional secondary subdomain elements. Recall that the number of mortar
663 // segments created is a function of node projection, *and* that if we split elems we will delete
664 // that elem which has already taken a unique id
665 for (const auto & pr : _primary_secondary_boundary_id_pairs)
666 {
667 const auto primary_bnd_id = pr.first;
668 const auto secondary_bnd_id = pr.second;
669 const auto num_primary_nodes =
670 std::distance(_mesh.bid_nodes_begin(primary_bnd_id), _mesh.bid_nodes_end(primary_bnd_id));
671 const auto num_secondary_nodes = std::distance(_mesh.bid_nodes_begin(secondary_bnd_id),
672 _mesh.bid_nodes_end(secondary_bnd_id));
673 mooseAssert(num_primary_nodes,
674 "There are no primary nodes on boundary ID "
675 << primary_bnd_id << ". Does that bondary ID even exist on the mesh?");
676 mooseAssert(num_secondary_nodes,
677 "There are no secondary nodes on boundary ID "
678 << secondary_bnd_id << ". Does that bondary ID even exist on the mesh?");
679
680 node_unique_id_offset += num_primary_nodes + 2 * num_secondary_nodes;
681 }
682
683 // 1.) Add all lower-dimensional secondary side elements as the "initial" mortar segments.
684 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
685 end_el = _mesh.active_elements_end();
686 el != end_el;
687 ++el)
688 {
689 const Elem * secondary_elem = *el;
690
691 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
692 if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
693 continue;
694
695 std::vector<Node *> new_nodes;
696 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
697 {
698 new_nodes.push_back(_mortar_segment_mesh->add_point(
699 secondary_elem->point(n), secondary_elem->node_id(n), secondary_elem->processor_id()));
700 Node * const new_node = new_nodes.back();
701 new_node->set_unique_id(new_node->id() + node_unique_id_offset);
702 }
703
704 std::unique_ptr<Elem> new_elem;
705 if (secondary_elem->default_order() == SECOND)
706 new_elem = std::make_unique<Edge3>();
707 else
708 new_elem = std::make_unique<Edge2>();
709
710 new_elem->processor_id() = secondary_elem->processor_id();
711 new_elem->subdomain_id() = secondary_elem->subdomain_id();
712 new_elem->set_id(local_id_index++);
713 new_elem->set_unique_id(new_elem->id());
714
715 for (MooseIndex(new_elem->n_nodes()) n = 0; n < new_elem->n_nodes(); ++n)
716 new_elem->set_node(n, new_nodes[n]);
717
718 Elem * new_elem_ptr = _mortar_segment_mesh->add_elem(new_elem.release());
719
720 // The xi^(1) values for this mortar segment are initially -1 and 1.
721 MortarSegmentInfo msinfo;
722 msinfo.xi1_a = -1;
723 msinfo.xi1_b = +1;
724 msinfo.secondary_elem = secondary_elem;
725
726 auto new_container_it0 = _secondary_node_and_elem_to_xi2_primary_elem.find(
727 std::make_pair(secondary_elem->node_ptr(0), secondary_elem)),
728 new_container_it1 = _secondary_node_and_elem_to_xi2_primary_elem.find(
729 std::make_pair(secondary_elem->node_ptr(1), secondary_elem));
730
731 bool new_container_node0_found =
732 (new_container_it0 != _secondary_node_and_elem_to_xi2_primary_elem.end()),
733 new_container_node1_found =
734 (new_container_it1 != _secondary_node_and_elem_to_xi2_primary_elem.end());
735
736 const Elem * node0_primary_candidate = nullptr;
737 const Elem * node1_primary_candidate = nullptr;
738
739 if (new_container_node0_found)
740 {
741 const auto & xi2_primary_elem_pair = new_container_it0->second;
742 msinfo.xi2_a = xi2_primary_elem_pair.first;
743 node0_primary_candidate = xi2_primary_elem_pair.second;
744 }
745
746 if (new_container_node1_found)
747 {
748 const auto & xi2_primary_elem_pair = new_container_it1->second;
749 msinfo.xi2_b = xi2_primary_elem_pair.first;
750 node1_primary_candidate = xi2_primary_elem_pair.second;
751 }
752
753 // If both node0 and node1 agree on the primary element they are
754 // projected into, then this mortar segment fits entirely within
755 // a single primary element, and we can go ahead and set the
756 // msinfo.primary_elem pointer now.
757 if (node0_primary_candidate == node1_primary_candidate)
758 msinfo.primary_elem = node0_primary_candidate;
759
760 // Associate this MSM elem with the MortarSegmentInfo.
761 _msm_elem_to_info.emplace(new_elem_ptr, msinfo);
762
763 // Maintain the mapping between secondary elems and mortar segment elems contained within them.
764 // Initially, only the original secondary_elem is present.
765 _secondary_elems_to_mortar_segments[secondary_elem->id()].insert(new_elem_ptr);
766 }
767
768 // 2.) Insert new nodes from primary side and split mortar segments as necessary.
770 {
771 auto key = pr.first;
772 auto val = pr.second;
773
774 const Node * primary_node = std::get<1>(key);
775 Real xi1 = val.first;
776 const Elem * secondary_elem = val.second;
777
778 // If this is an aligned node, we don't need to do anything.
779 if (abs(abs(xi1) - 1.) < _xi_tolerance)
780 continue;
781
782 auto && order = secondary_elem->default_order();
783
784 // Determine physical location of new point to be inserted.
785 Point new_pt(0);
786 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
787 new_pt += Moose::fe_lagrange_1D_shape(order, n, xi1) * secondary_elem->point(n);
788
789 // Find the current mortar segment that will have to be split.
790 auto & mortar_segment_set = _secondary_elems_to_mortar_segments[secondary_elem->id()];
791 Elem * current_mortar_segment = nullptr;
792 MortarSegmentInfo * info = nullptr;
793
794 for (const auto & mortar_segment_candidate : mortar_segment_set)
795 {
796 try
797 {
798 info = &_msm_elem_to_info.at(mortar_segment_candidate);
799 }
800 catch (std::out_of_range &)
801 {
802 mooseError("MortarSegmentInfo not found for the mortar segment candidate");
803 }
804 if (info->xi1_a <= xi1 && xi1 <= info->xi1_b)
805 {
806 current_mortar_segment = mortar_segment_candidate;
807 break;
808 }
809 }
810
811 // Make sure we found one.
812 if (current_mortar_segment == nullptr)
813 mooseError("Unable to find appropriate mortar segment during linear search!");
814
815 // If node lands on endpoint of segment, don't split.
816 // Jacob: This condition was getting missed by the < comparison a few lines above. To fix it I
817 // just made it <= and put this condition in to handle equality different. It probably could be
818 // done with a tolerance but the the toleranced equality is already handled later when we drop
819 // segments with small volume.
820 if (info->xi1_a == xi1 || xi1 == info->xi1_b)
821 continue;
822
823 const auto new_id = _mortar_segment_mesh->max_node_id();
824 mooseAssert(_mortar_segment_mesh->comm().verify(new_id),
825 "new_id must be the same on all processes");
826 Node * const new_node =
827 _mortar_segment_mesh->add_point(new_pt, new_id, secondary_elem->processor_id());
828 new_node->set_unique_id(new_id + node_unique_id_offset);
829
830 // Reconstruct the nodal normal at xi1. This will help us
831 // determine the orientation of the primary elems relative to the
832 // new mortar segments.
833 const Point normal = getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
834
835 // Get the set of primary_node neighbors.
836 if (this->_nodes_to_primary_elem_map.find(primary_node->id()) ==
837 this->_nodes_to_primary_elem_map.end())
838 mooseError("We should already have built this primary node to elem pair!");
839 const std::vector<const Elem *> & primary_node_neighbors =
840 this->_nodes_to_primary_elem_map[primary_node->id()];
841
842 // Sanity check
843 if (primary_node_neighbors.size() == 0 || primary_node_neighbors.size() > 2)
844 mooseError("We must have either 1 or 2 primary side nodal neighbors, but we had ",
845 primary_node_neighbors.size());
846
847 // Primary Elem pointers which we will eventually assign to the
848 // mortar segments being created. We start by assuming
849 // primary_node_neighbor[0] is on the "left" and
850 // primary_node_neighbor[1]/"nothing" is on the "right" and then
851 // swap them if that's not the case.
852 const Elem * left_primary_elem = primary_node_neighbors[0];
853 const Elem * right_primary_elem =
854 (primary_node_neighbors.size() == 2) ? primary_node_neighbors[1] : nullptr;
855
857
858 // Storage for z-component of cross products for determining
859 // orientation.
860 std::array<Real, 2> secondary_node_cps;
861 std::vector<Real> primary_node_cps(primary_node_neighbors.size());
862
863 // Store z-component of left and right secondary node cross products with the nodal normal.
864 for (unsigned int nid = 0; nid < 2; ++nid)
865 secondary_node_cps[nid] = normal.cross(secondary_elem->point(nid) - new_pt)(2);
866
867 for (MooseIndex(primary_node_neighbors) mnn = 0; mnn < primary_node_neighbors.size(); ++mnn)
868 {
869 const Elem * primary_neigh = primary_node_neighbors[mnn];
870 Point opposite = (primary_neigh->node_ptr(0) == primary_node) ? primary_neigh->point(1)
871 : primary_neigh->point(0);
872 Point cp = normal.cross(opposite - new_pt);
873 primary_node_cps[mnn] = cp(2);
874 }
875
876 // We will verify that only 1 orientation is actually valid.
877 bool orientation1_valid = false, orientation2_valid = false;
878
879 if (primary_node_neighbors.size() == 2)
880 {
881 // 2 primary neighbor case
882 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.) &&
883 (secondary_node_cps[1] * primary_node_cps[1] > 0.);
884
885 orientation2_valid = (secondary_node_cps[0] * primary_node_cps[1] > 0.) &&
886 (secondary_node_cps[1] * primary_node_cps[0] > 0.);
887 }
888 else if (primary_node_neighbors.size() == 1)
889 {
890 // 1 primary neighbor case
891 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.);
892 orientation2_valid = (secondary_node_cps[1] * primary_node_cps[0] > 0.);
893 }
894 else
895 mooseError("Invalid primary node neighbors size ", primary_node_neighbors.size());
896
897 // Verify that both orientations are not simultaneously valid/invalid. If they are not, then we
898 // are going to throw an exception instead of erroring out since we can easily reach this point
899 // if we have one bad linear solve. It's better in general to catch the error and then try a
900 // smaller time-step
901 if (orientation1_valid && orientation2_valid)
902 throw MooseException(
903 "AutomaticMortarGeneration: Both orientations cannot simultaneously be valid.");
904
905 // We are going to treat the case where both orientations are invalid as a case in which we
906 // should not be splitting the mortar mesh to incorporate primary mesh elements.
907 // In practice, this case has appeared for very oblique projections, so we assume these cases
908 // will not be considered in mortar thermomechanical contact.
909 if (!orientation1_valid && !orientation2_valid)
910 {
911 static const auto invalid_orientation_id =
913 "AutomaticMortarGeneration",
914 "Unable to determine valid secondary-primary orientation. Consequently we will "
915 "consider projection of the primary node invalid and not split the mortar segment. "
916 "This situation can indicate there are very oblique projections between primary "
917 "(mortar) and secondary (non-mortar) surfaces for a good problem set up. It can also "
918 "mean your time step is too large.",
919 true);
920 _app.solutionInvalidity().flagInvalidSolutionInternal(invalid_orientation_id);
921 continue;
922 }
923
924 // Make an Elem on the left
925 std::unique_ptr<Elem> new_elem_left;
926 if (order == SECOND)
927 new_elem_left = std::make_unique<Edge3>();
928 else
929 new_elem_left = std::make_unique<Edge2>();
930
931 new_elem_left->processor_id() = current_mortar_segment->processor_id();
932 new_elem_left->subdomain_id() = current_mortar_segment->subdomain_id();
933 new_elem_left->set_id(local_id_index++);
934 new_elem_left->set_unique_id(new_elem_left->id());
935 new_elem_left->set_node(0, current_mortar_segment->node_ptr(0));
936 new_elem_left->set_node(1, new_node);
937
938 // Make an Elem on the right
939 std::unique_ptr<Elem> new_elem_right;
940 if (order == SECOND)
941 new_elem_right = std::make_unique<Edge3>();
942 else
943 new_elem_right = std::make_unique<Edge2>();
944
945 new_elem_right->processor_id() = current_mortar_segment->processor_id();
946 new_elem_right->subdomain_id() = current_mortar_segment->subdomain_id();
947 new_elem_right->set_id(local_id_index++);
948 new_elem_right->set_unique_id(new_elem_right->id());
949 new_elem_right->set_node(0, new_node);
950 new_elem_right->set_node(1, current_mortar_segment->node_ptr(1));
951
952 if (order == SECOND)
953 {
954 // left
955 Point left_interior_point(0);
956 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
957
958 // This is eta for the current mortar segment that we're splitting
959 Real current_left_interior_eta =
960 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
961
962 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
963 n < current_mortar_segment->n_nodes();
964 ++n)
965 left_interior_point += Moose::fe_lagrange_1D_shape(order, n, current_left_interior_eta) *
966 current_mortar_segment->point(n);
967
968 const auto new_interior_left_id = _mortar_segment_mesh->max_node_id();
969 mooseAssert(_mortar_segment_mesh->comm().verify(new_interior_left_id),
970 "new_id must be the same on all processes");
971 Node * const new_interior_node_left = _mortar_segment_mesh->add_point(
972 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
973 new_elem_left->set_node(2, new_interior_node_left);
974 new_interior_node_left->set_unique_id(new_interior_left_id + node_unique_id_offset);
975
976 // right
977 Point right_interior_point(0);
978 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
979 // This is eta for the current mortar segment that we're splitting
980 Real current_right_interior_eta =
981 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
982
983 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
984 n < current_mortar_segment->n_nodes();
985 ++n)
986 right_interior_point += Moose::fe_lagrange_1D_shape(order, n, current_right_interior_eta) *
987 current_mortar_segment->point(n);
988
989 const auto new_interior_id_right = _mortar_segment_mesh->max_node_id();
990 mooseAssert(_mortar_segment_mesh->comm().verify(new_interior_id_right),
991 "new_id must be the same on all processes");
992 Node * const new_interior_node_right = _mortar_segment_mesh->add_point(
993 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
994 new_elem_right->set_node(2, new_interior_node_right);
995 new_interior_node_right->set_unique_id(new_interior_id_right + node_unique_id_offset);
996 }
997
998 // If orientation 2 was valid, swap the left and right primaries.
999 if (orientation2_valid)
1000 std::swap(left_primary_elem, right_primary_elem);
1001
1002 // Now that we know left_primary_elem and right_primary_elem, we can determine left_xi2 and
1003 // right_xi2.
1004 if (left_primary_elem)
1005 left_xi2 = (primary_node == left_primary_elem->node_ptr(0)) ? -1 : +1;
1006 if (right_primary_elem)
1007 right_xi2 = (primary_node == right_primary_elem->node_ptr(0)) ? -1 : +1;
1008
1009 // Grab the MortarSegmentInfo object associated with this
1010 // segment. We can use "at()" here since we want this to fail if
1011 // current_mortar_segment is not found... Since we're going to
1012 // erase this entry from the map momentarily, we make an actual
1013 // copy rather than grabbing a reference.
1014 auto msm_it = _msm_elem_to_info.find(current_mortar_segment);
1015 if (msm_it == _msm_elem_to_info.end())
1016 mooseError("MortarSegmentInfo not found for current_mortar_segment.");
1017 MortarSegmentInfo current_msinfo = msm_it->second;
1018
1019 // add_left
1020 {
1021 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem_left.release());
1022
1023 // Create new MortarSegmentInfo objects for new_elem_left
1024 MortarSegmentInfo new_msinfo_left;
1025
1026 // The new MortarSegmentInfo info objects inherit their "outer"
1027 // information from current_msinfo and the rest is determined by
1028 // the Node being inserted.
1029 new_msinfo_left.xi1_a = current_msinfo.xi1_a;
1030 new_msinfo_left.xi2_a = current_msinfo.xi2_a;
1031 new_msinfo_left.secondary_elem = secondary_elem;
1032 new_msinfo_left.xi1_b = xi1;
1033 new_msinfo_left.xi2_b = left_xi2;
1034 new_msinfo_left.primary_elem = left_primary_elem;
1035
1036 // Add new msinfo objects to the map.
1037 _msm_elem_to_info.emplace(msm_new_elem, new_msinfo_left);
1038
1039 // We need to insert new_elem_left in
1040 // the mortar_segment_set for this secondary_elem.
1041 mortar_segment_set.insert(msm_new_elem);
1042 }
1043
1044 // add_right
1045 {
1046 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem_right.release());
1047
1048 // Create new MortarSegmentInfo objects for new_elem_right
1049 MortarSegmentInfo new_msinfo_right;
1050
1051 new_msinfo_right.xi1_b = current_msinfo.xi1_b;
1052 new_msinfo_right.xi2_b = current_msinfo.xi2_b;
1053 new_msinfo_right.secondary_elem = secondary_elem;
1054 new_msinfo_right.xi1_a = xi1;
1055 new_msinfo_right.xi2_a = right_xi2;
1056 new_msinfo_right.primary_elem = right_primary_elem;
1057
1058 _msm_elem_to_info.emplace(msm_new_elem, new_msinfo_right);
1059
1060 mortar_segment_set.insert(msm_new_elem);
1061 }
1062
1063 // Erase the MortarSegmentInfo object for current_mortar_segment from the map.
1064 _msm_elem_to_info.erase(msm_it);
1065
1066 // current_mortar_segment must be erased from the
1067 // mortar_segment_set since it has now been split.
1068 mortar_segment_set.erase(current_mortar_segment);
1069
1070 // The original mortar segment has been split, so erase it from
1071 // the mortar segment mesh.
1072 _mortar_segment_mesh->delete_elem(current_mortar_segment);
1073 }
1074
1075 // Remove all MSM elements without a primary contribution
1081 for (auto msm_elem : _mortar_segment_mesh->active_element_ptr_range())
1082 {
1083 MortarSegmentInfo & msinfo = libmesh_map_find(_msm_elem_to_info, msm_elem);
1084 Elem * primary_elem = const_cast<Elem *>(msinfo.primary_elem);
1085 if (primary_elem == nullptr || abs(msinfo.xi2_a) > 1.0 + TOLERANCE ||
1086 abs(msinfo.xi2_b) > 1.0 + TOLERANCE)
1087 {
1088 // Erase from secondary to msms map
1089 auto it = _secondary_elems_to_mortar_segments.find(msinfo.secondary_elem->id());
1090 mooseAssert(it != _secondary_elems_to_mortar_segments.end(),
1091 "We should have found the element");
1092 auto & msm_set = it->second;
1093 msm_set.erase(msm_elem);
1094 // We may be creating nodes with only one element neighbor where before this removal there
1095 // were two. But the nodal normal used in computations will reflect the two-neighbor geometry.
1096 // For a lower-d secondary mesh corner, that will imply the corner node will have a tilted
1097 // normal vector (same for tangents) despite the mortar segment mesh not including its
1098 // vertical neighboring element. It is the secondary element neighbors (not mortar segment
1099 // mesh neighbors) that determine the nodal normal field.
1100 if (msm_set.empty())
1102
1103 // Erase msinfo
1104 _msm_elem_to_info.erase(msm_elem);
1105
1106 // Remove element from mortar segment mesh
1107 _mortar_segment_mesh->delete_elem(msm_elem);
1108 }
1109 else
1110 {
1111 _secondary_ip_sub_ids.insert(msinfo.secondary_elem->interior_parent()->subdomain_id());
1112 _primary_ip_sub_ids.insert(msinfo.primary_elem->interior_parent()->subdomain_id());
1113 }
1114 }
1115
1116 std::unordered_set<Node *> msm_connected_nodes;
1117
1118 // Deleting elements may produce isolated nodes.
1119 // Loops for identifying and removing such nodes from mortar segment mesh.
1120 for (const auto & element : _mortar_segment_mesh->element_ptr_range())
1121 for (auto & n : element->node_ref_range())
1122 msm_connected_nodes.insert(&n);
1123
1124 for (const auto & node : _mortar_segment_mesh->node_ptr_range())
1125 if (!msm_connected_nodes.count(node))
1126 _mortar_segment_mesh->delete_node(node);
1127
1128#ifdef DEBUG
1129 // Verify that all segments without primary contribution have been deleted
1130 for (auto msm_elem : _mortar_segment_mesh->active_element_ptr_range())
1131 {
1132 const MortarSegmentInfo & msinfo = libmesh_map_find(_msm_elem_to_info, msm_elem);
1133 mooseAssert(msinfo.primary_elem != nullptr,
1134 "All mortar segment elements should have valid "
1135 "primary element.");
1136 }
1137#endif
1138
1139 _mortar_segment_mesh->cache_elem_data();
1140
1141 // (Optionally) Write the mortar segment mesh to file for inspection
1142 if (_debug)
1144
1146}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
unsigned int count
Definition MortarUtils.C:53
for(PetscInt i=0;i< nvars;++i)
if(!dmm->_nl) SETERRQ(PETSC_COMM_WORLD
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 buildCouplingInformation()
build the _mortar_interface_coupling data
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.
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::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),...
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, 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...
SolutionInvalidity & solutionInvalidity()
Get the SolutionInvalidity for this app.
Definition MooseApp.h:182
Provides a way for users to bail out of the current solve.
void flagInvalidSolutionInternal(const InvalidSolutionID _invalid_solution_id)
Increments solution invalid occurrences for each solution id.
InvalidSolutionID registerInvalidity(const std::string &object_type, const std::string &message, const bool warning)
Call to register an invalid calculation.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition EigenADReal.h:50
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
MPI_Info info
uint8_t dof_id_type
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
SolutionInvalidityRegistry & getSolutionInvalidityRegistry()
Get the global SolutionInvalidityRegistry singleton.
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

Referenced by MortarInterfaceWarehouse::update().

◆ buildMortarSegmentMesh3d()

void AutomaticMortarGeneration::buildMortarSegmentMesh3d ( )

Builds the mortar segment mesh once the secondary and primary node projections have been completed.

Inputs:

  • mesh

Outputs:

  • mortar_segment_mesh
  • msm_elem_to_info

Step 1: Build mortar segments for all secondary elements

Step 1.1: Linearize secondary face elements

For first order face elements (Tri3 and Quad4) elements are simply linearized around center For second order (Tri6 and Quad9) and third order (Tri7) face elements, elements are sub-divided into four first order elements then each of the sub-elements is linearized around their respective centers For Quad8 elements, they are sub-divided into one quad and four triangle elements and each sub-element is linearized around their respective centers

Step 1.2: Coarse screening using a k-d tree to find nodes on the primary interface that are 'close to' a center point of the secondary element.

Step 1.3: Loop through primary candidate nodes, create mortar segments

Once an element with non-trivial projection onto secondary element identified, switch to breadth-first search (drop all current candidates and add only neighbors of elements with non-trivial overlap)

Step 1.3.2: Sub-divide primary element candidate, then project onto secondary sub-elements, perform polygon clipping, and triangulate to form mortar segments

Step 1.3.3: Create mortar segments and add to mortar segment mesh

Definition at line 1164 of file AutomaticMortarGeneration.C.

1165{
1166 const bool use_reference_interpolation =
1167 _mortar_3d_qp_mapping == Mortar3DQuadraturePointMapping::REFERENCE_INTERPOLATION;
1168
1169 // Add an integer flag to mortar segment mesh to keep track of which subelem
1170 // of second order primal elements mortar segments correspond to
1171 auto secondary_sub_elem = _mortar_segment_mesh->add_elem_integer("secondary_sub_elem");
1172 auto primary_sub_elem = _mortar_segment_mesh->add_elem_integer("primary_sub_elem");
1173
1174 // Assign globally unique node/element IDs via an exclusive prefix scan: each rank's bound is
1175 // local_secondary_sub_elems * visible_primary_sub_elems * 9, where 9 is the maximum nodes a
1176 // single secondary/primary sub-element pair can produce (8-vertex clipped polygon + center).
1177 // The result is cached and invalidated by meshChanged(), so the allgather only runs on topology
1178 // changes, not on every displaced-mesh residual update.
1179 if (!_msm_node_id_start.has_value())
1180 {
1181 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1182 for (const auto & [primary_sub_id, secondary_sub_id] : _primary_secondary_subdomain_id_pairs)
1183 {
1184 for (const auto * const el :
1185 _mesh.active_local_subdomain_elements_ptr_range(secondary_sub_id))
1186 local_secondary_sub_elems += el->n_sub_elem();
1187 for (const auto * const el : _mesh.active_subdomain_elements_ptr_range(primary_sub_id))
1188 visible_primary_sub_elems += el->n_sub_elem();
1189 }
1190 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1191 std::vector<dof_id_type> per_rank_bounds;
1192 _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
1193 dof_id_type start = 0;
1194 for (const auto r : make_range(_mesh.processor_id()))
1195 start += per_rank_bounds[r];
1196 _msm_node_id_start = start;
1197 }
1198 dof_id_type next_node_id = *_msm_node_id_start;
1199 // Element IDs use the same starting offset: node and element IDs are separately numbered, and
1200 // element count per clip (n triangles) is always <= node count (n+1), so per_rank_bound covers
1201 // both.
1202 dof_id_type next_elem_id = next_node_id;
1203
1204 // Loop through mortar secondary and primary pairs to create mortar segment mesh between each
1205 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1206 {
1207 const auto primary_subd_id = pr.first;
1208 const auto secondary_subd_id = pr.second;
1209
1210 // Build k-d tree for use in Step 1.2 for primary interface coarse screening
1211 NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, primary_subd_id);
1212 subdomain_kd_tree_t kd_tree(
1213 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
1214
1215 // Construct the KD tree.
1216 kd_tree.buildIndex();
1217
1218 // Return the unoriented geometric normal of a linearized subpatch. These expressions are the
1219 // TRI3 and QUAD4 mapping tangents evaluated at the reference center, equivalent to evaluating
1220 // the first-order finite-element normal there without constructing a temporary element.
1221 auto get_sub_elem_geometric_normal = [](const std::vector<Point> & nodes)
1222 {
1223 Point dxdxi;
1224 Point dxdeta;
1225 if (nodes.size() == 3)
1226 {
1227 dxdxi = nodes[1] - nodes[0];
1228 dxdeta = nodes[2] - nodes[0];
1229 }
1230 else if (nodes.size() == 4)
1231 {
1232 // Bilinear center tangents define one normal for the full quad instead of selecting one of
1233 // the two diagonal triangle normals.
1234 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1235 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1236 }
1237 else
1238 mooseError("GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1239 "triangular and quadrilateral subpatches, but received ",
1240 nodes.size(),
1241 " nodes.");
1242
1243 Point geometric_normal = dxdxi.cross(dxdeta);
1244 const auto normal_norm = geometric_normal.norm();
1245 // The cross product has units of area, so compare it with the product of tangent lengths.
1246 // Their ratio is the sine of the included angle and is independent of the mesh length scale.
1247 if (normal_norm <= TOLERANCE * dxdxi.norm() * dxdeta.norm())
1248 mooseError("GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1249 "degenerate subpatch.");
1250
1251 geometric_normal /= normal_norm;
1252 return geometric_normal;
1253 };
1254
1258 for (MeshBase::const_element_iterator el = _mesh.active_local_elements_begin(),
1259 end_el = _mesh.active_local_elements_end();
1260 el != end_el;
1261 ++el)
1262 {
1263 const Elem * secondary_side_elem = *el;
1264
1265 const Real secondary_volume = secondary_side_elem->volume();
1266
1267 // If this Elem is not in the current secondary subdomain, go on to the next one.
1268 if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1269 continue;
1270
1271 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1272 _secondary_elems_to_mortar_segments.emplace(secondary_side_elem->id(),
1273 std::set<Elem *, CompareDofObjectsByID>{});
1274 libmesh_ignore(insertion_happened);
1275 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1276
1277 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1278 secondary_side_elem->n_sub_elem());
1279 const auto nodal_normals = getNodalNormals(*secondary_side_elem);
1280
1291 for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1292 {
1293 // Get indices of sub-element nodes in element
1294 const auto sub_elem_nodes =
1295 Moose::Mortar::getMortarSubElementNodeIndices(*secondary_side_elem, sel);
1296
1297 // Secondary sub-element center, normal, and nodes
1298 Point center;
1299 Point normal;
1300 std::vector<Point> nodes(sub_elem_nodes.size());
1301
1302 // Collect the sub-element points and evaluate its center and averaged nodal normal.
1303 for (auto iv : make_range(sub_elem_nodes.size()))
1304 {
1305 const auto n = sub_elem_nodes[iv];
1306 nodes[iv] = secondary_side_elem->point(n);
1307 center += secondary_side_elem->point(n);
1308 normal += nodal_normals[n];
1309 }
1310 center /= sub_elem_nodes.size();
1311 normal = normal.unit();
1312
1313 if (_mortar_3d_subpatch_plane == Mortar3DSubpatchPlane::GEOMETRIC_NORMAL)
1314 {
1315 const Point averaged_normal = normal;
1316 normal = get_sub_elem_geometric_normal(nodes);
1317 if (normal * averaged_normal < 0)
1318 normal *= -1;
1319 }
1320
1321 if (use_reference_interpolation)
1322 {
1323 std::vector<Point> sub_elem_reference_points;
1324 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1325 for (const auto node_index : sub_elem_nodes)
1326 sub_elem_reference_points.push_back(secondary_side_elem->master_point(node_index));
1327
1328 mortar_segment_helper[sel] =
1329 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1330 std::move(sub_elem_reference_points),
1331 center,
1332 normal,
1335 }
1336 else
1337 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1338 std::move(nodes), center, normal, _triangulation_mode, _triangulate_triangles);
1339 }
1340
1346 // Search point for performing Nanoflann (k-d tree) searches.
1347 // In each case we use the center point of the original element (not sub-elements for second
1348 // order elements). This is to do search for all sub-elements simultaneously
1349 std::array<Real, 3> query_pt;
1350 Point center_point;
1351 switch (secondary_side_elem->type())
1352 {
1353 case TRI3:
1354 case QUAD4:
1355 center_point = mortar_segment_helper[0]->center();
1356 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1357 break;
1358 case TRI6:
1359 case TRI7:
1360 center_point = mortar_segment_helper[1]->center();
1361 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1362 break;
1363 case QUAD8:
1364 center_point = mortar_segment_helper[4]->center();
1365 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1366 break;
1367 case QUAD9:
1368 center_point = secondary_side_elem->point(8);
1369 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1370 break;
1371 default:
1372 mooseError(
1373 "Face element type: ", secondary_side_elem->type(), "not supported for 3D mortar");
1374 }
1375
1376 // The number of results we want to get. These results will only be used to find
1377 // a single element with non-trivial overlap, after an element is identified a breadth
1378 // first search is done on neighbors
1379 const std::size_t num_results = 3;
1380
1381 // Initialize result_set and do the search.
1382 std::vector<size_t> ret_index(num_results);
1383 std::vector<Real> out_dist_sqr(num_results);
1384 nanoflann::KNNResultSet<Real> result_set(num_results);
1385 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1386 kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
1387
1388 // Initialize list of processed primary elements, we don't want to revisit processed elements
1389 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1390
1391 // Initialize candidate set and flag for switching between coarse screening and breadth-first
1392 // search
1393 bool primary_elem_found = false;
1394 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1395 const bool use_geometric_subpatch_normals =
1396 _mortar_3d_subpatch_plane == Mortar3DSubpatchPlane::GEOMETRIC_NORMAL;
1397 // In geometric mode the projection-angle cutoff also rejects near-orthogonal subpatch pairs.
1398 // The absolute dot product below keeps opposing primary/secondary orientations admissible.
1399 const Real minimum_subpatch_normal_alignment =
1400 use_geometric_subpatch_normals ? std::sin(_minimum_projection_angle * libMesh::pi / 180.0)
1401 : 0.0;
1402
1403 // Loop candidate nodes (returned by Nanoflann) and add all adjoining elems to candidate set
1404 for (auto r : make_range(result_set.size()))
1405 {
1406 // Verify that the squared distance we compute is the same as nanoflann's
1407 mooseAssert(abs((_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1408 TOLERANCE,
1409 "Lower-dimensional element squared distance verification failed.");
1410
1411 // Get list of elems connected to node
1412 std::vector<const Elem *> & node_elems =
1413 this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
1414
1415 // Uniquely add elems to candidate set
1416 for (auto elem : node_elems)
1417 primary_elem_candidates.insert(elem);
1418 }
1419
1427 while (!primary_elem_candidates.empty())
1428 {
1429 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1430
1431 // If we've already processed this candidate, we don't need to check it again.
1432 if (processed_primary_elems.count(primary_elem_candidate))
1433 {
1434 primary_elem_candidates.erase(primary_elem_candidate);
1435 continue;
1436 }
1437
1438 // Initialize set of nodes used to construct mortar segment elements
1439 std::vector<Point> nodal_points;
1440
1441 // Initialize map from mortar segment elements to nodes
1442 std::vector<std::vector<unsigned int>> elem_to_node_map;
1443
1444 // Initialize list of secondary and primary sub-elements that formed each mortar segment
1445 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1446 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1447 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1448
1453 for (auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1454 {
1455 // Get nodes of primary sub-elements
1456 const auto sub_elem_nodes =
1457 Moose::Mortar::getMortarSubElementNodeIndices(*primary_elem_candidate, p_el);
1458
1459 // Get list of primary sub-element vertex nodes
1460 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1461 for (auto iv : make_range(sub_elem_nodes.size()))
1462 {
1463 const auto n = sub_elem_nodes[iv];
1464 primary_sub_elem[iv] = primary_elem_candidate->point(n);
1465 }
1466 Point primary_sub_elem_normal;
1467 if (use_geometric_subpatch_normals)
1468 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1469
1470 std::vector<Point> sub_elem_reference_points;
1471 if (use_reference_interpolation)
1472 {
1473 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1474 for (const auto node_index : sub_elem_nodes)
1475 sub_elem_reference_points.push_back(primary_elem_candidate->master_point(node_index));
1476 }
1477
1478 // Loop through secondary sub-elements
1479 for (auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1480 {
1481 // Nearby primary candidates can include adjacent corner faces. Those faces may clip to
1482 // numerical slivers, which we do not consider valid face-to-face mortar pairs for this
1483 // search.
1484 if (use_geometric_subpatch_normals &&
1485 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1486 minimum_subpatch_normal_alignment)
1487 continue;
1488
1489 // Mortar segment helpers were defined for each secondary sub-element, they will:
1490 // 1. Project primary sub-element onto linearized secondary sub-element
1491 // 2. Clip projected primary sub-element against secondary sub-element
1492 // 3. Triangulate clipped polygon to form mortar segments
1493 //
1494 // Mortar segment helpers append a list of mortar segment nodes and connectivities that
1495 // can be directly used to build mortar segments
1496 const auto segments_before_helper = elem_to_node_map.size();
1497 if (use_reference_interpolation)
1498 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1499 sub_elem_reference_points,
1500 nodal_points,
1501 elem_to_node_map,
1502 elem_to_secondary_reference_points,
1503 elem_to_primary_reference_points,
1504 TOLERANCE * secondary_volume);
1505 else
1506 mortar_segment_helper[s_el]->getMortarSegments(
1507 primary_sub_elem, nodal_points, elem_to_node_map);
1508
1509 // Keep track of which secondary and primary sub-elements created segment
1510 for (auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1511 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1512 }
1513 }
1514
1515 // Mark primary element as processed and remove from candidate list
1516 processed_primary_elems.insert(primary_elem_candidate);
1517 primary_elem_candidates.erase(primary_elem_candidate);
1518
1519 // If overlap of polygons was non-trivial (created mortar segment elements)
1520 if (!elem_to_node_map.empty())
1521 {
1522 if (sub_elem_map.size() != elem_to_node_map.size())
1523 mooseError("The mortar segment subpatch map is not aligned with the mortar segment "
1524 "connectivity map.");
1525 if (use_reference_interpolation &&
1526 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1527 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1528 mooseError("The mortar segment reference-point maps are not aligned with the mortar "
1529 "segment connectivity map.");
1530
1531 // Only overlap polygons large enough to become mortar segments may switch the candidate
1532 // search to breadth first.
1533 bool seed_breadth_first_search = false;
1534 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(), false);
1535 for (const auto el : index_range(elem_to_node_map))
1536 {
1537 const auto & node_map = elem_to_node_map[el];
1538 if (node_map.size() != 3)
1539 mooseError(
1540 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1541 node_map.size(),
1542 " provided.");
1543
1544 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1545 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1546 retained_mortar_segments[el] =
1547 0.5 * e1.cross(e2).norm() / secondary_volume >= TOLERANCE;
1548 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1549 }
1550
1551 if (seed_breadth_first_search)
1552 {
1553 // If this is the first element with a qualifying overlap, set flag. Candidates will
1554 // now be neighbors of elements that had qualifying overlap.
1555 if (!primary_elem_found)
1556 {
1557 primary_elem_found = true;
1558 primary_elem_candidates.clear();
1559 }
1560
1561 // Add neighbors to candidate list
1562 for (auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1563 {
1564 // If not valid or not on lower dimensional secondary subdomain, skip
1565 if (neighbor == nullptr || neighbor->subdomain_id() != primary_subd_id)
1566 continue;
1567 // If already processed, skip
1568 if (processed_primary_elems.count(neighbor))
1569 continue;
1570 // Otherwise, add to candidates
1571 primary_elem_candidates.insert(neighbor);
1572 }
1573 }
1574
1578 std::vector<Node *> new_nodes;
1579 // Clipping can append points for triangles later rejected by the area tolerance. Add only
1580 // points referenced by retained triangles so the mortar mesh has no orphan nodes.
1581 std::vector<bool> retained_nodes(nodal_points.size(), false);
1582 for (const auto el : index_range(elem_to_node_map))
1583 if (retained_mortar_segments[el])
1584 for (const auto node : elem_to_node_map[el])
1585 retained_nodes[node] = true;
1586
1587 new_nodes.resize(nodal_points.size(), nullptr);
1588 for (const auto node : index_range(nodal_points))
1589 if (retained_nodes[node])
1590 new_nodes[node] = _mortar_segment_mesh->add_point(
1591 nodal_points[node], next_node_id++, secondary_side_elem->processor_id());
1592
1593 // Loop through triangular elements in map
1594 for (auto el : index_range(elem_to_node_map))
1595 {
1596 if (!retained_mortar_segments[el])
1597 continue;
1598
1599 std::unique_ptr<Elem> new_elem;
1600 if (elem_to_node_map[el].size() == 3)
1601 new_elem = std::make_unique<Tri3>();
1602 else
1603 mooseError("Active mortar segments only supports TRI elements, 3 nodes expected "
1604 "but: ",
1605 elem_to_node_map[el].size(),
1606 " provided.");
1607
1608 new_elem->processor_id() = secondary_side_elem->processor_id();
1609 new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1610 new_elem->set_id(next_elem_id++);
1611
1612 // Attach newly created nodes
1613 for (auto i : index_range(elem_to_node_map[el]))
1614 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1615
1616 // If element is smaller than tolerance, don't add to msm
1617 if (new_elem->volume() / secondary_volume < TOLERANCE)
1618 continue;
1619
1620 // Add elements to mortar segment mesh
1621 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem.release());
1622
1623 msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1624 msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1625
1626 // Fill out mortar segment info
1627 MortarSegmentInfo msinfo;
1628 msinfo.secondary_elem = secondary_side_elem;
1629 msinfo.primary_elem = primary_elem_candidate;
1630
1631 // Associate this MSM elem with the MortarSegmentInfo.
1632 _msm_elem_to_info.emplace(msm_new_elem, msinfo);
1633
1634 // Store reference data only for retained segments.
1635 if (use_reference_interpolation)
1636 {
1637 MortarSegmentReferencePoints reference_points{elem_to_secondary_reference_points[el],
1638 elem_to_primary_reference_points[el]};
1639 _msm_elem_to_reference_points.emplace(msm_new_elem, reference_points);
1640 }
1641
1642 // Add this mortar segment to the secondary elem to mortar segment map
1643 secondary_to_msm_element_set.insert(msm_new_elem);
1644
1645 _secondary_ip_sub_ids.insert(msinfo.secondary_elem->interior_parent()->subdomain_id());
1646 // Unlike for 2D, we always have a primary when building the mortar mesh so we don't
1647 // have to check for null
1648 _primary_ip_sub_ids.insert(msinfo.primary_elem->interior_parent()->subdomain_id());
1649 }
1650 }
1651 // End loop through primary element candidates
1652 }
1653
1654 if (use_geometric_subpatch_normals)
1655 {
1656 // A geometric corner filter may intentionally leave individual subpatches uncovered. Warn
1657 // only when the complete secondary element failed to produce a retained segment.
1658 if (secondary_to_msm_element_set.empty())
1659 mooseDoOnce(
1660 mooseWarning("Some secondary elements on mortar interface were unable to identify"
1661 " a corresponding primary element; this may be expected depending on"
1662 " problem geometry but may indicate a failure of the element search"
1663 " or projection"));
1664 }
1665 else
1666 for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1667 if (mortar_segment_helper[sel]->remainder() == 1.0)
1668 mooseDoOnce(
1669 mooseWarning("Some secondary elements on mortar interface were unable to identify"
1670 " a corresponding primary element; this may be expected depending on"
1671 " problem geometry but may indicate a failure of the element search"
1672 " or projection"));
1673
1674 if (secondary_to_msm_element_set.empty())
1675 _secondary_elems_to_mortar_segments.erase(secondary_elem_to_msm_map_it);
1676 } // End loop through secondary elements
1677 } // End loop through mortar constraint pairs
1678
1679 mooseAssert(!use_reference_interpolation ||
1681 "Mortar segment info and reference-point maps must remain aligned.");
1682
1683 _mortar_segment_mesh->cache_elem_data();
1684
1685 // The mesh was built distributedly (each rank owns only its local elements), so mark it
1686 // as such so MeshSerializer correctly gathers it to proc 0 for Exodus output.
1687 _mortar_segment_mesh->set_distributed();
1688
1689 // Output mortar segment mesh
1690 if (_debug)
1691 {
1692 // If element is not triangular, increment subdomain id
1693 // (ExodusII does not support mixed element types in a single subdomain)
1694 for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1695 if (msm_el->type() != TRI3)
1696 msm_el->subdomain_id()++;
1697
1699
1700 // Undo increment
1701 for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1702 if (msm_el->type() != TRI3)
1703 msm_el->subdomain_id()--;
1704 }
1705
1707
1708 // Print mortar segment mesh statistics
1709 if (_debug)
1710 {
1711 msmStatistics();
1712 }
1713}
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition MooseError.h:345
Point center
Definition MortarUtils.C:58
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
std::unordered_map< const Elem *, MortarSegmentReferencePoints > _msm_elem_to_reference_points
Reference-coordinate data used only by the reference-interpolation mapping mode.
void msmStatistics()
Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally)
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
Special adaptor that works with subdomains of the Mesh.
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.
Definition MortarUtils.C:67
auto index_range(const T &sizable)
void libmesh_ignore(const Args &...)
const Real pi
IntRange< T > make_range(T beg, T end)
SearchParams SearchParameters
Parent-face reference coordinates associated with the vertices of one triangular mortar segment.

Referenced by MortarInterfaceWarehouse::update().

◆ buildNodeToElemMaps()

void AutomaticMortarGeneration::buildNodeToElemMaps ( )

Once the secondary_requested_boundary_ids and primary_requested_boundary_ids containers have been filled in, call this function to build node-to-Elem maps for the lower-dimensional elements.

Definition at line 505 of file AutomaticMortarGeneration.C.

506{
509 "Must specify secondary and primary boundary ids before building node-to-elem maps.");
510
511 // Construct nodes_to_secondary_elem_map
512 for (const auto & secondary_elem :
513 as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
514 {
515 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
516 if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
517 continue;
518
519 for (const auto & nd : secondary_elem->node_ref_range())
520 {
521 std::vector<const Elem *> & vec = _nodes_to_secondary_elem_map[nd.id()];
522 vec.push_back(secondary_elem);
523 }
524 }
525
526 // Construct nodes_to_primary_elem_map
527 for (const auto & primary_elem :
528 as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
529 {
530 // If this is not one of the lower-dimensional primary side elements, go on to the next one.
531 if (!this->_primary_boundary_subdomain_ids.count(primary_elem->subdomain_id()))
532 continue;
533
534 for (const auto & nd : primary_elem->node_ref_range())
535 {
536 std::vector<const Elem *> & vec = _nodes_to_primary_elem_map[nd.id()];
537 vec.push_back(primary_elem);
538 }
539 }
540}
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.
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)

Referenced by MortarInterfaceWarehouse::update().

◆ clear()

void AutomaticMortarGeneration::clear ( )

Clears the mortar segment mesh and accompanying data structures.

Definition at line 466 of file AutomaticMortarGeneration.C.

467{
469 _mortar_segment_mesh->clear();
474 _msm_elem_to_info.clear();
481 _secondary_ip_sub_ids.clear();
482 _primary_ip_sub_ids.clear();
485}
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
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_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
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...
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.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.

Referenced by MortarInterfaceWarehouse::update().

◆ computeADNodalNormals()

void AutomaticMortarGeneration::computeADNodalNormals ( const std::function< ADPoint(const Node &)> &  coordinate,
std::unordered_map< const Node *, ADRealVectorValue > &  nodal_normals 
) const

Build the normalized JxW-weighted secondary nodal normals from AD nodal coordinates.

The coordinate functor adds displacement derivatives to each node's current coordinates, so the stored normal values and their derivatives use the same geometry state.

Definition at line 2189 of file AutomaticMortarGeneration.C.

2192{
2193 const auto dim = _mesh.mesh_dimension();
2194 mooseAssert(dim == 2 || dim == 3, "AD nodal normals are only valid for 2D or 3D mortar.");
2195
2196 libMesh::QNodal qface(dim - 1);
2197 const auto & qweights = qface.get_weights();
2198 const Real sign = _periodic ? -1 : 1;
2199
2200 std::unordered_map<const Node *, ADRealVectorValue> weighted_area_vectors;
2201 std::unordered_map<const Node *, Real> weighted_area_magnitudes;
2202
2203 // One face FE object per secondary element order, reused across elements of that order
2204 std::map<Order, std::unique_ptr<FEBase>> fe_faces;
2205
2206 // AD propagates coordinate derivatives through the parent-side area vectors and normalization.
2207 for (const auto secondary_subdomain_id : _secondary_boundary_subdomain_ids)
2208 for (const Elem * const secondary_elem :
2209 as_range(_mesh.active_subdomain_elements_begin(secondary_subdomain_id),
2210 _mesh.active_subdomain_elements_end(secondary_subdomain_id)))
2211 {
2212 const Elem * const interior_parent = secondary_elem->interior_parent();
2213 mooseAssert(interior_parent,
2214 "No interior parent exists for element "
2215 << secondary_elem->id()
2216 << ". There may be a problem with your sideset set-up.");
2217
2218 const Real element_size = secondary_elem->hmax();
2219 if (element_size <= 0)
2220 mooseError("Cannot compute an AD nodal normal for secondary mortar element ",
2221 secondary_elem->id(),
2222 " because it has a non-positive characteristic length.");
2223
2224 const auto side = interior_parent->which_side_am_i(secondary_elem);
2225 auto & fe_face = fe_faces[secondary_elem->default_order()];
2226 if (!fe_face)
2227 {
2228 fe_face = FEBase::build(dim, FEType(secondary_elem->default_order(), LAGRANGE));
2229 fe_face->attach_quadrature_rule(&qface);
2230 }
2231
2232 const auto & face_normals = fe_face->get_normals();
2233 const auto & face_points = fe_face->get_xyz();
2234 const auto & map_dpsidxi = fe_face->get_fe_map().get_dpsidxi();
2235 const auto * const map_dpsideta = dim == 3 ? &fe_face->get_fe_map().get_dpsideta() : nullptr;
2236
2237 fe_face->reinit(interior_parent, side);
2238
2239 const auto qpoint_to_secondary_node =
2240 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2241
2242 mooseAssert(face_normals.size() == face_points.size() &&
2243 qweights.size() == face_points.size(),
2244 "Face nodal geometry vectors and quadrature weights must have the same size.");
2245
2246 // The parent-side map includes enriched face mappings such as TET14-to-TRI7.
2247 auto parent_side_elem = interior_parent->build_side_ptr(side);
2248 const unsigned int n_mapping_shape_functions =
2249 dim == 2 ? libMesh::FE<2, LAGRANGE>::n_dofs(parent_side_elem.get(),
2250 parent_side_elem->default_order())
2251 : libMesh::FE<3, LAGRANGE>::n_dofs(parent_side_elem.get(),
2252 parent_side_elem->default_order());
2253 if (map_dpsidxi.size() != n_mapping_shape_functions ||
2254 (map_dpsideta && map_dpsideta->size() != n_mapping_shape_functions))
2255 mooseError("The parent-side geometric map for secondary mortar element ",
2256 secondary_elem->id(),
2257 " of type ",
2258 libMesh::Utility::enum_to_string<ElemType>(secondary_elem->type()),
2259 " does not have one mapping shape function per element node.");
2260 mooseAssert(n_mapping_shape_functions <= parent_side_elem->n_nodes(),
2261 "The mapping basis must not index past the side element's nodes.");
2262
2263 std::vector<ADPoint> side_points;
2264 side_points.reserve(n_mapping_shape_functions);
2265 for (const auto side_node_index : make_range(n_mapping_shape_functions))
2266 side_points.push_back(coordinate(parent_side_elem->node_ref(side_node_index)));
2267
2268 for (const auto qp : index_range(face_points))
2269 {
2270 const auto secondary_node_index = qpoint_to_secondary_node[qp];
2271 const Node * const secondary_node = secondary_elem->node_ptr(secondary_node_index);
2272
2273 ADRealVectorValue tangent_xi;
2274 ADRealVectorValue tangent_eta;
2275 for (const auto side_node_index : make_range(n_mapping_shape_functions))
2276 {
2277 tangent_xi.add_scaled(side_points[side_node_index], map_dpsidxi[side_node_index][qp]);
2278 if (dim == 3)
2279 tangent_eta.add_scaled(side_points[side_node_index],
2280 (*map_dpsideta)[side_node_index][qp]);
2281 }
2282
2283 auto area_vector = dim == 2 ? ADRealVectorValue(tangent_xi(1), -tangent_xi(0), 0)
2284 : tangent_xi.cross(tangent_eta);
2285 const Real area = MetaPhysicL::raw_value(area_vector.norm());
2286 const Real degeneracy_tolerance =
2287 dim == 2 ? TOLERANCE * element_size : TOLERANCE * element_size * element_size;
2288 if (area <= degeneracy_tolerance)
2289 mooseError("Cannot compute an AD nodal normal on degenerate secondary mortar element ",
2290 secondary_elem->id(),
2291 ".");
2292
2293 // qweight * area_vector is equivalent to JxW * face_normal. Fix its orientation to the
2294 // Real-valued face normal because the incident-face set and orientation are not
2295 // differentiated.
2296 const Real orientation =
2297 MetaPhysicL::raw_value(area_vector) * face_normals[qp] < 0 ? -1 : 1;
2298
2299 weighted_area_vectors[secondary_node] += sign * orientation * qweights[qp] * area_vector;
2300 weighted_area_magnitudes[secondary_node] += std::abs(qweights[qp]) * area;
2301 }
2302 }
2303
2304 nodal_normals.clear();
2305 nodal_normals.reserve(weighted_area_vectors.size());
2306 for (auto & [secondary_node, weighted_area_vector] : weighted_area_vectors)
2307 {
2308 const Real weighted_area_vector_norm = MetaPhysicL::raw_value(weighted_area_vector.norm());
2309 const Real weighted_area_magnitude = libmesh_map_find(weighted_area_magnitudes, secondary_node);
2310 if (weighted_area_vector_norm <= TOLERANCE * weighted_area_magnitude)
2311 mooseError("Cannot compute a normalized AD nodal normal for secondary node ",
2312 secondary_node->id(),
2313 " because its incident face contributions cancel to a near-zero weighted normal.");
2314
2315 auto nodal_normal = weighted_area_vector.unit();
2316 const auto & stored_normal = libmesh_map_find(_secondary_node_to_nodal_normal, secondary_node);
2317 // Equivalent FE and AD evaluations of the same unit normal may differ through operation
2318 // ordering. The tolerance admits roundoff while detecting inconsistent geometry states.
2319 mooseAssert((MetaPhysicL::raw_value(nodal_normal) - stored_normal).norm() < 1e-10,
2320 "The stored and AD secondary nodal normals must use the same geometry state.");
2321 // Set the raw value from the stored contact direction while retaining the derivatives produced
2322 // by the equivalent AD area-vector calculation.
2323 for (const auto component : make_range(Moose::dim))
2324 nodal_normal(component).value() = stored_normal(component);
2325 nodal_normals.emplace(secondary_node, std::move(nodal_normal));
2326 }
2327}
libMesh::VectorValue< ADReal > ADRealVectorValue
AD Array typedefs.
Definition MooseTypes.h:411
unsigned int n_dofs(const ElemType t, const Order o)
void add_scaled(const TypeVector< T2 > &, const T &)
auto raw_value(const Eigen::Map< T > &in)
KOKKOS_INLINE_FUNCTION T sign(T x)
Returns the sign of a value.
Definition KokkosUtils.h:26
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
const Elem & get(const ElemType type_in)
auto norm(const T &a)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
const dof_id_type n_nodes

◆ computeInactiveLMElems()

void AutomaticMortarGeneration::computeInactiveLMElems ( )

Get list of secondary elems without any corresponding primary elements.

Used to enforce zero values on inactive DoFs of elemental variables.

Definition at line 2041 of file AutomaticMortarGeneration.C.

2042{
2043 // Mark all active secondary elements
2044 std::unordered_set<const Elem *> active_local_elems;
2045
2046 //****
2047 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
2048 // since the mortar segments are on the linearized element and comparing the volume of the
2049 // linearized element does not have the same volume as the warped element
2050 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
2051
2052 std::unordered_map<const Elem *, Real> active_volume;
2053
2054 // Compute fraction of elements with corresponding primary elements
2056 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
2057 {
2058 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
2059 const Elem * secondary_elem = msinfo.secondary_elem;
2060
2061 active_volume[secondary_elem] += msm_elem->volume();
2062 }
2063 //****
2064
2065 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
2066 {
2067 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
2068 const Elem * secondary_elem = msinfo.secondary_elem;
2069
2070 //****
2072 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
2073 continue;
2074 //****
2075
2076 active_local_elems.insert(secondary_elem);
2077 }
2078
2079 // Take complement of active elements in active local subdomain to get inactive local elements
2081 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2082 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
2083 /*secondary_subd_id*/ pr.second))
2084 if (active_local_elems.find(el) == active_local_elems.end())
2085 _inactive_local_lm_elems.insert(el);
2086}
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
Definition KokkosUtils.h:40

Referenced by MortarInterfaceWarehouse::update().

◆ computeInactiveLMNodes()

void AutomaticMortarGeneration::computeInactiveLMNodes ( )

Get list of secondary nodes that don't contribute to interaction with any primary element.

Used to enforce zero values on inactive DoFs of nodal variables.

Definition at line 1977 of file AutomaticMortarGeneration.C.

1978{
1980 {
1982 return;
1983 }
1984
1985 // Only the owner of a node zeroes its LM DoFs, but the owner need not own (or even ghost) any
1986 // secondary lower-dimensional element touching it. Each processor therefore sends every node of
1987 // its local secondary elements to the node's owner, flagged as active if a local mortar segment
1988 // touches it. A node is inactive if no processor flags it as active.
1989
1990 // List of active nodes on local secondary elements
1991 std::unordered_set<dof_id_type> active_local_nodes;
1992
1993 // Mark all active local nodes
1994 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1995 {
1996 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1997 const Elem * secondary_elem = msinfo.secondary_elem;
1998
1999 for (auto n : make_range(secondary_elem->n_nodes()))
2000 active_local_nodes.insert(secondary_elem->node_id(n));
2001 }
2002
2003 // List of all nodes on local secondary elements
2004 std::unordered_set<dof_id_type> local_nodes;
2005 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2006 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
2007 /*secondary_subd_id*/ pr.second))
2008 for (const auto n : make_range(el->n_nodes()))
2009 local_nodes.insert(el->node_id(n));
2010
2011 // Pairs of (node id, 1 if active on the sending processor and 0 otherwise)
2012 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, unsigned char>>>
2013 proc_to_nodes;
2014 for (const auto node_id : local_nodes)
2015 proc_to_nodes[_mesh.node_ptr(node_id)->processor_id()].emplace_back(
2016 node_id, active_local_nodes.count(node_id));
2017
2018 std::unordered_set<dof_id_type> candidate_nodes, active_nodes;
2019 auto action_functor = [&candidate_nodes, &active_nodes](
2020 const processor_id_type,
2021 const std::vector<std::pair<dof_id_type, unsigned char>> & sent_data)
2022 {
2023 for (const auto & [node_id, active] : sent_data)
2024 {
2025 candidate_nodes.insert(node_id);
2026 if (active)
2027 active_nodes.insert(node_id);
2028 }
2029 };
2030 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_nodes, action_functor);
2031
2032 // Store the owned inactive nodes to use later to zero LM DoFs on inactive nodes
2034 for (const auto node_id : candidate_nodes)
2035 if (!active_nodes.count(node_id))
2036 _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
2037}
std::unordered_set< const Node * > _inactive_local_lm_nodes
List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...

Referenced by MortarInterfaceWarehouse::update().

◆ computeIncorrectEdgeDroppingInactiveLMNodes()

void AutomaticMortarGeneration::computeIncorrectEdgeDroppingInactiveLMNodes ( )

Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching a partially or fully dropped element is dropped)

Definition at line 1921 of file AutomaticMortarGeneration.C.

1922{
1923 using std::abs;
1924
1925 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1926 // since the mortar segments are on the linearized element and comparing the volume of the
1927 // linearized element does not have the same volume as the warped element
1928 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1929
1930 // List of inactive nodes on local secondary elements
1931 std::unordered_set<dof_id_type> inactive_node_ids;
1932
1933 std::unordered_map<const Elem *, Real> active_volume{};
1934
1935 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1936 for (const auto el : _mesh.active_subdomain_elements_ptr_range(pr.second))
1937 active_volume[el] = 0.;
1938
1939 // Compute fraction of elements with corresponding primary elements
1940 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1941 {
1942 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1943 const Elem * secondary_elem = msinfo.secondary_elem;
1944
1945 active_volume[secondary_elem] += msm_elem->volume();
1946 }
1947
1948 // Mark all inactive local nodes
1949 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1950 // Loop through all elements on my processor
1951 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(pr.second))
1952 // If elem fully or partially dropped
1953 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1954 {
1955 // Add all nodes to list of inactive
1956 for (auto n : make_range(el->n_nodes()))
1957 inactive_node_ids.insert(el->node_id(n));
1958 }
1959
1960 // Only the owner of a node zeroes its LM DoFs, but the owner need not own (or even ghost) any
1961 // secondary lower-dimensional element touching it, so send each inactive node to its owner,
1962 // which takes the union
1963 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes;
1964 for (const auto node_id : inactive_node_ids)
1965 proc_to_inactive_nodes[_mesh.node_ptr(node_id)->processor_id()].push_back(node_id);
1966
1968 auto action_functor = [this](const processor_id_type, const std::vector<dof_id_type> & sent_data)
1969 {
1970 for (const auto node_id : sent_data)
1971 _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1972 };
1973 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_inactive_nodes, action_functor);
1974}
Real volume(const MeshBase &mesh, unsigned int dim=libMesh::invalid_uint)

Referenced by computeInactiveLMNodes().

◆ computeMsmStatistics()

std::vector< AutomaticMortarGeneration::MsmSubdomainStats > AutomaticMortarGeneration::computeMsmStatistics ( )

Computes mortar segment mesh statistics and returns one entry per subdomain pair.

Must be called collectively on all ranks.

Definition at line 1792 of file AutomaticMortarGeneration.C.

1793{
1794 std::vector<MsmSubdomainStats> result;
1798 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1799
1800 for (const auto & [primary_subd_id, secondary_subd_id] : _primary_secondary_subdomain_id_pairs)
1801 {
1802 for (const auto * const secondary_el :
1803 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1804 {
1805 secondary.push_back(secondary_el->volume());
1806 // We may not have projected onto a primary face in which case we may not have created mortar
1807 // segments
1808 if (auto it = _secondary_elems_to_mortar_segments.find(secondary_el->id());
1810 for (const auto * const msm_elem : it->second)
1811 {
1812 msm.push_back(msm_elem->volume());
1813 const auto & msm_info = libmesh_map_find(_msm_elem_to_info, msm_elem);
1814 // Now it's also possible that we didn't project onto a primary face and we *did* create
1815 // mortar segments
1816 if (msm_info.primary_elem)
1817 {
1818 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1819 mooseError("Unhandled primary-secondary pairing when computing mortar segment "
1820 "statistics. This could happen if you have the same secondary "
1821 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1822 "primary subdomain IDs. Contact a MOOSE developer for help.");
1823 if (const auto [it, inserted] =
1824 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1825 inserted)
1826 it->second = msm_info.primary_elem->volume();
1827 else
1828 mooseAssert(
1829 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1830 "Volumes should be consistent");
1831 }
1832 }
1833 }
1834
1835 _mesh.comm().set_union(primary_elems_to_volume);
1836 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1837 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1838 primary.reserve(primary_elems_to_volume.size());
1839 for (const auto [_, volume] : primary_elems_to_volume)
1840 primary.push_back(volume);
1841
1842 // An interface on which no mortar segments formed has empty primary and segment lists, for
1843 // which libMesh's maximum() and minimum() are undefined. Report 0, as median() does.
1844 const auto max_volume = [](const auto & volumes)
1845 { return volumes.empty() ? Real(0) : volumes.maximum(); };
1846 const auto min_volume = [](const auto & volumes)
1847 { return volumes.empty() ? Real(0) : volumes.minimum(); };
1848
1849 MsmSubdomainStats stats;
1850 stats.primary_subd_id = primary_subd_id;
1851 stats.secondary_subd_id = secondary_subd_id;
1852 stats.secondary_lower_n_elems = secondary.size();
1853 stats.secondary_lower_max_volume = max_volume(secondary);
1854 stats.secondary_lower_min_volume = min_volume(secondary);
1855 stats.secondary_lower_median_volume = secondary.median();
1856 stats.primary_lower_n_elems = primary.size();
1857 stats.primary_lower_max_volume = max_volume(primary);
1858 stats.primary_lower_min_volume = min_volume(primary);
1859 stats.primary_lower_median_volume = primary.median();
1860 stats.msm_n_elems = msm.size();
1861 stats.msm_max_volume = max_volume(msm);
1862 stats.msm_min_volume = min_volume(msm);
1863 stats.msm_median_volume = msm.median();
1864 result.push_back(stats);
1865
1866 primary.clear();
1867 secondary.clear();
1868 msm.clear();
1869 primary_elems_to_volume.clear();
1870 }
1871
1872 return result;
1873}

Referenced by msmStatistics().

◆ computeNodalGeometry()

void AutomaticMortarGeneration::computeNodalGeometry ( )

Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the ExplicitSystem/NumericVector approach.

This design was triggered by the way that the GhostingFunctor operates, but I think it is a better/more efficient way to do it anyway.

The _periodic flag tells us whether we want to inward vs outward facing normals

Definition at line 2089 of file AutomaticMortarGeneration.C.

2090{
2091 // The dimension according to Mesh::mesh_dimension().
2092 const auto dim = _mesh.mesh_dimension();
2093
2094 mooseAssert(dim == 2 || dim == 3,
2095 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2096 "mortar constraints on 2D or 3D meshes.");
2097 // A nodal lower-dimensional nodal quadrature rule to be used on faces.
2098 libMesh::QNodal qface(dim - 1);
2099
2100 // A map from the node id to the attached elemental normals/weights evaluated at the node. Th
2101 // length of the vector will correspond to the number of elements attached to the node. If it is a
2102 // vertex node, for a 1D mortar mesh, the vector length will be two. If it is an interior node,
2103 // the vector will be length 1. The first member of the pair is that element's normal at the node.
2104 // The second member is that element's JxW at the node
2105 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2106
2108 Real sign = _periodic ? -1 : 1;
2109
2110 // First loop over lower-dimensional secondary side elements and compute/save the outward normal
2111 // for each one. We loop over all active elements currently, but this procedure could be
2112 // parallelized as well.
2113 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
2114 end_el = _mesh.active_elements_end();
2115 el != end_el;
2116 ++el)
2117 {
2118 const Elem * secondary_elem = *el;
2119
2120 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
2121 if (!_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
2122 continue;
2123
2124 // We will create an FE object and attach the nodal quadrature rule such that we can get out the
2125 // normals at the element nodes
2126 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2127 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(dim, nnx_fe_type));
2128 nnx_fe_face->attach_quadrature_rule(&qface);
2129 const auto & face_normals = nnx_fe_face->get_normals();
2130 const auto & face_points = nnx_fe_face->get_xyz();
2131
2132 const auto & JxW = nnx_fe_face->get_JxW();
2133
2134 // Which side of the parent are we? We need to know this to know
2135 // which side to reinit.
2136 const Elem * interior_parent = secondary_elem->interior_parent();
2137 mooseAssert(interior_parent,
2138 "No interior parent exists for element "
2139 << secondary_elem->id()
2140 << ". There may be a problem with your sideset set-up.");
2141
2142 // Map to get lower dimensional element from interior parent on secondary surface
2143 // This map can be used to provide a handle to methods in this class that need to
2144 // operate on lower dimensional elements.
2145 _secondary_element_to_secondary_lowerd_element.emplace(interior_parent->id(), secondary_elem);
2146
2147 // Look up which side of the interior parent secondary_elem is.
2148 auto s = interior_parent->which_side_am_i(secondary_elem);
2149
2150 // Reinit the face FE object on side s.
2151 nnx_fe_face->reinit(interior_parent, s);
2152
2153 // Match by physical location instead of assuming that parent-side nodal
2154 // quadrature ordering and lower-dimensional side-element node ordering are
2155 // identical.
2156 const auto qpoint_to_secondary_node =
2157 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2158
2159 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2160 "Face nodal geometry vectors must have the same size.");
2161
2162 for (const auto qp : make_range(face_points.size()))
2163 {
2164 const auto n = qpoint_to_secondary_node[qp];
2165 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2166 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2167 }
2168 }
2169
2170 // Normalize the JxW-weighted sum of incident face normals at each secondary node.
2171 for (const auto & pr : node_to_normals_map)
2172 {
2173 // Compute normal vector
2174 const auto & node_id = pr.first;
2175 const auto & normals_and_weights_vec = pr.second;
2176
2177 Point nodal_normal;
2178 for (const auto & norm_and_weight : normals_and_weights_vec)
2179 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2180 nodal_normal = nodal_normal.unit();
2181
2182 _secondary_node_to_nodal_normal[_mesh.node_ptr(node_id)] = nodal_normal;
2183 _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)] =
2185 }
2186}
std::array< Vector, 2 > householderTangents(const Vector &normal)
Construct the two tangent vectors used by mortar from a unit normal.

Referenced by MortarInterfaceWarehouse::update().

◆ dim()

int AutomaticMortarGeneration::dim ( ) const
inline

◆ getInactiveLMElems()

const std::unordered_set< const Elem * > & AutomaticMortarGeneration::getInactiveLMElems ( ) const
inline
Returns
The list of secondary elems on which mortar constraint is not active

Definition at line 403 of file AutomaticMortarGeneration.h.

404 {
406 }

Referenced by ComputeMortarFunctor::operator()().

◆ getInactiveLMNodes()

const std::unordered_set< const Node * > & AutomaticMortarGeneration::getInactiveLMNodes ( ) const
inline
Returns
The set of nodes on which mortar constraints are not active

Definition at line 395 of file AutomaticMortarGeneration.h.

396 {
398 }

Referenced by ComputeMortarFunctor::operator()().

◆ getNodalNormals()

std::vector< Point > AutomaticMortarGeneration::getNodalNormals ( const Elem &  secondary_elem) const
Returns
The nodal normals associated with the provided secondary_elem

Definition at line 543 of file AutomaticMortarGeneration.C.

544{
545 std::vector<Point> nodal_normals(secondary_elem.n_nodes());
546 for (const auto n : make_range(secondary_elem.n_nodes()))
547 nodal_normals[n] = _secondary_node_to_nodal_normal.at(secondary_elem.node_ptr(n));
548
549 return nodal_normals;
550}

Referenced by buildMortarSegmentMesh3d(), getNormals(), and MortarConsumerInterface::setNormals().

◆ getNodalTangents()

std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > AutomaticMortarGeneration::getNodalTangents ( const Elem &  secondary_elem) const

Compute the two nodal tangents, which are built on-the-fly.

Returns
The nodal tangents associated with the provided secondary_elem

Definition at line 596 of file AutomaticMortarGeneration.C.

597{
598 // MetaPhysicL will check if we ran out of allocated space.
599 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
600 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
601
602 for (const auto n : make_range(secondary_elem.n_nodes()))
603 {
604 const auto & tangent_vectors =
605 libmesh_map_find(_secondary_node_to_hh_nodal_tangents, secondary_elem.node_ptr(n));
606 nodal_tangents_one.push_back(tangent_vectors[0]);
607 nodal_tangents_two.push_back(tangent_vectors[1]);
608 }
609
610 return {{nodal_tangents_one, nodal_tangents_two}};
611}

◆ getNormals() [1/2]

std::vector< Point > AutomaticMortarGeneration::getNormals ( const Elem &  secondary_elem,
const std::vector< Point > &  xi1_pts 
) const

Compute the normals at given reference points on a secondary element.

Parameters
secondary_elemThe secondary element used to query for associated nodal normals
xi1_ptsThe reference points on the secondary element to evaluate the normals at. The points should only be non-zero in the zeroth entry because right now our mortar mesh elements are always 1D
Returns
The normals

Definition at line 625 of file AutomaticMortarGeneration.C.

627{
628 const auto mortar_dim = _mesh.mesh_dimension() - 1;
629 const auto num_qps = xi1_pts.size();
630 const auto nodal_normals = getNodalNormals(secondary_elem);
631 std::vector<Point> normals(num_qps);
632
633 for (const auto n : make_range(secondary_elem.n_nodes()))
634 for (const auto qp : make_range(num_qps))
635 {
636 const auto phi =
637 (mortar_dim == 1)
638 ? Moose::fe_lagrange_1D_shape(secondary_elem.default_order(), n, xi1_pts[qp](0))
639 : Moose::fe_lagrange_2D_shape(secondary_elem.type(),
640 secondary_elem.default_order(),
641 n,
642 cast_ref<const TypeVector<Real> &>(xi1_pts[qp]));
643 normals[qp] += phi * nodal_normals[n];
644 }
645
646 if (_periodic)
647 for (auto & normal : normals)
648 normal *= -1;
649
650 return normals;
651}
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)

Referenced by buildMortarSegmentMesh(), getNormals(), and MortarConsumerInterface::setNormals().

◆ getNormals() [2/2]

std::vector< Point > AutomaticMortarGeneration::getNormals ( const Elem &  secondary_elem,
const std::vector< Real > &  oned_xi1_pts 
) const

Compute the normals at given reference points on a secondary element.

Parameters
secondary_elemThe secondary element used to query for associated nodal normals
1d_xi1_ptsThe reference points on the secondary element to evaluate the normals at. The "points" are single reals corresponding to xi because right now our mortar mesh elements are always 1D
Returns
The normals

Definition at line 614 of file AutomaticMortarGeneration.C.

616{
617 std::vector<Point> xi1_pts(oned_xi1_pts.size());
618 for (const auto qp : index_range(oned_xi1_pts))
619 xi1_pts[qp] = oned_xi1_pts[qp];
620
621 return getNormals(secondary_elem, xi1_pts);
622}

◆ getPrimaryIpToLowerElementMap()

std::map< unsigned int, unsigned int > AutomaticMortarGeneration::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 nodes.

Returns
The map from primary interior parent nodes to its corresponding lower dimensional nodes

Definition at line 579 of file AutomaticMortarGeneration.C.

583{
584 std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
585
586 for (const auto i : make_range(lower_primary_elem.n_nodes()))
587 {
588 const auto & nd = lower_primary_elem.node_ref(i);
589 primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
590 }
591
592 return primary_ip_i_to_lower_primary_i;
593}

◆ getSecondaryIpToLowerElementMap()

std::map< unsigned int, unsigned int > AutomaticMortarGeneration::getSecondaryIpToLowerElementMap ( const Elem &  lower_secondary_elem) const

Compute on-the-fly mapping from secondary interior parent nodes to lower dimensional nodes.

Returns
The map from secondary interior parent nodes to lower dimensional nodes

Definition at line 563 of file AutomaticMortarGeneration.C.

564{
565 std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
566 const Elem * const secondary_ip = lower_secondary_elem.interior_parent();
567 mooseAssert(secondary_ip, "This should be non-null");
568
569 for (const auto i : make_range(lower_secondary_elem.n_nodes()))
570 {
571 const auto & nd = lower_secondary_elem.node_ref(i);
572 secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
573 }
574
575 return secondary_ip_i_to_lower_secondary_i;
576}

◆ getSecondaryLowerdElemFromSecondaryElem()

const Elem * AutomaticMortarGeneration::getSecondaryLowerdElemFromSecondaryElem ( dof_id_type  secondary_elem_id) const

Return lower dimensional secondary element given its interior parent.

Helpful outside the mortar generation to locate mortar-related quantities.

Parameters
secondary_elem_idThe secondary interior parent element id used to query for associated lower dimensional element
Returns
The corresponding lower dimensional secondary element

Definition at line 553 of file AutomaticMortarGeneration.C.

555{
556 mooseAssert(_secondary_element_to_secondary_lowerd_element.count(secondary_elem_id),
557 "Map should locate secondary element");
558
559 return _secondary_element_to_secondary_lowerd_element.at(secondary_elem_id);
560}

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 408 of file AutomaticMortarGeneration.h.

408{ return !_correct_edge_dropping; }

Referenced by ComputeMortarFunctor::operator()().

◆ initOutput()

void AutomaticMortarGeneration::initOutput ( )

initialize mortar-mesh based output

Definition at line 450 of file AutomaticMortarGeneration.C.

451{
452 if (!_debug)
453 return;
454
455 _output_params = std::make_unique<InputParameters>(MortarNodalGeometryOutput::validParams());
456 _output_params->set<AutomaticMortarGeneration *>("_amg") = this;
457 _output_params->set<FEProblemBase *>("_fe_problem_base") = &_app.feProblem();
459 _output_params->set<std::string>(MooseBase::name_param) =
460 "mortar_nodal_geometry_" + mortarInterfaceName();
461 _output_params->finalize("MortarNodalGeometryOutput");
462 _app.getOutputWarehouse().addOutput(std::make_shared<MortarNodalGeometryOutput>(*_output_params));
463}
This class is a container/interface for the objects involved in automatic generation of mortar spaces...
std::unique_ptr< InputParameters > _output_params
Storage for the input parameters used by the mortar nodal geometry output.
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
Base class for MOOSE-based applications.
Definition MooseApp.h:110
OutputWarehouse & getOutputWarehouse()
Get the OutputWarehouse objects.
Definition MooseApp.C:2418
FEProblemBase & feProblem() const
Definition MooseApp.C:1861
static const std::string name_param
The name of the parameter that contains the object name.
Definition MooseBase.h:55
static const std::string app_param
The name of the parameter that contains the MooseApp.
Definition MooseBase.h:59
static InputParameters validParams()
void addOutput(std::shared_ptr< Output > output)
Adds an existing output object to the warehouse.

◆ meshChanged()

void AutomaticMortarGeneration::meshChanged ( )
inline

Invalidates the cached MSM node/element ID starting offset so that the next call to buildMortarSegmentMesh3d() recomputes it via allgather.

Call this when mesh topology changes.

Definition at line 260 of file AutomaticMortarGeneration.h.

260{ _msm_node_id_start = std::nullopt; }

Referenced by MortarInterfaceWarehouse::meshChanged().

◆ mortar3DQpMapping()

Mortar3DQuadraturePointMapping AutomaticMortarGeneration::mortar3DQpMapping ( ) const
inline

Return the 3D mortar quadrature-point mapping method.

Definition at line 386 of file AutomaticMortarGeneration.h.

386{ return _mortar_3d_qp_mapping; }

Referenced by Moose::Mortar::loopOverMortarSegments().

◆ mortarInterfaceCoupling()

const std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > & AutomaticMortarGeneration::mortarInterfaceCoupling ( ) const
inline
Returns
The mortar interface coupling

Definition at line 360 of file AutomaticMortarGeneration.h.

361 {
363 }

◆ mortarInterfaceName()

std::string AutomaticMortarGeneration::mortarInterfaceName ( ) const
private
Returns
A string uniquely identifying this mortar interface

Definition at line 436 of file AutomaticMortarGeneration.C.

437{
438 std::vector<std::string> string_vec(_primary_secondary_boundary_id_pairs.size() * 2 + 1);
440 {
441 const auto [primary_bnd_id, secondary_bnd_id] = _primary_secondary_boundary_id_pairs[i];
442 string_vec[2 * i] = std::to_string(primary_bnd_id);
443 string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
444 }
445 string_vec.back() = _on_displaced ? "displaced" : "undisplaced";
446 return MooseUtils::join(string_vec, "_");
447}

Referenced by initOutput(), and outputMortarMesh().

◆ mortarSegmentMesh()

const MeshBase & AutomaticMortarGeneration::mortarSegmentMesh ( ) const
inline
Returns
The mortar segment mesh

Definition at line 373 of file AutomaticMortarGeneration.h.

373{ return *_mortar_segment_mesh; }

Referenced by Moose::Mortar::loopOverMortarSegments().

◆ mortarSegmentMeshElemToInfo()

const std::unordered_map< const Elem *, MortarSegmentInfo > & AutomaticMortarGeneration::mortarSegmentMeshElemToInfo ( ) const
inline
Returns
The mortar segment element to corresponding information

Definition at line 378 of file AutomaticMortarGeneration.h.

379 {
380 return _msm_elem_to_info;
381 }

Referenced by Moose::Mortar::loopOverMortarSegments().

◆ mortarSegmentReferencePoints()

const MortarSegmentReferencePoints & AutomaticMortarGeneration::mortarSegmentReferencePoints ( const Elem &  mortar_segment_elem) const

Return the parent-face reference coordinates for a mortar segment.

Definition at line 488 of file AutomaticMortarGeneration.C.

489{
490 if (_mortar_3d_qp_mapping != Mortar3DQuadraturePointMapping::REFERENCE_INTERPOLATION)
491 mooseError("Mortar segment reference points were requested for mortar segment element ",
492 mortar_segment_elem.id(),
493 ", but the reference-interpolation mapping mode is not enabled.");
494
495 const auto reference_points_it = _msm_elem_to_reference_points.find(&mortar_segment_elem);
496 if (reference_points_it == _msm_elem_to_reference_points.end())
497 mooseError("No reference-point record was found for mortar segment element ",
498 mortar_segment_elem.id(),
499 ". The mortar segment info and reference-point maps are not aligned.");
500
501 return reference_points_it->second;
502}

Referenced by Moose::Mortar::loopOverMortarSegments().

◆ msmStatistics()

void AutomaticMortarGeneration::msmStatistics ( )

Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally)

Definition at line 1876 of file AutomaticMortarGeneration.C.

1877{
1878 const auto all_stats = computeMsmStatistics();
1879
1880 if (_mesh.processor_id() != 0)
1881 return;
1882
1883 Moose::out << "Mortar Interface Statistics:" << std::endl;
1884 for (const auto & stats : all_stats)
1885 {
1886 std::vector<std::string> col_names = {"mesh", "n_elems", "max", "min", "median"};
1887 std::vector<std::string> subds = {"secondary_lower", "primary_lower", "mortar_segment"};
1888 std::vector<size_t> n_elems = {
1889 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1890 std::vector<Real> maxs = {
1891 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1892 std::vector<Real> mins = {
1893 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1894 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1895 stats.primary_lower_median_volume,
1896 stats.msm_median_volume};
1897
1898 FormattedTable table;
1899 table.clear();
1900 for (auto i : index_range(subds))
1901 {
1902 table.addRow(i);
1903 table.addData<std::string>(col_names[0], subds[i]);
1904 table.addData<size_t>(col_names[1], n_elems[i]);
1905 table.addData<Real>(col_names[2], maxs[i]);
1906 table.addData<Real>(col_names[3], mins[i]);
1907 table.addData<Real>(col_names[4], medians[i]);
1908 }
1909
1910 Moose::out << "secondary subdomain: " << stats.secondary_subd_id
1911 << " \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1912 table.printTable(Moose::out, subds.size());
1913 }
1914}
std::vector< MsmSubdomainStats > computeMsmStatistics()
Computes mortar segment mesh statistics and returns one entry per subdomain pair.
This class is used for building, formatting, and outputting tables of numbers.
void printTable(std::ostream &out, unsigned int last_n_entries=0)
Methods for dumping the table to the stream - either by filename or by stream handle.
void addData(const std::string &name, const T &value)
Method for adding data to the output table.
void addRow(Real time)
Force a new row in the table with the passed in time.

Referenced by buildMortarSegmentMesh3d().

◆ nodesToSecondaryElem()

const std::unordered_map< dof_id_type, std::vector< const Elem * > > & AutomaticMortarGeneration::nodesToSecondaryElem ( ) const
inline
Returns
Map from node id to secondary lower-d element pointer

Definition at line 444 of file AutomaticMortarGeneration.h.

445 {
447 }

◆ onDisplaced()

bool AutomaticMortarGeneration::onDisplaced ( ) const
inline

returns whether this object is on the displaced mesh

Definition at line 265 of file AutomaticMortarGeneration.h.

265{ return _on_displaced; }

◆ outputMortarMesh()

void AutomaticMortarGeneration::outputMortarMesh ( )
private

Write the mortar segment mesh to exodus.

Definition at line 1149 of file AutomaticMortarGeneration.C.

1150{
1151 ExodusII_IO mortar_segment_mesh_writer(*_mortar_segment_mesh);
1152
1153 // Default to non-HDF5 output for wider compatibility
1154 mortar_segment_mesh_writer.set_hdf5_writing(false);
1155
1156 std::array<std::string, 3> file_pieces = {
1157 _app.getOutputFileBase(/*for_non_moose_build_output=*/true),
1159 "mortar_segment_mesh.e"};
1160 mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces, "_"));
1161}
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
Definition MooseApp.C:1541

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

◆ primaryIPSubIDs()

const std::set< SubdomainID > & AutomaticMortarGeneration::primaryIPSubIDs ( ) const
inline
Returns
All the primary interior parent subdomain IDs associated with the mortar mesh

Definition at line 439 of file AutomaticMortarGeneration.h.

439{ return _primary_ip_sub_ids; }

Referenced by Moose::Mortar::setupMortarMaterials(), and MortarInterfaceWarehouse::update().

◆ primarySecondaryBoundaryIDPair()

const std::pair< BoundaryID, BoundaryID > & AutomaticMortarGeneration::primarySecondaryBoundaryIDPair ( ) const
inline
Returns
The primary-secondary boundary ID pair

Definition at line 669 of file AutomaticMortarGeneration.h.

670{
671 mooseAssert(_primary_secondary_boundary_id_pairs.size() == 1,
672 "We currently only support a single boundary pair per mortar generation object");
673
675}

Referenced by Moose::Mortar::loopOverMortarSegments(), and Moose::Mortar::setupMortarMaterials().

◆ processAlignedNodes()

bool AutomaticMortarGeneration::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 
)
private

Process aligned nodes.

Returns
whether mortar segment(s) were created

Definition at line 2341 of file AutomaticMortarGeneration.C.

2349{
2350 if (!secondary_node_neighbors)
2351 secondary_node_neighbors = &libmesh_map_find(_nodes_to_secondary_elem_map, secondary_node.id());
2352 if (!primary_node_neighbors)
2353 primary_node_neighbors = &libmesh_map_find(_nodes_to_primary_elem_map, primary_node.id());
2354
2355 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(), false);
2356
2357 // Add entries to secondary_node_and_elem_to_xi2_primary_elem container.
2358 //
2359 // First, determine "on left" vs. "on right" orientation of the nodal neighbors.
2360 // There can be a max of 2 nodal neighbors, and we want to make sure that the
2361 // secondary nodal neighbor on the "left" is associated with the primary nodal
2362 // neighbor on the "left" and similarly for the "right". We use cross products to determine
2363 // alignment. In the below diagram, 'x' denotes a node, and connected '|' are lower dimensional
2364 // elements.
2365 // x
2366 // x |
2367 // | |
2368 // secondary x ----> x primary
2369 // | |
2370 // | x
2371 // x
2372 //
2373 // Looking at the aligned nodes, the secondary node first, if we pick the top secondary lower
2374 // dimensional element, then the cross product as written a few lines below points out of the
2375 // screen towards you. (Point in the direction of the secondary nodal normal, and then curl your
2376 // hand towards the secondary element's opposite node, then the thumb points in the direction of
2377 // the cross product). Doing the same with the aligned primary node, if we pick the top primary
2378 // element, then the cross product also points out of the screen. Because the cross products
2379 // point in the same direction (positive dot product), then we know to associate the
2380 // secondary-primary element pair. If we had picked the bottom primary element whose cross
2381 // product points into the screen, then clearly the cross products point in the opposite
2382 // direction and we don't have a match
2383 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2384
2385 for (const auto nn : index_range(*secondary_node_neighbors))
2386 {
2387 const Elem * const secondary_neigh = (*secondary_node_neighbors)[nn];
2388 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2389 ? secondary_neigh->point(1)
2390 : secondary_neigh->point(0);
2391 const Point cp = nodal_normal.cross(opposite - secondary_node);
2392 secondary_node_neighbor_cps[nn] = cp(2);
2393 }
2394
2395 for (const auto nn : index_range(*primary_node_neighbors))
2396 {
2397 const Elem * const primary_neigh = (*primary_node_neighbors)[nn];
2398 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2399 : primary_neigh->point(0);
2400 const Point cp = nodal_normal.cross(opposite - primary_node);
2401 primary_node_neighbor_cps[nn] = cp(2);
2402 }
2403
2404 // Associate secondary/primary elems on matching sides.
2405 bool found_match = false;
2406 for (const auto snn : index_range(*secondary_node_neighbors))
2407 for (const auto mnn : index_range(*primary_node_neighbors))
2408 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2409 {
2410 found_match = true;
2411 if (primary_elems_mapped[mnn])
2412 continue;
2413 primary_elems_mapped[mnn] = true;
2414
2415 // Figure out xi^(2) value by looking at which node primary_node is
2416 // of the current primary node neighbor.
2417 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2418 const auto secondary_key =
2419 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2420 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2421 _secondary_node_and_elem_to_xi2_primary_elem.emplace(secondary_key, primary_val);
2422
2423 // Also map in the other direction.
2424 const Real xi1 =
2425 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2426
2427 const auto primary_key =
2428 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2429 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2430 _primary_node_and_elem_to_xi1_secondary_elem.emplace(primary_key, secondary_val);
2431 }
2432
2433 if (!found_match)
2434 {
2435 // There could be coincident nodes and this might be a bad primary candidate (see
2436 // issue #21680). Instead of giving up, let's try continuing
2437 rejected_elem_candidates.insert(&candidate_element);
2438 return false;
2439 }
2440
2441 // We need to handle the case where we've exactly projected a secondary node onto a
2442 // primary node, but our secondary node is at one of the secondary boundary face endpoints and
2443 // our primary node is not.
2444 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2445 for (const auto i : index_range(primary_elems_mapped))
2446 if (!primary_elems_mapped[i])
2447 {
2449 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2450 std::make_pair(1, nullptr));
2451 }
2452
2453 return found_match;
2454}
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ projectPrimaryNodes()

void AutomaticMortarGeneration::projectPrimaryNodes ( )

(Inverse) project primary nodes to the points on the secondary surface where they would have come from (find (xi^(1) values)).

Inputs:

  • The nodal normals values
  • mesh
  • nodes_to_secondary_elem_map

Outputs:

  • primary_node_and_elem_to_xi1_secondary_elem

Defined in the file project_primary_nodes.C.

Definition at line 2702 of file AutomaticMortarGeneration.C.

2703{
2704 // For each primary/secondary boundary id pair, call the
2705 // project_primary_nodes_single_pair() helper function.
2706 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2707 projectPrimaryNodesSinglePair(pr.first, pr.second);
2708}
void projectPrimaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().

Referenced by MortarInterfaceWarehouse::update().

◆ projectPrimaryNodesSinglePair()

void AutomaticMortarGeneration::projectPrimaryNodesSinglePair ( SubdomainID  lower_dimensional_primary_subdomain_id,
SubdomainID  lower_dimensional_secondary_subdomain_id 
)
private

Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().

Definition at line 2711 of file AutomaticMortarGeneration.C.

2714{
2715 using std::abs;
2716
2717 // Build a Nanoflann object on the lower-dimensional secondary elements of the Mesh.
2718 NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_secondary_subdomain_id);
2719 subdomain_kd_tree_t kd_tree(
2720 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
2721
2722 // Construct the KD tree for lower-dimensional elements in the volume mesh.
2723 kd_tree.buildIndex();
2724
2725 std::unordered_set<dof_id_type> primary_nodes_visited;
2726
2727 for (const auto & primary_side_elem : _mesh.active_element_ptr_range())
2728 {
2729 // If this is not one of the lower-dimensional primary side elements, go on to the next one.
2730 if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2731 continue;
2732
2733 // For each node on this side, find the nearest node on the secondary side using the KDTree,
2734 // then search in nearby elements for where it projects along the nodal normal direction.
2735 for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2736 ++n)
2737 {
2738 // Get a pointer to this node.
2739 const Node * primary_node = primary_side_elem->node_ptr(n);
2740
2741 // Get the nodal neighbors connected to this primary node.
2742 const std::vector<const Elem *> & primary_node_neighbors =
2743 _nodes_to_primary_elem_map.at(primary_node->id());
2744
2745 // Check whether we have already successfully inverse mapped this primary node (whether during
2746 // secondary node projection or now during primary node projection) or we have already failed
2747 // to inverse map this primary node (now during primary node projection), and then skip if
2748 // either of those things is true
2749 auto primary_key =
2750 std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2751 if (!primary_nodes_visited.insert(primary_node->id()).second ||
2753 continue;
2754
2755 // Data structure for performing Nanoflann searches.
2756 Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2757
2758 // The number of results we want to get. We'll look for a
2759 // "few" nearest nodes, hopefully that is enough to let us
2760 // figure out which lower-dimensional Elem on the secondary side
2761 // we are across from.
2762 const size_t num_results = 3;
2763
2764 // Initialize result_set and do the search.
2765 std::vector<size_t> ret_index(num_results);
2766 std::vector<Real> out_dist_sqr(num_results);
2767 nanoflann::KNNResultSet<Real> result_set(num_results);
2768 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2769 kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2770
2771 // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2772 bool projection_succeeded = false;
2773
2774 // Once we've rejected a candidate for a given
2775 // primary_node, there's no reason to check it
2776 // again.
2777 std::set<const Elem *> rejected_secondary_elem_candidates;
2778
2779 // Loop over the closest nodes, check whether the secondary node successfully projects into
2780 // either of the closest neighbors, stop when the projection succeeds.
2781 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2782 {
2783 // Verify that the squared distance we compute is the same as nanoflann's
2784 mooseAssert(abs((_mesh.point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2785 TOLERANCE,
2786 "Lower-dimensional element squared distance verification failed.");
2787
2788 // Get a reference to the vector of lower dimensional elements from the
2789 // nodes_to_secondary_elem_map.
2790 const std::vector<const Elem *> & secondary_elem_candidates =
2791 _nodes_to_secondary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2792
2793 // Print the Elems connected to this node on the secondary mesh side.
2794 for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2795 {
2796 const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2797
2798 // If we've already rejected this candidate, we don't need to check it again.
2799 if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2800 continue;
2801
2802 std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2803 for (const auto n : make_range(secondary_elem_candidate->n_nodes()))
2804 nodal_normals[n] =
2805 _secondary_node_to_nodal_normal.at(secondary_elem_candidate->node_ptr(n));
2806
2807 // Use equation 2.4.6 from Bin Yang's dissertation to try and solve for
2808 // the position on the secondary element where this primary came from. This
2809 // requires a Newton iteration in general.
2810 DualNumber<Real> xi1_dn{0, 1}; // initial guess
2811 auto && order = secondary_elem_candidate->default_order();
2812 unsigned int current_iterate = 0, max_iterates = 10;
2813
2814 VectorValue<DualNumber<Real>> normals(0);
2815
2816 // Newton iteration loop - this to converge in 1 iteration when it
2817 // succeeds, and possibly two iterations when it converges to a
2818 // xi outside the reference element. I don't know any reason why it should
2819 // only take 1 iteration -- the Jacobian is not constant in general...
2820 do
2821 {
2822 VectorValue<DualNumber<Real>> x1(0);
2823 for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2824 n < secondary_elem_candidate->n_nodes();
2825 ++n)
2826 {
2827 const auto phi = Moose::fe_lagrange_1D_shape(order, n, xi1_dn);
2828 x1 += phi * secondary_elem_candidate->point(n);
2829 normals += phi * nodal_normals[n];
2830 }
2831
2832 const auto u = x1 - (*primary_node);
2833
2834 const auto F = u(0) * normals(1) - u(1) * normals(0);
2835
2836 if (abs(F) < _newton_tolerance)
2837 break;
2838
2839 // Unlike for projection of nodal normals onto primary surfaces, we should never have a
2840 // case where the nodal normal is completely orthogonal to the secondary surface, so we
2841 // do not have to guard against F.derivatives() == 0 here
2842 Real dxi1 = -F.value() / F.derivatives();
2843
2844 xi1_dn += dxi1;
2845
2846 normals = 0;
2847 } while (++current_iterate < max_iterates);
2848
2849 Real xi1 = xi1_dn.value();
2850
2851 // Check for convergence to a valid solution... The last condition checks for obliqueness
2852 // of the projection
2853 if ((current_iterate < max_iterates) && (abs(xi1) <= 1. + _xi_tolerance) &&
2854 (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2855 MetaPhysicL::raw_value(normals).unit()) <
2856 std::cos(_minimum_projection_angle * libMesh::pi / 180.0)))
2857 {
2858 if (abs(abs(xi1) - 1.) < _xi_tolerance)
2859 {
2860 // Special case: xi1=+/-1.
2861 // It is unlikely that we get here, because this primary node should already
2862 // have been mapped during the project_secondary_nodes() routine, but
2863 // there is still a chance since the tolerances are applied to
2864 // the xi coordinate and that value may be different on a primary element and a
2865 // secondary element since they may have different sizes. It's also possible that we
2866 // may reach this point if the solve has yielded a non-physical configuration such as
2867 // one block being pushed way out into space
2868 const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2869 : secondary_elem_candidate->node_ref(1);
2870 bool created_mortar_segment = false;
2871
2872 // If we have failed to project this secondary node, let's try again now
2873 if (_failed_secondary_node_projections.count(secondary_node.id()))
2874 created_mortar_segment = processAlignedNodes(secondary_node,
2875 *primary_node,
2876 nullptr,
2877 &primary_node_neighbors,
2878 MetaPhysicL::raw_value(normals),
2879 *secondary_elem_candidate,
2880 rejected_secondary_elem_candidates);
2881 else
2882 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2883
2884 if (!created_mortar_segment)
2885 // We used to throw an exception in this scope but now that we support processing
2886 // aligned nodes within this primary node projection method, I don't see any harm in
2887 // simply rejecting the secondary element candidate in the case of failure and
2888 // continuing just as we do when projecting secondary nodes
2889 continue;
2890 }
2891 else // somewhere in the middle of the Elem
2892 {
2893 // Add entry to primary_node_and_elem_to_xi1_secondary_elem
2894 //
2895 // Note: we originally duplicated the map values for the keys (node, left_neighbor)
2896 // and (node, right_neighbor) but I don't think that should be necessary. Instead we
2897 // just do it for neighbor 0, but really maybe we don't even need to do that since
2898 // we can always look up the neighbors later given the Node... keeping it like this
2899 // helps to maintain the "symmetry" of the two containers.
2900 const Elem * neigh = primary_node_neighbors[0];
2901 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2902 {
2903 const Node * neigh_node = neigh->node_ptr(nid);
2904 if (primary_node == neigh_node)
2905 {
2906 auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2907 auto val = std::make_pair(xi1, secondary_elem_candidate);
2909 }
2910 }
2911 }
2912
2913 projection_succeeded = true;
2914 break; // out of e-loop
2915 }
2916 else
2917 {
2918 // The current primary_point is not in this Elem, so keep track of the rejects.
2919 rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2920 }
2921 } // end e-loop over candidate elems
2922
2923 if (projection_succeeded)
2924 break; // out of r-loop
2925 } // r-loop
2926
2927 if (!projection_succeeded && _debug)
2928 {
2929 _console << "\nFailed to find point from which primary node "
2930 << cast_ref<const Point &>(*primary_node) << " was projected." << std::endl
2931 << std::endl;
2932 }
2933 } // loop over side nodes
2934 } // end loop over elements for finding where primary points would have projected from.
2935}
Real _newton_tolerance
Newton solve tolerance for node projections.
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.
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.

Referenced by projectPrimaryNodes().

◆ projectSecondaryNodes()

void AutomaticMortarGeneration::projectSecondaryNodes ( )

Project secondary nodes (find xi^(2) values) to the closest points on the primary surface.

Inputs:

  • The nodal normals values
  • mesh
  • nodes_to_primary_elem_map

Outputs:

  • secondary_node_and_elem_to_xi2_primary_elem

Defined in the file project_secondary_nodes.C.

Definition at line 2332 of file AutomaticMortarGeneration.C.

2333{
2334 // For each primary/secondary boundary id pair, call the
2335 // project_secondary_nodes_single_pair() helper function.
2336 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2337 projectSecondaryNodesSinglePair(pr.first, pr.second);
2338}
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...

Referenced by MortarInterfaceWarehouse::update().

◆ projectSecondaryNodesSinglePair()

void AutomaticMortarGeneration::projectSecondaryNodesSinglePair ( SubdomainID  lower_dimensional_primary_subdomain_id,
SubdomainID  lower_dimensional_secondary_subdomain_id 
)
private

Helper function responsible for projecting secondary nodes onto primary elements for a single primary/secondary pair.

Called by the class member AutomaticMortarGeneration::project_secondary_nodes().

Definition at line 2457 of file AutomaticMortarGeneration.C.

2460{
2461 using std::abs;
2462
2463 // Build the "subdomain" adaptor based KD Tree.
2464 NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_primary_subdomain_id);
2465 subdomain_kd_tree_t kd_tree(
2466 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
2467
2468 // Construct the KD tree.
2469 kd_tree.buildIndex();
2470
2471 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
2472 end_el = _mesh.active_elements_end();
2473 el != end_el;
2474 ++el)
2475 {
2476 const Elem * secondary_side_elem = *el;
2477
2478 // If this Elem is not in the current secondary subdomain, go on to the next one.
2479 if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2480 continue;
2481
2482 // For each node on the lower-dimensional element, find the nearest
2483 // node on the primary side using the KDTree, then
2484 // search in nearby elements for where it projects
2485 // along the nodal normal direction.
2486 for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2487 ++n)
2488 {
2489 const Node * secondary_node = secondary_side_elem->node_ptr(n);
2490
2491 // Get the nodal neighbors for secondary_node, so we can check whether we've
2492 // already successfully projected it.
2493 const std::vector<const Elem *> & secondary_node_neighbors =
2494 this->_nodes_to_secondary_elem_map.at(secondary_node->id());
2495
2496 // Check whether we've already mapped this secondary node
2497 // successfully for all of its nodal neighbors.
2498 bool is_mapped = true;
2499 for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2500 ++snn)
2501 {
2502 auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2503 if (!_secondary_node_and_elem_to_xi2_primary_elem.count(secondary_key))
2504 {
2505 is_mapped = false;
2506 break;
2507 }
2508 }
2509
2510 // Go to the next node if this one has already been mapped.
2511 if (is_mapped)
2512 continue;
2513
2514 // Look up the new nodal normal value in the local storage, error if not found.
2515 Point nodal_normal = _secondary_node_to_nodal_normal.at(secondary_node);
2516
2517 // Data structure for performing Nanoflann searches.
2518 std::array<Real, 3> query_pt = {
2519 {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2520
2521 // The number of results we want to get. We'll look for a
2522 // "few" nearest nodes, hopefully that is enough to let us
2523 // figure out which lower-dimensional Elem on the primary
2524 // side we are across from.
2525 const std::size_t num_results = 3;
2526
2527 // Initialize result_set and do the search.
2528 std::vector<size_t> ret_index(num_results);
2529 std::vector<Real> out_dist_sqr(num_results);
2530 nanoflann::KNNResultSet<Real> result_set(num_results);
2531 result_set.init(&ret_index[0], &out_dist_sqr[0]);
2532 kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2533
2534 // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2535 bool projection_succeeded = false;
2536
2537 // Once we've rejected a candidate for a given secondary_node,
2538 // there's no reason to check it again.
2539 std::set<const Elem *> rejected_primary_elem_candidates;
2540
2541 // Loop over the closest nodes, check whether
2542 // the secondary node successfully projects into
2543 // either of the closest neighbors, stop when
2544 // the projection succeeds.
2545 for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2546 {
2547 // Verify that the squared distance we compute is the same as nanoflann'sFss
2548 mooseAssert(abs((_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2549 out_dist_sqr[r]) <= TOLERANCE,
2550 "Lower-dimensional element squared distance verification failed.");
2551
2552 // Get a reference to the vector of lower dimensional elements from the
2553 // nodes_to_primary_elem_map.
2554 std::vector<const Elem *> & primary_elem_candidates =
2555 this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2556
2557 // Search the Elems connected to this node on the primary mesh side.
2558 for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2559 {
2560 const Elem * primary_elem_candidate = primary_elem_candidates[e];
2561
2562 // If we've already rejected this candidate, we don't need to check it again.
2563 if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2564 continue;
2565
2566 // Now generically solve for xi2
2567 const auto order = primary_elem_candidate->default_order();
2568 DualNumber<Real> xi2_dn{0, 1};
2569 unsigned int current_iterate = 0, max_iterates = 10;
2570
2571 // Newton loop
2572 do
2573 {
2574 VectorValue<DualNumber<Real>> x2(0);
2575 for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2576 n < primary_elem_candidate->n_nodes();
2577 ++n)
2578 x2 +=
2579 Moose::fe_lagrange_1D_shape(order, n, xi2_dn) * primary_elem_candidate->point(n);
2580 const auto u = x2 - (*secondary_node);
2581 const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2582
2583 if (abs(F) < _newton_tolerance)
2584 break;
2585
2586 if (F.derivatives())
2587 {
2588 Real dxi2 = -F.value() / F.derivatives();
2589
2590 xi2_dn += dxi2;
2591 }
2592 else
2593 // It's possible that the secondary surface nodal normal is completely orthogonal to
2594 // the primary surface normal, in which case the derivative is 0. We know in this case
2595 // that the projection should be a failure
2596 current_iterate = max_iterates;
2597 } while (++current_iterate < max_iterates);
2598
2599 Real xi2 = xi2_dn.value();
2600
2601 // Check whether the projection worked. The last condition checks for obliqueness of the
2602 // projection
2603 //
2604 // We are projecting on one side first and the other side second. If we make the
2605 // tolerance bigger and remove the (5) factor we are going to continue to miss the
2606 // second projection and fall into the exception message in
2607 // projectPrimaryNodesSinglePair. What makes this modification to not fall in the
2608 // exception is that we are projecting on one side more xi than in the other. There
2609 // should be a better way of doing this by using actual distances and not parametric
2610 // coordinates. But I believe making the tolerance uniformly larger or smaller won't do
2611 // the trick here.
2612 if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 * _xi_tolerance) &&
2613 (abs((primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2614 nodal_normal) < std::cos(_minimum_projection_angle * libMesh::pi / 180)))
2615 {
2616 // If xi2 == +1 or -1 then this secondary node mapped directly to a node on the primary
2617 // surface. This isn't as unlikely as you might think, it will happen if the meshes
2618 // on the interface start off being perfectly aligned. In this situation, we need to
2619 // associate the secondary node with two different elements (and two corresponding
2620 // xi^(2) values.
2621 if (abs(abs(xi2) - 1.) <= _xi_tolerance * 5.0)
2622 {
2623 const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2624 : primary_elem_candidate->node_ptr(1);
2625 const bool created_mortar_segment =
2626 processAlignedNodes(*secondary_node,
2627 *primary_node,
2628 &secondary_node_neighbors,
2629 nullptr,
2630 nodal_normal,
2631 *primary_elem_candidate,
2632 rejected_primary_elem_candidates);
2633
2634 if (!created_mortar_segment)
2635 continue;
2636 }
2637 else // Point falls somewhere in the middle of the Elem.
2638 {
2639 // Add two entries to secondary_node_and_elem_to_xi2_primary_elem.
2640 for (MooseIndex(secondary_node_neighbors) nn = 0;
2641 nn < secondary_node_neighbors.size();
2642 ++nn)
2643 {
2644 const Elem * neigh = secondary_node_neighbors[nn];
2645 for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2646 {
2647 const Node * neigh_node = neigh->node_ptr(nid);
2648 if (secondary_node == neigh_node)
2649 {
2650 auto key = std::make_pair(neigh_node, neigh);
2651 auto val = std::make_pair(xi2, primary_elem_candidate);
2653 }
2654 }
2655 }
2656 }
2657
2658 projection_succeeded = true;
2659 break; // out of e-loop
2660 }
2661 else
2662 // The current secondary_node is not in this Elem, so keep track of the rejects.
2663 rejected_primary_elem_candidates.insert(primary_elem_candidate);
2664 }
2665
2666 if (projection_succeeded)
2667 break; // out of r-loop
2668 } // r-loop
2669
2670 if (!projection_succeeded)
2671 {
2672 _failed_secondary_node_projections.insert(secondary_node->id());
2673 if (_debug)
2674 _console << "Failed to find primary Elem into which secondary node "
2675 << cast_ref<const Point &>(*secondary_node) << ", id '" << secondary_node->id()
2676 << "', projects onto\n"
2677 << std::endl;
2678 }
2679 else if (_debug)
2680 _projected_secondary_nodes.insert(secondary_node->id());
2681 } // loop over side nodes
2682 } // end loop over lower-dimensional elements
2683
2684 if (_distributed)
2685 {
2686 if (_debug)
2687 _mesh.comm().set_union(_projected_secondary_nodes);
2688 _mesh.comm().set_union(_failed_secondary_node_projections);
2689 }
2690
2691 if (_debug)
2692 _console << "\n"
2693 << _projected_secondary_nodes.size() << " out of "
2695 << " secondary nodes were successfully projected\n"
2696 << std::endl;
2697}

Referenced by projectSecondaryNodes().

◆ secondariesToMortarSegments() [1/2]

const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & AutomaticMortarGeneration::secondariesToMortarSegments ( ) const
inline
Returns
the lower dimensional secondary element ids and their associated mortar segment elements

Definition at line 426 of file AutomaticMortarGeneration.h.

427 {
429 }

◆ secondariesToMortarSegments() [2/2]

std::vector< AutomaticMortarGeneration::MortarFilterIter > AutomaticMortarGeneration::secondariesToMortarSegments ( const Node &  node) const
Returns
A vector of iterators that point to the lower dimensional secondary elements and their associated mortar segment elements that would have nonzero values for a Lagrange shape function associated with the provided node. This method may return an empty container if the node is away from the mortar mesh

Definition at line 2938 of file AutomaticMortarGeneration.C.

2939{
2940 auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2941 if (secondary_it == _nodes_to_secondary_elem_map.end())
2942 return {};
2943
2944 const auto & secondary_elems = secondary_it->second;
2945 std::vector<MortarFilterIter> ret;
2946 ret.reserve(secondary_elems.size());
2947
2948 for (const auto i : index_range(secondary_elems))
2949 {
2950 auto * const secondary_elem = secondary_elems[i];
2951 auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2952 if (msm_it == _secondary_elems_to_mortar_segments.end())
2953 // We may have removed this element key from this map
2954 continue;
2955
2956 mooseAssert(secondary_elem->active(),
2957 "We loop over active elements when building the mortar segment mesh, so we golly "
2958 "well hope this is active.");
2959 mooseAssert(!msm_it->second.empty(),
2960 "We should have removed all secondaries from this map if they do not have any "
2961 "mortar segments associated with them.");
2962 ret.push_back(msm_it);
2963 }
2964
2965 return ret;
2966}

Referenced by MortarUserObjectThread::operator()(), and ComputeMortarFunctor::operator()().

◆ secondaryIPSubIDs()

const std::set< SubdomainID > & AutomaticMortarGeneration::secondaryIPSubIDs ( ) const
inline
Returns
All the secondary interior parent subdomain IDs associated with the mortar mesh

Definition at line 434 of file AutomaticMortarGeneration.h.

434{ return _secondary_ip_sub_ids; }

Referenced by Moose::Mortar::setupMortarMaterials(), and MortarInterfaceWarehouse::update().

Friends And Related Symbol Documentation

◆ AugmentSparsityOnInterface

friend class AugmentSparsityOnInterface
friend

Definition at line 665 of file AutomaticMortarGeneration.h.

◆ MortarNodalGeometryOutput

friend class MortarNodalGeometryOutput
friend

Definition at line 664 of file AutomaticMortarGeneration.h.

Member Data Documentation

◆ _app

MooseApp& AutomaticMortarGeneration::_app
private

The Moose app.

Definition at line 471 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), initOutput(), and outputMortarMesh().

◆ _console

const ConsoleStream ConsoleStreamInterface::_console
inherited

An instance of helper class to write streams to the Console objects.

Definition at line 31 of file ConsoleStreamInterface.h.

Referenced by IterationAdaptiveDT::acceptStep(), MaterialOutputAction::act(), MeshOnlyAction::act(), SetupDebugAction::act(), FEProblemBase::adaptMesh(), Adaptivity::adaptMesh(), PerfGraph::addToExecutionList(), SimplePredictor::apply(), SystemBase::applyScalingFactors(), MultiApp::backup(), FEProblemBase::backupMultiApps(), CoarsenedPiecewiseLinear::buildCoarsenedGrid(), DefaultSteadyStateConvergence::checkConvergence(), MeshDiagnosticsGenerator::checkElementOverlap(), MeshDiagnosticsGenerator::checkElementTypes(), MeshDiagnosticsGenerator::checkElementVolumes(), FEProblemBase::checkExceptionAndStopSolve(), SolverSystem::checkInvalidSolution(), MeshDiagnosticsGenerator::checkLocalJacobians(), MeshDiagnosticsGenerator::checkNonConformalMesh(), MeshDiagnosticsGenerator::checkNonConformalMeshFromAdaptivity(), MeshDiagnosticsGenerator::checkNonConformingFaces(), MeshDiagnosticsGenerator::checkNonMatchingEdges(), MeshDiagnosticsGenerator::checkNonPlanarSides(), MeshDiagnosticsGenerator::checkPolygons(), FEProblemBase::checkProblemIntegrity(), ReferenceResidualConvergence::checkResidualConvergence(), MeshDiagnosticsGenerator::checkSidesetsOrientation(), MeshDiagnosticsGenerator::checkWatertightNodesets(), MeshDiagnosticsGenerator::checkWatertightSidesets(), CoarsenSurfaceMeshAlongSidesetGenerator::coarsenAlongSidesets(), IterationAdaptiveDT::computeAdaptiveDT(), TransientBase::computeConstrainedDT(), DefaultMultiAppFixedPointConvergence::computeCustomConvergencePostprocessor(), NonlinearSystemBase::computeDamping(), FixedPointIterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeFailedDT(), IterationAdaptiveDT::computeInitialDT(), IterationAdaptiveDT::computeInterpolationDT(), FEProblemBase::computeLinearSystemTags(), LinearSystem::computeLinearSystemTags(), NonlinearSystemBase::computeScaling(), Problem::console(), TimeStepper::constrainStep(), IterationAdaptiveDT::constrainStep(), MultiApp::createApp(), FEProblemBase::execMultiApps(), FEProblemBase::execMultiAppTransfers(), Eigenvalue::execute(), SteadyBase::execute(), MFEMSteady::execute(), MessageFromInput::execute(), ActionWarehouse::executeActionsWithAction(), ActionWarehouse::executeAllActions(), MeshGeneratorSystem::executeMeshGenerators(), SidesetAroundSubdomainUpdater::finalize(), ElementQualityChecker::finalize(), FEProblemBase::finishMultiAppStep(), MeshRepairGenerator::fixOverlappingNodes(), SurfaceSubdomainsDelaunayRemesher::General2DDelaunay(), CoarsenBlockGenerator::generate(), CoarsenSurfaceMeshAlongSidesetGenerator::generate(), OrientSurfaceMeshGenerator::generate(), PolyLineMeshFollowingNodeSetGenerator::generate(), MeshGenerator::generateInternal(), VariableCondensationPreconditioner::getDofToCondense(), InversePowerMethod::init(), NonlinearEigen::init(), FEProblemBase::initialAdaptMesh(), SubProblem::initialSetup(), EigenExecutionerBase::inversePowerIteration(), FEProblemBase::joinAndFinalize(), TransientBase::keepGoing(), IterationAdaptiveDT::limitDTByFunction(), IterationAdaptiveDT::limitDTToPostprocessorValue(), FEProblemBase::logAdd(), EigenExecutionerBase::makeBXConsistent(), Console::meshChanged(), SurfaceDelaunayGeneratorBase::meshNormalDeviation2D(), MooseBase::mooseDeprecated(), MooseBase::mooseDeprecatedNoTrace(), MooseBase::mooseInfo(), MooseBase::mooseWarning(), MooseBase::mooseWarningNonPrefixed(), ReferenceResidualConvergence::nonlinearConvergenceSetup(), Console::output(), DOFMapOutput::output(), MaterialPropertyDebugOutput::output(), PerfGraphOutput::output(), ReporterDebugOutput::output(), SolutionInvalidityOutput::output(), VariableResidualNormsDebugOutput::output(), ControlOutput::outputActiveObjects(), ControlOutput::outputChangedControls(), ControlOutput::outputControls(), Console::outputInput(), WebServerControl::outputMessage(), Console::outputPostprocessors(), PseudoTimestep::outputPseudoTimestep(), Console::outputReporters(), DefaultMultiAppFixedPointConvergence::outputResidualNorm(), Console::outputScalarVariables(), Console::outputSystemInformation(), FEProblemBase::possiblyRebuildGeomSearchPatches(), EigenExecutionerBase::postExecute(), AB2PredictorCorrector::postSolve(), DefaultMultiAppFixedPointConvergence::preLoop(), ActionWarehouse::printActionDependencySets(), BlockRestrictionDebugOutput::printBlockRestrictionGroups(), BlockRestrictionDebugOutput::printBlockRestrictionMap(), BlockRestrictionDebugOutput::printBoundaryRestrictionGroups(), SolutionInvalidity::printDebug(), EigenExecutionerBase::printEigenvalue(), PicardSolve::printFixedPointConvergenceHistory(), SecantSolve::printFixedPointConvergenceHistory(), SteffensenSolve::printFixedPointConvergenceHistory(), FixedPointSolve::printFixedPointConvergenceReason(), PerfGraphLivePrint::printLiveMessage(), MaterialPropertyDebugOutput::printMaterialMap(), PerfGraphLivePrint::printStats(), NEML2Action::printSummary(), projectPrimaryNodesSinglePair(), projectSecondaryNodesSinglePair(), CoarsenBlockGenerator::recursiveCoarsen(), SolutionTimeAdaptiveDT::rejectStep(), MultiApp::restore(), FEProblemBase::restoreMultiApps(), FEProblemBase::restoreSolutions(), NonlinearSystemBase::setInitialSolution(), MooseApp::setupOptions(), Checkpoint::shouldOutput(), SubProblem::showFunctorRequestors(), SubProblem::showFunctors(), FullSolveMultiApp::showStatusMessage(), FEProblemSolve::solve(), FixedPointSolve::solve(), LinearSystem::solve(), NonlinearSystem::solve(), AStableDirk4::solve(), ExplicitRK2::solve(), ExplicitTVDRK2::solve(), ImplicitMidpoint::solve(), LStableDirk2::solve(), LStableDirk3::solve(), LStableDirk4::solve(), EigenProblem::solve(), FixedPointSolve::solveStep(), TransientMultiApp::solveStep(), MeshRepairGenerator::splitNonConvexPolygons(), PerfGraphLivePrint::start(), WebServerControl::startServer(), AB2PredictorCorrector::step(), NonlinearEigen::takeStep(), TransientBase::takeStep(), MFEMTransient::takeStep(), TerminateChainControl::terminate(), SubProblem::timestepSetup(), FEProblemBase::updateMeshXFEM(), Convergence::verboseOutput(), Console::writeTimestepInformation(), Console::writeVariableNorms(), and FEProblemBase::~FEProblemBase().

◆ _correct_edge_dropping

const bool AutomaticMortarGeneration::_correct_edge_dropping
private

Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are strongly set to 0.

Definition at line 626 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMElems(), computeInactiveLMNodes(), and incorrectEdgeDropping().

◆ _debug

const bool AutomaticMortarGeneration::_debug
private

◆ _distributed

const bool AutomaticMortarGeneration::_distributed
private

Whether the mortar segment mesh is distributed.

Definition at line 614 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), and projectSecondaryNodesSinglePair().

◆ _failed_secondary_node_projections

std::unordered_set<dof_id_type> AutomaticMortarGeneration::_failed_secondary_node_projections
private

Secondary nodes that failed to project.

Definition at line 662 of file AutomaticMortarGeneration.h.

Referenced by clear(), projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ _inactive_local_lm_elems

std::unordered_set<const Elem *> AutomaticMortarGeneration::_inactive_local_lm_elems
private

List of inactive lagrange multiplier nodes (for elemental variables)

Definition at line 564 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMElems(), and getInactiveLMElems().

◆ _inactive_local_lm_nodes

std::unordered_set<const Node *> AutomaticMortarGeneration::_inactive_local_lm_nodes
private

List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)

Definition at line 561 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMNodes(), computeIncorrectEdgeDroppingInactiveLMNodes(), and getInactiveLMNodes().

◆ _lower_elem_to_side_id

std::unordered_map<const Elem *, unsigned int> AutomaticMortarGeneration::_lower_elem_to_side_id
private

Keeps track of the mapping between lower-dimensional elements and the side_id of the interior_parent which they are.

Definition at line 529 of file AutomaticMortarGeneration.h.

Referenced by clear().

◆ _mesh

MeshBase& AutomaticMortarGeneration::_mesh
private

◆ _minimum_projection_angle

const Real AutomaticMortarGeneration::_minimum_projection_angle
private

Parameter to control which angle (in degrees) is admissible for the creation of mortar segments.

If set to a value close to zero, very oblique projections are allowed, which can result in mortar segments solving physics not meaningfully and overprojection of primary nodes onto the mortar segment mesh in extreme cases. This parameter is mostly intended for mortar mesh debugging purposes in 2D.

Definition at line 632 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d(), projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ _mortar_3d_qp_mapping

const Mortar3DQuadraturePointMapping AutomaticMortarGeneration::_mortar_3d_qp_mapping
private

Method used to map 3D mortar segment quadrature points to primary and secondary faces.

Definition at line 644 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d(), mortar3DQpMapping(), and mortarSegmentReferencePoints().

◆ _mortar_3d_subpatch_plane

const Mortar3DSubpatchPlane AutomaticMortarGeneration::_mortar_3d_subpatch_plane
private

Method used to define the local projection planes for 3D secondary subpatches.

Definition at line 635 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d().

◆ _mortar_interface_coupling

std::unordered_map<dof_id_type, std::unordered_set<dof_id_type> > AutomaticMortarGeneration::_mortar_interface_coupling
private

Used by the AugmentSparsityOnInterface functor to determine whether a given Elem is coupled to any others across the gap, and to explicitly set up the dependence between interior_parent() elements on the secondary side and their lower-dimensional sides which are on the interface.

This latter type of coupling must be explicitly declared when there is no primary_elem for a given mortar segment and you are using e.g. a P^1-P^0 discretization which does not induce the coupling automatically.

Definition at line 548 of file AutomaticMortarGeneration.h.

Referenced by buildCouplingInformation(), clear(), and mortarInterfaceCoupling().

◆ _mortar_segment_mesh

std::unique_ptr<MeshBase> AutomaticMortarGeneration::_mortar_segment_mesh
private

1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

Definition at line 520 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), computeInactiveLMElems(), computeInactiveLMNodes(), computeIncorrectEdgeDroppingInactiveLMNodes(), mortarSegmentMesh(), and outputMortarMesh().

◆ _msm_elem_to_info

std::unordered_map<const Elem *, MortarSegmentInfo> AutomaticMortarGeneration::_msm_elem_to_info
private

Map between Elems in the mortar segment mesh and their info structs.

This gets filled in by the call to build_mortar_segment_mesh().

Definition at line 525 of file AutomaticMortarGeneration.h.

Referenced by buildCouplingInformation(), buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), computeInactiveLMElems(), computeInactiveLMNodes(), computeIncorrectEdgeDroppingInactiveLMNodes(), computeMsmStatistics(), and mortarSegmentMeshElemToInfo().

◆ _msm_elem_to_reference_points

std::unordered_map<const Elem *, MortarSegmentReferencePoints> AutomaticMortarGeneration::_msm_elem_to_reference_points
private

Reference-coordinate data used only by the reference-interpolation mapping mode.

Definition at line 647 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d(), clear(), and mortarSegmentReferencePoints().

◆ _msm_node_id_start

std::optional<dof_id_type> AutomaticMortarGeneration::_msm_node_id_start
private

Cached per-rank starting ID for 3D MSM nodes/elements.

nullopt forces recomputation on next buildMortarSegmentMesh3d() call. Reset by meshChanged() on topology change so the allgather is skipped for displaced-mesh residual updates that only move nodes.

Definition at line 655 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d(), and meshChanged().

◆ _newton_tolerance

Real AutomaticMortarGeneration::_newton_tolerance = 1e-12
private

Newton solve tolerance for node projections.

Definition at line 617 of file AutomaticMortarGeneration.h.

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ _nodes_to_primary_elem_map

std::unordered_map<dof_id_type, std::vector<const Elem *> > AutomaticMortarGeneration::_nodes_to_primary_elem_map
private

◆ _nodes_to_secondary_elem_map

std::unordered_map<dof_id_type, std::vector<const Elem *> > AutomaticMortarGeneration::_nodes_to_secondary_elem_map
private

Map from nodes to connected lower-dimensional elements on the secondary/primary subdomains.

Definition at line 487 of file AutomaticMortarGeneration.h.

Referenced by buildNodeToElemMaps(), clear(), nodesToSecondaryElem(), processAlignedNodes(), projectPrimaryNodesSinglePair(), projectSecondaryNodesSinglePair(), and secondariesToMortarSegments().

◆ _on_displaced

const bool AutomaticMortarGeneration::_on_displaced
private

Whether this object is on the displaced mesh.

Definition at line 608 of file AutomaticMortarGeneration.h.

Referenced by mortarInterfaceName(), onDisplaced(), and MortarNodalGeometryOutput::output().

◆ _output_params

std::unique_ptr<InputParameters> AutomaticMortarGeneration::_output_params
private

Storage for the input parameters used by the mortar nodal geometry output.

Definition at line 650 of file AutomaticMortarGeneration.h.

Referenced by initOutput().

◆ _periodic

const bool AutomaticMortarGeneration::_periodic
private

Whether this object will be generating a mortar segment mesh for periodic constraints.

Definition at line 611 of file AutomaticMortarGeneration.h.

Referenced by computeADNodalNormals(), computeNodalGeometry(), and getNormals().

◆ _primary_boundary_subdomain_ids

std::set<SubdomainID> AutomaticMortarGeneration::_primary_boundary_subdomain_ids
private

Definition at line 538 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), and buildNodeToElemMaps().

◆ _primary_ip_sub_ids

std::set<SubdomainID> AutomaticMortarGeneration::_primary_ip_sub_ids
private

All the primary interior parent subdomain IDs associated with the mortar mesh.

Definition at line 576 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), and primaryIPSubIDs().

◆ _primary_node_and_elem_to_xi1_secondary_elem

std::map<std::tuple<dof_id_type, const Node *, const Elem *>, std::pair<Real, const Elem *> > AutomaticMortarGeneration::_primary_node_and_elem_to_xi1_secondary_elem
private

Same type of container, but for mapping (Primary Node ID, Primary Node, Primary Elem) -> (xi^(1), Secondary Elem) where they are inverse-projected along the nodal normal direction.

Note that the first item of the key, the primary node ID, is important for storing the key-value pairs in a consistent order across processes, e.g. this container has to be ordered!

Definition at line 516 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), clear(), processAlignedNodes(), and projectPrimaryNodesSinglePair().

◆ _primary_requested_boundary_ids

std::set<BoundaryID> AutomaticMortarGeneration::_primary_requested_boundary_ids
private

The boundary ids corresponding to all the primary surfaces.

Definition at line 480 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), and buildNodeToElemMaps().

◆ _primary_secondary_boundary_id_pairs

std::vector<std::pair<BoundaryID, BoundaryID> > AutomaticMortarGeneration::_primary_secondary_boundary_id_pairs
private

A list of primary/secondary boundary id pairs corresponding to each side of the mortar interface.

Definition at line 484 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh(), mortarInterfaceName(), and primarySecondaryBoundaryIDPair().

◆ _primary_secondary_subdomain_id_pairs

std::vector<std::pair<SubdomainID, SubdomainID> > AutomaticMortarGeneration::_primary_secondary_subdomain_id_pairs
private

A list of primary/secondary subdomain id pairs corresponding to each side of the mortar interface.

Definition at line 533 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh3d(), computeInactiveLMElems(), computeInactiveLMNodes(), computeIncorrectEdgeDroppingInactiveLMNodes(), computeMsmStatistics(), projectPrimaryNodes(), and projectSecondaryNodes().

◆ _projected_secondary_nodes

std::unordered_set<dof_id_type> AutomaticMortarGeneration::_projected_secondary_nodes
private

Debugging container for printing information about fraction of successful projections for secondary nodes.

If !_debug then this should always be empty

Definition at line 659 of file AutomaticMortarGeneration.h.

Referenced by clear(), and projectSecondaryNodesSinglePair().

◆ _secondary_boundary_subdomain_ids

std::set<SubdomainID> AutomaticMortarGeneration::_secondary_boundary_subdomain_ids
private

The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids.

Definition at line 537 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh(), buildNodeToElemMaps(), computeADNodalNormals(), and computeNodalGeometry().

◆ _secondary_element_to_secondary_lowerd_element

std::unordered_map<dof_id_type, const Elem *> AutomaticMortarGeneration::_secondary_element_to_secondary_lowerd_element
private

Map from full dimensional secondary element id to lower dimensional secondary element.

Definition at line 558 of file AutomaticMortarGeneration.h.

Referenced by clear(), computeNodalGeometry(), and getSecondaryLowerdElemFromSecondaryElem().

◆ _secondary_elems_to_mortar_segments

std::unordered_map<dof_id_type, std::set<Elem *, CompareDofObjectsByID> > AutomaticMortarGeneration::_secondary_elems_to_mortar_segments
private

We maintain a mapping from lower-dimensional secondary elements in the original mesh to (sets of) elements in mortar_segment_mesh.

This allows us to quickly determine which elements need to be split.

Definition at line 570 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), computeMsmStatistics(), secondariesToMortarSegments(), and secondariesToMortarSegments().

◆ _secondary_ip_sub_ids

std::set<SubdomainID> AutomaticMortarGeneration::_secondary_ip_sub_ids
private

All the secondary interior parent subdomain IDs associated with the mortar mesh.

Definition at line 573 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), and secondaryIPSubIDs().

◆ _secondary_node_and_elem_to_xi2_primary_elem

std::unordered_map<std::pair<const Node *, const Elem *>, std::pair<Real, const Elem *> > AutomaticMortarGeneration::_secondary_node_and_elem_to_xi2_primary_elem
private

Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2), primary Elem) pair.

This allows a single secondary node, which is potentially connected to two elements on the secondary side, to be associated with multiple primary Elem/xi^(2) values to handle the case where the primary and secondary nodes are "matching". In this configuration:

A B o--—o--—o (secondary orientation ->) | v ---—x---— (primary orientation <-) C D

The entries in the map should be: (Elem A, Node 1) -> (Elem C, xi^(2)=-1) (Elem B, Node 0) -> (Elem D, xi^(2)=+1)

Definition at line 508 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), clear(), processAlignedNodes(), and projectSecondaryNodesSinglePair().

◆ _secondary_node_to_hh_nodal_tangents

std::unordered_map<const Node *, std::array<Point, 2> > AutomaticMortarGeneration::_secondary_node_to_hh_nodal_tangents
private

Container for storing the nodal tangent/binormal vectors associated with each secondary node (Householder approach).

Definition at line 555 of file AutomaticMortarGeneration.h.

Referenced by clear(), computeNodalGeometry(), getNodalTangents(), and MortarNodalGeometryOutput::output().

◆ _secondary_node_to_nodal_normal

std::unordered_map<const Node *, Point> AutomaticMortarGeneration::_secondary_node_to_nodal_normal
private

Container for storing the nodal normal vector associated with each secondary node.

Definition at line 551 of file AutomaticMortarGeneration.h.

Referenced by clear(), computeADNodalNormals(), computeNodalGeometry(), getNodalNormals(), MortarNodalGeometryOutput::output(), projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ _secondary_requested_boundary_ids

std::set<BoundaryID> AutomaticMortarGeneration::_secondary_requested_boundary_ids
private

The boundary ids corresponding to all the secondary surfaces.

Definition at line 477 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), and buildNodeToElemMaps().

◆ _triangulate_triangles

const bool AutomaticMortarGeneration::_triangulate_triangles
private

Whether already-triangular clipped polygons should still be centroid-subdivided.

Definition at line 641 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d().

◆ _triangulation_mode

const MortarSegmentTriangulationMode AutomaticMortarGeneration::_triangulation_mode
private

Triangulation mode used for clipped 3D mortar polygons.

Definition at line 638 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh3d().

◆ _xi_tolerance

Real AutomaticMortarGeneration::_xi_tolerance = 1e-6
private

Tolerance for checking projection xi values.

Usually we are checking whether we projected onto a certain element (in which case -1 <= xi <= 1) or whether we should have already projected a primary node (in which case we error if abs(xi) is sufficiently close to 1)

Definition at line 622 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ system_name

const std::string AutomaticMortarGeneration::system_name
static

The name of the nodal normals system.

We store this in one place so it's easy to change later.

Definition at line 118 of file AutomaticMortarGeneration.h.


The documentation for this class was generated from the following files: