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< MsmSubdomainStatscomputeMsmStatistics ()
 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.
 
std::map< unsigned int, unsigned intgetSecondaryIpToLowerElementMap (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 intgetPrimaryIpToLowerElementMap (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 MortarSegmentReferencePointsmortarSegmentReferencePoints (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< MortarFilterItersecondariesToMortarSegments (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().
 
void householderOrthogolization (const Point &normal, Point &tangent_one, Point &tangent_two) const
 Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors.
 
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
 
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 55 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 344 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 387 of file AutomaticMortarGeneration.C.

402 _app(app),
403 _mesh(mesh_in),
404 _debug(debug),
405 _on_displaced(on_displaced),
406 _periodic(periodic),
407 // 3D mortar always builds the mortar segment mesh distributedly (each rank adds only its local
408 // secondary elements). For 2D, we ghost the entire mortar interface when displaced, so
409 // displaced meshes are always replicated; otherwise follow the parent mesh.
410 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
411 _correct_edge_dropping(correct_edge_dropping),
412 _minimum_projection_angle(minimum_projection_angle),
413 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
414 _triangulation_mode(triangulation_mode),
415 _triangulate_triangles(triangulate_triangles),
416 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
417{
418 _primary_secondary_boundary_id_pairs.push_back(boundary_key);
419 _primary_requested_boundary_ids.insert(boundary_key.first);
420 _secondary_requested_boundary_ids.insert(boundary_key.second);
421 _primary_secondary_subdomain_id_pairs.push_back(subdomain_key);
422 _primary_boundary_subdomain_ids.insert(subdomain_key.first);
423 _secondary_boundary_subdomain_ids.insert(subdomain_key.second);
424
425 if (_distributed)
427 std::make_unique<DistributedMesh>(_mesh.comm(), _mesh.spatial_dimension());
428 else
430 std::make_unique<ReplicatedMesh>(_mesh.comm(), _mesh.spatial_dimension());
431}
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 1711 of file AutomaticMortarGeneration.C.

1712{
1713 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1714 coupling_info;
1715
1716 // Loop over the msm_elem_to_info object and build a bi-directional
1717 // multimap from secondary elements to the primary Elems which they are
1718 // coupled to and vice-versa. This is used in the
1719 // AugmentSparsityOnInterface functor to determine whether a given
1720 // secondary Elem is coupled across the mortar interface to a primary
1721 // element.
1722 for (const auto & pr : _msm_elem_to_info)
1723 {
1724 const Elem * secondary_elem = pr.second.secondary_elem;
1725 const Elem * primary_elem = pr.second.primary_elem;
1726
1727 // LowerSecondary
1728 coupling_info[secondary_elem->processor_id()].emplace_back(
1729 secondary_elem->id(), secondary_elem->interior_parent()->id());
1730 if (secondary_elem->processor_id() != _mesh.processor_id())
1731 // We want to keep information for nonlocal lower-dimensional secondary element point
1732 // neighbors for mortar nodal aux kernels
1733 _mortar_interface_coupling[secondary_elem->id()].insert(
1734 secondary_elem->interior_parent()->id());
1735
1736 // LowerPrimary
1737 coupling_info[secondary_elem->processor_id()].emplace_back(
1738 secondary_elem->id(), primary_elem->interior_parent()->id());
1739 if (secondary_elem->processor_id() != _mesh.processor_id())
1740 // We want to keep information for nonlocal lower-dimensional secondary element point
1741 // neighbors for mortar nodal aux kernels
1742 _mortar_interface_coupling[secondary_elem->id()].insert(
1743 primary_elem->interior_parent()->id());
1744
1745 // Lower-LowerDimensionalPrimary
1746 coupling_info[secondary_elem->processor_id()].emplace_back(secondary_elem->id(),
1747 primary_elem->id());
1748 if (secondary_elem->processor_id() != _mesh.processor_id())
1749 // We want to keep information for nonlocal lower-dimensional secondary element point
1750 // neighbors for mortar nodal aux kernels
1751 _mortar_interface_coupling[secondary_elem->id()].insert(primary_elem->id());
1752
1753 // SecondaryLower
1754 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1755 secondary_elem->interior_parent()->id(), secondary_elem->id());
1756
1757 // SecondaryPrimary
1758 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1759 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1760
1761 // PrimaryLower
1762 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1763 primary_elem->interior_parent()->id(), secondary_elem->id());
1764
1765 // PrimarySecondary
1766 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1767 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1768 }
1769
1770 // Push the coupling information
1771 auto action_functor =
1772 [this](processor_id_type,
1773 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1774 {
1775 for (auto [i, j] : coupling_info)
1776 _mortar_interface_coupling[i].insert(j);
1777 };
1778 TIMPI::push_parallel_vector_data(_mesh.comm(), coupling_info, action_functor);
1779}
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 652 of file AutomaticMortarGeneration.C.

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

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

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

465{
467 _mortar_segment_mesh->clear();
472 _msm_elem_to_info.clear();
479 _secondary_ip_sub_ids.clear();
480 _primary_ip_sub_ids.clear();
483}
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().

◆ 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 2082 of file AutomaticMortarGeneration.C.

2083{
2084 // Mark all active secondary elements
2085 std::unordered_set<const Elem *> active_local_elems;
2086
2087 //****
2088 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
2089 // since the mortar segments are on the linearized element and comparing the volume of the
2090 // linearized element does not have the same volume as the warped element
2091 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
2092
2093 std::unordered_map<const Elem *, Real> active_volume;
2094
2095 // Compute fraction of elements with corresponding primary elements
2097 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
2098 {
2099 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
2100 const Elem * secondary_elem = msinfo.secondary_elem;
2101
2102 active_volume[secondary_elem] += msm_elem->volume();
2103 }
2104 //****
2105
2106 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
2107 {
2108 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
2109 const Elem * secondary_elem = msinfo.secondary_elem;
2110
2111 //****
2113 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
2114 continue;
2115 //****
2116
2117 active_local_elems.insert(secondary_elem);
2118 }
2119
2120 // Take complement of active elements in active local subdomain to get inactive local elements
2122 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2123 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
2124 /*secondary_subd_id*/ pr.second))
2125 if (active_local_elems.find(el) == active_local_elems.end())
2126 _inactive_local_lm_elems.insert(el);
2127}
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 1998 of file AutomaticMortarGeneration.C.

1999{
2001 {
2003 return;
2004 }
2005
2006 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
2007 const auto my_pid = _mesh.processor_id();
2008
2009 // List of active nodes on local secondary elements
2010 std::unordered_set<dof_id_type> active_local_nodes;
2011
2012 // Mark all active local nodes
2013 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
2014 {
2015 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
2016 const Elem * secondary_elem = msinfo.secondary_elem;
2017
2018 for (auto n : make_range(secondary_elem->n_nodes()))
2019 active_local_nodes.insert(secondary_elem->node_id(n));
2020 }
2021
2022 // Assemble list of procs that nodes contribute to
2023 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2024 {
2025 const auto secondary_subd_id = pr.second;
2026
2027 // Loop through all elements not on my processor
2028 for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
2029 {
2030 // Get processor_id
2031 const auto pid = el->processor_id();
2032
2033 // If element is in my subdomain, skip
2034 if (pid == my_pid)
2035 continue;
2036
2037 // If element on proc pid shares any of my active nodes, mark to send
2038 for (const auto n : make_range(el->n_nodes()))
2039 {
2040 const auto node_id = el->node_id(n);
2041 if (active_local_nodes.find(node_id) != active_local_nodes.end())
2042 proc_to_active_nodes_set[pid].insert(node_id);
2043 }
2044 }
2045 }
2046
2047 // Send list of active nodes
2048 {
2049 // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
2050 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
2051 for (const auto & proc_set : proc_to_active_nodes_set)
2052 {
2053 proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
2054 for (const auto node_id : proc_set.second)
2055 proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
2056 }
2057
2058 // First push data
2059 auto action_functor = [this, &active_local_nodes](const processor_id_type pid,
2060 const std::vector<dof_id_type> & sent_data)
2061 {
2062 if (pid == _mesh.processor_id())
2063 mooseError("Should not be communicating with self.");
2064 active_local_nodes.insert(sent_data.begin(), sent_data.end());
2065 };
2066 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_active_nodes_vector, action_functor);
2067 }
2068
2069 // Every proc has correct list of active local nodes, now take complement (list of inactive nodes)
2070 // and store to use later to zero LM DoFs on inactive nodes
2072 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2073 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
2074 /*secondary_subd_id*/ pr.second))
2075 for (const auto n : make_range(el->n_nodes()))
2076 if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
2077 _inactive_local_lm_nodes.insert(el->node_ptr(n));
2078}
std::unordered_set< const Node * > _inactive_local_lm_nodes
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...
const dof_id_type n_nodes

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 1904 of file AutomaticMortarGeneration.C.

