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

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

#include <AutomaticMortarGeneration.h>

Inheritance diagram for AutomaticMortarGeneration:
[legend]

Classes

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

Public Types

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

Public Member Functions

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

Public Attributes

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

Static Public Attributes

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

Private Member Functions

void outputMortarMesh ()
 Write the mortar segment mesh to exodus.
 
std::string mortarInterfaceName () const
 
void buildCouplingInformation ()
 build the _mortar_interface_coupling data
 
void projectSecondaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function responsible for projecting secondary nodes onto primary elements for a single primary/secondary pair.
 
void projectPrimaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
 
bool processAlignedNodes (const Node &secondary_node, const Node &primary_node, const std::vector< const Elem * > *secondary_node_neighbors, const std::vector< const Elem * > *primary_node_neighbors, const VectorValue< Real > &nodal_normal, const Elem &candidate_element, std::set< const Elem * > &rejected_element_candidates)
 Process aligned nodes.
 

Private Attributes

MooseApp & _app
 The Moose app.
 
MeshBase & _mesh
 Reference to the mesh stored in equation_systems.
 
std::set< BoundaryID > _secondary_requested_boundary_ids
 The boundary ids corresponding to all the secondary surfaces.
 
std::set< BoundaryID > _primary_requested_boundary_ids
 The boundary ids corresponding to all the primary surfaces.
 
std::vector< std::pair< BoundaryID, BoundaryID > > _primary_secondary_boundary_id_pairs
 A list of primary/secondary boundary id pairs corresponding to each side of the mortar interface.
 
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_secondary_elem_map
 Map from nodes to connected lower-dimensional elements on the secondary/primary subdomains.
 
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
 
std::unordered_map< std::pair< const Node *, const Elem * >, std::pair< Real, const Elem * > > _secondary_node_and_elem_to_xi2_primary_elem
 Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2), primary Elem) pair.
 
std::map< std::tuple< dof_id_type, const Node *, const Elem * >, std::pair< Real, const Elem * > > _primary_node_and_elem_to_xi1_secondary_elem
 Same type of container, but for mapping (Primary Node ID, Primary Node, Primary Elem) -> (xi^(1), Secondary Elem) where they are inverse-projected along the nodal normal direction.
 
std::unique_ptr< MeshBase > _mortar_segment_mesh
 1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
 
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
 Map between Elems in the mortar segment mesh and their info structs.
 
std::unordered_map< const Elem *, unsigned int > _lower_elem_to_side_id
 Keeps track of the mapping between lower-dimensional elements and the side_id of the interior_parent which they are.
 
std::vector< std::pair< SubdomainID, SubdomainID > > _primary_secondary_subdomain_id_pairs
 A list of primary/secondary subdomain id pairs corresponding to each side of the mortar interface.
 
std::set< SubdomainID > _secondary_boundary_subdomain_ids
 The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids.
 
std::set< SubdomainID > _primary_boundary_subdomain_ids
 
std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > _mortar_interface_coupling
 Used by the AugmentSparsityOnInterface functor to determine whether a given Elem is coupled to any others across the gap, and to explicitly set up the dependence between interior_parent() elements on the secondary side and their lower-dimensional sides which are on the interface.
 
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
 Container for storing the nodal normal vector associated with each secondary node.
 
std::unordered_map< const Node *, std::array< Point, 2 > > _secondary_node_to_hh_nodal_tangents
 Container for storing the nodal tangent/binormal vectors associated with each secondary node (Householder approach).
 
std::unordered_map< dof_id_type, const Elem * > _secondary_element_to_secondary_lowerd_element
 Map from full dimensional secondary element id to lower dimensional secondary element.
 
std::unordered_set< const Node * > _inactive_local_lm_nodes
 List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)
 
std::unordered_set< const Elem * > _inactive_local_lm_elems
 List of inactive lagrange multiplier nodes (for elemental variables)
 
std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > _secondary_elems_to_mortar_segments
 We maintain a mapping from lower-dimensional secondary elements in the original mesh to (sets of) elements in mortar_segment_mesh.
 
std::set< SubdomainID > _secondary_ip_sub_ids
 All the secondary interior parent subdomain IDs associated with the mortar mesh.
 
std::set< SubdomainID > _primary_ip_sub_ids
 All the primary interior parent subdomain IDs associated with the mortar mesh.
 
const bool _debug
 Whether to print debug output.
 
const bool _on_displaced
 Whether this object is on the displaced mesh.
 
const bool _periodic
 Whether this object will be generating a mortar segment mesh for periodic constraints.
 
const bool _distributed
 Whether the mortar segment mesh is distributed.
 
bool _nonlocal_secondary_couplings = false
 Whether buildCouplingInformation() also gathers the couplings of nonlocal secondary lower-dimensional elements.
 
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 133 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 442 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 221 of file AutomaticMortarGeneration.C.

236 _app(app),
237 _mesh(mesh_in),
238 _debug(debug),
239 _on_displaced(on_displaced),
240 _periodic(periodic),
241 // 3D mortar always builds the mortar segment mesh distributedly (each rank adds only its local
242 // secondary elements). For 2D, we ghost the entire mortar interface when displaced, so
243 // displaced meshes are always replicated; otherwise follow the parent mesh.
244 _distributed(_mesh.mesh_dimension() == 3 ? true : (!_on_displaced && !_mesh.is_replicated())),
245 _correct_edge_dropping(correct_edge_dropping),
246 _minimum_projection_angle(minimum_projection_angle),
247 _mortar_3d_subpatch_plane(mortar_3d_subpatch_plane),
248 _triangulation_mode(triangulation_mode),
249 _triangulate_triangles(triangulate_triangles),
250 _mortar_3d_qp_mapping(mortar_3d_qp_mapping)
251{
252 _primary_secondary_boundary_id_pairs.push_back(boundary_key);
253 _primary_requested_boundary_ids.insert(boundary_key.first);
254 _secondary_requested_boundary_ids.insert(boundary_key.second);
255 _primary_secondary_subdomain_id_pairs.push_back(subdomain_key);
256 _primary_boundary_subdomain_ids.insert(subdomain_key.first);
257 _secondary_boundary_subdomain_ids.insert(subdomain_key.second);
258
259 if (_distributed)
261 std::make_unique<DistributedMesh>(_mesh.comm(), _mesh.spatial_dimension());
262 else
264 std::make_unique<ReplicatedMesh>(_mesh.comm(), _mesh.spatial_dimension());
265}
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 1548 of file AutomaticMortarGeneration.C.

