https://mooseframework.inl.gov
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)
 Must be constructed with a reference to the Mesh we are generating mortar spaces for. More...
 
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. More...
 
void computeNodalGeometry ()
 Computes and stores the nodal normal/tangent vectors in a local data structure instead of using the ExplicitSystem/NumericVector approach. More...
 
void projectSecondaryNodes ()
 Project secondary nodes (find xi^(2) values) to the closest points on the primary surface. More...
 
void projectPrimaryNodes ()
 (Inverse) project primary nodes to the points on the secondary surface where they would have come from (find (xi^(1) values)). More...
 
void buildMortarSegmentMesh ()
 Builds the mortar segment mesh once the secondary and primary node projections have been completed. More...
 
void buildMortarSegmentMesh3d ()
 Builds the mortar segment mesh once the secondary and primary node projections have been completed. More...
 
std::vector< MsmSubdomainStatscomputeMsmStatistics ()
 Computes mortar segment mesh statistics and returns one entry per subdomain pair. More...
 
void msmStatistics ()
 Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally) More...
 
void clear ()
 Clears the mortar segment mesh and accompanying data structures. More...
 
void meshChanged ()
 Invalidates the cached MSM node/element ID starting offset so that the next call to buildMortarSegmentMesh3d() recomputes it via allgather. More...
 
bool onDisplaced () const
 returns whether this object is on the displaced mesh More...
 
std::vector< PointgetNodalNormals (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. More...
 
std::map< unsigned int, unsigned intgetSecondaryIpToLowerElementMap (const Elem &lower_secondary_elem) const
 Compute on-the-fly mapping from secondary interior parent nodes to lower dimensional nodes. More...
 
std::map< unsigned int, unsigned intgetPrimaryIpToLowerElementMap (const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
 Compute on-the-fly mapping from primary interior parent nodes to its corresponding lower dimensional nodes. More...
 
std::vector< PointgetNormals (const Elem &secondary_elem, const std::vector< Point > &xi1_pts) const
 Compute the normals at given reference points on a secondary element. More...
 
std::vector< PointgetNormals (const Elem &secondary_elem, const std::vector< Real > &oned_xi1_pts) const
 Compute the normals at given reference points on a secondary element. More...
 
const ElemgetSecondaryLowerdElemFromSecondaryElem (dof_id_type secondary_elem_id) const
 Return lower dimensional secondary element given its interior parent. More...
 
void computeInactiveLMNodes ()
 Get list of secondary nodes that don't contribute to interaction with any primary element. More...
 
void computeIncorrectEdgeDroppingInactiveLMNodes ()
 Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching a partially or fully dropped element is dropped) More...
 
void computeInactiveLMElems ()
 Get list of secondary elems without any corresponding primary elements. More...
 
const std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > & mortarInterfaceCoupling () const
 
const std::pair< BoundaryID, BoundaryID > & primarySecondaryBoundaryIDPair () const
 
const MeshBasemortarSegmentMesh () const
 
const std::unordered_map< const Elem *, MortarSegmentInfo > & mortarSegmentMeshElemToInfo () const
 
int dim () const
 
const std::unordered_set< const Node * > & getInactiveLMNodes () const
 
const std::unordered_set< const Elem * > & getInactiveLMElems () const
 
bool incorrectEdgeDropping () const
 
std::vector< MortarFilterItersecondariesToMortarSegments (const Node &node) const
 
const std::unordered_map< dof_id_type, std::set< Elem *, CompareDofObjectsByID > > & secondariesToMortarSegments () const
 
const std::set< SubdomainID > & secondaryIPSubIDs () const
 
const std::set< SubdomainID > & primaryIPSubIDs () const
 
const std::unordered_map< dof_id_type, std::vector< const Elem * > > & nodesToSecondaryElem () const
 
void initOutput ()
 initialize mortar-mesh based output More...
 

Public Attributes

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

Static Public Attributes

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

Private Member Functions

void outputMortarMesh ()
 Write the mortar segment mesh to exodus. More...
 
std::string mortarInterfaceName () const
 
void buildCouplingInformation ()
 build the _mortar_interface_coupling data More...
 
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. More...
 
void projectPrimaryNodesSinglePair (SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
 Helper function used internally by AutomaticMortarGeneration::project_primary_nodes(). More...
 
void householderOrthogolization (const Point &normal, Point &tangent_one, Point &tangent_two) const
 Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors. More...
 
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. More...
 

Private Attributes

MooseApp_app
 The Moose app. More...
 
MeshBase_mesh
 Reference to the mesh stored in equation_systems. More...
 
std::set< BoundaryID_secondary_requested_boundary_ids
 The boundary ids corresponding to all the secondary surfaces. More...
 
std::set< BoundaryID_primary_requested_boundary_ids
 The boundary ids corresponding to all the primary surfaces. More...
 
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. More...
 
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. More...
 
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. More...
 
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. More...
 
std::unique_ptr< MeshBase_mortar_segment_mesh
 1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh(). More...
 
std::unordered_map< const Elem *, MortarSegmentInfo_msm_elem_to_info
 Map between Elems in the mortar segment mesh and their info structs. More...
 
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. More...
 
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. More...
 
std::set< SubdomainID_secondary_boundary_subdomain_ids
 The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids. More...
 
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. More...
 
std::unordered_map< const Node *, Point_secondary_node_to_nodal_normal
 Container for storing the nodal normal vector associated with each secondary node. More...
 
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). More...
 
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. More...
 
std::unordered_set< const Node * > _inactive_local_lm_nodes
 
std::unordered_set< const Elem * > _inactive_local_lm_elems
 List of inactive lagrange multiplier nodes (for elemental variables) More...
 
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. More...
 
std::set< SubdomainID_secondary_ip_sub_ids
 All the secondary interior parent subdomain IDs associated with the mortar mesh. More...
 
std::set< SubdomainID_primary_ip_sub_ids
 All the primary interior parent subdomain IDs associated with the mortar mesh. More...
 
const bool _debug
 Whether to print debug output. More...
 
const bool _on_displaced
 Whether this object is on the displaced mesh. More...
 
const bool _periodic
 Whether this object will be generating a mortar segment mesh for periodic constraints. More...
 
const bool _distributed
 Whether the mortar segment mesh is distributed. More...
 
Real _newton_tolerance = 1e-12
 Newton solve tolerance for node projections. More...
 
Real _xi_tolerance = 1e-6
 Tolerance for checking projection xi values. More...
 
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. More...
 
const Real _minimum_projection_angle
 Parameter to control which angle (in degrees) is admissible for the creation of mortar segments. More...
 
std::unique_ptr< InputParameters_output_params
 Storage for the input parameters used by the mortar nodal geometry output. More...
 
std::optional< dof_id_type_msm_node_id_start
 Cached per-rank starting ID for 3D MSM nodes/elements. More...
 
std::unordered_set< dof_id_type_projected_secondary_nodes
 Debugging container for printing information about fraction of successful projections for secondary nodes. More...
 
std::unordered_set< dof_id_type_failed_secondary_node_projections
 Secondary nodes that failed to project. More...
 

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 57 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 335 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 
)

Must be constructed with a reference to the Mesh we are generating mortar spaces for.

Definition at line 217 of file AutomaticMortarGeneration.C.

227  : ConsoleStreamInterface(app),
228  _app(app),
229  _mesh(mesh_in),
230  _debug(debug),
231  _on_displaced(on_displaced),
232  _periodic(periodic),
233  // 3D mortar always builds the mortar segment mesh distributedly (each rank adds only its local
234  // secondary elements). For 2D, we ghost the entire mortar interface when displaced, so
235  // displaced meshes are always replicated; otherwise follow the parent mesh.
237  _correct_edge_dropping(correct_edge_dropping),
238  _minimum_projection_angle(minimum_projection_angle)
239 {
240  _primary_secondary_boundary_id_pairs.push_back(boundary_key);
241  _primary_requested_boundary_ids.insert(boundary_key.first);
242  _secondary_requested_boundary_ids.insert(boundary_key.second);
243  _primary_secondary_subdomain_id_pairs.push_back(subdomain_key);
244  _primary_boundary_subdomain_ids.insert(subdomain_key.first);
245  _secondary_boundary_subdomain_ids.insert(subdomain_key.second);
246 
247  if (_distributed)
249  std::make_unique<DistributedMesh>(_mesh.comm(), _mesh.spatial_dimension());
250  else
252  std::make_unique<ReplicatedMesh>(_mesh.comm(), _mesh.spatial_dimension());
253 }
std::set< SubdomainID > _primary_boundary_subdomain_ids
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::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
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.
const Parallel::Communicator & comm() const
const bool _debug
Whether to print debug output.
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
ConsoleStreamInterface(MooseApp &app)
A class for providing a helper stream object for writting message to all the Output objects...
std::set< BoundaryID > _primary_requested_boundary_ids
The boundary ids corresponding to all the primary surfaces.
const bool _on_displaced
Whether this object is on the displaced mesh.
unsigned int spatial_dimension() const
virtual bool is_replicated() const
unsigned int mesh_dimension() const
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments...
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
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< BoundaryID > _secondary_requested_boundary_ids
The boundary ids corresponding to all the secondary surfaces.
MooseApp & _app
The Moose app.
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

Member Function Documentation

◆ buildCouplingInformation()

void AutomaticMortarGeneration::buildCouplingInformation ( )
private

build the _mortar_interface_coupling data

Definition at line 1407 of file AutomaticMortarGeneration.C.

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

1408 {
1409  std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1410  coupling_info;
1411 
1412  // Loop over the msm_elem_to_info object and build a bi-directional
1413  // multimap from secondary elements to the primary Elems which they are
1414  // coupled to and vice-versa. This is used in the
1415  // AugmentSparsityOnInterface functor to determine whether a given
1416  // secondary Elem is coupled across the mortar interface to a primary
1417  // element.
1418  for (const auto & pr : _msm_elem_to_info)
1419  {
1420  const Elem * secondary_elem = pr.second.secondary_elem;
1421  const Elem * primary_elem = pr.second.primary_elem;
1422 
1423  // LowerSecondary
1424  coupling_info[secondary_elem->processor_id()].emplace_back(
1425  secondary_elem->id(), secondary_elem->interior_parent()->id());
1426  if (secondary_elem->processor_id() != _mesh.processor_id())
1427  // We want to keep information for nonlocal lower-dimensional secondary element point
1428  // neighbors for mortar nodal aux kernels
1429  _mortar_interface_coupling[secondary_elem->id()].insert(
1430  secondary_elem->interior_parent()->id());
1431 
1432  // LowerPrimary
1433  coupling_info[secondary_elem->processor_id()].emplace_back(
1434  secondary_elem->id(), primary_elem->interior_parent()->id());
1435  if (secondary_elem->processor_id() != _mesh.processor_id())
1436  // We want to keep information for nonlocal lower-dimensional secondary element point
1437  // neighbors for mortar nodal aux kernels
1438  _mortar_interface_coupling[secondary_elem->id()].insert(
1439  primary_elem->interior_parent()->id());
1440 
1441  // Lower-LowerDimensionalPrimary
1442  coupling_info[secondary_elem->processor_id()].emplace_back(secondary_elem->id(),
1443  primary_elem->id());
1444  if (secondary_elem->processor_id() != _mesh.processor_id())
1445  // We want to keep information for nonlocal lower-dimensional secondary element point
1446  // neighbors for mortar nodal aux kernels
1447  _mortar_interface_coupling[secondary_elem->id()].insert(primary_elem->id());
1448 
1449  // SecondaryLower
1450  coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1451  secondary_elem->interior_parent()->id(), secondary_elem->id());
1452 
1453  // SecondaryPrimary
1454  coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1455  secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1456 
1457  // PrimaryLower
1458  coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1459  primary_elem->interior_parent()->id(), secondary_elem->id());
1460 
1461  // PrimarySecondary
1462  coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1463  primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1464  }
1465 
1466  // Push the coupling information
1467  auto action_functor =
1468  [this](processor_id_type,
1469  const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1470  {
1471  for (auto [i, j] : coupling_info)
1472  _mortar_interface_coupling[i].insert(j);
1473  };
1474  TIMPI::push_parallel_vector_data(_mesh.comm(), coupling_info, action_functor);
1475 }
const Elem * interior_parent() const
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const Parallel::Communicator & comm() const
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
uint8_t processor_id_type
dof_id_type id() const
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
processor_id_type processor_id() const
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...
processor_id_type processor_id() const

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