1905{
1906 using std::abs;
1907
1908 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1909 // since the mortar segments are on the linearized element and comparing the volume of the
1910 // linearized element does not have the same volume as the warped element
1911 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1912
1913 std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1914 const auto my_pid = _mesh.processor_id();
1915
1916 // List of inactive nodes on local secondary elements
1917 std::unordered_set<dof_id_type> inactive_node_ids;
1918
1919 std::unordered_map<const Elem *, Real> active_volume{};
1920
1921 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1922 for (const auto el : _mesh.active_subdomain_elements_ptr_range(pr.second))
1923 active_volume[el] = 0.;
1924
1925 // Compute fraction of elements with corresponding primary elements
1926 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1927 {
1928 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1929 const Elem * secondary_elem = msinfo.secondary_elem;
1930
1931 active_volume[secondary_elem] += msm_elem->volume();
1932 }
1933
1934 // Mark all inactive local nodes
1935 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1936 // Loop through all elements on my processor
1937 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(pr.second))
1938 // If elem fully or partially dropped
1939 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1940 {
1941 // Add all nodes to list of inactive
1942 for (auto n : make_range(el->n_nodes()))
1943 inactive_node_ids.insert(el->node_id(n));
1944 }
1945
1946 // Assemble list of procs that nodes contribute to
1947 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1948 {
1949 const auto secondary_subd_id = pr.second;
1950
1951 // Loop through all elements not on my processor
1952 for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1953 {
1954 // Get processor_id
1955 const auto pid = el->processor_id();
1956
1957 // If element is in my subdomain, skip
1958 if (pid == my_pid)
1959 continue;
1960
1961 // If element on proc pid shares any of my inactive nodes, mark to send
1962 for (const auto n : make_range(el->n_nodes()))
1963 {
1964 const auto node_id = el->node_id(n);
1965 if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1966 proc_to_inactive_nodes_set[pid].insert(node_id);
1967 }
1968 }
1969 }
1970
1971 // Send list of inactive nodes
1972 {
1973 // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
1974 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1975 for (const auto & proc_set : proc_to_inactive_nodes_set)
1976 proc_to_inactive_nodes_vector[proc_set.first].insert(
1977 proc_to_inactive_nodes_vector[proc_set.first].end(),
1978 proc_set.second.begin(),
1979 proc_set.second.end());
1980
1981 // First push data
1982 auto action_functor = [this, &inactive_node_ids](const processor_id_type pid,
1983 const std::vector<dof_id_type> & sent_data)
1984 {
1985 if (pid == _mesh.processor_id())
1986 mooseError("Should not be communicating with self.");
1987 for (const auto pr : sent_data)
1988 inactive_node_ids.insert(pr);
1989 };
1990 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_inactive_nodes_vector, action_functor);
1991 }
1993 for (const auto node_id : inactive_node_ids)
1994 _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1995}
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 1782 of file AutomaticMortarGeneration.C.