1549{
1550 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1551 coupling_info;
1552
1553 // Loop over the msm_elem_to_info object and build a bi-directional
1554 // multimap from secondary elements to the primary Elems which they are
1555 // coupled to and vice-versa. This is used in the
1556 // AugmentSparsityOnInterface functor to determine whether a given
1557 // secondary Elem is coupled across the mortar interface to a primary
1558 // element.
1559 for (const auto & pr : _msm_elem_to_info)
1560 {
1561 const Elem * secondary_elem = pr.second.secondary_elem;
1562 const Elem * primary_elem = pr.second.primary_elem;
1563
1564 // Every process owning a row on a secondary node or either volume parent needs the complete
1565 // secondary face-star coupling, including when that process also owns the lower-dimensional
1566 // face.
1567 std::set<processor_id_type> secondary_face_coupling_processors{
1568 secondary_elem->processor_id(),
1569 secondary_elem->interior_parent()->processor_id(),
1570 primary_elem->interior_parent()->processor_id()};
1571 for (const auto n : secondary_elem->node_index_range())
1572 secondary_face_coupling_processors.insert(secondary_elem->node_ptr(n)->processor_id());
1573
1574 // LowerSecondary
1575 for (const auto processor : secondary_face_coupling_processors)
1576 coupling_info[processor].emplace_back(secondary_elem->id(),
1577 secondary_elem->interior_parent()->id());
1578
1579 // LowerPrimary
1580 for (const auto processor : secondary_face_coupling_processors)
1581 coupling_info[processor].emplace_back(secondary_elem->id(),
1582 primary_elem->interior_parent()->id());
1583
1584 // Lower-LowerDimensionalPrimary
1585 for (const auto processor : secondary_face_coupling_processors)
1586 coupling_info[processor].emplace_back(secondary_elem->id(), primary_elem->id());
1587
1588 // SecondaryLower
1589 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1590 secondary_elem->interior_parent()->id(), secondary_elem->id());
1591
1592 // SecondaryPrimary
1593 coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1594 secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1595
1596 // PrimaryLower
1597 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1598 primary_elem->interior_parent()->id(), secondary_elem->id());
1599
1600 // PrimarySecondary
1601 coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1602 primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1603 }
1604
1605 // A face pair may produce several mortar segments. Remove repeated entries before communication.
1606 for (auto & [_, couplings] : coupling_info)
1607 {
1608 std::sort(couplings.begin(), couplings.end());
1609 couplings.erase(std::unique(couplings.begin(), couplings.end()), couplings.end());
1610 }
1611
1612 // Push the coupling information
1613 auto action_functor =
1614 [this](processor_id_type,
1615 const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1616 {
1617 for (auto [i, j] : coupling_info)
1618 _mortar_interface_coupling[i].insert(j);
1619 };
1620 TIMPI::push_parallel_vector_data(_mesh.comm(), coupling_info, action_functor);
1621
1623 return;
1624
1625 // The push above sends a secondary lower-dimensional element's couplings only to the processes
1626 // owning it, its nodes, or its segments' volume parents. Some consumers (e.g. penalty contact)
1627 // apply a secondary node's constraint on every process that contributes to it, not only on the
1628 // node's owner. The weighted gap at a node shared by a local element E and a nonlocal element F
1629 // carries the derivatives of the primary elements coupled to F, and the process owning E (or a
1630 // volume parent coupled to E) applies that gap to its own rows. When AugmentSparsityOnInterface
1631 // ghosts point neighbors, it reads the couplings of F to add those primary columns to the rows.
1632 // For a local element, it reads the couplings of the point neighbors of the element's own
1633 // secondary lower-dimensional elements and of the secondary elements coupled to it (e.g. those
1634 // paired with a local primary element), so pull the couplings of those point neighbors from
1635 // their owners, which hold the complete sets after the push
1636 const auto is_secondary = [this](const Elem * const elem)
1637 { return _secondary_boundary_subdomain_ids.count(elem->subdomain_id()); };
1638 const auto pid = _mesh.processor_id();
1639 std::unordered_set<const Elem *> secondary_elems_read;
1640 for (const auto secondary_subdomain_id : _secondary_boundary_subdomain_ids)
1641 for (const Elem * const secondary_elem :
1642 as_range(_mesh.active_subdomain_elements_begin(secondary_subdomain_id),
1643 _mesh.active_subdomain_elements_end(secondary_subdomain_id)))
1644 if (secondary_elem->processor_id() == pid ||
1645 secondary_elem->interior_parent()->processor_id() == pid)
1646 secondary_elems_read.insert(secondary_elem);
1647 for (const auto & [elem_id, coupled_elem_ids] : _mortar_interface_coupling)
1648 if (const Elem * const elem = _mesh.query_elem_ptr(elem_id);
1649 elem && elem->processor_id() == pid)
1650 for (const auto coupled_elem_id : coupled_elem_ids)
1651 if (const Elem * const coupled_elem = _mesh.query_elem_ptr(coupled_elem_id);
1652 coupled_elem && is_secondary(coupled_elem))
1653 secondary_elems_read.insert(coupled_elem);
1654
1655 std::unordered_set<const Elem *> secondary_elems_queried;
1656 std::unordered_map<processor_id_type, std::vector<dof_id_type>> secondary_elems_to_query;
1657 for (const Elem * const secondary_elem : secondary_elems_read)
1658 {
1659 std::set<const Elem *> point_neighbors;
1660 secondary_elem->find_point_neighbors(point_neighbors);
1661 point_neighbors.insert(secondary_elem);
1662 for (const Elem * const neigh : point_neighbors)
1663 if (neigh->active() && is_secondary(neigh) && neigh->processor_id() != pid &&
1664 secondary_elems_queried.insert(neigh).second)
1665 secondary_elems_to_query[neigh->processor_id()].push_back(neigh->id());
1666 }
1667
1668 auto gather_functor = [this](processor_id_type,
1669 const std::vector<dof_id_type> & secondary_elem_ids,
1670 std::vector<std::vector<dof_id_type>> & coupled_elem_ids)
1671 {
1672 coupled_elem_ids.resize(secondary_elem_ids.size());
1673 for (const auto i : index_range(secondary_elem_ids))
1674 if (const auto it = _mortar_interface_coupling.find(secondary_elem_ids[i]);
1675 it != _mortar_interface_coupling.end())
1676 coupled_elem_ids[i].assign(it->second.begin(), it->second.end());
1677 };
1678 auto act_on_data_functor = [this](processor_id_type,
1679 const std::vector<dof_id_type> & secondary_elem_ids,
1680 const std::vector<std::vector<dof_id_type>> & coupled_elem_ids)
1681 {
1682 for (const auto i : index_range(secondary_elem_ids))
1683 _mortar_interface_coupling[secondary_elem_ids[i]].insert(coupled_elem_ids[i].begin(),
1684 coupled_elem_ids[i].end());
1685 };
1686 std::vector<dof_id_type> * example = nullptr;
1688 _mesh.comm(), secondary_elems_to_query, gather_functor, act_on_data_functor, example);
1689}
for(PetscInt i=0;i< nvars;++i)
if(!dmm->_nl) SETERRQ(PETSC_COMM_WORLD
bool _nonlocal_secondary_couplings
Whether buildCouplingInformation() also gathers the couplings of nonlocal secondary lower-dimensional...
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.
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
void pull_parallel_vector_data(const Communicator &comm, const MapToVectors &queries, GatherFunctor &gather_data, const ActionFunctor &act_on_data, const datum *example)
uint8_t processor_id_type
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
auto index_range(const T &sizable)

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

487{
488 using std::abs;
489
490 dof_id_type local_id_index = 0;
491 std::size_t node_unique_id_offset = 0;
492
493 // Create an offset by the maximum number of mortar segment elements that can be created *plus*
494 // the number of lower-dimensional secondary subdomain elements. Recall that the number of mortar
495 // segments created is a function of node projection, *and* that if we split elems we will delete
496 // that elem which has already taken a unique id
497 for (const auto & pr : _primary_secondary_boundary_id_pairs)
498 {
499 const auto primary_bnd_id = pr.first;
500 const auto secondary_bnd_id = pr.second;
501 const auto num_primary_nodes =
502 std::distance(_mesh.bid_nodes_begin(primary_bnd_id), _mesh.bid_nodes_end(primary_bnd_id));
503 const auto num_secondary_nodes = std::distance(_mesh.bid_nodes_begin(secondary_bnd_id),
504 _mesh.bid_nodes_end(secondary_bnd_id));
505 mooseAssert(num_primary_nodes,
506 "There are no primary nodes on boundary ID "
507 << primary_bnd_id << ". Does that bondary ID even exist on the mesh?");
508 mooseAssert(num_secondary_nodes,
509 "There are no secondary nodes on boundary ID "
510 << secondary_bnd_id << ". Does that bondary ID even exist on the mesh?");
511
512 node_unique_id_offset += num_primary_nodes + 2 * num_secondary_nodes;
513 }
514
515 // 1.) Add all lower-dimensional secondary side elements as the "initial" mortar segments.
516 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
517 end_el = _mesh.active_elements_end();
518 el != end_el;
519 ++el)
520 {
521 const Elem * secondary_elem = *el;
522
523 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
524 if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
525 continue;
526
527 std::vector<Node *> new_nodes;
528 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
529 {
530 new_nodes.push_back(_mortar_segment_mesh->add_point(
531 secondary_elem->point(n), secondary_elem->node_id(n), secondary_elem->processor_id()));
532 Node * const new_node = new_nodes.back();
533 new_node->set_unique_id(new_node->id() + node_unique_id_offset);
534 }
535
536 std::unique_ptr<Elem> new_elem;
537 if (secondary_elem->default_order() == SECOND)
538 new_elem = std::make_unique<Edge3>();
539 else
540 new_elem = std::make_unique<Edge2>();
541
542 new_elem->processor_id() = secondary_elem->processor_id();
543 new_elem->subdomain_id() = secondary_elem->subdomain_id();
544 new_elem->set_id(local_id_index++);
545 new_elem->set_unique_id(new_elem->id());
546
547 for (MooseIndex(new_elem->n_nodes()) n = 0; n < new_elem->n_nodes(); ++n)
548 new_elem->set_node(n, new_nodes[n]);
549
550 Elem * new_elem_ptr = _mortar_segment_mesh->add_elem(new_elem.release());
551
552 // The xi^(1) values for this mortar segment are initially -1 and 1.
553 MortarSegmentInfo msinfo;
554 msinfo.xi1_a = -1;
555 msinfo.xi1_b = +1;
556 msinfo.secondary_elem = secondary_elem;
557
558 auto new_container_it0 = _secondary_node_and_elem_to_xi2_primary_elem.find(
559 std::make_pair(secondary_elem->node_ptr(0), secondary_elem)),
560 new_container_it1 = _secondary_node_and_elem_to_xi2_primary_elem.find(
561 std::make_pair(secondary_elem->node_ptr(1), secondary_elem));
562
563 bool new_container_node0_found =
564 (new_container_it0 != _secondary_node_and_elem_to_xi2_primary_elem.end()),
565 new_container_node1_found =
566 (new_container_it1 != _secondary_node_and_elem_to_xi2_primary_elem.end());
567
568 const Elem * node0_primary_candidate = nullptr;
569 const Elem * node1_primary_candidate = nullptr;
570
571 if (new_container_node0_found)
572 {
573 const auto & xi2_primary_elem_pair = new_container_it0->second;
574 msinfo.xi2_a = xi2_primary_elem_pair.first;
575 node0_primary_candidate = xi2_primary_elem_pair.second;
576 }
577
578 if (new_container_node1_found)
579 {
580 const auto & xi2_primary_elem_pair = new_container_it1->second;
581 msinfo.xi2_b = xi2_primary_elem_pair.first;
582 node1_primary_candidate = xi2_primary_elem_pair.second;
583 }
584
585 // If both node0 and node1 agree on the primary element they are
586 // projected into, then this mortar segment fits entirely within
587 // a single primary element, and we can go ahead and set the
588 // msinfo.primary_elem pointer now.
589 if (node0_primary_candidate == node1_primary_candidate)
590 msinfo.primary_elem = node0_primary_candidate;
591
592 // Associate this MSM elem with the MortarSegmentInfo.
593 _msm_elem_to_info.emplace(new_elem_ptr, msinfo);
594
595 // Maintain the mapping between secondary elems and mortar segment elems contained within them.
596 // Initially, only the original secondary_elem is present.
597 _secondary_elems_to_mortar_segments[secondary_elem->id()].insert(new_elem_ptr);
598 }
599
600 // 2.) Insert new nodes from primary side and split mortar segments as necessary.
602 {
603 auto key = pr.first;
604 auto val = pr.second;
605
606 const Node * primary_node = std::get<1>(key);
607 Real xi1 = val.first;
608 const Elem * secondary_elem = val.second;
609
610 // If this is an aligned node, we don't need to do anything.
611 if (abs(abs(xi1) - 1.) < _xi_tolerance)
612 continue;
613
614 auto && order = secondary_elem->default_order();
615
616 // Determine physical location of new point to be inserted.
617 Point new_pt(0);
618 for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
619 new_pt += Moose::fe_lagrange_1D_shape(order, n, xi1) * secondary_elem->point(n);
620
621 // Find the current mortar segment that will have to be split.
622 auto & mortar_segment_set = _secondary_elems_to_mortar_segments[secondary_elem->id()];
623 Elem * current_mortar_segment = nullptr;
624 MortarSegmentInfo * info = nullptr;
625
626 for (const auto & mortar_segment_candidate : mortar_segment_set)
627 {
628 try
629 {
630 info = &_msm_elem_to_info.at(mortar_segment_candidate);
631 }
632 catch (std::out_of_range &)
633 {
634 mooseError("MortarSegmentInfo not found for the mortar segment candidate");
635 }
636 if (info->xi1_a <= xi1 && xi1 <= info->xi1_b)
637 {
638 current_mortar_segment = mortar_segment_candidate;
639 break;
640 }
641 }
642
643 // Make sure we found one.
644 if (current_mortar_segment == nullptr)
645 mooseError("Unable to find appropriate mortar segment during linear search!");
646
647 // If node lands on endpoint of segment, don't split.
648 // Jacob: This condition was getting missed by the < comparison a few lines above. To fix it I
649 // just made it <= and put this condition in to handle equality different. It probably could be
650 // done with a tolerance but the the toleranced equality is already handled later when we drop
651 // segments with small volume.
652 if (info->xi1_a == xi1 || xi1 == info->xi1_b)
653 continue;
654
655 const auto new_id = _mortar_segment_mesh->max_node_id();
656 mooseAssert(_mortar_segment_mesh->comm().verify(new_id),
657 "new_id must be the same on all processes");
658 Node * const new_node =
659 _mortar_segment_mesh->add_point(new_pt, new_id, secondary_elem->processor_id());
660 new_node->set_unique_id(new_id + node_unique_id_offset);
661
662 // Reconstruct the nodal normal at xi1. This will help us
663 // determine the orientation of the primary elems relative to the
664 // new mortar segments.
665 const Point normal = getNormals(*secondary_elem, std::vector<Real>({xi1}))[0];
666
667 // Get the set of primary_node neighbors.
668 if (this->_nodes_to_primary_elem_map.find(primary_node->id()) ==
669 this->_nodes_to_primary_elem_map.end())
670 mooseError("We should already have built this primary node to elem pair!");
671 const std::vector<const Elem *> & primary_node_neighbors =
672 this->_nodes_to_primary_elem_map[primary_node->id()];
673
674 // Sanity check
675 if (primary_node_neighbors.size() == 0 || primary_node_neighbors.size() > 2)
676 mooseError("We must have either 1 or 2 primary side nodal neighbors, but we had ",
677 primary_node_neighbors.size());
678
679 // Primary Elem pointers which we will eventually assign to the
680 // mortar segments being created. We start by assuming
681 // primary_node_neighbor[0] is on the "left" and
682 // primary_node_neighbor[1]/"nothing" is on the "right" and then
683 // swap them if that's not the case.
684 const Elem * left_primary_elem = primary_node_neighbors[0];
685 const Elem * right_primary_elem =
686 (primary_node_neighbors.size() == 2) ? primary_node_neighbors[1] : nullptr;
687
689
690 // Storage for z-component of cross products for determining
691 // orientation.
692 std::array<Real, 2> secondary_node_cps;
693 std::vector<Real> primary_node_cps(primary_node_neighbors.size());
694
695 // Store z-component of left and right secondary node cross products with the nodal normal.
696 for (unsigned int nid = 0; nid < 2; ++nid)
697 secondary_node_cps[nid] = normal.cross(secondary_elem->point(nid) - new_pt)(2);
698
699 for (MooseIndex(primary_node_neighbors) mnn = 0; mnn < primary_node_neighbors.size(); ++mnn)
700 {
701 const Elem * primary_neigh = primary_node_neighbors[mnn];
702 Point opposite = (primary_neigh->node_ptr(0) == primary_node) ? primary_neigh->point(1)
703 : primary_neigh->point(0);
704 Point cp = normal.cross(opposite - new_pt);
705 primary_node_cps[mnn] = cp(2);
706 }
707
708 // We will verify that only 1 orientation is actually valid.
709 bool orientation1_valid = false, orientation2_valid = false;
710
711 if (primary_node_neighbors.size() == 2)
712 {
713 // 2 primary neighbor case
714 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.) &&
715 (secondary_node_cps[1] * primary_node_cps[1] > 0.);
716
717 orientation2_valid = (secondary_node_cps[0] * primary_node_cps[1] > 0.) &&
718 (secondary_node_cps[1] * primary_node_cps[0] > 0.);
719 }
720 else if (primary_node_neighbors.size() == 1)
721 {
722 // 1 primary neighbor case
723 orientation1_valid = (secondary_node_cps[0] * primary_node_cps[0] > 0.);
724 orientation2_valid = (secondary_node_cps[1] * primary_node_cps[0] > 0.);
725 }
726 else
727 mooseError("Invalid primary node neighbors size ", primary_node_neighbors.size());
728
729 // Verify that both orientations are not simultaneously valid/invalid. If they are not, then we
730 // are going to throw an exception instead of erroring out since we can easily reach this point
731 // if we have one bad linear solve. It's better in general to catch the error and then try a
732 // smaller time-step
733 if (orientation1_valid && orientation2_valid)
734 throw MooseException(
735 "AutomaticMortarGeneration: Both orientations cannot simultaneously be valid.");
736
737 // We are going to treat the case where both orientations are invalid as a case in which we
738 // should not be splitting the mortar mesh to incorporate primary mesh elements.
739 // In practice, this case has appeared for very oblique projections, so we assume these cases
740 // will not be considered in mortar thermomechanical contact.
741 if (!orientation1_valid && !orientation2_valid)
742 {
743 static const auto invalid_orientation_id =
745 "AutomaticMortarGeneration",
746 "Unable to determine valid secondary-primary orientation. Consequently we will "
747 "consider projection of the primary node invalid and not split the mortar segment. "
748 "This situation can indicate there are very oblique projections between primary "
749 "(mortar) and secondary (non-mortar) surfaces for a good problem set up. It can also "
750 "mean your time step is too large.",
751 true);
752 _app.solutionInvalidity().flagInvalidSolutionInternal(invalid_orientation_id);
753 continue;
754 }
755
756 // Make an Elem on the left
757 std::unique_ptr<Elem> new_elem_left;
758 if (order == SECOND)
759 new_elem_left = std::make_unique<Edge3>();
760 else
761 new_elem_left = std::make_unique<Edge2>();
762
763 new_elem_left->processor_id() = current_mortar_segment->processor_id();
764 new_elem_left->subdomain_id() = current_mortar_segment->subdomain_id();
765 new_elem_left->set_id(local_id_index++);
766 new_elem_left->set_unique_id(new_elem_left->id());
767 new_elem_left->set_node(0, current_mortar_segment->node_ptr(0));
768 new_elem_left->set_node(1, new_node);
769
770 // Make an Elem on the right
771 std::unique_ptr<Elem> new_elem_right;
772 if (order == SECOND)
773 new_elem_right = std::make_unique<Edge3>();
774 else
775 new_elem_right = std::make_unique<Edge2>();
776
777 new_elem_right->processor_id() = current_mortar_segment->processor_id();
778 new_elem_right->subdomain_id() = current_mortar_segment->subdomain_id();
779 new_elem_right->set_id(local_id_index++);
780 new_elem_right->set_unique_id(new_elem_right->id());
781 new_elem_right->set_node(0, new_node);
782 new_elem_right->set_node(1, current_mortar_segment->node_ptr(1));
783
784 if (order == SECOND)
785 {
786 // left
787 Point left_interior_point(0);
788 Real left_interior_xi = (xi1 + info->xi1_a) / 2;
789
790 // This is eta for the current mortar segment that we're splitting
791 Real current_left_interior_eta =
792 (2. * left_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
793
794 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
795 n < current_mortar_segment->n_nodes();
796 ++n)
797 left_interior_point += Moose::fe_lagrange_1D_shape(order, n, current_left_interior_eta) *
798 current_mortar_segment->point(n);
799
800 const auto new_interior_left_id = _mortar_segment_mesh->max_node_id();
801 mooseAssert(_mortar_segment_mesh->comm().verify(new_interior_left_id),
802 "new_id must be the same on all processes");
803 Node * const new_interior_node_left = _mortar_segment_mesh->add_point(
804 left_interior_point, new_interior_left_id, new_elem_left->processor_id());
805 new_elem_left->set_node(2, new_interior_node_left);
806 new_interior_node_left->set_unique_id(new_interior_left_id + node_unique_id_offset);
807
808 // right
809 Point right_interior_point(0);
810 Real right_interior_xi = (xi1 + info->xi1_b) / 2;
811 // This is eta for the current mortar segment that we're splitting
812 Real current_right_interior_eta =
813 (2. * right_interior_xi - info->xi1_a - info->xi1_b) / (info->xi1_b - info->xi1_a);
814
815 for (MooseIndex(current_mortar_segment->n_nodes()) n = 0;
816 n < current_mortar_segment->n_nodes();
817 ++n)
818 right_interior_point += Moose::fe_lagrange_1D_shape(order, n, current_right_interior_eta) *
819 current_mortar_segment->point(n);
820
821 const auto new_interior_id_right = _mortar_segment_mesh->max_node_id();
822 mooseAssert(_mortar_segment_mesh->comm().verify(new_interior_id_right),
823 "new_id must be the same on all processes");
824 Node * const new_interior_node_right = _mortar_segment_mesh->add_point(
825 right_interior_point, new_interior_id_right, new_elem_right->processor_id());
826 new_elem_right->set_node(2, new_interior_node_right);
827 new_interior_node_right->set_unique_id(new_interior_id_right + node_unique_id_offset);
828 }
829
830 // If orientation 2 was valid, swap the left and right primaries.
831 if (orientation2_valid)
832 std::swap(left_primary_elem, right_primary_elem);
833
834 // Now that we know left_primary_elem and right_primary_elem, we can determine left_xi2 and
835 // right_xi2.
836 if (left_primary_elem)
837 left_xi2 = (primary_node == left_primary_elem->node_ptr(0)) ? -1 : +1;
838 if (right_primary_elem)
839 right_xi2 = (primary_node == right_primary_elem->node_ptr(0)) ? -1 : +1;
840
841 // Grab the MortarSegmentInfo object associated with this
842 // segment. We can use "at()" here since we want this to fail if
843 // current_mortar_segment is not found... Since we're going to
844 // erase this entry from the map momentarily, we make an actual
845 // copy rather than grabbing a reference.
846 auto msm_it = _msm_elem_to_info.find(current_mortar_segment);
847 if (msm_it == _msm_elem_to_info.end())
848 mooseError("MortarSegmentInfo not found for current_mortar_segment.");
849 MortarSegmentInfo current_msinfo = msm_it->second;
850
851 // add_left
852 {
853 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem_left.release());
854
855 // Create new MortarSegmentInfo objects for new_elem_left
856 MortarSegmentInfo new_msinfo_left;
857
858 // The new MortarSegmentInfo info objects inherit their "outer"
859 // information from current_msinfo and the rest is determined by
860 // the Node being inserted.
861 new_msinfo_left.xi1_a = current_msinfo.xi1_a;
862 new_msinfo_left.xi2_a = current_msinfo.xi2_a;
863 new_msinfo_left.secondary_elem = secondary_elem;
864 new_msinfo_left.xi1_b = xi1;
865 new_msinfo_left.xi2_b = left_xi2;
866 new_msinfo_left.primary_elem = left_primary_elem;
867
868 // Add new msinfo objects to the map.
869 _msm_elem_to_info.emplace(msm_new_elem, new_msinfo_left);
870
871 // We need to insert new_elem_left in
872 // the mortar_segment_set for this secondary_elem.
873 mortar_segment_set.insert(msm_new_elem);
874 }
875
876 // add_right
877 {
878 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem_right.release());
879
880 // Create new MortarSegmentInfo objects for new_elem_right
881 MortarSegmentInfo new_msinfo_right;
882
883 new_msinfo_right.xi1_b = current_msinfo.xi1_b;
884 new_msinfo_right.xi2_b = current_msinfo.xi2_b;
885 new_msinfo_right.secondary_elem = secondary_elem;
886 new_msinfo_right.xi1_a = xi1;
887 new_msinfo_right.xi2_a = right_xi2;
888 new_msinfo_right.primary_elem = right_primary_elem;
889
890 _msm_elem_to_info.emplace(msm_new_elem, new_msinfo_right);
891
892 mortar_segment_set.insert(msm_new_elem);
893 }
894
895 // Erase the MortarSegmentInfo object for current_mortar_segment from the map.
896 _msm_elem_to_info.erase(msm_it);
897
898 // current_mortar_segment must be erased from the
899 // mortar_segment_set since it has now been split.
900 mortar_segment_set.erase(current_mortar_segment);
901
902 // The original mortar segment has been split, so erase it from
903 // the mortar segment mesh.
904 _mortar_segment_mesh->delete_elem(current_mortar_segment);
905 }
906
907 // Remove all MSM elements without a primary contribution
913 for (auto msm_elem : _mortar_segment_mesh->active_element_ptr_range())
914 {
915 MortarSegmentInfo & msinfo = libmesh_map_find(_msm_elem_to_info, msm_elem);
916 Elem * primary_elem = const_cast<Elem *>(msinfo.primary_elem);
917 if (primary_elem == nullptr || abs(msinfo.xi2_a) > 1.0 + TOLERANCE ||
918 abs(msinfo.xi2_b) > 1.0 + TOLERANCE)
919 {
920 // Erase from secondary to msms map
921 auto it = _secondary_elems_to_mortar_segments.find(msinfo.secondary_elem->id());
922 mooseAssert(it != _secondary_elems_to_mortar_segments.end(),
923 "We should have found the element");
924 auto & msm_set = it->second;
925 msm_set.erase(msm_elem);
926 // We may be creating nodes with only one element neighbor where before this removal there
927 // were two. But the nodal normal used in computations will reflect the two-neighbor geometry.
928 // For a lower-d secondary mesh corner, that will imply the corner node will have a tilted
929 // normal vector (same for tangents) despite the mortar segment mesh not including its
930 // vertical neighboring element. It is the secondary element neighbors (not mortar segment
931 // mesh neighbors) that determine the nodal normal field.
932 if (msm_set.empty())
934
935 // Erase msinfo
936 _msm_elem_to_info.erase(msm_elem);
937
938 // Remove element from mortar segment mesh
939 _mortar_segment_mesh->delete_elem(msm_elem);
940 }
941 else
942 {
943 _secondary_ip_sub_ids.insert(msinfo.secondary_elem->interior_parent()->subdomain_id());
944 _primary_ip_sub_ids.insert(msinfo.primary_elem->interior_parent()->subdomain_id());
945 }
946 }
947
948 std::unordered_set<Node *> msm_connected_nodes;
949
950 // Deleting elements may produce isolated nodes.
951 // Loops for identifying and removing such nodes from mortar segment mesh.
952 for (const auto & element : _mortar_segment_mesh->element_ptr_range())
953 for (auto & n : element->node_ref_range())
954 msm_connected_nodes.insert(&n);
955
956 for (const auto & node : _mortar_segment_mesh->node_ptr_range())
957 if (!msm_connected_nodes.count(node))
958 _mortar_segment_mesh->delete_node(node);
959
960#ifdef DEBUG
961 // Verify that all segments without primary contribution have been deleted
962 for (auto msm_elem : _mortar_segment_mesh->active_element_ptr_range())
963 {
964 const MortarSegmentInfo & msinfo = libmesh_map_find(_msm_elem_to_info, msm_elem);
965 mooseAssert(msinfo.primary_elem != nullptr,
966 "All mortar segment elements should have valid "
967 "primary element.");
968 }
969#endif
970
971 _mortar_segment_mesh->cache_elem_data();
972
973 // (Optionally) Write the mortar segment mesh to file for inspection
974 if (_debug)
976
978}
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
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
Real _xi_tolerance
Tolerance for checking projection xi values.
void outputMortarMesh()
Write the mortar segment mesh to exodus.
void buildCouplingInformation()
build the _mortar_interface_coupling data
std::vector< Point > getNormals(const Elem &secondary_elem, const std::vector< Point > &xi1_pts) const
Compute the normals at given reference points on a secondary element.
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
std::unordered_map< std::pair< const Node *, const Elem * >, std::pair< Real, const Elem * > > _secondary_node_and_elem_to_xi2_primary_elem
Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2),...
std::map< std::tuple< dof_id_type, const Node *, const Elem * >, std::pair< Real, const Elem * > > _primary_node_and_elem_to_xi1_secondary_elem
Same type of container, but for mapping (Primary Node ID, Primary Node, Primary Elem) -> (xi^(1),...
std::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > _secondary_elems_to_mortar_segments
We maintain a mapping from lower-dimensional secondary elements in the original mesh to (sets of) ele...
SolutionInvalidity & solutionInvalidity()
Get the SolutionInvalidity for this app.
Definition MooseApp.h:182
Provides a way for users to bail out of the current solve.
void flagInvalidSolutionInternal(const InvalidSolutionID _invalid_solution_id)
Increments solution invalid occurrences for each solution id.
InvalidSolutionID registerInvalidity(const std::string &object_type, const std::string &message, const bool warning)
Call to register an invalid calculation.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition EigenADReal.h:50
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
MPI_Info info
uint8_t dof_id_type
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
SolutionInvalidityRegistry & getSolutionInvalidityRegistry()
Get the global SolutionInvalidityRegistry singleton.
Holds xi^(1), xi^(2), and other data for a given mortar segment.
const Elem * primary_elem
static const Real invalid_xi
const Elem * secondary_elem

Referenced by MortarInterfaceWarehouse::update().

◆ buildMortarSegmentMesh3d()

void AutomaticMortarGeneration::buildMortarSegmentMesh3d ( )

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

Inputs:

  • mesh

Outputs:

  • mortar_segment_mesh
  • msm_elem_to_info

Step 1: Build mortar segments for all secondary elements

Step 1.1: Linearize secondary face elements

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

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

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

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

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

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

Definition at line 996 of file AutomaticMortarGeneration.C.

997{
998 const bool use_reference_interpolation =
999 _mortar_3d_qp_mapping == Mortar3DQuadraturePointMapping::REFERENCE_INTERPOLATION;
1000
1001 // Add an integer flag to mortar segment mesh to keep track of which subelem
1002 // of second order primal elements mortar segments correspond to
1003 auto secondary_sub_elem = _mortar_segment_mesh->add_elem_integer("secondary_sub_elem");
1004 auto primary_sub_elem = _mortar_segment_mesh->add_elem_integer("primary_sub_elem");
1005
1006 // Assign globally unique node/element IDs via an exclusive prefix scan: each rank's bound is
1007 // local_secondary_sub_elems * visible_primary_sub_elems * 9, where 9 is the maximum nodes a
1008 // single secondary/primary sub-element pair can produce (8-vertex clipped polygon + center).
1009 // The result is cached and invalidated by meshChanged(), so the allgather only runs on topology
1010 // changes, not on every displaced-mesh residual update.
1011 if (!_msm_node_id_start.has_value())
1012 {
1013 dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
1014 for (const auto & [primary_sub_id, secondary_sub_id] : _primary_secondary_subdomain_id_pairs)
1015 {
1016 for (const auto * const el :
1017 _mesh.active_local_subdomain_elements_ptr_range(secondary_sub_id))
1018 local_secondary_sub_elems += el->n_sub_elem();
1019 for (const auto * const el : _mesh.active_subdomain_elements_ptr_range(primary_sub_id))
1020 visible_primary_sub_elems += el->n_sub_elem();
1021 }
1022 const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
1023 std::vector<dof_id_type> per_rank_bounds;
1024 _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
1025 dof_id_type start = 0;
1026 for (const auto r : make_range(_mesh.processor_id()))
1027 start += per_rank_bounds[r];
1028 _msm_node_id_start = start;
1029 }
1030 dof_id_type next_node_id = *_msm_node_id_start;
1031 // Element IDs use the same starting offset: node and element IDs are separately numbered, and
1032 // element count per clip (n triangles) is always <= node count (n+1), so per_rank_bound covers
1033 // both.
1034 dof_id_type next_elem_id = next_node_id;
1035
1036 // Loop through mortar secondary and primary pairs to create mortar segment mesh between each
1037 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1038 {
1039 const auto primary_subd_id = pr.first;
1040 const auto secondary_subd_id = pr.second;
1041
1042 // Build k-d tree for use in Step 1.2 for primary interface coarse screening
1043 NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, primary_subd_id);
1044 subdomain_kd_tree_t kd_tree(
1045 3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
1046
1047 // Construct the KD tree.
1048 kd_tree.buildIndex();
1049
1050 // Return the unoriented geometric normal of a linearized subpatch. These expressions are the
1051 // TRI3 and QUAD4 mapping tangents evaluated at the reference center, equivalent to evaluating
1052 // the first-order finite-element normal there without constructing a temporary element.
1053 auto get_sub_elem_geometric_normal = [](const std::vector<Point> & nodes)
1054 {
1055 Point dxdxi;
1056 Point dxdeta;
1057 if (nodes.size() == 3)
1058 {
1059 dxdxi = nodes[1] - nodes[0];
1060 dxdeta = nodes[2] - nodes[0];
1061 }
1062 else if (nodes.size() == 4)
1063 {
1064 // Bilinear center tangents define one normal for the full quad instead of selecting one of
1065 // the two diagonal triangle normals.
1066 dxdxi = 0.25 * (nodes[1] + nodes[2] - nodes[0] - nodes[3]);
1067 dxdeta = 0.25 * (nodes[2] + nodes[3] - nodes[0] - nodes[1]);
1068 }
1069 else
1070 mooseError("GEOMETRIC_NORMAL 3D mortar subpatch plane construction only supports "
1071 "triangular and quadrilateral subpatches, but received ",
1072 nodes.size(),
1073 " nodes.");
1074
1075 Point geometric_normal = dxdxi.cross(dxdeta);
1076 const auto normal_norm = geometric_normal.norm();
1077 // The cross product has units of area, so compare it with the product of tangent lengths.
1078 // Their ratio is the sine of the included angle and is independent of the mesh length scale.
1079 if (normal_norm <= TOLERANCE * dxdxi.norm() * dxdeta.norm())
1080 mooseError("GEOMETRIC_NORMAL 3D mortar subpatch plane construction encountered a "
1081 "degenerate subpatch.");
1082
1083 geometric_normal /= normal_norm;
1084 return geometric_normal;
1085 };
1086
1090 for (MeshBase::const_element_iterator el = _mesh.active_local_elements_begin(),
1091 end_el = _mesh.active_local_elements_end();
1092 el != end_el;
1093 ++el)
1094 {
1095 const Elem * secondary_side_elem = *el;
1096
1097 const Real secondary_volume = secondary_side_elem->volume();
1098
1099 // If this Elem is not in the current secondary subdomain, go on to the next one.
1100 if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1101 continue;
1102
1103 auto [secondary_elem_to_msm_map_it, insertion_happened] =
1104 _secondary_elems_to_mortar_segments.emplace(secondary_side_elem->id(),
1105 std::set<Elem *, CompareDofObjectsByID>{});
1106 libmesh_ignore(insertion_happened);
1107 auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1108
1109 std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1110 secondary_side_elem->n_sub_elem());
1111 const auto nodal_normals = getNodalNormals(*secondary_side_elem);
1112
1123 for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1124 {
1125 // Get indices of sub-element nodes in element
1126 const auto sub_elem_nodes =
1127 Moose::Mortar::getMortarSubElementNodeIndices(*secondary_side_elem, sel);
1128
1129 // Secondary sub-element center, normal, and nodes
1130 Point center;
1131 Point normal;
1132 std::vector<Point> nodes(sub_elem_nodes.size());
1133
1134 // Collect the sub-element points and evaluate its center and averaged nodal normal.
1135 for (auto iv : make_range(sub_elem_nodes.size()))
1136 {
1137 const auto n = sub_elem_nodes[iv];
1138 nodes[iv] = secondary_side_elem->point(n);
1139 center += secondary_side_elem->point(n);
1140 normal += nodal_normals[n];
1141 }
1142 center /= sub_elem_nodes.size();
1143 normal = normal.unit();
1144
1145 if (_mortar_3d_subpatch_plane == Mortar3DSubpatchPlane::GEOMETRIC_NORMAL)
1146 {
1147 const Point averaged_normal = normal;
1148 normal = get_sub_elem_geometric_normal(nodes);
1149 if (normal * averaged_normal < 0)
1150 normal *= -1;
1151 }
1152
1153 if (use_reference_interpolation)
1154 {
1155 std::vector<Point> sub_elem_reference_points;
1156 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1157 for (const auto node_index : sub_elem_nodes)
1158 sub_elem_reference_points.push_back(secondary_side_elem->master_point(node_index));
1159
1160 mortar_segment_helper[sel] =
1161 std::make_unique<MortarSegmentHelper>(std::move(nodes),
1162 std::move(sub_elem_reference_points),
1163 center,
1164 normal,
1167 }
1168 else
1169 mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(
1170 std::move(nodes), center, normal, _triangulation_mode, _triangulate_triangles);
1171 }
1172
1178 // Search point for performing Nanoflann (k-d tree) searches.
1179 // In each case we use the center point of the original element (not sub-elements for second
1180 // order elements). This is to do search for all sub-elements simultaneously
1181 std::array<Real, 3> query_pt;
1182 Point center_point;
1183 switch (secondary_side_elem->type())
1184 {
1185 case TRI3:
1186 case QUAD4:
1187 center_point = mortar_segment_helper[0]->center();
1188 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1189 break;
1190 case TRI6:
1191 case TRI7:
1192 center_point = mortar_segment_helper[1]->center();
1193 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1194 break;
1195 case QUAD8:
1196 center_point = mortar_segment_helper[4]->center();
1197 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1198 break;
1199 case QUAD9:
1200 center_point = secondary_side_elem->point(8);
1201 query_pt = {{center_point(0), center_point(1), center_point(2)}};
1202 break;
1203 default:
1204 mooseError(
1205 "Face element type: ", secondary_side_elem->type(), "not supported for 3D mortar");
1206 }
1207
1208 // The number of results we want to get. These results will only be used to find
1209 // a single element with non-trivial overlap, after an element is identified a breadth
1210 // first search is done on neighbors
1211 const std::size_t num_results = 3;
1212
1213 // Initialize result_set and do the search.
1214 std::vector<size_t> ret_index(num_results);
1215 std::vector<Real> out_dist_sqr(num_results);
1216 nanoflann::KNNResultSet<Real> result_set(num_results);
1217 result_set.init(&ret_index[0], &out_dist_sqr[0]);
1218 kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
1219
1220 // Initialize list of processed primary elements, we don't want to revisit processed elements
1221 std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1222
1223 // Initialize candidate set and flag for switching between coarse screening and breadth-first
1224 // search
1225 bool primary_elem_found = false;
1226 std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1227 const bool use_geometric_subpatch_normals =
1228 _mortar_3d_subpatch_plane == Mortar3DSubpatchPlane::GEOMETRIC_NORMAL;
1229 // In geometric mode the projection-angle cutoff also rejects near-orthogonal subpatch pairs.
1230 // The absolute dot product below keeps opposing primary/secondary orientations admissible.
1231 const Real minimum_subpatch_normal_alignment =
1232 use_geometric_subpatch_normals ? std::sin(_minimum_projection_angle * libMesh::pi / 180.0)
1233 : 0.0;
1234
1235 // Loop candidate nodes (returned by Nanoflann) and add all adjoining elems to candidate set
1236 for (auto r : make_range(result_set.size()))
1237 {
1238 // Verify that the squared distance we compute is the same as nanoflann's
1239 mooseAssert(abs((_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1240 TOLERANCE,
1241 "Lower-dimensional element squared distance verification failed.");
1242
1243 // Get list of elems connected to node
1244 std::vector<const Elem *> & node_elems =
1245 this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
1246
1247 // Uniquely add elems to candidate set
1248 for (auto elem : node_elems)
1249 primary_elem_candidates.insert(elem);
1250 }
1251
1259 while (!primary_elem_candidates.empty())
1260 {
1261 const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1262
1263 // If we've already processed this candidate, we don't need to check it again.
1264 if (processed_primary_elems.count(primary_elem_candidate))
1265 {
1266 primary_elem_candidates.erase(primary_elem_candidate);
1267 continue;
1268 }
1269
1270 // Initialize set of nodes used to construct mortar segment elements
1271 std::vector<Point> nodal_points;
1272
1273 // Initialize map from mortar segment elements to nodes
1274 std::vector<std::vector<unsigned int>> elem_to_node_map;
1275
1276 // Initialize list of secondary and primary sub-elements that formed each mortar segment
1277 std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1278 std::vector<std::array<Point, 3>> elem_to_secondary_reference_points;
1279 std::vector<std::array<Point, 3>> elem_to_primary_reference_points;
1280
1285 for (auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1286 {
1287 // Get nodes of primary sub-elements
1288 const auto sub_elem_nodes =
1289 Moose::Mortar::getMortarSubElementNodeIndices(*primary_elem_candidate, p_el);
1290
1291 // Get list of primary sub-element vertex nodes
1292 std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1293 for (auto iv : make_range(sub_elem_nodes.size()))
1294 {
1295 const auto n = sub_elem_nodes[iv];
1296 primary_sub_elem[iv] = primary_elem_candidate->point(n);
1297 }
1298 Point primary_sub_elem_normal;
1299 if (use_geometric_subpatch_normals)
1300 primary_sub_elem_normal = get_sub_elem_geometric_normal(primary_sub_elem);
1301
1302 std::vector<Point> sub_elem_reference_points;
1303 if (use_reference_interpolation)
1304 {
1305 sub_elem_reference_points.reserve(sub_elem_nodes.size());
1306 for (const auto node_index : sub_elem_nodes)
1307 sub_elem_reference_points.push_back(primary_elem_candidate->master_point(node_index));
1308 }
1309
1310 // Loop through secondary sub-elements
1311 for (auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1312 {
1313 // Nearby primary candidates can include adjacent corner faces. Those faces may clip to
1314 // numerical slivers, which we do not consider valid face-to-face mortar pairs for this
1315 // search.
1316 if (use_geometric_subpatch_normals &&
1317 std::abs(primary_sub_elem_normal * mortar_segment_helper[s_el]->normal()) <
1318 minimum_subpatch_normal_alignment)
1319 continue;
1320
1321 // Mortar segment helpers were defined for each secondary sub-element, they will:
1322 // 1. Project primary sub-element onto linearized secondary sub-element
1323 // 2. Clip projected primary sub-element against secondary sub-element
1324 // 3. Triangulate clipped polygon to form mortar segments
1325 //
1326 // Mortar segment helpers append a list of mortar segment nodes and connectivities that
1327 // can be directly used to build mortar segments
1328 const auto segments_before_helper = elem_to_node_map.size();
1329 if (use_reference_interpolation)
1330 mortar_segment_helper[s_el]->getMortarSegments(primary_sub_elem,
1331 sub_elem_reference_points,
1332 nodal_points,
1333 elem_to_node_map,
1334 elem_to_secondary_reference_points,
1335 elem_to_primary_reference_points,
1336 TOLERANCE * secondary_volume);
1337 else
1338 mortar_segment_helper[s_el]->getMortarSegments(
1339 primary_sub_elem, nodal_points, elem_to_node_map);
1340
1341 // Keep track of which secondary and primary sub-elements created segment
1342 for (auto i = segments_before_helper; i < elem_to_node_map.size(); ++i)
1343 sub_elem_map.push_back(std::make_pair(s_el, p_el));
1344 }
1345 }
1346
1347 // Mark primary element as processed and remove from candidate list
1348 processed_primary_elems.insert(primary_elem_candidate);
1349 primary_elem_candidates.erase(primary_elem_candidate);
1350
1351 // If overlap of polygons was non-trivial (created mortar segment elements)
1352 if (!elem_to_node_map.empty())
1353 {
1354 if (sub_elem_map.size() != elem_to_node_map.size())
1355 mooseError("The mortar segment subpatch map is not aligned with the mortar segment "
1356 "connectivity map.");
1357 if (use_reference_interpolation &&
1358 (elem_to_secondary_reference_points.size() != elem_to_node_map.size() ||
1359 elem_to_primary_reference_points.size() != elem_to_node_map.size()))
1360 mooseError("The mortar segment reference-point maps are not aligned with the mortar "
1361 "segment connectivity map.");
1362
1363 // Only overlap polygons large enough to become mortar segments may switch the candidate
1364 // search to breadth first.
1365 bool seed_breadth_first_search = false;
1366 std::vector<bool> retained_mortar_segments(elem_to_node_map.size(), false);
1367 for (const auto el : index_range(elem_to_node_map))
1368 {
1369 const auto & node_map = elem_to_node_map[el];
1370 if (node_map.size() != 3)
1371 mooseError(
1372 "Active mortar segments only supports TRI elements, 3 nodes expected but: ",
1373 node_map.size(),
1374 " provided.");
1375
1376 const Point e1 = nodal_points[node_map[1]] - nodal_points[node_map[0]];
1377 const Point e2 = nodal_points[node_map[2]] - nodal_points[node_map[0]];
1378 retained_mortar_segments[el] =
1379 0.5 * e1.cross(e2).norm() / secondary_volume >= TOLERANCE;
1380 seed_breadth_first_search = seed_breadth_first_search || retained_mortar_segments[el];
1381 }
1382
1383 if (seed_breadth_first_search)
1384 {
1385 // If this is the first element with a qualifying overlap, set flag. Candidates will
1386 // now be neighbors of elements that had qualifying overlap.
1387 if (!primary_elem_found)
1388 {
1389 primary_elem_found = true;
1390 primary_elem_candidates.clear();
1391 }
1392
1393 // Add neighbors to candidate list
1394 for (auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1395 {
1396 // If not valid or not on lower dimensional secondary subdomain, skip
1397 if (neighbor == nullptr || neighbor->subdomain_id() != primary_subd_id)
1398 continue;
1399 // If already processed, skip
1400 if (processed_primary_elems.count(neighbor))
1401 continue;
1402 // Otherwise, add to candidates
1403 primary_elem_candidates.insert(neighbor);
1404 }
1405 }
1406
1410 std::vector<Node *> new_nodes;
1411 // Clipping can append points for triangles later rejected by the area tolerance. Add only
1412 // points referenced by retained triangles so the mortar mesh has no orphan nodes.
1413 std::vector<bool> retained_nodes(nodal_points.size(), false);
1414 for (const auto el : index_range(elem_to_node_map))
1415 if (retained_mortar_segments[el])
1416 for (const auto node : elem_to_node_map[el])
1417 retained_nodes[node] = true;
1418
1419 new_nodes.resize(nodal_points.size(), nullptr);
1420 for (const auto node : index_range(nodal_points))
1421 if (retained_nodes[node])
1422 new_nodes[node] = _mortar_segment_mesh->add_point(
1423 nodal_points[node], next_node_id++, secondary_side_elem->processor_id());
1424
1425 // Loop through triangular elements in map
1426 for (auto el : index_range(elem_to_node_map))
1427 {
1428 if (!retained_mortar_segments[el])
1429 continue;
1430
1431 std::unique_ptr<Elem> new_elem;
1432 if (elem_to_node_map[el].size() == 3)
1433 new_elem = std::make_unique<Tri3>();
1434 else
1435 mooseError("Active mortar segments only supports TRI elements, 3 nodes expected "
1436 "but: ",
1437 elem_to_node_map[el].size(),
1438 " provided.");
1439
1440 new_elem->processor_id() = secondary_side_elem->processor_id();
1441 new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1442 new_elem->set_id(next_elem_id++);
1443
1444 // Attach newly created nodes
1445 for (auto i : index_range(elem_to_node_map[el]))
1446 new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1447
1448 // If element is smaller than tolerance, don't add to msm
1449 if (new_elem->volume() / secondary_volume < TOLERANCE)
1450 continue;
1451
1452 // Add elements to mortar segment mesh
1453 Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem.release());
1454
1455 msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1456 msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1457
1458 // Fill out mortar segment info
1459 MortarSegmentInfo msinfo;
1460 msinfo.secondary_elem = secondary_side_elem;
1461 msinfo.primary_elem = primary_elem_candidate;
1462
1463 // Associate this MSM elem with the MortarSegmentInfo.
1464 _msm_elem_to_info.emplace(msm_new_elem, msinfo);
1465
1466 // Store reference data only for retained segments.
1467 if (use_reference_interpolation)
1468 {
1469 MortarSegmentReferencePoints reference_points{elem_to_secondary_reference_points[el],
1470 elem_to_primary_reference_points[el]};
1471 _msm_elem_to_reference_points.emplace(msm_new_elem, reference_points);
1472 }
1473
1474 // Add this mortar segment to the secondary elem to mortar segment map
1475 secondary_to_msm_element_set.insert(msm_new_elem);
1476
1477 _secondary_ip_sub_ids.insert(msinfo.secondary_elem->interior_parent()->subdomain_id());
1478 // Unlike for 2D, we always have a primary when building the mortar mesh so we don't
1479 // have to check for null
1480 _primary_ip_sub_ids.insert(msinfo.primary_elem->interior_parent()->subdomain_id());
1481 }
1482 }
1483 // End loop through primary element candidates
1484 }
1485
1486 if (use_geometric_subpatch_normals)
1487 {
1488 // A geometric corner filter may intentionally leave individual subpatches uncovered. Warn
1489 // only when the complete secondary element failed to produce a retained segment.
1490 if (secondary_to_msm_element_set.empty())
1491 mooseDoOnce(
1492 mooseWarning("Some secondary elements on mortar interface were unable to identify"
1493 " a corresponding primary element; this may be expected depending on"
1494 " problem geometry but may indicate a failure of the element search"
1495 " or projection"));
1496 }
1497 else
1498 for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1499 if (mortar_segment_helper[sel]->remainder() == 1.0)
1500 mooseDoOnce(
1501 mooseWarning("Some secondary elements on mortar interface were unable to identify"
1502 " a corresponding primary element; this may be expected depending on"
1503 " problem geometry but may indicate a failure of the element search"
1504 " or projection"));
1505
1506 if (secondary_to_msm_element_set.empty())
1507 _secondary_elems_to_mortar_segments.erase(secondary_elem_to_msm_map_it);
1508 } // End loop through secondary elements
1509 } // End loop through mortar constraint pairs
1510
1511 mooseAssert(!use_reference_interpolation ||
1513 "Mortar segment info and reference-point maps must remain aligned.");
1514
1515 _mortar_segment_mesh->cache_elem_data();
1516
1517 // The mesh was built distributedly (each rank owns only its local elements), so mark it
1518 // as such so MeshSerializer correctly gathers it to proc 0 for Exodus output.
1519 _mortar_segment_mesh->set_distributed();
1520
1521 // Output mortar segment mesh
1522 if (_debug)
1523 {
1524 // If element is not triangular, increment subdomain id
1525 // (ExodusII does not support mixed element types in a single subdomain)
1526 for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1527 if (msm_el->type() != TRI3)
1528 msm_el->subdomain_id()++;
1529
1531
1532 // Undo increment
1533 for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1534 if (msm_el->type() != TRI3)
1535 msm_el->subdomain_id()--;
1536 }
1537
1539
1540 // Print mortar segment mesh statistics
1541 if (_debug)
1542 {
1543 msmStatistics();
1544 }
1545}
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
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 337 of file AutomaticMortarGeneration.C.

338{
341 "Must specify secondary and primary boundary ids before building node-to-elem maps.");
342
343 // Construct nodes_to_secondary_elem_map
344 for (const auto & secondary_elem :
345 as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
346 {
347 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
348 if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
349 continue;
350
351 for (const auto & nd : secondary_elem->node_ref_range())
352 {
353 std::vector<const Elem *> & vec = _nodes_to_secondary_elem_map[nd.id()];
354 vec.push_back(secondary_elem);
355 }
356 }
357
358 // Construct nodes_to_primary_elem_map
359 for (const auto & primary_elem :
360 as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
361 {
362 // If this is not one of the lower-dimensional primary side elements, go on to the next one.
363 if (!this->_primary_boundary_subdomain_ids.count(primary_elem->subdomain_id()))
364 continue;
365
366 for (const auto & nd : primary_elem->node_ref_range())
367 {
368 std::vector<const Elem *> & vec = _nodes_to_primary_elem_map[nd.id()];
369 vec.push_back(primary_elem);
370 }
371 }
372}
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.

Referenced by MortarInterfaceWarehouse::update().

◆ clear()

void AutomaticMortarGeneration::clear ( )

Clears the mortar segment mesh and accompanying data structures.

Definition at line 298 of file AutomaticMortarGeneration.C.

299{
301 _mortar_segment_mesh->clear();
306 _msm_elem_to_info.clear();
313 _secondary_ip_sub_ids.clear();
314 _primary_ip_sub_ids.clear();
317}
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
std::unordered_map< const Elem *, unsigned int > _lower_elem_to_side_id
Keeps track of the mapping between lower-dimensional elements and the side_id of the interior_parent ...
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
std::unordered_map< const Node *, std::array< Point, 2 > > _secondary_node_to_hh_nodal_tangents
Container for storing the nodal tangent/binormal vectors associated with each secondary node (Househo...
std::unordered_map< dof_id_type, const Elem * > _secondary_element_to_secondary_lowerd_element
Map from full dimensional secondary element id to lower dimensional secondary element.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.

Referenced by MortarInterfaceWarehouse::update().

◆ computeADNodalNormals()

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

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

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

Definition at line 2089 of file AutomaticMortarGeneration.C.

2092{
2093 const auto dim = _mesh.mesh_dimension();
2094 mooseAssert(dim == 2 || dim == 3, "AD nodal normals are only valid for 2D or 3D mortar.");
2095
2096 libMesh::QNodal qface(dim - 1);
2097 const auto & qweights = qface.get_weights();
2098 const Real sign = _periodic ? -1 : 1;
2099
2100 std::unordered_map<const Node *, ADRealVectorValue> weighted_area_vectors;
2101 std::unordered_map<const Node *, Real> weighted_area_magnitudes;
2102
2103 // One face FE object per secondary element order, reused across elements of that order
2104 std::map<Order, std::unique_ptr<FEBase>> fe_faces;
2105
2106 // AD propagates coordinate derivatives through the parent-side area vectors and normalization.
2107 for (const auto secondary_subdomain_id : _secondary_boundary_subdomain_ids)
2108 for (const Elem * const secondary_elem :
2109 as_range(_mesh.active_subdomain_elements_begin(secondary_subdomain_id),
2110 _mesh.active_subdomain_elements_end(secondary_subdomain_id)))
2111 {
2112 const Elem * const interior_parent = secondary_elem->interior_parent();
2113 mooseAssert(interior_parent,
2114 "No interior parent exists for element "
2115 << secondary_elem->id()
2116 << ". There may be a problem with your sideset set-up.");
2117
2118 const Real element_size = secondary_elem->hmax();
2119 if (element_size <= 0)
2120 mooseError("Cannot compute an AD nodal normal for secondary mortar element ",
2121 secondary_elem->id(),
2122 " because it has a non-positive characteristic length.");
2123
2124 const auto side = interior_parent->which_side_am_i(secondary_elem);
2125 auto & fe_face = fe_faces[secondary_elem->default_order()];
2126 if (!fe_face)
2127 {
2128 fe_face = FEBase::build(dim, FEType(secondary_elem->default_order(), LAGRANGE));
2129 fe_face->attach_quadrature_rule(&qface);
2130 }
2131
2132 const auto & face_normals = fe_face->get_normals();
2133 const auto & face_points = fe_face->get_xyz();
2134 const auto & map_dpsidxi = fe_face->get_fe_map().get_dpsidxi();
2135 const auto * const map_dpsideta = dim == 3 ? &fe_face->get_fe_map().get_dpsideta() : nullptr;
2136
2137 fe_face->reinit(interior_parent, side);
2138
2139 const auto qpoint_to_secondary_node =
2140 MooseMeshUtils::nodalQuadraturePointToNodeMap(*secondary_elem, face_points);
2141
2142 mooseAssert(face_normals.size() == face_points.size() &&
2143 qweights.size() == face_points.size(),
2144 "Face nodal geometry vectors and quadrature weights must have the same size.");
2145
2146 // The parent-side map includes enriched face mappings such as TET14-to-TRI7.
2147 auto parent_side_elem = interior_parent->build_side_ptr(side);
2148 const unsigned int n_mapping_shape_functions =
2149 dim == 2 ? libMesh::FE<2, LAGRANGE>::n_dofs(parent_side_elem.get(),
2150 parent_side_elem->default_order())
2151 : libMesh::FE<3, LAGRANGE>::n_dofs(parent_side_elem.get(),
2152 parent_side_elem->default_order());
2153 if (map_dpsidxi.size() != n_mapping_shape_functions ||
2154 (map_dpsideta && map_dpsideta->size() != n_mapping_shape_functions))
2155 mooseError("The parent-side geometric map for secondary mortar element ",
2156 secondary_elem->id(),
2157 " of type ",
2158 libMesh::Utility::enum_to_string<ElemType>(secondary_elem->type()),
2159 " does not have one mapping shape function per element node.");
2160 mooseAssert(n_mapping_shape_functions <= parent_side_elem->n_nodes(),
2161 "The mapping basis must not index past the side element's nodes.");
2162
2163 std::vector<ADPoint> side_points;
2164 side_points.reserve(n_mapping_shape_functions);
2165 for (const auto side_node_index : make_range(n_mapping_shape_functions))
2166 side_points.push_back(coordinate(parent_side_elem->node_ref(side_node_index)));
2167
2168 for (const auto qp : index_range(face_points))
2169 {
2170 const auto secondary_node_index = qpoint_to_secondary_node[qp];
2171 const Node * const secondary_node = secondary_elem->node_ptr(secondary_node_index);
2172
2173 ADRealVectorValue tangent_xi;
2174 ADRealVectorValue tangent_eta;
2175 for (const auto side_node_index : make_range(n_mapping_shape_functions))
2176 {
2177 tangent_xi.add_scaled(side_points[side_node_index], map_dpsidxi[side_node_index][qp]);
2178 if (dim == 3)
2179 tangent_eta.add_scaled(side_points[side_node_index],
2180 (*map_dpsideta)[side_node_index][qp]);
2181 }
2182
2183 auto area_vector = dim == 2 ? ADRealVectorValue(tangent_xi(1), -tangent_xi(0), 0)
2184 : tangent_xi.cross(tangent_eta);
2185 const Real area = MetaPhysicL::raw_value(area_vector.norm());
2186 const Real degeneracy_tolerance =
2187 dim == 2 ? TOLERANCE * element_size : TOLERANCE * element_size * element_size;
2188 if (area <= degeneracy_tolerance)
2189 mooseError("Cannot compute an AD nodal normal on degenerate secondary mortar element ",
2190 secondary_elem->id(),
2191 ".");
2192
2193 // qweight * area_vector is equivalent to JxW * face_normal. Fix its orientation to the
2194 // Real-valued face normal because the incident-face set and orientation are not
2195 // differentiated.
2196 const Real orientation =
2197 MetaPhysicL::raw_value(area_vector) * face_normals[qp] < 0 ? -1 : 1;
2198
2199 weighted_area_vectors[secondary_node] += sign * orientation * qweights[qp] * area_vector;
2200 weighted_area_magnitudes[secondary_node] += std::abs(qweights[qp]) * area;
2201 }
2202 }
2203
2204 nodal_normals.clear();
2205 nodal_normals.reserve(weighted_area_vectors.size());
2206 for (auto & [secondary_node, weighted_area_vector] : weighted_area_vectors)
2207 {
2208 const Real weighted_area_vector_norm = MetaPhysicL::raw_value(weighted_area_vector.norm());
2209 const Real weighted_area_magnitude = libmesh_map_find(weighted_area_magnitudes, secondary_node);
2210 if (weighted_area_vector_norm <= TOLERANCE * weighted_area_magnitude)
2211 mooseError("Cannot compute a normalized AD nodal normal for secondary node ",
2212 secondary_node->id(),
2213 " because its incident face contributions cancel to a near-zero weighted normal.");
2214
2215 auto nodal_normal = weighted_area_vector.unit();
2216 const auto & stored_normal = libmesh_map_find(_secondary_node_to_nodal_normal, secondary_node);
2217 // Equivalent FE and AD evaluations of the same unit normal may differ through operation
2218 // ordering. The tolerance admits roundoff while detecting inconsistent geometry states.
2219 mooseAssert((MetaPhysicL::raw_value(nodal_normal) - stored_normal).norm() < 1e-10,
2220 "The stored and AD secondary nodal normals must use the same geometry state.");
2221 // Set the raw value from the stored contact direction while retaining the derivatives produced
2222 // by the equivalent AD area-vector calculation.
2223 for (const auto component : make_range(Moose::dim))
2224 nodal_normal(component).value() = stored_normal(component);
2225 nodal_normals.emplace(secondary_node, std::move(nodal_normal));
2226 }
2227}
libMesh::VectorValue< ADReal > ADRealVectorValue
AD Array typedefs.
Definition MooseTypes.h:411
unsigned int n_dofs(const ElemType t, const Order o)
LIBMESH_DEVICE_INLINE void add_scaled(const TypeVector< T2 > &, const T &)
auto raw_value(const Eigen::Map< T > &in)
std::vector< unsigned int > nodalQuadraturePointToNodeMap(const Elem &elem, const std::vector< Point > &q_points)
Match the points of a nodal quadrature rule (e.g.
KOKKOS_INLINE_FUNCTION T sign(T x)
Returns the sign of a value.
Definition KokkosUtils.h:26
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
const Elem & get(const ElemType type_in)
LIBMESH_DEVICE_INLINE auto norm(const T &a)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
const dof_id_type n_nodes

◆ computeInactiveLMElems()

void AutomaticMortarGeneration::computeInactiveLMElems ( )

Get list of secondary elems without any corresponding primary elements.

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

Definition at line 1941 of file AutomaticMortarGeneration.C.

1942{
1943 // Mark all active secondary elements
1944 std::unordered_set<const Elem *> active_local_elems;
1945
1946 //****
1947 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1948 // since the mortar segments are on the linearized element and comparing the volume of the
1949 // linearized element does not have the same volume as the warped element
1950 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1951
1952 std::unordered_map<const Elem *, Real> active_volume;
1953
1954 // Compute fraction of elements with corresponding primary elements
1956 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1957 {
1958 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1959 const Elem * secondary_elem = msinfo.secondary_elem;
1960
1961 active_volume[secondary_elem] += msm_elem->volume();
1962 }
1963 //****
1964
1965 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1966 {
1967 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1968 const Elem * secondary_elem = msinfo.secondary_elem;
1969
1970 //****
1972 if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
1973 continue;
1974 //****
1975
1976 active_local_elems.insert(secondary_elem);
1977 }
1978
1979 // Take complement of active elements in active local subdomain to get inactive local elements
1981 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1982 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1983 /*secondary_subd_id*/ pr.second))
1984 if (active_local_elems.find(el) == active_local_elems.end())
1985 _inactive_local_lm_elems.insert(el);
1986}
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)

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

1878{
1880 {
1882 return;
1883 }
1884
1885 // Only the owner of a node zeroes its LM DoFs, but the owner need not own (or even ghost) any
1886 // secondary lower-dimensional element touching it. Each processor therefore sends every node of
1887 // its local secondary elements to the node's owner, flagged as active if a local mortar segment
1888 // touches it. A node is inactive if no processor flags it as active.
1889
1890 // List of active nodes on local secondary elements
1891 std::unordered_set<dof_id_type> active_local_nodes;
1892
1893 // Mark all active local nodes
1894 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1895 {
1896 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1897 const Elem * secondary_elem = msinfo.secondary_elem;
1898
1899 for (auto n : make_range(secondary_elem->n_nodes()))
1900 active_local_nodes.insert(secondary_elem->node_id(n));
1901 }
1902
1903 // List of all nodes on local secondary elements
1904 std::unordered_set<dof_id_type> local_nodes;
1905 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1906 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1907 /*secondary_subd_id*/ pr.second))
1908 for (const auto n : make_range(el->n_nodes()))
1909 local_nodes.insert(el->node_id(n));
1910
1911 // Pairs of (node id, 1 if active on the sending processor and 0 otherwise)
1912 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, unsigned char>>>
1913 proc_to_nodes;
1914 for (const auto node_id : local_nodes)
1915 proc_to_nodes[_mesh.node_ptr(node_id)->processor_id()].emplace_back(
1916 node_id, active_local_nodes.count(node_id));
1917
1918 std::unordered_set<dof_id_type> candidate_nodes, active_nodes;
1919 auto action_functor = [&candidate_nodes, &active_nodes](
1920 const processor_id_type,
1921 const std::vector<std::pair<dof_id_type, unsigned char>> & sent_data)
1922 {
1923 for (const auto & [node_id, active] : sent_data)
1924 {
1925 candidate_nodes.insert(node_id);
1926 if (active)
1927 active_nodes.insert(node_id);
1928 }
1929 };
1930 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_nodes, action_functor);
1931
1932 // Store the owned inactive nodes to use later to zero LM DoFs on inactive nodes
1934 for (const auto node_id : candidate_nodes)
1935 if (!active_nodes.count(node_id))
1936 _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1937}
std::unordered_set< const Node * > _inactive_local_lm_nodes
List of inactive lagrange multiplier nodes owned by this processor (for nodal variables)
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...

Referenced by MortarInterfaceWarehouse::update().

◆ computeIncorrectEdgeDroppingInactiveLMNodes()

void AutomaticMortarGeneration::computeIncorrectEdgeDroppingInactiveLMNodes ( )

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

Definition at line 1821 of file AutomaticMortarGeneration.C.

1822{
1823 using std::abs;
1824
1825 // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1826 // since the mortar segments are on the linearized element and comparing the volume of the
1827 // linearized element does not have the same volume as the warped element
1828 const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1829
1830 // List of inactive nodes on local secondary elements
1831 std::unordered_set<dof_id_type> inactive_node_ids;
1832
1833 std::unordered_map<const Elem *, Real> active_volume{};
1834
1835 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1836 for (const auto el : _mesh.active_subdomain_elements_ptr_range(pr.second))
1837 active_volume[el] = 0.;
1838
1839 // Compute fraction of elements with corresponding primary elements
1840 for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1841 {
1842 const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1843 const Elem * secondary_elem = msinfo.secondary_elem;
1844
1845 active_volume[secondary_elem] += msm_elem->volume();
1846 }
1847
1848 // Mark all inactive local nodes
1849 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1850 // Loop through all elements on my processor
1851 for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(pr.second))
1852 // If elem fully or partially dropped
1853 if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1854 {
1855 // Add all nodes to list of inactive
1856 for (auto n : make_range(el->n_nodes()))
1857 inactive_node_ids.insert(el->node_id(n));
1858 }
1859
1860 // Only the owner of a node zeroes its LM DoFs, but the owner need not own (or even ghost) any
1861 // secondary lower-dimensional element touching it, so send each inactive node to its owner,
1862 // which takes the union
1863 std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes;
1864 for (const auto node_id : inactive_node_ids)
1865 proc_to_inactive_nodes[_mesh.node_ptr(node_id)->processor_id()].push_back(node_id);
1866
1868 auto action_functor = [this](const processor_id_type, const std::vector<dof_id_type> & sent_data)
1869 {
1870 for (const auto node_id : sent_data)
1871 _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1872 };
1873 TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_inactive_nodes, action_functor);
1874}
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 1692 of file AutomaticMortarGeneration.C.