Referenced by MortarInterfaceWarehouse::update().

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

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

Referenced by MortarInterfaceWarehouse::update().

964 {
965  // Add an integer flag to mortar segment mesh to keep track of which subelem
966  // of second order primal elements mortar segments correspond to
967  auto secondary_sub_elem = _mortar_segment_mesh->add_elem_integer("secondary_sub_elem");
968  auto primary_sub_elem = _mortar_segment_mesh->add_elem_integer("primary_sub_elem");
969 
970  // Assign globally unique node/element IDs via an exclusive prefix scan: each rank's bound is
971  // local_secondary_sub_elems * visible_primary_sub_elems * 9, where 9 is the maximum nodes a
972  // single secondary/primary sub-element pair can produce (8-vertex clipped polygon + center).
973  // The result is cached and invalidated by meshChanged(), so the allgather only runs on topology
974  // changes, not on every displaced-mesh residual update.
975  if (!_msm_node_id_start.has_value())
976  {
977  dof_id_type local_secondary_sub_elems = 0, visible_primary_sub_elems = 0;
978  for (const auto & [primary_sub_id, secondary_sub_id] : _primary_secondary_subdomain_id_pairs)
979  {
980  for (const auto * const el :
982  local_secondary_sub_elems += el->n_sub_elem();
983  for (const auto * const el : _mesh.active_subdomain_elements_ptr_range(primary_sub_id))
984  visible_primary_sub_elems += el->n_sub_elem();
985  }
986  const dof_id_type per_rank_bound = local_secondary_sub_elems * visible_primary_sub_elems * 9;
987  std::vector<dof_id_type> per_rank_bounds;
988  _mesh.comm().allgather(per_rank_bound, per_rank_bounds);
989  dof_id_type start = 0;
990  for (const auto r : make_range(_mesh.processor_id()))
991  start += per_rank_bounds[r];
992  _msm_node_id_start = start;
993  }
994  dof_id_type next_node_id = *_msm_node_id_start;
995  // Element IDs use the same starting offset: node and element IDs are separately numbered, and
996  // element count per clip (n triangles) is always <= node count (n+1), so per_rank_bound covers
997  // both.
998  dof_id_type next_elem_id = next_node_id;
999 
1000  // Loop through mortar secondary and primary pairs to create mortar segment mesh between each
1001  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1002  {
1003  const auto primary_subd_id = pr.first;
1004  const auto secondary_subd_id = pr.second;
1005 
1006  // Build k-d tree for use in Step 1.2 for primary interface coarse screening
1007  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, primary_subd_id);
1008  subdomain_kd_tree_t kd_tree(
1009  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
1010 
1011  // Construct the KD tree.
1012  kd_tree.buildIndex();
1013 
1014  // Define expression for getting sub-elements nodes (for sub-dividing secondary and primary
1015  // elements)
1016  auto get_sub_elem_nodes = [](const ElemType type,
1017  const unsigned int sub_elem) -> std::vector<unsigned int>
1018  {
1019  switch (type)
1020  {
1021  case TRI3:
1022  return {{0, 1, 2}};
1023  case QUAD4:
1024  return {{0, 1, 2, 3}};
1025  case TRI6:
1026  case TRI7:
1027  switch (sub_elem)
1028  {
1029  case 0:
1030  return {{0, 3, 5}};
1031  case 1:
1032  return {{3, 4, 5}};
1033  case 2:
1034  return {{3, 1, 4}};
1035  case 3:
1036  return {{5, 4, 2}};
1037  default:
1038  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
1039  }
1040  case QUAD8:
1041  switch (sub_elem)
1042  {
1043  case 0:
1044  return {{0, 4, 7}};
1045  case 1:
1046  return {{4, 1, 5}};
1047  case 2:
1048  return {{5, 2, 6}};
1049  case 3:
1050  return {{7, 6, 3}};
1051  case 4:
1052  return {{4, 5, 6, 7}};
1053  default:
1054  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
1055  }
1056  case QUAD9:
1057  switch (sub_elem)
1058  {
1059  case 0:
1060  return {{0, 4, 8, 7}};
1061  case 1:
1062  return {{4, 1, 5, 8}};
1063  case 2:
1064  return {{8, 5, 2, 6}};
1065  case 3:
1066  return {{7, 8, 6, 3}};
1067  default:
1068  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
1069  }
1070  default:
1071  mooseError("get_sub_elem_inds: Face element type: ",
1072  libMesh::Utility::enum_to_string<ElemType>(type),
1073  " invalid for 3D mortar");
1074  }
1075  };
1076 
1080  for (MeshBase::const_element_iterator el = _mesh.active_local_elements_begin(),
1081  end_el = _mesh.active_local_elements_end();
1082  el != end_el;
1083  ++el)
1084  {
1085  const Elem * secondary_side_elem = *el;
1086 
1087  const Real secondary_volume = secondary_side_elem->volume();
1088 
1089  // If this Elem is not in the current secondary subdomain, go on to the next one.
1090  if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1091  continue;
1092 
1093  auto [secondary_elem_to_msm_map_it, insertion_happened] =
1094  _secondary_elems_to_mortar_segments.emplace(secondary_side_elem->id(),
1095  std::set<Elem *, CompareDofObjectsByID>{});
1096  libmesh_ignore(insertion_happened);
1097  auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1098 
1099  std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1100  secondary_side_elem->n_sub_elem());
1101  const auto nodal_normals = getNodalNormals(*secondary_side_elem);
1102 
1113  for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1114  {
1115  // Get indices of sub-element nodes in element
1116  auto sub_elem_nodes = get_sub_elem_nodes(secondary_side_elem->type(), sel);
1117 
1118  // Secondary sub-element center, normal, and nodes
1119  Point center;
1120  Point normal;
1121  std::vector<Point> nodes(sub_elem_nodes.size());
1122 
1123  // Loop through sub_element nodes, collect points and compute center and normal
1124  for (auto iv : make_range(sub_elem_nodes.size()))
1125  {
1126  const auto n = sub_elem_nodes[iv];
1127  nodes[iv] = secondary_side_elem->point(n);
1128  center += secondary_side_elem->point(n);
1129  normal += nodal_normals[n];
1130  }
1131  center /= sub_elem_nodes.size();
1132  normal = normal.unit();
1133 
1134  // Build and store linearized sub-elements for later use
1135  mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(nodes, center, normal);
1136  }
1137 
1143  // Search point for performing Nanoflann (k-d tree) searches.
1144  // In each case we use the center point of the original element (not sub-elements for second
1145  // order elements). This is to do search for all sub-elements simultaneously
1146  std::array<Real, 3> query_pt;
1147  Point center_point;
1148  switch (secondary_side_elem->type())
1149  {
1150  case TRI3:
1151  case QUAD4:
1152  center_point = mortar_segment_helper[0]->center();
1153  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1154  break;
1155  case TRI6:
1156  case TRI7:
1157  center_point = mortar_segment_helper[1]->center();
1158  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1159  break;
1160  case QUAD8:
1161  center_point = mortar_segment_helper[4]->center();
1162  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1163  break;
1164  case QUAD9:
1165  center_point = secondary_side_elem->point(8);
1166  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1167  break;
1168  default:
1169  mooseError(
1170  "Face element type: ", secondary_side_elem->type(), "not supported for 3D mortar");
1171  }
1172 
1173  // The number of results we want to get. These results will only be used to find
1174  // a single element with non-trivial overlap, after an element is identified a breadth
1175  // first search is done on neighbors
1176  const std::size_t num_results = 3;
1177 
1178  // Initialize result_set and do the search.
1179  std::vector<size_t> ret_index(num_results);
1180  std::vector<Real> out_dist_sqr(num_results);
1181  nanoflann::KNNResultSet<Real> result_set(num_results);
1182  result_set.init(&ret_index[0], &out_dist_sqr[0]);
1183  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
1184 
1185  // Initialize list of processed primary elements, we don't want to revisit processed elements
1186  std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1187 
1188  // Initialize candidate set and flag for switching between coarse screening and breadth-first
1189  // search
1190  bool primary_elem_found = false;
1191  std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1192 
1193  // Loop candidate nodes (returned by Nanoflann) and add all adjoining elems to candidate set
1194  for (auto r : make_range(result_set.size()))
1195  {
1196  // Verify that the squared distance we compute is the same as nanoflann's
1197  mooseAssert(abs((_mesh.point(ret_index[r]) - center_point).norm_sq() - out_dist_sqr[r]) <=
1198  TOLERANCE,
1199  "Lower-dimensional element squared distance verification failed.");
1200 
1201  // Get list of elems connected to node
1202  std::vector<const Elem *> & node_elems =
1203  this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
1204 
1205  // Uniquely add elems to candidate set
1206  for (auto elem : node_elems)
1207  primary_elem_candidates.insert(elem);
1208  }
1209 
1217  while (!primary_elem_candidates.empty())
1218  {
1219  const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1220 
1221  // If we've already processed this candidate, we don't need to check it again.
1222  if (processed_primary_elems.count(primary_elem_candidate))
1223  continue;
1224 
1225  // Initialize set of nodes used to construct mortar segment elements
1226  std::vector<Point> nodal_points;
1227 
1228  // Initialize map from mortar segment elements to nodes
1229  std::vector<std::vector<unsigned int>> elem_to_node_map;
1230 
1231  // Initialize list of secondary and primary sub-elements that formed each mortar segment
1232  std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1233 
1238  for (auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1239  {
1240  // Get nodes of primary sub-elements
1241  auto sub_elem_nodes = get_sub_elem_nodes(primary_elem_candidate->type(), p_el);
1242 
1243  // Get list of primary sub-element vertex nodes
1244  std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1245  for (auto iv : make_range(sub_elem_nodes.size()))
1246  {
1247  const auto n = sub_elem_nodes[iv];
1248  primary_sub_elem[iv] = primary_elem_candidate->point(n);
1249  }
1250 
1251  // Loop through secondary sub-elements
1252  for (auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1253  {
1254  // Mortar segment helpers were defined for each secondary sub-element, they will:
1255  // 1. Project primary sub-element onto linearized secondary sub-element
1256  // 2. Clip projected primary sub-element against secondary sub-element
1257  // 3. Triangulate clipped polygon to form mortar segments
1258  //
1259  // Mortar segment helpers append a list of mortar segment nodes and connectivities that
1260  // can be directly used to build mortar segments
1261  mortar_segment_helper[s_el]->getMortarSegments(
1262  primary_sub_elem, nodal_points, elem_to_node_map);
1263 
1264  // Keep track of which secondary and primary sub-elements created segment
1265  for (auto i = sub_elem_map.size(); i < elem_to_node_map.size(); ++i)
1266  sub_elem_map.push_back(std::make_pair(s_el, p_el));
1267  }
1268  }
1269 
1270  // Mark primary element as processed and remove from candidate list
1271  processed_primary_elems.insert(primary_elem_candidate);
1272  primary_elem_candidates.erase(primary_elem_candidate);
1273 
1274  // If overlap of polygons was non-trivial (created mortar segment elements)
1275  if (!elem_to_node_map.empty())
1276  {
1277  // If this is the first element with non-trivial overlap, set flag
1278  // Candidates will now be neighbors of elements that had non-trivial overlap
1279  // (i.e. we'll do a breadth first search now)
1280  if (!primary_elem_found)
1281  {
1282  primary_elem_found = true;
1283  primary_elem_candidates.clear();
1284  }
1285 
1286  // Add neighbors to candidate list
1287  for (auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1288  {
1289  // If not valid or not on lower dimensional secondary subdomain, skip
1290  if (neighbor == nullptr || neighbor->subdomain_id() != primary_subd_id)
1291  continue;
1292  // If already processed, skip
1293  if (processed_primary_elems.count(neighbor))
1294  continue;
1295  // Otherwise, add to candidates
1296  primary_elem_candidates.insert(neighbor);
1297  }
1298 
1302  std::vector<Node *> new_nodes;
1303  for (auto pt : nodal_points)
1304  new_nodes.push_back(_mortar_segment_mesh->add_point(
1305  pt, next_node_id++, secondary_side_elem->processor_id()));
1306 
1307  // Loop through triangular elements in map
1308  for (auto el : index_range(elem_to_node_map))
1309  {
1310  // Create new triangular element
1311  std::unique_ptr<Elem> new_elem;
1312  if (elem_to_node_map[el].size() == 3)
1313  new_elem = std::make_unique<Tri3>();
1314  else
1315  mooseError("Active mortar segments only supports TRI elements, 3 nodes expected "
1316  "but: ",
1317  elem_to_node_map[el].size(),
1318  " provided.");
1319 
1320  new_elem->processor_id() = secondary_side_elem->processor_id();
1321  new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1322  new_elem->set_id(next_elem_id++);
1323 
1324  // Attach newly created nodes
1325  for (auto i : index_range(elem_to_node_map[el]))
1326  new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1327 
1328  // If element is smaller than tolerance, don't add to msm
1329  if (new_elem->volume() / secondary_volume < TOLERANCE)
1330  continue;
1331 
1332  // Add elements to mortar segment mesh
1333  Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem.release());
1334 
1335  msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1336  msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1337 
1338  // Fill out mortar segment info
1339  MortarSegmentInfo msinfo;
1340  msinfo.secondary_elem = secondary_side_elem;
1341  msinfo.primary_elem = primary_elem_candidate;
1342 
1343  // Associate this MSM elem with the MortarSegmentInfo.
1344  _msm_elem_to_info.emplace(msm_new_elem, msinfo);
1345 
1346  // Add this mortar segment to the secondary elem to mortar segment map
1347  secondary_to_msm_element_set.insert(msm_new_elem);
1348 
1350  // Unlike for 2D, we always have a primary when building the mortar mesh so we don't
1351  // have to check for null
1353  }
1354  }
1355  // End loop through primary element candidates
1356  }
1357 
1358  for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1359  {
1360  // Check if any segments failed to project
1361  if (mortar_segment_helper[sel]->remainder() == 1.0)
1362  mooseDoOnce(
1363  mooseWarning("Some secondary elements on mortar interface were unable to identify"
1364  " a corresponding primary element; this may be expected depending on"
1365  " problem geometry but may indicate a failure of the element search"
1366  " or projection"));
1367  }
1368 
1369  if (secondary_to_msm_element_set.empty())
1370  _secondary_elems_to_mortar_segments.erase(secondary_elem_to_msm_map_it);
1371  } // End loop through secondary elements
1372  } // End loop through mortar constraint pairs
1373 
1374  _mortar_segment_mesh->cache_elem_data();
1375 
1376  // The mesh was built distributedly (each rank owns only its local elements), so mark it
1377  // as such so MeshSerializer correctly gathers it to proc 0 for Exodus output.
1378  _mortar_segment_mesh->set_distributed();
1379 
1380  // Output mortar segment mesh
1381  if (_debug)
1382  {
1383  // If element is not triangular, increment subdomain id
1384  // (ExodusII does not support mixed element types in a single subdomain)
1385  for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1386  if (msm_el->type() != TRI3)
1387  msm_el->subdomain_id()++;
1388 
1389  outputMortarMesh();
1390 
1391  // Undo increment
1392  for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1393  if (msm_el->type() != TRI3)
1394  msm_el->subdomain_id()--;
1395  }
1396 
1398 
1399  // Print mortar segment mesh statistics
1400  if (_debug)
1401  {
1402  msmStatistics();
1403  }
1404 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
ElemType
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
QUAD8
const Elem * interior_parent() const
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
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...
auto norm_sq(const T &a)
void outputMortarMesh()
Write the mortar segment mesh to exodus.
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition: MooseError.h:345
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const Parallel::Communicator & comm() const
void msmStatistics()
Prints mortar segment mesh statistics to console (calls computeMsmStatistics internally) ...
void buildCouplingInformation()
build the _mortar_interface_coupling data
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
Special adaptor that works with subdomains of the Mesh.
TRI3
QUAD4
const bool _debug
Whether to print debug output.
const Elem * primary_elem
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.
TypeVector< Real > unit() const
void libmesh_ignore(const Args &...)
dof_id_type id() const
TRI6
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
const Elem * secondary_elem
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Holds xi^(1), xi^(2), and other data for a given mortar segment.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
subdomain_id_type subdomain_id() const
virtual Real volume() const
IntRange< T > make_range(T beg, T end)
TRI7
unsigned int level ElemType type std::set< subdomain_id_type > ss processor_id_type pid unsigned int level std::set< subdomain_id_type > virtual ss SimpleRange< element_iterator > active_subdomain_elements_ptr_range(subdomain_id_type sid)=0
QUAD9
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
virtual const Point & point(const dof_id_type i) const=0
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...
SimpleRange< NeighborPtrIter > neighbor_ptr_range()
virtual unsigned int n_sub_elem() const=0
processor_id_type processor_id() const
std::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
SearchParams SearchParameters
processor_id_type processor_id() const
virtual ElemType type() const=0
const Point & point(const unsigned int i) const
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().
auto index_range(const T &sizable)
void set_extra_integer(const unsigned int index, const dof_id_type value)
uint8_t dof_id_type

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

Referenced by MortarInterfaceWarehouse::update().

308 {
310  mooseError(
311  "Must specify secondary and primary boundary ids before building node-to-elem maps.");
312 
313  // Construct nodes_to_secondary_elem_map
314  for (const auto & secondary_elem :
315  as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
316  {
317  // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
318  if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
319  continue;
320 
321  for (const auto & nd : secondary_elem->node_ref_range())
322  {
323  std::vector<const Elem *> & vec = _nodes_to_secondary_elem_map[nd.id()];
324  vec.push_back(secondary_elem);
325  }
326  }
327 
328  // Construct nodes_to_primary_elem_map
329  for (const auto & primary_elem :
330  as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
331  {
332  // If this is not one of the lower-dimensional primary side elements, go on to the next one.
333  if (!this->_primary_boundary_subdomain_ids.count(primary_elem->subdomain_id()))
334  continue;
335 
336  for (const auto & nd : primary_elem->node_ref_range())
337  {
338  std::vector<const Elem *> & vec = _nodes_to_primary_elem_map[nd.id()];
339  vec.push_back(primary_elem);
340  }
341  }
342 }
std::set< SubdomainID > _primary_boundary_subdomain_ids
std::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
std::set< BoundaryID > _primary_requested_boundary_ids
The boundary ids corresponding to all the primary surfaces.
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::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.

◆ clear()

void AutomaticMortarGeneration::clear ( )

Clears the mortar segment mesh and accompanying data structures.

Definition at line 286 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

287 {
288  _mortar_segment_mesh->clear();
293  _msm_elem_to_info.clear();
294  _lower_elem_to_side_id.clear();
300  _secondary_ip_sub_ids.clear();
301  _primary_ip_sub_ids.clear();
304 }
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...
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.
std::unordered_map< dof_id_type, const Elem * > _secondary_element_to_secondary_lowerd_element
Map from full dimensional secondary element id to lower dimensional secondary element.
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::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
std::unordered_map< std::pair< const Node *, const Elem * >, std::pair< Real, const Elem * > > _secondary_node_and_elem_to_xi2_primary_elem
Similar to the map above, but associates a (Secondary Node, Secondary Elem) pair to a (xi^(2)...
std::unordered_map< const 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< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
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::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.
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< 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< 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 ...

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

Referenced by MortarInterfaceWarehouse::update().

1779 {
1780  // Mark all active secondary elements
1781  std::unordered_set<const Elem *> active_local_elems;
1782 
1783  //****
1784  // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1785  // since the mortar segments are on the linearized element and comparing the volume of the
1786  // linearized element does not have the same volume as the warped element
1787  const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1788 
1789  std::unordered_map<const Elem *, Real> active_volume;
1790 
1791  // Compute fraction of elements with corresponding primary elements
1793  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1794  {
1795  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1796  const Elem * secondary_elem = msinfo.secondary_elem;
1797 
1798  active_volume[secondary_elem] += msm_elem->volume();
1799  }
1800  //****
1801 
1802  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1803  {
1804  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1805  const Elem * secondary_elem = msinfo.secondary_elem;
1806 
1807  //****
1809  if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
1810  continue;
1811  //****
1812 
1813  active_local_elems.insert(secondary_elem);
1814  }
1815 
1816  // Take complement of active elements in active local subdomain to get inactive local elements
1817  _inactive_local_lm_elems.clear();
1818  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1819  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1820  /*secondary_subd_id*/ pr.second))
1821  if (active_local_elems.find(el) == active_local_elems.end())
1822  _inactive_local_lm_elems.insert(el);
1823 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
const Elem * secondary_elem
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Holds xi^(1), xi^(2), and other data for a given mortar segment.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
virtual Real volume() const
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
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().

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

Referenced by MortarInterfaceWarehouse::update().

1695 {
1697  {
1699  return;
1700  }
1701 
1702  std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
1703  const auto my_pid = _mesh.processor_id();
1704 
1705  // List of active nodes on local secondary elements
1706  std::unordered_set<dof_id_type> active_local_nodes;
1707 
1708  // Mark all active local nodes
1709  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1710  {
1711  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1712  const Elem * secondary_elem = msinfo.secondary_elem;
1713 
1714  for (auto n : make_range(secondary_elem->n_nodes()))
1715  active_local_nodes.insert(secondary_elem->node_id(n));
1716  }
1717 
1718  // Assemble list of procs that nodes contribute to
1719  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1720  {
1721  const auto secondary_subd_id = pr.second;
1722 
1723  // Loop through all elements not on my processor
1724  for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1725  {
1726  // Get processor_id
1727  const auto pid = el->processor_id();
1728 
1729  // If element is in my subdomain, skip
1730  if (pid == my_pid)
1731  continue;
1732 
1733  // If element on proc pid shares any of my active nodes, mark to send
1734  for (const auto n : make_range(el->n_nodes()))
1735  {
1736  const auto node_id = el->node_id(n);
1737  if (active_local_nodes.find(node_id) != active_local_nodes.end())
1738  proc_to_active_nodes_set[pid].insert(node_id);
1739  }
1740  }
1741  }
1742 
1743  // Send list of active nodes
1744  {
1745  // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
1746  std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
1747  for (const auto & proc_set : proc_to_active_nodes_set)
1748  {
1749  proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
1750  for (const auto node_id : proc_set.second)
1751  proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
1752  }
1753 
1754  // First push data
1755  auto action_functor = [this, &active_local_nodes](const processor_id_type pid,
1756  const std::vector<dof_id_type> & sent_data)
1757  {
1758  if (pid == _mesh.processor_id())
1759  mooseError("Should not be communicating with self.");
1760  active_local_nodes.insert(sent_data.begin(), sent_data.end());
1761  };
1762  TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_active_nodes_vector, action_functor);
1763  }
1764 
1765  // Every proc has correct list of active local nodes, now take complement (list of inactive nodes)
1766  // and store to use later to zero LM DoFs on inactive nodes
1767  _inactive_local_lm_nodes.clear();
1768  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1769  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1770  /*secondary_subd_id*/ pr.second))
1771  for (const auto n : make_range(el->n_nodes()))
1772  if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
1773  _inactive_local_lm_nodes.insert(el->node_ptr(n));
1774 }
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const Parallel::Communicator & comm() const
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
uint8_t processor_id_type
virtual unsigned int n_nodes() const=0
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
void computeIncorrectEdgeDroppingInactiveLMNodes()
Computes inactive secondary nodes when incorrect edge dropping behavior is enabled (any node touching...
const Elem * secondary_elem
Holds xi^(1), xi^(2), and other data for a given mortar segment.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
IntRange< T > make_range(T beg, T end)
unsigned int level ElemType type std::set< subdomain_id_type > ss processor_id_type pid unsigned int level std::set< subdomain_id_type > virtual ss SimpleRange< element_iterator > active_subdomain_elements_ptr_range(subdomain_id_type sid)=0
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
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::unordered_set< const Node * > _inactive_local_lm_nodes
processor_id_type processor_id() const
processor_id_type processor_id() const
dof_id_type node_id(const unsigned int i) const
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

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

Referenced by computeInactiveLMNodes().

1601 {
1602  using std::abs;
1603 
1604  // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1605  // since the mortar segments are on the linearized element and comparing the volume of the
1606  // linearized element does not have the same volume as the warped element
1607  const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1608 
1609  std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1610  const auto my_pid = _mesh.processor_id();
1611 
1612  // List of inactive nodes on local secondary elements
1613  std::unordered_set<dof_id_type> inactive_node_ids;
1614 
1615  std::unordered_map<const Elem *, Real> active_volume{};
1616 
1617  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1618  for (const auto el : _mesh.active_subdomain_elements_ptr_range(pr.second))
1619  active_volume[el] = 0.;
1620 
1621  // Compute fraction of elements with corresponding primary elements
1622  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1623  {
1624  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1625  const Elem * secondary_elem = msinfo.secondary_elem;
1626 
1627  active_volume[secondary_elem] += msm_elem->volume();
1628  }
1629 
1630  // Mark all inactive local nodes
1631  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1632  // Loop through all elements on my processor
1633  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(pr.second))
1634  // If elem fully or partially dropped
1635  if (abs(active_volume[el] / el->volume() - 1.0) > tol)
1636  {
1637  // Add all nodes to list of inactive
1638  for (auto n : make_range(el->n_nodes()))
1639  inactive_node_ids.insert(el->node_id(n));
1640  }
1641 
1642  // Assemble list of procs that nodes contribute to
1643  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1644  {
1645  const auto secondary_subd_id = pr.second;
1646 
1647  // Loop through all elements not on my processor
1648  for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1649  {
1650  // Get processor_id
1651  const auto pid = el->processor_id();
1652 
1653  // If element is in my subdomain, skip
1654  if (pid == my_pid)
1655  continue;
1656 
1657  // If element on proc pid shares any of my inactive nodes, mark to send
1658  for (const auto n : make_range(el->n_nodes()))
1659  {
1660  const auto node_id = el->node_id(n);
1661  if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1662  proc_to_inactive_nodes_set[pid].insert(node_id);
1663  }
1664  }
1665  }
1666 
1667  // Send list of inactive nodes
1668  {
1669  // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
1670  std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1671  for (const auto & proc_set : proc_to_inactive_nodes_set)
1672  proc_to_inactive_nodes_vector[proc_set.first].insert(
1673  proc_to_inactive_nodes_vector[proc_set.first].end(),
1674  proc_set.second.begin(),
1675  proc_set.second.end());
1676 
1677  // First push data
1678  auto action_functor = [this, &inactive_node_ids](const processor_id_type pid,
1679  const std::vector<dof_id_type> & sent_data)
1680  {
1681  if (pid == _mesh.processor_id())
1682  mooseError("Should not be communicating with self.");
1683  for (const auto pr : sent_data)
1684  inactive_node_ids.insert(pr);
1685  };
1686  TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_inactive_nodes_vector, action_functor);
1687  }
1688  _inactive_local_lm_nodes.clear();
1689  for (const auto node_id : inactive_node_ids)
1690  _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1691 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const Parallel::Communicator & comm() const
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
uint8_t processor_id_type
const Elem * secondary_elem
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Holds xi^(1), xi^(2), and other data for a given mortar segment.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
virtual Real volume() const
IntRange< T > make_range(T beg, T end)
unsigned int level ElemType type std::set< subdomain_id_type > ss processor_id_type pid unsigned int level std::set< subdomain_id_type > virtual ss SimpleRange< element_iterator > active_subdomain_elements_ptr_range(subdomain_id_type sid)=0
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
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...
virtual const Node * node_ptr(const dof_id_type i) const=0
std::unordered_set< const Node * > _inactive_local_lm_nodes
processor_id_type processor_id() const
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

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

Referenced by msmStatistics().

1479 {
1480  std::vector<MsmSubdomainStats> result;
1481  StatisticsVector<Real> primary;
1482  StatisticsVector<Real> secondary;
1484  std::unordered_map<dof_id_type, Real> primary_elems_to_volume;
1485 
1486  for (const auto & [primary_subd_id, secondary_subd_id] : _primary_secondary_subdomain_id_pairs)
1487  {
1488  for (const auto * const secondary_el :
1489  _mesh.active_local_subdomain_element_ptr_range(secondary_subd_id))
1490  {
1491  secondary.push_back(secondary_el->volume());
1492  // We may not have projected onto a primary face in which case we may not have created mortar
1493  // segments
1494  if (auto it = _secondary_elems_to_mortar_segments.find(secondary_el->id());
1496  for (const auto * const msm_elem : it->second)
1497  {
1498  msm.push_back(msm_elem->volume());
1499  const auto & msm_info = libmesh_map_find(_msm_elem_to_info, msm_elem);
1500  // Now it's also possible that we didn't project onto a primary face and we *did* create
1501  // mortar segments
1502  if (msm_info.primary_elem)
1503  {
1504  if (msm_info.primary_elem->subdomain_id() != primary_subd_id)
1505  mooseError("Unhandled primary-secondary pairing when computing mortar segment "
1506  "statistics. This could happen if you have the same secondary "
1507  "lower-dimensional subdomain ID paired with multiple lower-dimensional "
1508  "primary subdomain IDs. Contact a MOOSE developer for help.");
1509  if (const auto [it, inserted] =
1510  primary_elems_to_volume.emplace(msm_info.primary_elem->id(), Real{});
1511  inserted)
1512  it->second = msm_info.primary_elem->volume();
1513  else
1514  mooseAssert(
1515  MooseUtils::absoluteFuzzyEqual(it->second, msm_info.primary_elem->volume()),
1516  "Volumes should be consistent");
1517  }
1518  }
1519  }
1520 
1521  _mesh.comm().set_union(primary_elems_to_volume);
1522  _mesh.comm().allgather(static_cast<std::vector<Real> &>(secondary));
1523  _mesh.comm().allgather(static_cast<std::vector<Real> &>(msm));
1524  primary.reserve(primary_elems_to_volume.size());
1525  for (const auto [_, volume] : primary_elems_to_volume)
1526  primary.push_back(volume);
1527 
1528  MsmSubdomainStats stats;
1529  stats.primary_subd_id = primary_subd_id;
1530  stats.secondary_subd_id = secondary_subd_id;
1531  stats.secondary_lower_n_elems = secondary.size();
1532  stats.secondary_lower_max_volume = secondary.maximum();
1533  stats.secondary_lower_min_volume = secondary.minimum();
1534  stats.secondary_lower_median_volume = secondary.median();
1535  stats.primary_lower_n_elems = primary.size();
1536  stats.primary_lower_max_volume = primary.maximum();
1537  stats.primary_lower_min_volume = primary.minimum();
1538  stats.primary_lower_median_volume = primary.median();
1539  stats.msm_n_elems = msm.size();
1540  stats.msm_max_volume = msm.maximum();
1541  stats.msm_min_volume = msm.minimum();
1542  stats.msm_median_volume = msm.median();
1543  result.push_back(stats);
1544 
1545  primary.clear();
1546  secondary.clear();
1547  msm.clear();
1548  primary_elems_to_volume.clear();
1549  }
1550 
1551  return result;
1552 }
virtual T maximum() const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
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...
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const Parallel::Communicator & comm() const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual T minimum() const
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
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...
void set_union(T &data, const unsigned int root_id) const

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

Referenced by MortarInterfaceWarehouse::update().

1827 {
1828  // The dimension according to Mesh::mesh_dimension().
1829  const auto dim = _mesh.mesh_dimension();
1830 
1831  mooseAssert(dim == 2 || dim == 3,
1832  "AutomaticMortarGeneration::computeNodalGeometry() is only valid for "
1833  "mortar constraints on 2D or 3D meshes.");
1834  // A nodal lower-dimensional nodal quadrature rule to be used on faces.
1835  QNodal qface(dim - 1);
1836 
1837  // A map from the node id to the attached elemental normals/weights evaluated at the node. Th
1838  // length of the vector will correspond to the number of elements attached to the node. If it is a
1839  // vertex node, for a 1D mortar mesh, the vector length will be two. If it is an interior node,
1840  // the vector will be length 1. The first member of the pair is that element's normal at the node.
1841  // The second member is that element's JxW at the node
1842  std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
1843 
1845  Real sign = _periodic ? -1 : 1;
1846 
1847  // First loop over lower-dimensional secondary side elements and compute/save the outward normal
1848  // for each one. We loop over all active elements currently, but this procedure could be
1849  // parallelized as well.
1850  for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
1851  end_el = _mesh.active_elements_end();
1852  el != end_el;
1853  ++el)
1854  {
1855  const Elem * secondary_elem = *el;
1856 
1857  // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
1858  if (!_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
1859  continue;
1860 
1861  // We will create an FE object and attach the nodal quadrature rule such that we can get out the
1862  // normals at the element nodes
1863  FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
1864  std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(dim, nnx_fe_type));
1865  nnx_fe_face->attach_quadrature_rule(&qface);
1866  const std::vector<Point> & face_normals = nnx_fe_face->get_normals();
1867 
1868  const auto & JxW = nnx_fe_face->get_JxW();
1869 
1870  // Which side of the parent are we? We need to know this to know
1871  // which side to reinit.
1872  const Elem * interior_parent = secondary_elem->interior_parent();
1873  mooseAssert(interior_parent,
1874  "No interior parent exists for element "
1875  << secondary_elem->id()
1876  << ". There may be a problem with your sideset set-up.");
1877 
1878  // Map to get lower dimensional element from interior parent on secondary surface
1879  // This map can be used to provide a handle to methods in this class that need to
1880  // operate on lower dimensional elements.
1881  _secondary_element_to_secondary_lowerd_element.emplace(interior_parent->id(), secondary_elem);
1882 
1883  // Look up which side of the interior parent secondary_elem is.
1884  auto s = interior_parent->which_side_am_i(secondary_elem);
1885 
1886  // Reinit the face FE object on side s.
1887  nnx_fe_face->reinit(interior_parent, s);
1888 
1889  for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
1890  {
1891  auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
1892  normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[n], JxW[n]));
1893  }
1894  }
1895 
1896  // Note that contrary to the Bin Yang dissertation, we are not weighting by the face element
1897  // lengths/volumes. It's not clear to me that this type of weighting is a good algorithm for cases
1898  // where the face can be curved
1899  for (const auto & pr : node_to_normals_map)
1900  {
1901  // Compute normal vector
1902  const auto & node_id = pr.first;
1903  const auto & normals_and_weights_vec = pr.second;
1904 
1905  Point nodal_normal;
1906  for (const auto & norm_and_weight : normals_and_weights_vec)
1907  nodal_normal += norm_and_weight.first * norm_and_weight.second;
1908  nodal_normal = nodal_normal.unit();
1909 
1910  _secondary_node_to_nodal_normal[_mesh.node_ptr(node_id)] = nodal_normal;
1911 
1912  Point nodal_tangent_one;
1913  Point nodal_tangent_two;
1914  householderOrthogolization(nodal_normal, nodal_tangent_one, nodal_tangent_two);
1915 
1916  _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][0] = nodal_tangent_one;
1917  _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][1] = nodal_tangent_two;
1918  }
1919 }
LAGRANGE
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
const Elem * interior_parent() const
std::set< SubdomainID > _secondary_boundary_subdomain_ids
The secondary/primary lower-dimensional boundary subdomain ids are the secondary/primary boundary ids...
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
unsigned int which_side_am_i(const Elem *e) const
TypeVector< Real > unit() const
dof_id_type id() const
virtual unsigned int n_nodes() const=0
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.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
subdomain_id_type subdomain_id() const
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...
KOKKOS_INLINE_FUNCTION T sign(T x)
Returns the sign of a value.
Definition: KokkosUtils.h:26
unsigned int mesh_dimension() const
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
virtual const Node * node_ptr(const dof_id_type i) const=0
virtual Order default_order() const=0
void householderOrthogolization(const Point &normal, Point &tangent_one, Point &tangent_two) const
Householder orthogonalization procedure to obtain proper basis for tangent and binormal vectors...
dof_id_type node_id(const unsigned int i) const

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