1783{
1784 std::vector<MsmSubdomainStats> result;
1788 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1789
1790 for (const auto & [primary_subd_id, secondary_subd_id] : _primary_secondary_subdomain_id_pairs)
1791 {
1792 for (const auto * const secondary_el :
1793 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1794 {
1795 secondary.push_back(secondary_el->volume());
1796 // We may not have projected onto a primary face in which case we may not have created mortar
1797 // segments
1798 if (auto it = _secondary_elems_to_mortar_segments.find(secondary_el->id());
1800 for (const auto * const msm_elem : it->second)
1801 {
1802 msm.push_back(msm_elem->volume());
1803 const auto & msm_info = libmesh_map_find(_msm_elem_to_info, msm_elem);
1804 // Now it's also possible that we didn't project onto a primary face and we *did* create
1805 // mortar segments
1806 if (msm_info.primary_elem)
1807 {
1808 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1809 mooseError("Unhandled primary-secondary pairing when computing mortar segment "
1810 "statistics. This could happen if you have the same secondary "
1811 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1812 "primary subdomain IDs. Contact a MOOSE developer for help.");
1813 if (const auto [it, inserted] =
1814 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1815 inserted)
1816 it->second = msm_info.primary_elem->volume();
1817 else
1818 mooseAssert(
1819 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1820 "Volumes should be consistent");
1821 }
1822 }
1823 }
1824
1825 _mesh.comm().set_union(primary_elems_to_volume);
1826 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1827 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1828 primary.reserve(primary_elems_to_volume.size());
1829 for (const auto [_, volume] : primary_elems_to_volume)
1830 primary.push_back(volume);
1831
1832 MsmSubdomainStats stats;
1833 stats.primary_subd_id = primary_subd_id;
1834 stats.secondary_subd_id = secondary_subd_id;
1835 stats.secondary_lower_n_elems = secondary.size();
1836 stats.secondary_lower_max_volume = secondary.maximum();
1837 stats.secondary_lower_min_volume = secondary.minimum();
1838 stats.secondary_lower_median_volume = secondary.median();
1839 stats.primary_lower_n_elems = primary.size();
1840 stats.primary_lower_max_volume = primary.maximum();
1841 stats.primary_lower_min_volume = primary.minimum();
1842 stats.primary_lower_median_volume = primary.median();
1843 stats.msm_n_elems = msm.size();
1844 stats.msm_max_volume = msm.maximum();
1845 stats.msm_min_volume = msm.minimum();
1846 stats.msm_median_volume = msm.median();
1847 result.push_back(stats);
1848
1849 primary.clear();
1850 secondary.clear();
1851 msm.clear();
1852 primary_elems_to_volume.clear();
1853 }
1854
1855 return result;
1856}
virtual T maximum() const
virtual T minimum() const

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 2130 of file AutomaticMortarGeneration.C.

2131{
2132 // The dimension according to Mesh::mesh_dimension().
2133 const auto dim = _mesh.mesh_dimension();
2134
2135 mooseAssert(dim == 2 || dim == 3,
2136 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
2137 "mortar constraints on 2D or 3D meshes.");
2138 // A nodal lower-dimensional nodal quadrature rule to be used on faces.
2139 libMesh::QNodal qface(dim - 1);
2140
2141 // A map from the node id to the attached elemental normals/weights evaluated at the node. Th
2142 // length of the vector will correspond to the number of elements attached to the node. If it is a
2143 // vertex node, for a 1D mortar mesh, the vector length will be two. If it is an interior node,
2144 // the vector will be length 1. The first member of the pair is that element's normal at the node.
2145 // The second member is that element's JxW at the node
2146 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2147
2149 Real sign = _periodic ? -1 : 1;
2150
2151 // First loop over lower-dimensional secondary side elements and compute/save the outward normal
2152 // for each one. We loop over all active elements currently, but this procedure could be
2153 // parallelized as well.
2154 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
2155 end_el = _mesh.active_elements_end();
2156 el != end_el;
2157 ++el)
2158 {
2159 const Elem * secondary_elem = *el;
2160
2161 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
2162 if (!_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
2163 continue;
2164
2165 // We will create an FE object and attach the nodal quadrature rule such that we can get out the
2166 // normals at the element nodes
2167 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2168 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(dim, nnx_fe_type));
2169 nnx_fe_face->attach_quadrature_rule(&qface);
2170 const auto & face_normals = nnx_fe_face->get_normals();
2171 const auto & face_points = nnx_fe_face->get_xyz();
2172
2173 const auto & JxW = nnx_fe_face->get_JxW();
2174
2175 // Which side of the parent are we? We need to know this to know
2176 // which side to reinit.
2177 const Elem * interior_parent = secondary_elem->interior_parent();
2178 mooseAssert(interior_parent,
2179 "No interior parent exists for element "
2180 << secondary_elem->id()
2181 << ". There may be a problem with your sideset set-up.");
2182
2183 // Map to get lower dimensional element from interior parent on secondary surface
2184 // This map can be used to provide a handle to methods in this class that need to
2185 // operate on lower dimensional elements.
2186 _secondary_element_to_secondary_lowerd_element.emplace(interior_parent->id(), secondary_elem);
2187
2188 // Look up which side of the interior parent secondary_elem is.
2189 auto s = interior_parent->which_side_am_i(secondary_elem);
2190
2191 // Reinit the face FE object on side s.
2192 nnx_fe_face->reinit(interior_parent, s);
2193
2194 // Match by physical location instead of assuming that parent-side nodal
2195 // quadrature ordering and lower-dimensional side-element node ordering are
2196 // identical.
2197 const auto qpoint_to_secondary_node =
2198 nodalQuadraturePointToSecondaryNodeMap(*secondary_elem, face_points);
2199
2200 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2201 "Face nodal geometry vectors must have the same size.");
2202
2203 for (const auto qp : make_range(face_points.size()))
2204 {
2205 const auto n = qpoint_to_secondary_node[qp];
2206 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2207 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2208 }
2209 }
2210
2211 for (const auto & pr : node_to_normals_map)
2212 {
2213 // Compute normal vector
2214 const auto & node_id = pr.first;
2215 const auto & normals_and_weights_vec = pr.second;
2216
2217 Point nodal_normal;
2218 for (const auto & norm_and_weight : normals_and_weights_vec)
2219 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2220 nodal_normal = nodal_normal.unit();
2221
2222 _secondary_node_to_nodal_normal[_mesh.node_ptr(node_id)] = nodal_normal;
2223
2224 Point nodal_tangent_one;
2225 Point nodal_tangent_two;
2226 householderOrthogolization(nodal_normal, nodal_tangent_one, nodal_tangent_two);
2227
2228 _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][0] = nodal_tangent_one;
2229 _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][1] = nodal_tangent_two;
2230 }
2231}
void householderOrthogolization(const Point &normal, Point &tangent_one, Point &tangent_two) const
Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors.
KOKKOS_INLINE_FUNCTION T sign(T x)
Returns the sign of a value.
Definition KokkosUtils.h:26

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 337 of file AutomaticMortarGeneration.h.