1693{
1694 std::vector<MsmSubdomainStats> result;
1698 std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1699
1700 for (const auto & [primary_subd_id, secondary_subd_id] : _primary_secondary_subdomain_id_pairs)
1701 {
1702 for (const auto * const secondary_el :
1703 _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1704 {
1705 secondary.push_back(secondary_el->volume());
1706 // We may not have projected onto a primary face in which case we may not have created mortar
1707 // segments
1708 if (auto it = _secondary_elems_to_mortar_segments.find(secondary_el->id());
1710 for (const auto * const msm_elem : it->second)
1711 {
1712 msm.push_back(msm_elem->volume());
1713 const auto & msm_info = libmesh_map_find(_msm_elem_to_info, msm_elem);
1714 // Now it's also possible that we didn't project onto a primary face and we *did* create
1715 // mortar segments
1716 if (msm_info.primary_elem)
1717 {
1718 if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1719 mooseError("Unhandled primary-secondary pairing when computing mortar segment "
1720 "statistics. This could happen if you have the same secondary "
1721 "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1722 "primary subdomain IDs. Contact a MOOSE developer for help.");
1723 if (const auto [it, inserted] =
1724 primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1725 inserted)
1726 it->second = msm_info.primary_elem->volume();
1727 else
1728 mooseAssert(
1729 MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1730 "Volumes should be consistent");
1731 }
1732 }
1733 }
1734
1735 _mesh.comm().set_union(primary_elems_to_volume);
1736 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(secondary));
1737 _mesh.comm().allgather(cast_ref<std::vector<Real> &>(msm));
1738 primary.reserve(primary_elems_to_volume.size());
1739 for (const auto [_, volume] : primary_elems_to_volume)
1740 primary.push_back(volume);
1741
1742 // An interface on which no mortar segments formed has empty primary and segment lists, for
1743 // which libMesh's maximum() and minimum() are undefined. Report 0, as median() does.
1744 const auto max_volume = [](const auto & volumes)
1745 { return volumes.empty() ? Real(0) : volumes.maximum(); };
1746 const auto min_volume = [](const auto & volumes)
1747 { return volumes.empty() ? Real(0) : volumes.minimum(); };
1748
1749 MsmSubdomainStats stats;
1750 stats.primary_subd_id = primary_subd_id;
1751 stats.secondary_subd_id = secondary_subd_id;
1752 stats.secondary_lower_n_elems = secondary.size();
1753 stats.secondary_lower_max_volume = max_volume(secondary);
1754 stats.secondary_lower_min_volume = min_volume(secondary);
1755 stats.secondary_lower_median_volume = secondary.median();
1756 stats.primary_lower_n_elems = primary.size();
1757 stats.primary_lower_max_volume = max_volume(primary);
1758 stats.primary_lower_min_volume = min_volume(primary);
1759 stats.primary_lower_median_volume = primary.median();
1760 stats.msm_n_elems = msm.size();
1761 stats.msm_max_volume = max_volume(msm);
1762 stats.msm_min_volume = min_volume(msm);
1763 stats.msm_median_volume = msm.median();
1764 result.push_back(stats);
1765
1766 primary.clear();
1767 secondary.clear();
1768 msm.clear();
1769 primary_elems_to_volume.clear();
1770 }
1771
1772 return result;
1773}

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

1990{
1991 // The dimension according to Mesh::mesh_dimension().
1992 const auto dim = _mesh.mesh_dimension();
1993
1994 mooseAssert(dim == 2 || dim == 3,
1995 "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
1996 "mortar constraints on 2D or 3D meshes.");
1997 // A nodal lower-dimensional nodal quadrature rule to be used on faces.
1998 libMesh::QNodal qface(dim - 1);
1999
2000 // A map from the node id to the attached elemental normals/weights evaluated at the node. Th
2001 // length of the vector will correspond to the number of elements attached to the node. If it is a
2002 // vertex node, for a 1D mortar mesh, the vector length will be two. If it is an interior node,
2003 // the vector will be length 1. The first member of the pair is that element's normal at the node.
2004 // The second member is that element's JxW at the node
2005 std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
2006
2008 Real sign = _periodic ? -1 : 1;
2009
2010 // First loop over lower-dimensional secondary side elements and compute/save the outward normal
2011 // for each one. We loop over all active elements currently, but this procedure could be
2012 // parallelized as well.
2013 for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
2014 end_el = _mesh.active_elements_end();
2015 el != end_el;
2016 ++el)
2017 {
2018 const Elem * secondary_elem = *el;
2019
2020 // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
2021 if (!_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
2022 continue;
2023
2024 // We will create an FE object and attach the nodal quadrature rule such that we can get out the
2025 // normals at the element nodes
2026 FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
2027 std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(dim, nnx_fe_type));
2028 nnx_fe_face->attach_quadrature_rule(&qface);
2029 const auto & face_normals = nnx_fe_face->get_normals();
2030 const auto & face_points = nnx_fe_face->get_xyz();
2031
2032 const auto & JxW = nnx_fe_face->get_JxW();
2033
2034 // Which side of the parent are we? We need to know this to know
2035 // which side to reinit.
2036 const Elem * interior_parent = secondary_elem->interior_parent();
2037 mooseAssert(interior_parent,
2038 "No interior parent exists for element "
2039 << secondary_elem->id()
2040 << ". There may be a problem with your sideset set-up.");
2041
2042 // Map to get lower dimensional element from interior parent on secondary surface
2043 // This map can be used to provide a handle to methods in this class that need to
2044 // operate on lower dimensional elements.
2045 _secondary_element_to_secondary_lowerd_element.emplace(interior_parent->id(), secondary_elem);
2046
2047 // Look up which side of the interior parent secondary_elem is.
2048 auto s = interior_parent->which_side_am_i(secondary_elem);
2049
2050 // Reinit the face FE object on side s.
2051 nnx_fe_face->reinit(interior_parent, s);
2052
2053 // Match by physical location instead of assuming that parent-side nodal
2054 // quadrature ordering and lower-dimensional side-element node ordering are
2055 // identical.
2056 const auto qpoint_to_secondary_node =
2057 MooseMeshUtils::nodalQuadraturePointToNodeMap(*secondary_elem, face_points);
2058
2059 mooseAssert(face_normals.size() == face_points.size() && JxW.size() == face_points.size(),
2060 "Face nodal geometry vectors must have the same size.");
2061
2062 for (const auto qp : make_range(face_points.size()))
2063 {
2064 const auto n = qpoint_to_secondary_node[qp];
2065 auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
2066 normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[qp], JxW[qp]));
2067 }
2068 }
2069
2070 // Normalize the JxW-weighted sum of incident face normals at each secondary node.
2071 for (const auto & pr : node_to_normals_map)
2072 {
2073 // Compute normal vector
2074 const auto & node_id = pr.first;
2075 const auto & normals_and_weights_vec = pr.second;
2076
2077 Point nodal_normal;
2078 for (const auto & norm_and_weight : normals_and_weights_vec)
2079 nodal_normal += norm_and_weight.first * norm_and_weight.second;
2080 nodal_normal = nodal_normal.unit();
2081
2082 _secondary_node_to_nodal_normal[_mesh.node_ptr(node_id)] = nodal_normal;
2083 _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)] =
2084 Moose::Mortar::nodalTangents(nodal_normal, dim);
2085 }
2086}
std::array< Vector, 2 > nodalTangents(const Vector &normal, const unsigned int dim)
Construct the two tangent vectors used by mortar from a unit normal on a dim dimensional mesh.

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