Referenced by ComputeMortarFunctor::operator()().

328  {
330  }
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)

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

Referenced by ComputeMortarFunctor::operator()().

320  {
322  }
std::unordered_set< const Node * > _inactive_local_lm_nodes

◆ getNodalNormals()

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

Definition at line 345 of file AutomaticMortarGeneration.C.

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

346 {
347  std::vector<Point> nodal_normals(secondary_elem.n_nodes());
348  for (const auto n : make_range(secondary_elem.n_nodes()))
349  nodal_normals[n] = _secondary_node_to_nodal_normal.at(secondary_elem.node_ptr(n));
350 
351  return nodal_normals;
352 }
virtual unsigned int n_nodes() const=0
const Node * node_ptr(const unsigned int i) const
IntRange< T > make_range(T beg, T end)
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.

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

399 {
400  // MetaPhysicL will check if we ran out of allocated space.
401  MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
402  MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
403 
404  for (const auto n : make_range(secondary_elem.n_nodes()))
405  {
406  const auto & tangent_vectors =
407  libmesh_map_find(_secondary_node_to_hh_nodal_tangents, secondary_elem.node_ptr(n));
408  nodal_tangents_one.push_back(tangent_vectors[0]);
409  nodal_tangents_two.push_back(tangent_vectors[1]);
410  }
411 
412  return {{nodal_tangents_one, nodal_tangents_two}};
413 }
virtual unsigned int n_nodes() const=0
const Node * node_ptr(const unsigned int i) const
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...
IntRange< T > make_range(T beg, T end)

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

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