338 {
340 }

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 329 of file AutomaticMortarGeneration.h.

330 {
332 }

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 541 of file AutomaticMortarGeneration.C.

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

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 594 of file AutomaticMortarGeneration.C.

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

◆ 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 623 of file AutomaticMortarGeneration.C.

625{
626 const auto mortar_dim = _mesh.mesh_dimension() - 1;
627 const auto num_qps = xi1_pts.size();
628 const auto nodal_normals = getNodalNormals(secondary_elem);
629 std::vector<Point> normals(num_qps);
630
631 for (const auto n : make_range(secondary_elem.n_nodes()))
632 for (const auto qp : make_range(num_qps))
633 {
634 const auto phi =
635 (mortar_dim == 1)
636 ? Moose::fe_lagrange_1D_shape(secondary_elem.default_order(), n, xi1_pts[qp](0))
637 : Moose::fe_lagrange_2D_shape(secondary_elem.type(),
638 secondary_elem.default_order(),
639 n,
640 cast_ref<const TypeVector<Real> &>(xi1_pts[qp]));
641 normals[qp] += phi * nodal_normals[n];
642 }
643
644 if (_periodic)
645 for (auto & normal : normals)
646 normal *= -1;
647
648 return normals;
649}
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 612 of file AutomaticMortarGeneration.C.

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