436 {
438 }

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

428 {
430 }

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

376{
377 std::vector<Point> nodal_normals(secondary_elem.n_nodes());
378 for (const auto n : make_range(secondary_elem.n_nodes()))
379 nodal_normals[n] = _secondary_node_to_nodal_normal.at(secondary_elem.node_ptr(n));
380
381 return nodal_normals;
382}

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

429{
430 // MetaPhysicL will check if we ran out of allocated space.
431 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
432 MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
433
434 for (const auto n : make_range(secondary_elem.n_nodes()))
435 {
436 const auto & tangent_vectors =
437 libmesh_map_find(_secondary_node_to_hh_nodal_tangents, secondary_elem.node_ptr(n));
438 nodal_tangents_one.push_back(tangent_vectors[0]);
439 nodal_tangents_two.push_back(tangent_vectors[1]);
440 }
441
442 return {{nodal_tangents_one, nodal_tangents_two}};
443}

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

459{
460 const auto mortar_dim = _mesh.mesh_dimension() - 1;
461 const auto num_qps = xi1_pts.size();
462 const auto nodal_normals = getNodalNormals(secondary_elem);
463 std::vector<Point> normals(num_qps);
464
465 for (const auto n : make_range(secondary_elem.n_nodes()))
466 for (const auto qp : make_range(num_qps))
467 {
468 const auto phi =
469 (mortar_dim == 1)
470 ? Moose::fe_lagrange_1D_shape(secondary_elem.default_order(), n, xi1_pts[qp](0))
471 : Moose::fe_lagrange_2D_shape(secondary_elem.type(),
472 secondary_elem.default_order(),
473 n,
474 cast_ref<const TypeVector<Real> &>(xi1_pts[qp]));
475 normals[qp] += phi * nodal_normals[n];
476 }
477
478 if (_periodic)
479 for (auto & normal : normals)
480 normal *= -1;
481
482 return normals;
483}
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 446 of file AutomaticMortarGeneration.C.