429 {
430  const auto mortar_dim = _mesh.mesh_dimension() - 1;
431  const auto num_qps = xi1_pts.size();
432  const auto nodal_normals = getNodalNormals(secondary_elem);
433  std::vector<Point> normals(num_qps);
434 
435  for (const auto n : make_range(secondary_elem.n_nodes()))
436  for (const auto qp : make_range(num_qps))
437  {
438  const auto phi =
439  (mortar_dim == 1)
440  ? Moose::fe_lagrange_1D_shape(secondary_elem.default_order(), n, xi1_pts[qp](0))
441  : Moose::fe_lagrange_2D_shape(secondary_elem.type(),
442  secondary_elem.default_order(),
443  n,
444  static_cast<const TypeVector<Real> &>(xi1_pts[qp]));
445  normals[qp] += phi * nodal_normals[n];
446  }
447 
448  if (_periodic)
449  for (auto & normal : normals)
450  normal *= -1;
451 
452  return normals;
453 }
T fe_lagrange_2D_shape(const libMesh::ElemType type, const Order order, const unsigned int i, const VectorType< T > &p)
const bool _periodic
Whether this object will be generating a mortar segment mesh for periodic constraints.
std::vector< Point > getNodalNormals(const Elem &secondary_elem) const
virtual unsigned int n_nodes() const=0
IntRange< T > make_range(T beg, T end)
unsigned int mesh_dimension() const
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
virtual Order default_order() const=0
virtual ElemType type() const=0

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