◆ 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 577 of file AutomaticMortarGeneration.C.

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

◆ 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 561 of file AutomaticMortarGeneration.C.

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

◆ 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 551 of file AutomaticMortarGeneration.C.

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

◆ householderOrthogolization()

void AutomaticMortarGeneration::householderOrthogolization ( const Point &  normal,
Point &  tangent_one,
Point &  tangent_two 
) const
private

Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors.

Definition at line 2234 of file AutomaticMortarGeneration.C.

2237{
2238 using std::abs;
2239
2240 mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.norm(), 1),
2241 "The input nodal normal should have unity norm");
2242
2243 const Real nx = nodal_normal(0);
2244 const Real ny = nodal_normal(1);
2245 const Real nz = nodal_normal(2);
2246
2247 // See Lopes DS, Silva MT, Ambrosio JA. Tangent vectors to a 3-D surface normal: A geometric tool
2248 // to find orthogonal vectors based on the Householder transformation. Computer-Aided Design. 2013
2249 // Mar 1;45(3):683-94. We choose one definition of h_vector and deal with special case.
2250 const Point h_vector(nx + 1.0, ny, nz);
2251
2252 // Avoid singularity of the equations at the end of routine by providing the solution to
2253 // (nx,ny,nz)=(-1,0,0) Normal/tangent fields can be visualized by outputting nodal geometry mesh
2254 // on a spherical problem.
2255 if (abs(h_vector(0)) < TOLERANCE)
2256 {
2257 nodal_tangent_one(0) = 0;
2258 nodal_tangent_one(1) = 1;
2259 nodal_tangent_one(2) = 0;
2260
2261 nodal_tangent_two(0) = 0;
2262 nodal_tangent_two(1) = 0;
2263 nodal_tangent_two(2) = -1;
2264
2265 return;
2266 }
2267
2268 const Real h = h_vector.norm();
2269
2270 nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
2271 nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
2272 nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2273
2274 nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
2275 nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
2276 nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
2277}