448{
449 std::vector<Point> xi1_pts(oned_xi1_pts.size());
450 for (const auto qp : index_range(oned_xi1_pts))
451 xi1_pts[qp] = oned_xi1_pts[qp];
452
453 return getNormals(secondary_elem, xi1_pts);
454}

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

415{
416 std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
417
418 for (const auto i : make_range(lower_primary_elem.n_nodes()))
419 {
420 const auto & nd = lower_primary_elem.node_ref(i);
421 primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
422 }
423
424 return primary_ip_i_to_lower_primary_i;
425}

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

396{
397 std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
398 const Elem * const secondary_ip = lower_secondary_elem.interior_parent();
399 mooseAssert(secondary_ip, "This should be non-null");
400
401 for (const auto i : make_range(lower_secondary_elem.n_nodes()))
402 {
403 const auto & nd = lower_secondary_elem.node_ref(i);
404 secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
405 }
406
407 return secondary_ip_i_to_lower_secondary_i;
408}

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

387{
388 mooseAssert(_secondary_element_to_secondary_lowerd_element.count(secondary_elem_id),
389 "Map should locate secondary element");
390
391 return _secondary_element_to_secondary_lowerd_element.at(secondary_elem_id);
392}

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 440 of file AutomaticMortarGeneration.h.