418 {
419  std::vector<Point> xi1_pts(oned_xi1_pts.size());
420  for (const auto qp : index_range(oned_xi1_pts))
421  xi1_pts[qp] = oned_xi1_pts[qp];
422 
423  return getNormals(secondary_elem, xi1_pts);
424 }
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.
auto index_range(const T &sizable)

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

385 {
386  std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
387 
388  for (const auto i : make_range(lower_primary_elem.n_nodes()))
389  {
390  const auto & nd = lower_primary_elem.node_ref(i);
391  primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
392  }
393 
394  return primary_ip_i_to_lower_primary_i;
395 }
unsigned int get_node_index(const Node *node_ptr) const
IntRange< T > make_range(T beg, T end)

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

366 {
367  std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
368  const Elem * const secondary_ip = lower_secondary_elem.interior_parent();
369  mooseAssert(secondary_ip, "This should be non-null");
370 
371  for (const auto i : make_range(lower_secondary_elem.n_nodes()))
372  {
373  const auto & nd = lower_secondary_elem.node_ref(i);
374  secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
375  }
376 
377  return secondary_ip_i_to_lower_secondary_i;
378 }
unsigned int get_node_index(const Node *node_ptr) const
const Elem * interior_parent() const
const Node & node_ref(const unsigned int i) const
virtual unsigned int n_nodes() const=0
IntRange< T > make_range(T beg, T end)

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

357 {
358  mooseAssert(_secondary_element_to_secondary_lowerd_element.count(secondary_elem_id),
359  "Map should locate secondary element");
360 
361  return _secondary_element_to_secondary_lowerd_element.at(secondary_elem_id);
362 }
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.

◆ householderOrthogolization()

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

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

Definition at line 1922 of file AutomaticMortarGeneration.C.

Referenced by computeNodalGeometry().

1925 {
1926  using std::abs;
1927 
1928  mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.norm(), 1),
1929  "The input nodal normal should have unity norm");
1930 
1931  const Real nx = nodal_normal(0);
1932  const Real ny = nodal_normal(1);
1933  const Real nz = nodal_normal(2);
1934 
1935  // See Lopes DS, Silva MT, Ambrosio JA. Tangent vectors to a 3-D surface normal: A geometric tool
1936  // to find orthogonal vectors based on the Householder transformation. Computer-Aided Design. 2013
1937  // Mar 1;45(3):683-94. We choose one definition of h_vector and deal with special case.
1938  const Point h_vector(nx + 1.0, ny, nz);
1939 
1940  // Avoid singularity of the equations at the end of routine by providing the solution to
1941  // (nx,ny,nz)=(-1,0,0) Normal/tangent fields can be visualized by outputting nodal geometry mesh
1942  // on a spherical problem.
1943  if (abs(h_vector(0)) < TOLERANCE)
1944  {
1945  nodal_tangent_one(0) = 0;
1946  nodal_tangent_one(1) = 1;
1947  nodal_tangent_one(2) = 0;
1948 
1949  nodal_tangent_two(0) = 0;
1950  nodal_tangent_two(1) = 0;
1951  nodal_tangent_two(2) = -1;
1952 
1953  return;
1954  }
1955 
1956  const Real h = h_vector.norm();
1957 
1958  nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
1959  nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
1960  nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
1961 
1962  nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
1963  nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
1964  nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
1965 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 332 of file AutomaticMortarGeneration.h.

Referenced by ComputeMortarFunctor::operator()().

332 { return !_correct_edge_dropping; }
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...

◆ initOutput()

void AutomaticMortarGeneration::initOutput ( )

initialize mortar-mesh based output

Definition at line 270 of file AutomaticMortarGeneration.C.

271 {
272  if (!_debug)
273  return;
274 
275  _output_params = std::make_unique<InputParameters>(MortarNodalGeometryOutput::validParams());
276  _output_params->set<AutomaticMortarGeneration *>("_amg") = this;
277  _output_params->set<FEProblemBase *>("_fe_problem_base") = &_app.feProblem();
279  _output_params->set<std::string>(MooseBase::name_param) =
280  "mortar_nodal_geometry_" + mortarInterfaceName();
281  _output_params->finalize("MortarNodalGeometryOutput");
282  _app.getOutputWarehouse().addOutput(std::make_shared<MortarNodalGeometryOutput>(*_output_params));
283 }
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
Base class for MOOSE-based applications.
Definition: MooseApp.h:108
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
FEProblemBase & feProblem() const
Definition: MooseApp.C:1652
This class is a container/interface for the objects involved in automatic generation of mortar spaces...
const bool _debug
Whether to print debug output.
std::unique_ptr< InputParameters > _output_params
Storage for the input parameters used by the mortar nodal geometry output.
static InputParameters validParams()
void addOutput(std::shared_ptr< Output > output)
Adds an existing output object to the warehouse.
MooseApp & _app
The Moose app.
OutputWarehouse & getOutputWarehouse()
Get the OutputWarehouse objects.
Definition: MooseApp.C:2136

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

201 { _msm_node_id_start = std::nullopt; }
std::optional< dof_id_type > _msm_node_id_start
Cached per-rank starting ID for 3D MSM nodes/elements.

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

292  {
294  }
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...

◆ mortarInterfaceName()

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

Definition at line 256 of file AutomaticMortarGeneration.C.

Referenced by initOutput(), and outputMortarMesh().

257 {
258  std::vector<std::string> string_vec(_primary_secondary_boundary_id_pairs.size() * 2 + 1);
260  {
261  const auto [primary_bnd_id, secondary_bnd_id] = _primary_secondary_boundary_id_pairs[i];
262  string_vec[2 * i] = std::to_string(primary_bnd_id);
263  string_vec[2 * i + 1] = std::to_string(secondary_bnd_id);
264  }
265  string_vec.back() = _on_displaced ? "displaced" : "undisplaced";
266  return MooseUtils::join(string_vec, "_");
267 }
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 _on_displaced
Whether this object is on the displaced mesh.
auto index_range(const T &sizable)

◆ mortarSegmentMesh()

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

Definition at line 304 of file AutomaticMortarGeneration.h.

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

304 { return *_mortar_segment_mesh; }
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

◆ mortarSegmentMeshElemToInfo()

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

Definition at line 309 of file AutomaticMortarGeneration.h.

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

310  {
311  return _msm_elem_to_info;
312  }
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.

◆ msmStatistics()

void AutomaticMortarGeneration::msmStatistics ( )

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

Definition at line 1555 of file AutomaticMortarGeneration.C.

Referenced by buildMortarSegmentMesh3d().

1556 {
1557  const auto all_stats = computeMsmStatistics();
1558 
1559  if (_mesh.processor_id() != 0)
1560  return;
1561 
1562  Moose::out << "Mortar Interface Statistics:" << std::endl;
1563  for (const auto & stats : all_stats)
1564  {
1565  std::vector<std::string> col_names = {"mesh", "n_elems", "max", "min", "median"};
1566  std::vector<std::string> subds = {"secondary_lower", "primary_lower", "mortar_segment"};
1567  std::vector<size_t> n_elems = {
1568  stats.secondary_lower_n_elems, stats.primary_lower_n_elems, stats.msm_n_elems};
1569  std::vector<Real> maxs = {
1570  stats.secondary_lower_max_volume, stats.primary_lower_max_volume, stats.msm_max_volume};
1571  std::vector<Real> mins = {
1572  stats.secondary_lower_min_volume, stats.primary_lower_min_volume, stats.msm_min_volume};
1573  std::vector<Real> medians = {stats.secondary_lower_median_volume,
1574  stats.primary_lower_median_volume,
1575  stats.msm_median_volume};
1576 
1577  FormattedTable table;
1578  table.clear();
1579  for (auto i : index_range(subds))
1580  {
1581  table.addRow(i);
1582  table.addData<std::string>(col_names[0], subds[i]);
1583  table.addData<size_t>(col_names[1], n_elems[i]);
1584  table.addData<Real>(col_names[2], maxs[i]);
1585  table.addData<Real>(col_names[3], mins[i]);
1586  table.addData<Real>(col_names[4], medians[i]);
1587  }
1588 
1589  Moose::out << "secondary subdomain: " << stats.secondary_subd_id
1590  << " \tprimary subdomain: " << stats.primary_subd_id << std::endl;
1591  table.printTable(Moose::out, subds.size());
1592  }
1593 }
void addData(const std::string &name, const T &value)
Method for adding data to the output table.
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.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< MsmSubdomainStats > computeMsmStatistics()
Computes mortar segment mesh statistics and returns one entry per subdomain pair. ...
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
void addRow(Real time)
Force a new row in the table with the passed in time.
processor_id_type processor_id() const
auto index_range(const T &sizable)

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