Referenced by computeNodalGeometry().

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 342 of file AutomaticMortarGeneration.h.

342{ return !_correct_edge_dropping; }

Referenced by ComputeMortarFunctor::operator()().

◆ initOutput()

void AutomaticMortarGeneration::initOutput ( )

initialize mortar-mesh based output

Definition at line 448 of file AutomaticMortarGeneration.C.

449{
450 if (!_debug)
451 return;
452
453 _output_params = std::make_unique<InputParameters>(MortarNodalGeometryOutput::validParams());
454 _output_params->set<AutomaticMortarGeneration *>("_amg") = this;
455 _output_params->set<FEProblemBase *>("_fe_problem_base") = &_app.feProblem();
457 _output_params->set<std::string>(MooseBase::name_param) =
458 "mortar_nodal_geometry_" + mortarInterfaceName();
459 _output_params->finalize("MortarNodalGeometryOutput");
460 _app.getOutputWarehouse().addOutput(std::make_shared<MortarNodalGeometryOutput>(*_output_params));
461}
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:2415
FEProblemBase & feProblem() const
Definition MooseApp.C:1858
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 204 of file AutomaticMortarGeneration.h.

204{ _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 320 of file AutomaticMortarGeneration.h.

320{ 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 294 of file AutomaticMortarGeneration.h.

295 {
297 }

◆ mortarInterfaceName()

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

Definition at line 434 of file AutomaticMortarGeneration.C.

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

Referenced by initOutput(), and outputMortarMesh().

◆ mortarSegmentMesh()

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

Definition at line 307 of file AutomaticMortarGeneration.h.

307{ 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 312 of file AutomaticMortarGeneration.h.

313 {
314 return _msm_elem_to_info;
315 }

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 486 of file AutomaticMortarGeneration.C.

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

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

◆ msmStatistics()

void AutomaticMortarGeneration::msmStatistics ( )

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

Definition at line 1859 of file AutomaticMortarGeneration.C.

1860{
1861 const auto all_stats = computeMsmStatistics();
1862
1863 if (_mesh.processor_id() != 0)
1864 return;
1865
1866 Moose::out << "Mortar Interface Statistics:" << std::endl;
1867 for (const auto & stats : all_stats)
1868 {
1869 std::vector<std::string> col_names = {"mesh", "n_elems", "max", "min", "median"};
1870 std::vector<std::string> subds = {"secondary_lower", "primary_lower", "mortar_segment"};
1871 std::vector<size_t> n_elems = {
1872 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1873 std::vector<Real> maxs = {
1874 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1875 std::vector<Real> mins = {
1876 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1877 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1878 stats.primary_lower_median_volume,
1879 stats.msm_median_volume};
1880
1881 FormattedTable table;
1882 table.clear();
1883 for (auto i : index_range(subds))
1884 {
1885 table.addRow(i);
1886 table.addData<std::string>(col_names[0], subds[i]);
1887 table.addData<size_t>(col_names[1], n_elems[i]);
1888 table.addData<Real>(col_names[2], maxs[i]);
1889 table.addData<Real>(col_names[3], mins[i]);
1890 table.addData<Real>(col_names[4], medians[i]);
1891 }
1892
1893 Moose::out << "secondary subdomain: " << stats.secondary_subd_id
1894 << " \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1895 table.printTable(Moose::out, subds.size());
1896 }
1897}
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 378 of file AutomaticMortarGeneration.h.

379 {
381 }

◆ onDisplaced()

bool AutomaticMortarGeneration::onDisplaced ( ) const
inline

returns whether this object is on the displaced mesh

Definition at line 209 of file AutomaticMortarGeneration.h.

209{ return _on_displaced; }

◆ outputMortarMesh()

void AutomaticMortarGeneration::outputMortarMesh ( )
private

Write the mortar segment mesh to exodus.

Definition at line 1144 of file AutomaticMortarGeneration.C.

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

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 373 of file AutomaticMortarGeneration.h.

373{ return _primary_ip_sub_ids; }

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

◆ primarySecondaryBoundaryIDPair()

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

Definition at line 609 of file AutomaticMortarGeneration.h.

610{
611 mooseAssert(_primary_secondary_boundary_id_pairs.size() == 1,
612 "We currently only support a single boundary pair per mortar generation object");
613
615}

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 2291 of file AutomaticMortarGeneration.C.

2299{
2300 if (!secondary_node_neighbors)
2301 secondary_node_neighbors = &libmesh_map_find(_nodes_to_secondary_elem_map, secondary_node.id());
2302 if (!primary_node_neighbors)
2303 primary_node_neighbors = &libmesh_map_find(_nodes_to_primary_elem_map, primary_node.id());
2304
2305 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(), false);
2306
2307 // Add entries to secondary_node_and_elem_to_xi2_primary_elem container.
2308 //
2309 // First, determine "on left" vs. "on right" orientation of the nodal neighbors.
2310 // There can be a max of 2 nodal neighbors, and we want to make sure that the
2311 // secondary nodal neighbor on the "left" is associated with the primary nodal
2312 // neighbor on the "left" and similarly for the "right". We use cross products to determine
2313 // alignment. In the below diagram, 'x' denotes a node, and connected '|' are lower dimensional
2314 // elements.
2315 // x
2316 // x |
2317 // | |
2318 // secondary x ----> x primary
2319 // | |
2320 // | x
2321 // x
2322 //
2323 // Looking at the aligned nodes, the secondary node first, if we pick the top secondary lower
2324 // dimensional element, then the cross product as written a few lines below points out of the
2325 // screen towards you. (Point in the direction of the secondary nodal normal, and then curl your
2326 // hand towards the secondary element's opposite node, then the thumb points in the direction of
2327 // the cross product). Doing the same with the aligned primary node, if we pick the top primary
2328 // element, then the cross product also points out of the screen. Because the cross products
2329 // point in the same direction (positive dot product), then we know to associate the
2330 // secondary-primary element pair. If we had picked the bottom primary element whose cross
2331 // product points into the screen, then clearly the cross products point in the opposite
2332 // direction and we don't have a match
2333 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2334
2335 for (const auto nn : index_range(*secondary_node_neighbors))
2336 {
2337 const Elem * const secondary_neigh = (*secondary_node_neighbors)[nn];
2338 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2339 ? secondary_neigh->point(1)
2340 : secondary_neigh->point(0);
2341 const Point cp = nodal_normal.cross(opposite - secondary_node);
2342 secondary_node_neighbor_cps[nn] = cp(2);
2343 }
2344
2345 for (const auto nn : index_range(*primary_node_neighbors))
2346 {
2347 const Elem * const primary_neigh = (*primary_node_neighbors)[nn];
2348 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2349 : primary_neigh->point(0);
2350 const Point cp = nodal_normal.cross(opposite - primary_node);
2351 primary_node_neighbor_cps[nn] = cp(2);
2352 }
2353
2354 // Associate secondary/primary elems on matching sides.
2355 bool found_match = false;
2356 for (const auto snn : index_range(*secondary_node_neighbors))
2357 for (const auto mnn : index_range(*primary_node_neighbors))
2358 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2359 {
2360 found_match = true;
2361 if (primary_elems_mapped[mnn])
2362 continue;
2363 primary_elems_mapped[mnn] = true;
2364
2365 // Figure out xi^(2) value by looking at which node primary_node is
2366 // of the current primary node neighbor.
2367 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2368 const auto secondary_key =
2369 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2370 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2371 _secondary_node_and_elem_to_xi2_primary_elem.emplace(secondary_key, primary_val);
2372
2373 // Also map in the other direction.
2374 const Real xi1 =
2375 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2376
2377 const auto primary_key =
2378 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2379 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2380 _primary_node_and_elem_to_xi1_secondary_elem.emplace(primary_key, secondary_val);
2381 }
2382
2383 if (!found_match)
2384 {
2385 // There could be coincident nodes and this might be a bad primary candidate (see
2386 // issue #21680). Instead of giving up, let's try continuing
2387 rejected_elem_candidates.insert(&candidate_element);
2388 return false;
2389 }
2390
2391 // We need to handle the case where we've exactly projected a secondary node onto a
2392 // primary node, but our secondary node is at one of the secondary boundary face endpoints and
2393 // our primary node is not.
2394 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2395 for (const auto i : index_range(primary_elems_mapped))
2396 if (!primary_elems_mapped[i])
2397 {
2399 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2400 std::make_pair(1, nullptr));
2401 }
2402
2403 return found_match;
2404}
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 2652 of file AutomaticMortarGeneration.C.

2653{
2654 // For each primary/secondary boundary id pair, call the
2655 // project_primary_nodes_single_pair() helper function.
2656 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2657 projectPrimaryNodesSinglePair(pr.first, pr.second);
2658}
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 2661 of file AutomaticMortarGeneration.C.

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

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 2282 of file AutomaticMortarGeneration.C.

2283{
2284 // For each primary/secondary boundary id pair, call the
2285 // project_secondary_nodes_single_pair() helper function.
2286 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2287 projectSecondaryNodesSinglePair(pr.first, pr.second);
2288}
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 2407 of file AutomaticMortarGeneration.C.

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

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 360 of file AutomaticMortarGeneration.h.

361 {
363 }

◆ 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 2888 of file AutomaticMortarGeneration.C.

2889{
2890 auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2891 if (secondary_it == _nodes_to_secondary_elem_map.end())
2892 return {};
2893
2894 const auto & secondary_elems = secondary_it->second;
2895 std::vector<MortarFilterIter> ret;
2896 ret.reserve(secondary_elems.size());
2897
2898 for (const auto i : index_range(secondary_elems))
2899 {
2900 auto * const secondary_elem = secondary_elems[i];
2901 auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2902 if (msm_it == _secondary_elems_to_mortar_segments.end())
2903 // We may have removed this element key from this map
2904 continue;
2905
2906 mooseAssert(secondary_elem->active(),
2907 "We loop over active elements when building the mortar segment mesh, so we golly "
2908 "well hope this is active.");
2909 mooseAssert(!msm_it->second.empty(),
2910 "We should have removed all secondaries from this map if they do not have any "
2911 "mortar segments associated with them.");
2912 ret.push_back(msm_it);
2913 }
2914
2915 return ret;
2916}

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 368 of file AutomaticMortarGeneration.h.

368{ return _secondary_ip_sub_ids; }

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

Friends And Related Symbol Documentation

◆ AugmentSparsityOnInterface

friend class AugmentSparsityOnInterface
friend

Definition at line 605 of file AutomaticMortarGeneration.h.

◆ MortarNodalGeometryOutput

friend class MortarNodalGeometryOutput
friend

Definition at line 604 of file AutomaticMortarGeneration.h.

Member Data Documentation

◆ _app

MooseApp& AutomaticMortarGeneration::_app
private

The Moose app.

Definition at line 405 of file AutomaticMortarGeneration.h.

Referenced by 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(), DefaultMultiAppFixedPointConvergence::initialize(), 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(), 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 566 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 554 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 602 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 498 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMElems(), and getInactiveLMElems().

◆ _inactive_local_lm_nodes

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

◆ _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 463 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 572 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 584 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 575 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 482 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 454 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 459 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 587 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 595 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 557 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 421 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 548 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 590 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 551 of file AutomaticMortarGeneration.h.

Referenced by computeNodalGeometry(), and getNormals().

◆ _primary_boundary_subdomain_ids

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

Definition at line 472 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 510 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 450 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 414 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 418 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 467 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 599 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 471 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh(), buildNodeToElemMaps(), 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 492 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 504 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 507 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 442 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 489 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 485 of file AutomaticMortarGeneration.h.

Referenced by clear(), 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 411 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 581 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 578 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 562 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 62 of file AutomaticMortarGeneration.h.


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