440{ return !_correct_edge_dropping; }

Referenced by ComputeMortarFunctor::operator()().

◆ initOutput()

void AutomaticMortarGeneration::initOutput ( )

initialize mortar-mesh based output

Definition at line 282 of file AutomaticMortarGeneration.C.

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

◆ meshChanged()

void AutomaticMortarGeneration::meshChanged ( )
inline

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

Call this when mesh topology changes.

Definition at line 282 of file AutomaticMortarGeneration.h.

282{ _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 418 of file AutomaticMortarGeneration.h.

418{ 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 382 of file AutomaticMortarGeneration.h.

383 {
385 }

◆ mortarInterfaceName()

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

Definition at line 268 of file AutomaticMortarGeneration.C.

269{
270 std::vector<std::string> string_vec(_primary_secondary_boundary_id_pairs.size() * 2 + 1);
272 {
273 const auto [primary_bnd_id, secondary_bnd_id] = _primary_secondary_boundary_id_pairs[i];
274 string_vec[2 * i] = std::to_string(primary_bnd_id);
275 string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
276 }
277 string_vec.back() = _on_displaced ? "displaced" : "undisplaced";
278 return MooseUtils::join(string_vec, "_");
279}

Referenced by initOutput(), and outputMortarMesh().

◆ mortarSegmentMesh()

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

Definition at line 405 of file AutomaticMortarGeneration.h.

405{ 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 410 of file AutomaticMortarGeneration.h.

411 {
412 return _msm_elem_to_info;
413 }

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

321{
322 if (_mortar_3d_qp_mapping != Mortar3DQuadraturePointMapping::REFERENCE_INTERPOLATION)
323 mooseError("Mortar segment reference points were requested for mortar segment element ",
324 mortar_segment_elem.id(),
325 ", but the reference-interpolation mapping mode is not enabled.");
326
327 const auto reference_points_it = _msm_elem_to_reference_points.find(&mortar_segment_elem);
328 if (reference_points_it == _msm_elem_to_reference_points.end())
329 mooseError("No reference-point record was found for mortar segment element ",
330 mortar_segment_elem.id(),
331 ". The mortar segment info and reference-point maps are not aligned.");
332
333 return reference_points_it->second;
334}

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

◆ msmStatistics()

void AutomaticMortarGeneration::msmStatistics ( )

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

Definition at line 1776 of file AutomaticMortarGeneration.C.

1777{
1778 const auto all_stats = computeMsmStatistics();
1779
1780 if (_mesh.processor_id() != 0)
1781 return;
1782
1783 Moose::out << "Mortar Interface Statistics:" << std::endl;
1784 for (const auto & stats : all_stats)
1785 {
1786 std::vector<std::string> col_names = {"mesh", "n_elems", "max", "min", "median"};
1787 std::vector<std::string> subds = {"secondary_lower", "primary_lower", "mortar_segment"};
1788 std::vector<size_t> n_elems = {
1789 stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1790 std::vector<Real> maxs = {
1791 stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1792 std::vector<Real> mins = {
1793 stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1794 std::vector<Real> medians = {stats.secondary_lower_median_volume,
1795 stats.primary_lower_median_volume,
1796 stats.msm_median_volume};
1797
1798 FormattedTable table;
1799 table.clear();
1800 for (auto i : index_range(subds))
1801 {
1802 table.addRow(i);
1803 table.addData<std::string>(col_names[0], subds[i]);
1804 table.addData<size_t>(col_names[1], n_elems[i]);
1805 table.addData<Real>(col_names[2], maxs[i]);
1806 table.addData<Real>(col_names[3], mins[i]);
1807 table.addData<Real>(col_names[4], medians[i]);
1808 }
1809
1810 Moose::out << "secondary subdomain: " << stats.secondary_subd_id
1811 << " \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1812 table.printTable(Moose::out, subds.size());
1813 }
1814}
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 476 of file AutomaticMortarGeneration.h.

477 {
479 }

◆ onDisplaced()

bool AutomaticMortarGeneration::onDisplaced ( ) const
inline

returns whether this object is on the displaced mesh

Definition at line 287 of file AutomaticMortarGeneration.h.

287{ return _on_displaced; }

◆ outputMortarMesh()

void AutomaticMortarGeneration::outputMortarMesh ( )
private

Write the mortar segment mesh to exodus.

Definition at line 981 of file AutomaticMortarGeneration.C.

982{
983 ExodusII_IO mortar_segment_mesh_writer(*_mortar_segment_mesh);
984
985 // Default to non-HDF5 output for wider compatibility
986 mortar_segment_mesh_writer.set_hdf5_writing(false);
987
988 std::array<std::string, 3> file_pieces = {
989 _app.getOutputFileBase(/*for_non_moose_build_output=*/true),
991 "mortar_segment_mesh.e"};
992 mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces, "_"));
993}
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
Definition MooseApp.C:1541

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

◆ primaryIPSubIDs()

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

Definition at line 471 of file AutomaticMortarGeneration.h.

471{ 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 705 of file AutomaticMortarGeneration.h.

706{
707 mooseAssert(_primary_secondary_boundary_id_pairs.size() == 1,
708 "We currently only support a single boundary pair per mortar generation object");
709
711}

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

2249{
2250 if (!secondary_node_neighbors)
2251 secondary_node_neighbors = &libmesh_map_find(_nodes_to_secondary_elem_map, secondary_node.id());
2252 if (!primary_node_neighbors)
2253 primary_node_neighbors = &libmesh_map_find(_nodes_to_primary_elem_map, primary_node.id());
2254
2255 std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(), false);
2256
2257 // Add entries to secondary_node_and_elem_to_xi2_primary_elem container.
2258 //
2259 // First, determine "on left" vs. "on right" orientation of the nodal neighbors.
2260 // There can be a max of 2 nodal neighbors, and we want to make sure that the
2261 // secondary nodal neighbor on the "left" is associated with the primary nodal
2262 // neighbor on the "left" and similarly for the "right". We use cross products to determine
2263 // alignment. In the below diagram, 'x' denotes a node, and connected '|' are lower dimensional
2264 // elements.
2265 // x
2266 // x |
2267 // | |
2268 // secondary x ----> x primary
2269 // | |
2270 // | x
2271 // x
2272 //
2273 // Looking at the aligned nodes, the secondary node first, if we pick the top secondary lower
2274 // dimensional element, then the cross product as written a few lines below points out of the
2275 // screen towards you. (Point in the direction of the secondary nodal normal, and then curl your
2276 // hand towards the secondary element's opposite node, then the thumb points in the direction of
2277 // the cross product). Doing the same with the aligned primary node, if we pick the top primary
2278 // element, then the cross product also points out of the screen. Because the cross products
2279 // point in the same direction (positive dot product), then we know to associate the
2280 // secondary-primary element pair. If we had picked the bottom primary element whose cross
2281 // product points into the screen, then clearly the cross products point in the opposite
2282 // direction and we don't have a match
2283 std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2284
2285 for (const auto nn : index_range(*secondary_node_neighbors))
2286 {
2287 const Elem * const secondary_neigh = (*secondary_node_neighbors)[nn];
2288 const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2289 ? secondary_neigh->point(1)
2290 : secondary_neigh->point(0);
2291 const Point cp = nodal_normal.cross(opposite - secondary_node);
2292 secondary_node_neighbor_cps[nn] = cp(2);
2293 }
2294
2295 for (const auto nn : index_range(*primary_node_neighbors))
2296 {
2297 const Elem * const primary_neigh = (*primary_node_neighbors)[nn];
2298 const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2299 : primary_neigh->point(0);
2300 const Point cp = nodal_normal.cross(opposite - primary_node);
2301 primary_node_neighbor_cps[nn] = cp(2);
2302 }
2303
2304 // Associate secondary/primary elems on matching sides.
2305 bool found_match = false;
2306 for (const auto snn : index_range(*secondary_node_neighbors))
2307 for (const auto mnn : index_range(*primary_node_neighbors))
2308 if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2309 {
2310 found_match = true;
2311 if (primary_elems_mapped[mnn])
2312 continue;
2313 primary_elems_mapped[mnn] = true;
2314
2315 // Figure out xi^(2) value by looking at which node primary_node is
2316 // of the current primary node neighbor.
2317 const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2318 const auto secondary_key =
2319 std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2320 const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2321 _secondary_node_and_elem_to_xi2_primary_elem.emplace(secondary_key, primary_val);
2322
2323 // Also map in the other direction.
2324 const Real xi1 =
2325 (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2326
2327 const auto primary_key =
2328 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2329 const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2330 _primary_node_and_elem_to_xi1_secondary_elem.emplace(primary_key, secondary_val);
2331 }
2332
2333 if (!found_match)
2334 {
2335 // There could be coincident nodes and this might be a bad primary candidate (see
2336 // issue #21680). Instead of giving up, let's try continuing
2337 rejected_elem_candidates.insert(&candidate_element);
2338 return false;
2339 }
2340
2341 // We need to handle the case where we've exactly projected a secondary node onto a
2342 // primary node, but our secondary node is at one of the secondary boundary face endpoints and
2343 // our primary node is not.
2344 if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2345 for (const auto i : index_range(primary_elems_mapped))
2346 if (!primary_elems_mapped[i])
2347 {
2349 std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2350 std::make_pair(1, nullptr));
2351 }
2352
2353 return found_match;
2354}
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 2602 of file AutomaticMortarGeneration.C.

2603{
2604 // For each primary/secondary boundary id pair, call the
2605 // project_primary_nodes_single_pair() helper function.
2606 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2607 projectPrimaryNodesSinglePair(pr.first, pr.second);
2608}
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 2611 of file AutomaticMortarGeneration.C.

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

Referenced by projectPrimaryNodes().

◆ projectSecondaryNodes()

void AutomaticMortarGeneration::projectSecondaryNodes ( )

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

Inputs:

  • The nodal normals values
  • mesh
  • nodes_to_primary_elem_map

Outputs:

  • secondary_node_and_elem_to_xi2_primary_elem

Defined in the file project_secondary_nodes.C.

Definition at line 2232 of file AutomaticMortarGeneration.C.

2233{
2234 // For each primary/secondary boundary id pair, call the
2235 // project_secondary_nodes_single_pair() helper function.
2236 for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2237 projectSecondaryNodesSinglePair(pr.first, pr.second);
2238}
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 2357 of file AutomaticMortarGeneration.C.

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

Referenced by projectSecondaryNodes().

◆ requestNonlocalSecondaryCouplings()

void AutomaticMortarGeneration::requestNonlocalSecondaryCouplings ( )
inline

Request that every process receive the mortar interface couplings of the nonlocal secondary lower-dimensional elements that point-neighbor ghosting reads for its local elements: the point neighbors of its local secondary elements and of the secondary elements coupled to its local elements.

Consumers that apply a node's constraint on every process contributing to it, rather than only on the node's owner, need these couplings to preallocate the rows of their local elements.

Definition at line 395 of file AutomaticMortarGeneration.h.

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

459 {
461 }

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

2839{
2840 auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2841 if (secondary_it == _nodes_to_secondary_elem_map.end())
2842 return {};
2843
2844 const auto & secondary_elems = secondary_it->second;
2845 std::vector<MortarFilterIter> ret;
2846 ret.reserve(secondary_elems.size());
2847
2848 for (const auto i : index_range(secondary_elems))
2849 {
2850 auto * const secondary_elem = secondary_elems[i];
2851 auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2852 if (msm_it == _secondary_elems_to_mortar_segments.end())
2853 // We may have removed this element key from this map
2854 continue;
2855
2856 mooseAssert(secondary_elem->active(),
2857 "We loop over active elements when building the mortar segment mesh, so we golly "
2858 "well hope this is active.");
2859 mooseAssert(!msm_it->second.empty(),
2860 "We should have removed all secondaries from this map if they do not have any "
2861 "mortar segments associated with them.");
2862 ret.push_back(msm_it);
2863 }
2864
2865 return ret;
2866}

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

466{ 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 701 of file AutomaticMortarGeneration.h.

◆ MortarNodalGeometryOutput

friend class MortarNodalGeometryOutput
friend

Definition at line 700 of file AutomaticMortarGeneration.h.

Member Data Documentation

◆ _app

MooseApp& AutomaticMortarGeneration::_app
private

The Moose app.

Definition at line 503 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(), StitchMeshGenerator::generate(), MeshGenerator::generateInternal(), VariableCondensationPreconditioner::getDofToCondense(), InversePowerMethod::init(), NonlinearEigen::init(), FEProblemBase::initialAdaptMesh(), SubProblem::initialSetup(), EigenExecutionerBase::inversePowerIteration(), FEProblemBase::joinAndFinalize(), TransientBase::keepGoing(), IterationAdaptiveDT::limitDTByFunction(), IterationAdaptiveDT::limitDTToPostprocessorValue(), FEProblemBase::logAdd(), EigenExecutionerBase::makeBXConsistent(), Console::meshChanged(), SurfaceDelaunayGeneratorBase::meshNormalDeviation2D(), MooseBase::mooseDeprecated(), MooseBase::mooseDeprecatedNoTrace(), MooseBase::mooseInfo(), MooseBase::mooseWarning(), MooseBase::mooseWarningNonPrefixed(), NEML2Action::NEML2Action(), ReferenceResidualConvergence::nonlinearConvergenceSetup(), Console::output(), DOFMapOutput::output(), MaterialPropertyDebugOutput::output(), PerfGraphOutput::output(), ReporterDebugOutput::output(), SolutionInvalidityOutput::output(), VariableResidualNormsDebugOutput::output(), ControlOutput::outputActiveObjects(), ControlOutput::outputChangedControls(), ControlOutput::outputControls(), Console::outputInput(), WebServerControl::outputMessage(), Console::outputPostprocessors(), PseudoTimestep::outputPseudoTimestep(), Console::outputReporters(), DefaultMultiAppFixedPointConvergence::outputResidualNorm(), Console::outputScalarVariables(), Console::outputSystemInformation(), FEProblemBase::possiblyRebuildGeomSearchPatches(), EigenExecutionerBase::postExecute(), AB2PredictorCorrector::postSolve(), DefaultMultiAppFixedPointConvergence::preLoop(), ActionWarehouse::printActionDependencySets(), BlockRestrictionDebugOutput::printBlockRestrictionGroups(), BlockRestrictionDebugOutput::printBlockRestrictionMap(), BlockRestrictionDebugOutput::printBoundaryRestrictionGroups(), SolutionInvalidity::printDebug(), EigenExecutionerBase::printEigenvalue(), PicardSolve::printFixedPointConvergenceHistory(), SecantSolve::printFixedPointConvergenceHistory(), SteffensenSolve::printFixedPointConvergenceHistory(), FixedPointSolve::printFixedPointConvergenceReason(), PerfGraphLivePrint::printLiveMessage(), MaterialPropertyDebugOutput::printMaterialMap(), PerfGraphLivePrint::printStats(), NEML2Action::printSummary(), projectPrimaryNodesSinglePair(), projectSecondaryNodesSinglePair(), CoarsenBlockGenerator::recursiveCoarsen(), SolutionTimeAdaptiveDT::rejectStep(), MultiApp::restore(), FEProblemBase::restoreMultiApps(), FEProblemBase::restoreSolutions(), NonlinearSystemBase::setInitialSolution(), MooseApp::setupOptions(), Checkpoint::shouldOutput(), SubProblem::showFunctorRequestors(), SubProblem::showFunctors(), FullSolveMultiApp::showStatusMessage(), FEProblemSolve::solve(), FixedPointSolve::solve(), LinearSystem::solve(), NonlinearSystem::solve(), AStableDirk4::solve(), ExplicitRK2::solve(), ExplicitTVDRK2::solve(), ImplicitMidpoint::solve(), LStableDirk2::solve(), LStableDirk3::solve(), LStableDirk4::solve(), EigenProblem::solve(), FixedPointSolve::solveStep(), TransientMultiApp::solveStep(), MeshRepairGenerator::splitNonConvexPolygons(), PerfGraphLivePrint::start(), WebServerControl::startServer(), AB2PredictorCorrector::step(), NonlinearEigen::takeStep(), TransientBase::takeStep(), MFEMTransient::takeStep(), TerminateChainControl::terminate(), SubProblem::timestepSetup(), FEProblemBase::updateMeshXFEM(), Convergence::verboseOutput(), Console::writeTimestepInformation(), Console::writeVariableNorms(), and FEProblemBase::~FEProblemBase().

◆ _correct_edge_dropping

const bool AutomaticMortarGeneration::_correct_edge_dropping
private

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

Definition at line 662 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 646 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 698 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 596 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMElems(), and getInactiveLMElems().

◆ _inactive_local_lm_nodes

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

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

Definition at line 593 of file AutomaticMortarGeneration.h.

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

◆ _lower_elem_to_side_id

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

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

Definition at line 561 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 668 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 680 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 671 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 580 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 552 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 557 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 683 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 691 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 653 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 519 of file AutomaticMortarGeneration.h.

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

◆ _nonlocal_secondary_couplings

bool AutomaticMortarGeneration::_nonlocal_secondary_couplings = false
private

Whether buildCouplingInformation() also gathers the couplings of nonlocal secondary lower-dimensional elements.

Definition at line 650 of file AutomaticMortarGeneration.h.

Referenced by buildCouplingInformation(), and requestNonlocalSecondaryCouplings().

◆ _on_displaced

const bool AutomaticMortarGeneration::_on_displaced
private

Whether this object is on the displaced mesh.

Definition at line 640 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 686 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 643 of file AutomaticMortarGeneration.h.

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

◆ _primary_boundary_subdomain_ids

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

Definition at line 570 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 608 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 548 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 512 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 516 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 565 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 695 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 569 of file AutomaticMortarGeneration.h.

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

◆ _secondary_element_to_secondary_lowerd_element

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

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

Definition at line 590 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 602 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 605 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 540 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 587 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 583 of file AutomaticMortarGeneration.h.

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

◆ _secondary_requested_boundary_ids

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

The boundary ids corresponding to all the secondary surfaces.

Definition at line 509 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 677 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 674 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 658 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 140 of file AutomaticMortarGeneration.h.


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