369  {
371  }
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.

◆ onDisplaced()

bool AutomaticMortarGeneration::onDisplaced ( ) const
inline

returns whether this object is on the displaced mesh

Definition at line 206 of file AutomaticMortarGeneration.h.

206 { return _on_displaced; }
const bool _on_displaced
Whether this object is on the displaced mesh.

◆ outputMortarMesh()

void AutomaticMortarGeneration::outputMortarMesh ( )
private

Write the mortar segment mesh to exodus.

Definition at line 948 of file AutomaticMortarGeneration.C.

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

949 {
950  ExodusII_IO mortar_segment_mesh_writer(*_mortar_segment_mesh);
951 
952  // Default to non-HDF5 output for wider compatibility
953  mortar_segment_mesh_writer.set_hdf5_writing(false);
954 
955  std::array<std::string, 3> file_pieces = {
956  _app.getOutputFileBase(/*for_non_moose_build_output=*/true),
958  "mortar_segment_mesh.e"};
959  mortar_segment_mesh_writer.write(MooseUtils::join(file_pieces, "_"));
960 }
std::string getOutputFileBase(bool for_non_moose_build_output=false) const
Get the output file base name.
Definition: MooseApp.C:1356
MooseApp & _app
The Moose app.
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

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

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

363 { return _primary_ip_sub_ids; }
std::set< SubdomainID > _primary_ip_sub_ids
All the primary interior parent subdomain IDs associated with the mortar mesh.

◆ primarySecondaryBoundaryIDPair()

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

Definition at line 584 of file AutomaticMortarGeneration.h.

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

585 {
586  mooseAssert(_primary_secondary_boundary_id_pairs.size() == 1,
587  "We currently only support a single boundary pair per mortar generation object");
588 
590 }
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...

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

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

1987 {
1988  if (!secondary_node_neighbors)
1989  secondary_node_neighbors = &libmesh_map_find(_nodes_to_secondary_elem_map, secondary_node.id());
1990  if (!primary_node_neighbors)
1991  primary_node_neighbors = &libmesh_map_find(_nodes_to_primary_elem_map, primary_node.id());
1992 
1993  std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(), false);
1994 
1995  // Add entries to secondary_node_and_elem_to_xi2_primary_elem container.
1996  //
1997  // First, determine "on left" vs. "on right" orientation of the nodal neighbors.
1998  // There can be a max of 2 nodal neighbors, and we want to make sure that the
1999  // secondary nodal neighbor on the "left" is associated with the primary nodal
2000  // neighbor on the "left" and similarly for the "right". We use cross products to determine
2001  // alignment. In the below diagram, 'x' denotes a node, and connected '|' are lower dimensional
2002  // elements.
2003  // x
2004  // x |
2005  // | |
2006  // secondary x ----> x primary
2007  // | |
2008  // | x
2009  // x
2010  //
2011  // Looking at the aligned nodes, the secondary node first, if we pick the top secondary lower
2012  // dimensional element, then the cross product as written a few lines below points out of the
2013  // screen towards you. (Point in the direction of the secondary nodal normal, and then curl your
2014  // hand towards the secondary element's opposite node, then the thumb points in the direction of
2015  // the cross product). Doing the same with the aligned primary node, if we pick the top primary
2016  // element, then the cross product also points out of the screen. Because the cross products
2017  // point in the same direction (positive dot product), then we know to associate the
2018  // secondary-primary element pair. If we had picked the bottom primary element whose cross
2019  // product points into the screen, then clearly the cross products point in the opposite
2020  // direction and we don't have a match
2021  std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
2022 
2023  for (const auto nn : index_range(*secondary_node_neighbors))
2024  {
2025  const Elem * const secondary_neigh = (*secondary_node_neighbors)[nn];
2026  const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
2027  ? secondary_neigh->point(1)
2028  : secondary_neigh->point(0);
2029  const Point cp = nodal_normal.cross(opposite - secondary_node);
2030  secondary_node_neighbor_cps[nn] = cp(2);
2031  }
2032 
2033  for (const auto nn : index_range(*primary_node_neighbors))
2034  {
2035  const Elem * const primary_neigh = (*primary_node_neighbors)[nn];
2036  const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
2037  : primary_neigh->point(0);
2038  const Point cp = nodal_normal.cross(opposite - primary_node);
2039  primary_node_neighbor_cps[nn] = cp(2);
2040  }
2041 
2042  // Associate secondary/primary elems on matching sides.
2043  bool found_match = false;
2044  for (const auto snn : index_range(*secondary_node_neighbors))
2045  for (const auto mnn : index_range(*primary_node_neighbors))
2046  if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
2047  {
2048  found_match = true;
2049  if (primary_elems_mapped[mnn])
2050  continue;
2051  primary_elems_mapped[mnn] = true;
2052 
2053  // Figure out xi^(2) value by looking at which node primary_node is
2054  // of the current primary node neighbor.
2055  const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
2056  const auto secondary_key =
2057  std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
2058  const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
2059  _secondary_node_and_elem_to_xi2_primary_elem.emplace(secondary_key, primary_val);
2060 
2061  // Also map in the other direction.
2062  const Real xi1 =
2063  (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
2064 
2065  const auto primary_key =
2066  std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
2067  const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
2068  _primary_node_and_elem_to_xi1_secondary_elem.emplace(primary_key, secondary_val);
2069  }
2070 
2071  if (!found_match)
2072  {
2073  // There could be coincident nodes and this might be a bad primary candidate (see
2074  // issue #21680). Instead of giving up, let's try continuing
2075  rejected_elem_candidates.insert(&candidate_element);
2076  return false;
2077  }
2078 
2079  // We need to handle the case where we've exactly projected a secondary node onto a
2080  // primary node, but our secondary node is at one of the secondary boundary face endpoints and
2081  // our primary node is not.
2082  if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
2083  for (const auto i : index_range(primary_elems_mapped))
2084  if (!primary_elems_mapped[i])
2085  {
2087  std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
2088  std::make_pair(1, nullptr));
2089  }
2090 
2091  return found_match;
2092 }
dof_id_type id() const
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::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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)...
const Node * node_ptr(const unsigned int i) const
const Point & point(const unsigned int i) const
auto index_range(const T &sizable)
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.

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

Referenced by MortarInterfaceWarehouse::update().

2341 {
2342  // For each primary/secondary boundary id pair, call the
2343  // project_primary_nodes_single_pair() helper function.
2344  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2345  projectPrimaryNodesSinglePair(pr.first, pr.second);
2346 }
void projectPrimaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function used internally by AutomaticMortarGeneration::project_primary_nodes().
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...

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

Referenced by projectPrimaryNodes().

2352 {
2353  using std::abs;
2354 
2355  // Build a Nanoflann object on the lower-dimensional secondary elements of the Mesh.
2356  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_secondary_subdomain_id);
2357  subdomain_kd_tree_t kd_tree(
2358  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
2359 
2360  // Construct the KD tree for lower-dimensional elements in the volume mesh.
2361  kd_tree.buildIndex();
2362 
2363  std::unordered_set<dof_id_type> primary_nodes_visited;
2364 
2365  for (const auto & primary_side_elem : _mesh.active_element_ptr_range())
2366  {
2367  // If this is not one of the lower-dimensional primary side elements, go on to the next one.
2368  if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2369  continue;
2370 
2371  // For each node on this side, find the nearest node on the secondary side using the KDTree,
2372  // then search in nearby elements for where it projects along the nodal normal direction.
2373  for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2374  ++n)
2375  {
2376  // Get a pointer to this node.
2377  const Node * primary_node = primary_side_elem->node_ptr(n);
2378 
2379  // Get the nodal neighbors connected to this primary node.
2380  const std::vector<const Elem *> & primary_node_neighbors =
2381  _nodes_to_primary_elem_map.at(primary_node->id());
2382 
2383  // Check whether we have already successfully inverse mapped this primary node (whether during
2384  // secondary node projection or now during primary node projection) or we have already failed
2385  // to inverse map this primary node (now during primary node projection), and then skip if
2386  // either of those things is true
2387  auto primary_key =
2388  std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2389  if (!primary_nodes_visited.insert(primary_node->id()).second ||
2391  continue;
2392 
2393  // Data structure for performing Nanoflann searches.
2394  Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2395 
2396  // The number of results we want to get. We'll look for a
2397  // "few" nearest nodes, hopefully that is enough to let us
2398  // figure out which lower-dimensional Elem on the secondary side
2399  // we are across from.
2400  const size_t num_results = 3;
2401 
2402  // Initialize result_set and do the search.
2403  std::vector<size_t> ret_index(num_results);
2404  std::vector<Real> out_dist_sqr(num_results);
2405  nanoflann::KNNResultSet<Real> result_set(num_results);
2406  result_set.init(&ret_index[0], &out_dist_sqr[0]);
2407  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2408 
2409  // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2410  bool projection_succeeded = false;
2411 
2412  // Once we've rejected a candidate for a given
2413  // primary_node, there's no reason to check it
2414  // again.
2415  std::set<const Elem *> rejected_secondary_elem_candidates;
2416 
2417  // Loop over the closest nodes, check whether the secondary node successfully projects into
2418  // either of the closest neighbors, stop when the projection succeeds.
2419  for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2420  {
2421  // Verify that the squared distance we compute is the same as nanoflann's
2422  mooseAssert(abs((_mesh.point(ret_index[r]) - *primary_node).norm_sq() - out_dist_sqr[r]) <=
2423  TOLERANCE,
2424  "Lower-dimensional element squared distance verification failed.");
2425 
2426  // Get a reference to the vector of lower dimensional elements from the
2427  // nodes_to_secondary_elem_map.
2428  const std::vector<const Elem *> & secondary_elem_candidates =
2429  _nodes_to_secondary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2430 
2431  // Print the Elems connected to this node on the secondary mesh side.
2432  for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2433  {
2434  const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2435 
2436  // If we've already rejected this candidate, we don't need to check it again.
2437  if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2438  continue;
2439 
2440  std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2441  for (const auto n : make_range(secondary_elem_candidate->n_nodes()))
2442  nodal_normals[n] =
2443  _secondary_node_to_nodal_normal.at(secondary_elem_candidate->node_ptr(n));
2444 
2445  // Use equation 2.4.6 from Bin Yang's dissertation to try and solve for
2446  // the position on the secondary element where this primary came from. This
2447  // requires a Newton iteration in general.
2448  DualNumber<Real> xi1_dn{0, 1}; // initial guess
2449  auto && order = secondary_elem_candidate->default_order();
2450  unsigned int current_iterate = 0, max_iterates = 10;
2451 
2452  VectorValue<DualNumber<Real>> normals(0);
2453 
2454  // Newton iteration loop - this to converge in 1 iteration when it
2455  // succeeds, and possibly two iterations when it converges to a
2456  // xi outside the reference element. I don't know any reason why it should
2457  // only take 1 iteration -- the Jacobian is not constant in general...
2458  do
2459  {
2461  for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2462  n < secondary_elem_candidate->n_nodes();
2463  ++n)
2464  {
2465  const auto phi = Moose::fe_lagrange_1D_shape(order, n, xi1_dn);
2466  x1 += phi * secondary_elem_candidate->point(n);
2467  normals += phi * nodal_normals[n];
2468  }
2469 
2470  const auto u = x1 - (*primary_node);
2471 
2472  const auto F = u(0) * normals(1) - u(1) * normals(0);
2473 
2474  if (abs(F) < _newton_tolerance)
2475  break;
2476 
2477  // Unlike for projection of nodal normals onto primary surfaces, we should never have a
2478  // case where the nodal normal is completely orthogonal to the secondary surface, so we
2479  // do not have to guard against F.derivatives() == 0 here
2480  Real dxi1 = -F.value() / F.derivatives();
2481 
2482  xi1_dn += dxi1;
2483 
2484  normals = 0;
2485  } while (++current_iterate < max_iterates);
2486 
2487  Real xi1 = xi1_dn.value();
2488 
2489  // Check for convergence to a valid solution... The last condition checks for obliqueness
2490  // of the projection
2491  if ((current_iterate < max_iterates) && (abs(xi1) <= 1. + _xi_tolerance) &&
2492  (abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2493  MetaPhysicL::raw_value(normals).unit()) <
2495  {
2496  if (abs(abs(xi1) - 1.) < _xi_tolerance)
2497  {
2498  // Special case: xi1=+/-1.
2499  // It is unlikely that we get here, because this primary node should already
2500  // have been mapped during the project_secondary_nodes() routine, but
2501  // there is still a chance since the tolerances are applied to
2502  // the xi coordinate and that value may be different on a primary element and a
2503  // secondary element since they may have different sizes. It's also possible that we
2504  // may reach this point if the solve has yielded a non-physical configuration such as
2505  // one block being pushed way out into space
2506  const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2507  : secondary_elem_candidate->node_ref(1);
2508  bool created_mortar_segment = false;
2509 
2510  // If we have failed to project this secondary node, let's try again now
2511  if (_failed_secondary_node_projections.count(secondary_node.id()))
2512  created_mortar_segment = processAlignedNodes(secondary_node,
2513  *primary_node,
2514  nullptr,
2515  &primary_node_neighbors,
2516  MetaPhysicL::raw_value(normals),
2517  *secondary_elem_candidate,
2518  rejected_secondary_elem_candidates);
2519  else
2520  rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2521 
2522  if (!created_mortar_segment)
2523  // We used to throw an exception in this scope but now that we support processing
2524  // aligned nodes within this primary node projection method, I don't see any harm in
2525  // simply rejecting the secondary element candidate in the case of failure and
2526  // continuing just as we do when projecting secondary nodes
2527  continue;
2528  }
2529  else // somewhere in the middle of the Elem
2530  {
2531  // Add entry to primary_node_and_elem_to_xi1_secondary_elem
2532  //
2533  // Note: we originally duplicated the map values for the keys (node, left_neighbor)
2534  // and (node, right_neighbor) but I don't think that should be necessary. Instead we
2535  // just do it for neighbor 0, but really maybe we don't even need to do that since
2536  // we can always look up the neighbors later given the Node... keeping it like this
2537  // helps to maintain the "symmetry" of the two containers.
2538  const Elem * neigh = primary_node_neighbors[0];
2539  for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2540  {
2541  const Node * neigh_node = neigh->node_ptr(nid);
2542  if (primary_node == neigh_node)
2543  {
2544  auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2545  auto val = std::make_pair(xi1, secondary_elem_candidate);
2547  }
2548  }
2549  }
2550 
2551  projection_succeeded = true;
2552  break; // out of e-loop
2553  }
2554  else
2555  {
2556  // The current primary_point is not in this Elem, so keep track of the rejects.
2557  rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2558  }
2559  } // end e-loop over candidate elems
2560 
2561  if (projection_succeeded)
2562  break; // out of r-loop
2563  } // r-loop
2564 
2565  if (!projection_succeeded && _debug)
2566  {
2567  _console << "\nFailed to find point from which primary node "
2568  << static_cast<const Point &>(*primary_node) << " was projected." << std::endl
2569  << std::endl;
2570  }
2571  } // loop over side nodes
2572  } // end loop over elements for finding where primary points would have projected from.
2573 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
auto norm_sq(const T &a)
Real _newton_tolerance
Newton solve tolerance for node projections.
auto raw_value(const Eigen::Map< T > &in)
Definition: EigenADReal.h:100
Special adaptor that works with subdomains of the Mesh.
const bool _debug
Whether to print debug output.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template cos(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(cos
const Node & node_ref(const unsigned int i) const
dof_id_type id() const
virtual unsigned int n_nodes() const=0
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::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
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.
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
Real _xi_tolerance
Tolerance for checking projection xi values.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
virtual unsigned int n_vertices() const=0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const Node * node_ptr(const unsigned int i) const
IntRange< T > make_range(T beg, T end)
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments...
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
virtual const Point & point(const dof_id_type i) const=0
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
virtual Order default_order() const=0
SearchParams SearchParameters
const Point & point(const unsigned int i) const
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.
const Real pi

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

Referenced by MortarInterfaceWarehouse::update().

1971 {
1972  // For each primary/secondary boundary id pair, call the
1973  // project_secondary_nodes_single_pair() helper function.
1974  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1975  projectSecondaryNodesSinglePair(pr.first, pr.second);
1976 }
void projectSecondaryNodesSinglePair(SubdomainID lower_dimensional_primary_subdomain_id, SubdomainID lower_dimensional_secondary_subdomain_id)
Helper function responsible for projecting secondary nodes onto primary elements for a single primary...
std::vector< 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...

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

Referenced by projectSecondaryNodes().

2098 {
2099  using std::abs;
2100 
2101  // Build the "subdomain" adaptor based KD Tree.
2102  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_primary_subdomain_id);
2103  subdomain_kd_tree_t kd_tree(
2104  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
2105 
2106  // Construct the KD tree.
2107  kd_tree.buildIndex();
2108 
2109  for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
2110  end_el = _mesh.active_elements_end();
2111  el != end_el;
2112  ++el)
2113  {
2114  const Elem * secondary_side_elem = *el;
2115 
2116  // If this Elem is not in the current secondary subdomain, go on to the next one.
2117  if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2118  continue;
2119 
2120  // For each node on the lower-dimensional element, find the nearest
2121  // node on the primary side using the KDTree, then
2122  // search in nearby elements for where it projects
2123  // along the nodal normal direction.
2124  for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2125  ++n)
2126  {
2127  const Node * secondary_node = secondary_side_elem->node_ptr(n);
2128 
2129  // Get the nodal neighbors for secondary_node, so we can check whether we've
2130  // already successfully projected it.
2131  const std::vector<const Elem *> & secondary_node_neighbors =
2132  this->_nodes_to_secondary_elem_map.at(secondary_node->id());
2133 
2134  // Check whether we've already mapped this secondary node
2135  // successfully for all of its nodal neighbors.
2136  bool is_mapped = true;
2137  for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2138  ++snn)
2139  {
2140  auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2141  if (!_secondary_node_and_elem_to_xi2_primary_elem.count(secondary_key))
2142  {
2143  is_mapped = false;
2144  break;
2145  }
2146  }
2147 
2148  // Go to the next node if this one has already been mapped.
2149  if (is_mapped)
2150  continue;
2151 
2152  // Look up the new nodal normal value in the local storage, error if not found.
2153  Point nodal_normal = _secondary_node_to_nodal_normal.at(secondary_node);
2154 
2155  // Data structure for performing Nanoflann searches.
2156  std::array<Real, 3> query_pt = {
2157  {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2158 
2159  // The number of results we want to get. We'll look for a
2160  // "few" nearest nodes, hopefully that is enough to let us
2161  // figure out which lower-dimensional Elem on the primary
2162  // side we are across from.
2163  const std::size_t num_results = 3;
2164 
2165  // Initialize result_set and do the search.
2166  std::vector<size_t> ret_index(num_results);
2167  std::vector<Real> out_dist_sqr(num_results);
2168  nanoflann::KNNResultSet<Real> result_set(num_results);
2169  result_set.init(&ret_index[0], &out_dist_sqr[0]);
2170  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2171 
2172  // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2173  bool projection_succeeded = false;
2174 
2175  // Once we've rejected a candidate for a given secondary_node,
2176  // there's no reason to check it again.
2177  std::set<const Elem *> rejected_primary_elem_candidates;
2178 
2179  // Loop over the closest nodes, check whether
2180  // the secondary node successfully projects into
2181  // either of the closest neighbors, stop when
2182  // the projection succeeds.
2183  for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2184  {
2185  // Verify that the squared distance we compute is the same as nanoflann'sFss
2186  mooseAssert(abs((_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2187  out_dist_sqr[r]) <= TOLERANCE,
2188  "Lower-dimensional element squared distance verification failed.");
2189 
2190  // Get a reference to the vector of lower dimensional elements from the
2191  // nodes_to_primary_elem_map.
2192  std::vector<const Elem *> & primary_elem_candidates =
2193  this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2194 
2195  // Search the Elems connected to this node on the primary mesh side.
2196  for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2197  {
2198  const Elem * primary_elem_candidate = primary_elem_candidates[e];
2199 
2200  // If we've already rejected this candidate, we don't need to check it again.
2201  if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2202  continue;
2203 
2204  // Now generically solve for xi2
2205  const auto order = primary_elem_candidate->default_order();
2206  DualNumber<Real> xi2_dn{0, 1};
2207  unsigned int current_iterate = 0, max_iterates = 10;
2208 
2209  // Newton loop
2210  do
2211  {
2213  for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2214  n < primary_elem_candidate->n_nodes();
2215  ++n)
2216  x2 +=
2217  Moose::fe_lagrange_1D_shape(order, n, xi2_dn) * primary_elem_candidate->point(n);
2218  const auto u = x2 - (*secondary_node);
2219  const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2220 
2221  if (abs(F) < _newton_tolerance)
2222  break;
2223 
2224  if (F.derivatives())
2225  {
2226  Real dxi2 = -F.value() / F.derivatives();
2227 
2228  xi2_dn += dxi2;
2229  }
2230  else
2231  // It's possible that the secondary surface nodal normal is completely orthogonal to
2232  // the primary surface normal, in which case the derivative is 0. We know in this case
2233  // that the projection should be a failure
2234  current_iterate = max_iterates;
2235  } while (++current_iterate < max_iterates);
2236 
2237  Real xi2 = xi2_dn.value();
2238 
2239  // Check whether the projection worked. The last condition checks for obliqueness of the
2240  // projection
2241  //
2242  // We are projecting on one side first and the other side second. If we make the
2243  // tolerance bigger and remove the (5) factor we are going to continue to miss the
2244  // second projection and fall into the exception message in
2245  // projectPrimaryNodesSinglePair. What makes this modification to not fall in the
2246  // exception is that we are projecting on one side more xi than in the other. There
2247  // should be a better way of doing this by using actual distances and not parametric
2248  // coordinates. But I believe making the tolerance uniformly larger or smaller won't do
2249  // the trick here.
2250  if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 * _xi_tolerance) &&
2251  (abs((primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2252  nodal_normal) < std::cos(_minimum_projection_angle * libMesh::pi / 180)))
2253  {
2254  // If xi2 == +1 or -1 then this secondary node mapped directly to a node on the primary
2255  // surface. This isn't as unlikely as you might think, it will happen if the meshes
2256  // on the interface start off being perfectly aligned. In this situation, we need to
2257  // associate the secondary node with two different elements (and two corresponding
2258  // xi^(2) values.
2259  if (abs(abs(xi2) - 1.) <= _xi_tolerance * 5.0)
2260  {
2261  const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2262  : primary_elem_candidate->node_ptr(1);
2263  const bool created_mortar_segment =
2264  processAlignedNodes(*secondary_node,
2265  *primary_node,
2266  &secondary_node_neighbors,
2267  nullptr,
2268  nodal_normal,
2269  *primary_elem_candidate,
2270  rejected_primary_elem_candidates);
2271 
2272  if (!created_mortar_segment)
2273  continue;
2274  }
2275  else // Point falls somewhere in the middle of the Elem.
2276  {
2277  // Add two entries to secondary_node_and_elem_to_xi2_primary_elem.
2278  for (MooseIndex(secondary_node_neighbors) nn = 0;
2279  nn < secondary_node_neighbors.size();
2280  ++nn)
2281  {
2282  const Elem * neigh = secondary_node_neighbors[nn];
2283  for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2284  {
2285  const Node * neigh_node = neigh->node_ptr(nid);
2286  if (secondary_node == neigh_node)
2287  {
2288  auto key = std::make_pair(neigh_node, neigh);
2289  auto val = std::make_pair(xi2, primary_elem_candidate);
2291  }
2292  }
2293  }
2294  }
2295 
2296  projection_succeeded = true;
2297  break; // out of e-loop
2298  }
2299  else
2300  // The current secondary_node is not in this Elem, so keep track of the rejects.
2301  rejected_primary_elem_candidates.insert(primary_elem_candidate);
2302  }
2303 
2304  if (projection_succeeded)
2305  break; // out of r-loop
2306  } // r-loop
2307 
2308  if (!projection_succeeded)
2309  {
2310  _failed_secondary_node_projections.insert(secondary_node->id());
2311  if (_debug)
2312  _console << "Failed to find primary Elem into which secondary node "
2313  << static_cast<const Point &>(*secondary_node) << ", id '"
2314  << secondary_node->id() << "', projects onto\n"
2315  << std::endl;
2316  }
2317  else if (_debug)
2318  _projected_secondary_nodes.insert(secondary_node->id());
2319  } // loop over side nodes
2320  } // end loop over lower-dimensional elements
2321 
2322  if (_distributed)
2323  {
2324  if (_debug)
2327  }
2328 
2329  if (_debug)
2330  _console << "\n"
2331  << _projected_secondary_nodes.size() << " out of "
2333  << " secondary nodes were successfully projected\n"
2334  << std::endl;
2335 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
auto norm_sq(const T &a)
Real _newton_tolerance
Newton solve tolerance for node projections.
const bool _distributed
Whether the mortar segment mesh is distributed.
const Parallel::Communicator & comm() const
Special adaptor that works with subdomains of the Mesh.
std::unordered_set< dof_id_type > _projected_secondary_nodes
Debugging container for printing information about fraction of successful projections for secondary n...
const bool _debug
Whether to print debug output.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template cos(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(cos
dof_id_type id() const
virtual unsigned int n_nodes() const=0
std::unordered_map< dof_id_type, std::vector< const Elem * > > _nodes_to_primary_elem_map
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.
nanoflann::KDTreeSingleIndexAdaptor< subdomain_adatper_t, NanoflannMeshSubdomainAdaptor< 3 >, 3 > subdomain_kd_tree_t
Real _xi_tolerance
Tolerance for checking projection xi values.
std::unordered_set< dof_id_type > _failed_secondary_node_projections
Secondary nodes that failed to project.
virtual unsigned int n_vertices() const=0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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)...
subdomain_id_type subdomain_id() const
const Node * node_ptr(const unsigned int i) const
T fe_lagrange_1D_shape(const Order order, const unsigned int i, const T &xi)
const Real _minimum_projection_angle
Parameter to control which angle (in degrees) is admissible for the creation of mortar segments...
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
virtual const Point & point(const dof_id_type i) const=0
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
std::unordered_map< const Node *, Point > _secondary_node_to_nodal_normal
Container for storing the nodal normal vector associated with each secondary node.
virtual Order default_order() const=0
SearchParams SearchParameters
const Point & point(const unsigned int i) const
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.
const Real pi
void set_union(T &data, const unsigned int root_id) const

◆ secondariesToMortarSegments() [1/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 2576 of file AutomaticMortarGeneration.C.

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

2577 {
2578  auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2579  if (secondary_it == _nodes_to_secondary_elem_map.end())
2580  return {};
2581 
2582  const auto & secondary_elems = secondary_it->second;
2583  std::vector<MortarFilterIter> ret;
2584  ret.reserve(secondary_elems.size());
2585 
2586  for (const auto i : index_range(secondary_elems))
2587  {
2588  auto * const secondary_elem = secondary_elems[i];
2589  auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2590  if (msm_it == _secondary_elems_to_mortar_segments.end())
2591  // We may have removed this element key from this map
2592  continue;
2593 
2594  mooseAssert(secondary_elem->active(),
2595  "We loop over active elements when building the mortar segment mesh, so we golly "
2596  "well hope this is active.");
2597  mooseAssert(!msm_it->second.empty(),
2598  "We should have removed all secondaries from this map if they do not have any "
2599  "mortar segments associated with them.");
2600  ret.push_back(msm_it);
2601  }
2602 
2603  return ret;
2604 }
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...
dof_id_type id() const
auto index_range(const T &sizable)
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.

◆ secondariesToMortarSegments() [2/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 350 of file AutomaticMortarGeneration.h.

351  {
353  }
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...

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

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

358 { return _secondary_ip_sub_ids; }
std::set< SubdomainID > _secondary_ip_sub_ids
All the secondary interior parent subdomain IDs associated with the mortar mesh.

Friends And Related Function Documentation

◆ AugmentSparsityOnInterface

friend class AugmentSparsityOnInterface
friend

Definition at line 580 of file AutomaticMortarGeneration.h.

◆ MortarNodalGeometryOutput

friend class MortarNodalGeometryOutput
friend

Definition at line 579 of file AutomaticMortarGeneration.h.

Member Data Documentation

◆ _app

MooseApp& AutomaticMortarGeneration::_app
private

The Moose app.

Definition at line 395 of file AutomaticMortarGeneration.h.

Referenced by initOutput(), and outputMortarMesh().

◆ _console

const ConsoleStream ConsoleStreamInterface::_console
inherited

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

Definition at line 31 of file ConsoleStreamInterface.h.

Referenced by IterationAdaptiveDT::acceptStep(), MeshOnlyAction::act(), SetupDebugAction::act(), MaterialOutputAction::act(), Adaptivity::adaptMesh(), FEProblemBase::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::checkNonMatchingEdges(), MeshDiagnosticsGenerator::checkNonPlanarSides(), MeshDiagnosticsGenerator::checkPolygons(), FEProblemBase::checkProblemIntegrity(), ReferenceResidualConvergence::checkRelativeConvergence(), MeshDiagnosticsGenerator::checkSidesetsOrientation(), MeshDiagnosticsGenerator::checkWatertightNodesets(), MeshDiagnosticsGenerator::checkWatertightSidesets(), IterationAdaptiveDT::computeAdaptiveDT(), TransientBase::computeConstrainedDT(), DefaultMultiAppFixedPointConvergence::computeCustomConvergencePostprocessor(), NonlinearSystemBase::computeDamping(), FixedPointIterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeDT(), IterationAdaptiveDT::computeFailedDT(), IterationAdaptiveDT::computeInitialDT(), IterationAdaptiveDT::computeInterpolationDT(), LinearSystem::computeLinearSystemTags(), FEProblemBase::computeLinearSystemTags(), NonlinearSystemBase::computeScaling(), Problem::console(), IterationAdaptiveDT::constrainStep(), TimeStepper::constrainStep(), MultiApp::createApp(), FEProblemBase::execMultiApps(), FEProblemBase::execMultiAppTransfers(), MFEMSteady::execute(), MessageFromInput::execute(), SteadyBase::execute(), Eigenvalue::execute(), ActionWarehouse::executeActionsWithAction(), ActionWarehouse::executeAllActions(), MeshGeneratorSystem::executeMeshGenerators(), ElementQualityChecker::finalize(), SidesetAroundSubdomainUpdater::finalize(), FEProblemBase::finishMultiAppStep(), MeshRepairGenerator::fixOverlappingNodes(), SurfaceSubdomainsDelaunayRemesher::General2DDelaunay(), OrientSurfaceMeshGenerator::generate(), CoarsenBlockGenerator::generate(), PolyLineMeshFollowingNodeSetGenerator::generate(), MeshGenerator::generateInternal(), VariableCondensationPreconditioner::getDofToCondense(), InversePowerMethod::init(), NonlinearEigen::init(), FEProblemBase::initialAdaptMesh(), DefaultMultiAppFixedPointConvergence::initialize(), SubProblem::initialSetup(), EigenExecutionerBase::inversePowerIteration(), FEProblemBase::joinAndFinalize(), TransientBase::keepGoing(), IterationAdaptiveDT::limitDTByFunction(), IterationAdaptiveDT::limitDTToPostprocessorValue(), FEProblemBase::logAdd(), EigenExecutionerBase::makeBXConsistent(), Console::meshChanged(), SurfaceDelaunayGeneratorBase::meshNormalDeviation2D(), MooseBase::mooseDeprecated(), MooseBase::mooseDeprecatedNoTrace(), MooseBase::mooseInfo(), MooseBase::mooseWarning(), MooseBase::mooseWarningNonPrefixed(), ReferenceResidualConvergence::nonlinearConvergenceSetup(), ReporterDebugOutput::output(), PerfGraphOutput::output(), SolutionInvalidityOutput::output(), MaterialPropertyDebugOutput::output(), DOFMapOutput::output(), VariableResidualNormsDebugOutput::output(), Console::output(), ControlOutput::outputActiveObjects(), ControlOutput::outputChangedControls(), ControlOutput::outputControls(), Console::outputInput(), WebServerControl::outputMessage(), Console::outputPostprocessors(), PseudoTimestep::outputPseudoTimestep(), Console::outputReporters(), DefaultMultiAppFixedPointConvergence::outputResidualNorm(), Console::outputScalarVariables(), Console::outputSystemInformation(), FEProblemBase::possiblyRebuildGeomSearchPatches(), EigenExecutionerBase::postExecute(), AB2PredictorCorrector::postSolve(), ActionWarehouse::printActionDependencySets(), BlockRestrictionDebugOutput::printBlockRestrictionMap(), SolutionInvalidity::printDebug(), EigenExecutionerBase::printEigenvalue(), SecantSolve::printFixedPointConvergenceHistory(), SteffensenSolve::printFixedPointConvergenceHistory(), PicardSolve::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(), EigenProblem::solve(), FEProblemSolve::solve(), FixedPointSolve::solve(), NonlinearSystem::solve(), LinearSystem::solve(), LStableDirk2::solve(), LStableDirk3::solve(), ImplicitMidpoint::solve(), ExplicitTVDRK2::solve(), AStableDirk4::solve(), LStableDirk4::solve(), ExplicitRK2::solve(), TransientMultiApp::solveStep(), FixedPointSolve::solveStep(), MeshRepairGenerator::splitNonConvexPolygons(), PerfGraphLivePrint::start(), WebServerControl::startServer(), AB2PredictorCorrector::step(), NonlinearEigen::takeStep(), MFEMTransient::takeStep(), TransientBase::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 556 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 544 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 577 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 488 of file AutomaticMortarGeneration.h.

Referenced by computeInactiveLMElems(), and getInactiveLMElems().

◆ _inactive_local_lm_nodes

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

◆ _lower_elem_to_side_id

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

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

Definition at line 453 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 562 of file AutomaticMortarGeneration.h.

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

◆ _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 472 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 444 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 449 of file AutomaticMortarGeneration.h.

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

◆ _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 570 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 547 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 411 of file AutomaticMortarGeneration.h.

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

◆ _on_displaced

const bool AutomaticMortarGeneration::_on_displaced
private

Whether this object is on the displaced mesh.

Definition at line 538 of file AutomaticMortarGeneration.h.

Referenced by mortarInterfaceName(), and onDisplaced().

◆ _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 565 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 541 of file AutomaticMortarGeneration.h.

Referenced by computeNodalGeometry(), and getNormals().

◆ _primary_boundary_subdomain_ids

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

Definition at line 462 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 500 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 440 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 404 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 408 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 457 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 574 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 461 of file AutomaticMortarGeneration.h.

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

◆ _secondary_element_to_secondary_lowerd_element

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

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

Definition at line 482 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 494 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), computeMsmStatistics(), 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 497 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 432 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 479 of file AutomaticMortarGeneration.h.

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

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

Referenced by clear(), computeNodalGeometry(), getNodalNormals(), 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 401 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), and buildNodeToElemMaps().

◆ _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 552 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 64 of file AutomaticMortarGeneration.h.


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