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

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

#include <AutomaticMortarGeneration.h>

Inheritance diagram for AutomaticMortarGeneration:
[legend]

Classes

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

Public Types

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

Public Member Functions

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

Public Attributes

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

Static Public Attributes

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

Private Member Functions

void outputMortarMesh ()
 Write the mortar segment mesh to exodus.
 
std::string mortarInterfaceName () const
 
void buildCouplingInformation ()
 build the _mortar_interface_coupling data
 
void projectSecondaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function responsible for projecting secondary nodes onto primary elements for a single primary/secondary pair.
 
void projectPrimaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
 
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 388 of file AutomaticMortarGeneration.C.

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

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

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

Referenced by MortarInterfaceWarehouse::update().

◆ buildMortarSegmentMesh3d()

void AutomaticMortarGeneration::buildMortarSegmentMesh3d ( )

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

Inputs:

  • mesh

Outputs:

  • mortar_segment_mesh
  • msm_elem_to_info

Step 1: Build mortar segments for all secondary elements

Step 1.1: Linearize secondary face elements

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

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

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

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

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

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

Definition at line 1163 of file AutomaticMortarGeneration.C.

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

Referenced by MortarInterfaceWarehouse::update().

◆ buildNodeToElemMaps()

void AutomaticMortarGeneration::buildNodeToElemMaps ( )

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

Definition at line 504 of file AutomaticMortarGeneration.C.

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

466{
468 _mortar_segment_mesh->clear();
473 _msm_elem_to_info.clear();
480 _secondary_ip_sub_ids.clear();
481 _primary_ip_sub_ids.clear();
484}
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 2086 of file AutomaticMortarGeneration.C.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

◆ msmStatistics()

void AutomaticMortarGeneration::msmStatistics ( )

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

Definition at line 1863 of file AutomaticMortarGeneration.C.

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

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

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(), and MortarInterfaceWarehouse::update().

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

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

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

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

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

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

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

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

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(), and MortarInterfaceWarehouse::update().

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 buildMortarSegmentMesh(), initOutput(), and outputMortarMesh().

◆ _console

const ConsoleStream ConsoleStreamInterface::_console
inherited

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

Definition at line 31 of file ConsoleStreamInterface.h.

Referenced by IterationAdaptiveDT::acceptStep(), MaterialOutputAction::act(), MeshOnlyAction::act(), SetupDebugAction::act(), FEProblemBase::adaptMesh(), Adaptivity::adaptMesh(), PerfGraph::addToExecutionList(), SimplePredictor::apply(), SystemBase::applyScalingFactors(), MultiApp::backup(), FEProblemBase::backupMultiApps(), CoarsenedPiecewiseLinear::buildCoarsenedGrid(), DefaultSteadyStateConvergence::checkConvergence(), MeshDiagnosticsGenerator::checkElementOverlap(), MeshDiagnosticsGenerator::checkElementTypes(), MeshDiagnosticsGenerator::checkElementVolumes(), FEProblemBase::checkExceptionAndStopSolve(), SolverSystem::checkInvalidSolution(), MeshDiagnosticsGenerator::checkLocalJacobians(), MeshDiagnosticsGenerator::checkNonConformalMesh(), MeshDiagnosticsGenerator::checkNonConformalMeshFromAdaptivity(), MeshDiagnosticsGenerator::checkNonConformingFaces(), MeshDiagnosticsGenerator::checkNonMatchingEdges(), MeshDiagnosticsGenerator::checkNonPlanarSides(), MeshDiagnosticsGenerator::checkPolygons(), FEProblemBase::checkProblemIntegrity(), ReferenceResidualConvergence::checkResidualConvergence(), MeshDiagnosticsGenerator::checkSidesetsOrientation(), MeshDiagnosticsGenerator::checkWatertightNodesets(), MeshDiagnosticsGenerator::checkWatertightSidesets(), CoarsenSurfaceMeshAlongSidesetGenerator::coarsenAlongSidesets(), IterationAdaptiveDT::computeAdaptiveDT(), TransientBase::computeConstrainedDT(), DefaultMultiAppFixedPointConvergence::computeCustomConvergencePostprocessor(), NonlinearSystemBase::computeDamping(), FixedPointIterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeFailedDT(), IterationAdaptiveDT::computeInitialDT(), IterationAdaptiveDT::computeInterpolationDT(), FEProblemBase::computeLinearSystemTags(), LinearSystem::computeLinearSystemTags(), NonlinearSystemBase::computeScaling(), Problem::console(), TimeStepper::constrainStep(), IterationAdaptiveDT::constrainStep(), MultiApp::createApp(), FEProblemBase::execMultiApps(), FEProblemBase::execMultiAppTransfers(), Eigenvalue::execute(), SteadyBase::execute(), MFEMSteady::execute(), MessageFromInput::execute(), ActionWarehouse::executeActionsWithAction(), ActionWarehouse::executeAllActions(), MeshGeneratorSystem::executeMeshGenerators(), SidesetAroundSubdomainUpdater::finalize(), ElementQualityChecker::finalize(), FEProblemBase::finishMultiAppStep(), MeshRepairGenerator::fixOverlappingNodes(), SurfaceSubdomainsDelaunayRemesher::General2DDelaunay(), CoarsenBlockGenerator::generate(), CoarsenSurfaceMeshAlongSidesetGenerator::generate(), OrientSurfaceMeshGenerator::generate(), PolyLineMeshFollowingNodeSetGenerator::generate(), MeshGenerator::generateInternal(), VariableCondensationPreconditioner::getDofToCondense(), InversePowerMethod::init(), NonlinearEigen::init(), FEProblemBase::initialAdaptMesh(), 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: