https://mooseframework.inl.gov
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]

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...
 
void msmStatistics ()
 Outputs mesh statistics for mortar segment mesh. More...
 
void clear ()
 Clears the mortar segment mesh and accompanying data structures. 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 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::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 56 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 300 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  // We always ghost the entire mortar interface when it is allowed to displace
235  _correct_edge_dropping(correct_edge_dropping),
236  _minimum_projection_angle(minimum_projection_angle)
237 {
238  _primary_secondary_boundary_id_pairs.push_back(boundary_key);
239  _primary_requested_boundary_ids.insert(boundary_key.first);
240  _secondary_requested_boundary_ids.insert(boundary_key.second);
241  _primary_secondary_subdomain_id_pairs.push_back(subdomain_key);
242  _primary_boundary_subdomain_ids.insert(subdomain_key.first);
243  _secondary_boundary_subdomain_ids.insert(subdomain_key.second);
244 
245  if (_distributed)
246  _mortar_segment_mesh = std::make_unique<DistributedMesh>(_mesh.comm());
247  else
248  _mortar_segment_mesh = std::make_unique<ReplicatedMesh>(_mesh.comm());
249 }
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.
virtual bool is_replicated() 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 1358 of file AutomaticMortarGeneration.C.

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

1359 {
1360  std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>>
1361  coupling_info;
1362 
1363  // Loop over the msm_elem_to_info object and build a bi-directional
1364  // multimap from secondary elements to the primary Elems which they are
1365  // coupled to and vice-versa. This is used in the
1366  // AugmentSparsityOnInterface functor to determine whether a given
1367  // secondary Elem is coupled across the mortar interface to a primary
1368  // element.
1369  for (const auto & pr : _msm_elem_to_info)
1370  {
1371  const Elem * secondary_elem = pr.second.secondary_elem;
1372  const Elem * primary_elem = pr.second.primary_elem;
1373 
1374  // LowerSecondary
1375  coupling_info[secondary_elem->processor_id()].emplace_back(
1376  secondary_elem->id(), secondary_elem->interior_parent()->id());
1377  if (secondary_elem->processor_id() != _mesh.processor_id())
1378  // We want to keep information for nonlocal lower-dimensional secondary element point
1379  // neighbors for mortar nodal aux kernels
1380  _mortar_interface_coupling[secondary_elem->id()].insert(
1381  secondary_elem->interior_parent()->id());
1382 
1383  // LowerPrimary
1384  coupling_info[secondary_elem->processor_id()].emplace_back(
1385  secondary_elem->id(), primary_elem->interior_parent()->id());
1386  if (secondary_elem->processor_id() != _mesh.processor_id())
1387  // We want to keep information for nonlocal lower-dimensional secondary element point
1388  // neighbors for mortar nodal aux kernels
1389  _mortar_interface_coupling[secondary_elem->id()].insert(
1390  primary_elem->interior_parent()->id());
1391 
1392  // Lower-LowerDimensionalPrimary
1393  coupling_info[secondary_elem->processor_id()].emplace_back(secondary_elem->id(),
1394  primary_elem->id());
1395  if (secondary_elem->processor_id() != _mesh.processor_id())
1396  // We want to keep information for nonlocal lower-dimensional secondary element point
1397  // neighbors for mortar nodal aux kernels
1398  _mortar_interface_coupling[secondary_elem->id()].insert(primary_elem->id());
1399 
1400  // SecondaryLower
1401  coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1402  secondary_elem->interior_parent()->id(), secondary_elem->id());
1403 
1404  // SecondaryPrimary
1405  coupling_info[secondary_elem->interior_parent()->processor_id()].emplace_back(
1406  secondary_elem->interior_parent()->id(), primary_elem->interior_parent()->id());
1407 
1408  // PrimaryLower
1409  coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1410  primary_elem->interior_parent()->id(), secondary_elem->id());
1411 
1412  // PrimarySecondary
1413  coupling_info[primary_elem->interior_parent()->processor_id()].emplace_back(
1414  primary_elem->interior_parent()->id(), secondary_elem->interior_parent()->id());
1415  }
1416 
1417  // Push the coupling information
1418  auto action_functor =
1419  [this](processor_id_type,
1420  const std::vector<std::pair<dof_id_type, dof_id_type>> & coupling_info)
1421  {
1422  for (auto [i, j] : coupling_info)
1423  _mortar_interface_coupling[i].insert(j);
1424  };
1425  TIMPI::push_parallel_vector_data(_mesh.comm(), coupling_info, action_functor);
1426 }
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 441 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

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

Referenced by MortarInterfaceWarehouse::update().

939 {
940  // Add an integer flag to mortar segment mesh to keep track of which subelem
941  // of second order primal elements mortar segments correspond to
942  auto secondary_sub_elem = _mortar_segment_mesh->add_elem_integer("secondary_sub_elem");
943  auto primary_sub_elem = _mortar_segment_mesh->add_elem_integer("primary_sub_elem");
944 
945  dof_id_type local_id_index = 0;
946 
947  // Loop through mortar secondary and primary pairs to create mortar segment mesh between each
948  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
949  {
950  const auto primary_subd_id = pr.first;
951  const auto secondary_subd_id = pr.second;
952 
953  // Build k-d tree for use in Step 1.2 for primary interface coarse screening
954  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, primary_subd_id);
955  subdomain_kd_tree_t kd_tree(
956  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
957 
958  // Construct the KD tree.
959  kd_tree.buildIndex();
960 
961  // Define expression for getting sub-elements nodes (for sub-dividing secondary elements)
962  auto get_sub_elem_nodes = [](const ElemType type,
963  const unsigned int sub_elem) -> std::vector<unsigned int>
964  {
965  switch (type)
966  {
967  case TRI3:
968  return {{0, 1, 2}};
969  case QUAD4:
970  return {{0, 1, 2, 3}};
971  case TRI6:
972  case TRI7:
973  switch (sub_elem)
974  {
975  case 0:
976  return {{0, 3, 5}};
977  case 1:
978  return {{3, 4, 5}};
979  case 2:
980  return {{3, 1, 4}};
981  case 3:
982  return {{5, 4, 2}};
983  default:
984  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
985  }
986  case QUAD8:
987  switch (sub_elem)
988  {
989  case 0:
990  return {{0, 4, 7}};
991  case 1:
992  return {{4, 1, 5}};
993  case 2:
994  return {{5, 2, 6}};
995  case 3:
996  return {{7, 6, 3}};
997  case 4:
998  return {{4, 5, 6, 7}};
999  default:
1000  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
1001  }
1002  case QUAD9:
1003  switch (sub_elem)
1004  {
1005  case 0:
1006  return {{0, 4, 8, 7}};
1007  case 1:
1008  return {{4, 1, 5, 8}};
1009  case 2:
1010  return {{8, 5, 2, 6}};
1011  case 3:
1012  return {{7, 8, 6, 3}};
1013  default:
1014  mooseError("get_sub_elem_nodes: Invalid sub_elem: ", sub_elem);
1015  }
1016  default:
1017  mooseError("get_sub_elem_inds: Face element type: ",
1018  libMesh::Utility::enum_to_string<ElemType>(type),
1019  " invalid for 3D mortar");
1020  }
1021  };
1022 
1026  for (MeshBase::const_element_iterator el = _mesh.active_local_elements_begin(),
1027  end_el = _mesh.active_local_elements_end();
1028  el != end_el;
1029  ++el)
1030  {
1031  const Elem * secondary_side_elem = *el;
1032 
1033  const Real secondary_volume = secondary_side_elem->volume();
1034 
1035  // If this Elem is not in the current secondary subdomain, go on to the next one.
1036  if (secondary_side_elem->subdomain_id() != secondary_subd_id)
1037  continue;
1038 
1039  auto [secondary_elem_to_msm_map_it, insertion_happened] =
1040  _secondary_elems_to_mortar_segments.emplace(secondary_side_elem->id(),
1041  std::set<Elem *, CompareDofObjectsByID>{});
1042  libmesh_ignore(insertion_happened);
1043  auto & secondary_to_msm_element_set = secondary_elem_to_msm_map_it->second;
1044 
1045  std::vector<std::unique_ptr<MortarSegmentHelper>> mortar_segment_helper(
1046  secondary_side_elem->n_sub_elem());
1047  const auto nodal_normals = getNodalNormals(*secondary_side_elem);
1048 
1059  for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1060  {
1061  // Get indices of sub-element nodes in element
1062  auto sub_elem_nodes = get_sub_elem_nodes(secondary_side_elem->type(), sel);
1063 
1064  // Secondary sub-element center, normal, and nodes
1065  Point center;
1066  Point normal;
1067  std::vector<Point> nodes(sub_elem_nodes.size());
1068 
1069  // Loop through sub_element nodes, collect points and compute center and normal
1070  for (auto iv : make_range(sub_elem_nodes.size()))
1071  {
1072  const auto n = sub_elem_nodes[iv];
1073  nodes[iv] = secondary_side_elem->point(n);
1074  center += secondary_side_elem->point(n);
1075  normal += nodal_normals[n];
1076  }
1077  center /= sub_elem_nodes.size();
1078  normal = normal.unit();
1079 
1080  // Build and store linearized sub-elements for later use
1081  mortar_segment_helper[sel] = std::make_unique<MortarSegmentHelper>(nodes, center, normal);
1082  }
1083 
1089  // Search point for performing Nanoflann (k-d tree) searches.
1090  // In each case we use the center point of the original element (not sub-elements for second
1091  // order elements). This is to do search for all sub-elements simultaneously
1092  std::array<Real, 3> query_pt;
1093  Point center_point;
1094  switch (secondary_side_elem->type())
1095  {
1096  case TRI3:
1097  case QUAD4:
1098  center_point = mortar_segment_helper[0]->center();
1099  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1100  break;
1101  case TRI6:
1102  case TRI7:
1103  center_point = mortar_segment_helper[1]->center();
1104  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1105  break;
1106  case QUAD8:
1107  center_point = mortar_segment_helper[4]->center();
1108  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1109  break;
1110  case QUAD9:
1111  center_point = secondary_side_elem->point(8);
1112  query_pt = {{center_point(0), center_point(1), center_point(2)}};
1113  break;
1114  default:
1115  mooseError(
1116  "Face element type: ", secondary_side_elem->type(), "not supported for 3D mortar");
1117  }
1118 
1119  // The number of results we want to get. These results will only be used to find
1120  // a single element with non-trivial overlap, after an element is identified a breadth
1121  // first search is done on neighbors
1122  const std::size_t num_results = 3;
1123 
1124  // Initialize result_set and do the search.
1125  std::vector<size_t> ret_index(num_results);
1126  std::vector<Real> out_dist_sqr(num_results);
1127  nanoflann::KNNResultSet<Real> result_set(num_results);
1128  result_set.init(&ret_index[0], &out_dist_sqr[0]);
1129  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
1130 
1131  // Initialize list of processed primary elements, we don't want to revisit processed elements
1132  std::set<const Elem *, CompareDofObjectsByID> processed_primary_elems;
1133 
1134  // Initialize candidate set and flag for switching between coarse screening and breadth-first
1135  // search
1136  bool primary_elem_found = false;
1137  std::set<const Elem *, CompareDofObjectsByID> primary_elem_candidates;
1138 
1139  // Loop candidate nodes (returned by Nanoflann) and add all adjoining elems to candidate set
1140  for (auto r : make_range(result_set.size()))
1141  {
1142  // Verify that the squared distance we compute is the same as nanoflann's
1143  mooseAssert(std::abs((_mesh.point(ret_index[r]) - center_point).norm_sq() -
1144  out_dist_sqr[r]) <= TOLERANCE,
1145  "Lower-dimensional element squared distance verification failed.");
1146 
1147  // Get list of elems connected to node
1148  std::vector<const Elem *> & node_elems =
1149  this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
1150 
1151  // Uniquely add elems to candidate set
1152  for (auto elem : node_elems)
1153  primary_elem_candidates.insert(elem);
1154  }
1155 
1163  while (!primary_elem_candidates.empty())
1164  {
1165  const Elem * primary_elem_candidate = *primary_elem_candidates.begin();
1166 
1167  // If we've already processed this candidate, we don't need to check it again.
1168  if (processed_primary_elems.count(primary_elem_candidate))
1169  continue;
1170 
1171  // Initialize set of nodes used to construct mortar segment elements
1172  std::vector<Point> nodal_points;
1173 
1174  // Initialize map from mortar segment elements to nodes
1175  std::vector<std::vector<unsigned int>> elem_to_node_map;
1176 
1177  // Initialize list of secondary and primary sub-elements that formed each mortar segment
1178  std::vector<std::pair<unsigned int, unsigned int>> sub_elem_map;
1179 
1184  for (auto p_el : make_range(primary_elem_candidate->n_sub_elem()))
1185  {
1186  // Get nodes of primary sub-elements
1187  auto sub_elem_nodes = get_sub_elem_nodes(primary_elem_candidate->type(), p_el);
1188 
1189  // Get list of primary sub-element vertex nodes
1190  std::vector<Point> primary_sub_elem(sub_elem_nodes.size());
1191  for (auto iv : make_range(sub_elem_nodes.size()))
1192  {
1193  const auto n = sub_elem_nodes[iv];
1194  primary_sub_elem[iv] = primary_elem_candidate->point(n);
1195  }
1196 
1197  // Loop through secondary sub-elements
1198  for (auto s_el : make_range(secondary_side_elem->n_sub_elem()))
1199  {
1200  // Mortar segment helpers were defined for each secondary sub-element, they will:
1201  // 1. Project primary sub-element onto linearized secondary sub-element
1202  // 2. Clip projected primary sub-element against secondary sub-element
1203  // 3. Triangulate clipped polygon to form mortar segments
1204  //
1205  // Mortar segment helpers append a list of mortar segment nodes and connectivities that
1206  // can be directly used to build mortar segments
1207  mortar_segment_helper[s_el]->getMortarSegments(
1208  primary_sub_elem, nodal_points, elem_to_node_map);
1209 
1210  // Keep track of which secondary and primary sub-elements created segment
1211  for (auto i = sub_elem_map.size(); i < elem_to_node_map.size(); ++i)
1212  sub_elem_map.push_back(std::make_pair(s_el, p_el));
1213  }
1214  }
1215 
1216  // Mark primary element as processed and remove from candidate list
1217  processed_primary_elems.insert(primary_elem_candidate);
1218  primary_elem_candidates.erase(primary_elem_candidate);
1219 
1220  // If overlap of polygons was non-trivial (created mortar segment elements)
1221  if (!elem_to_node_map.empty())
1222  {
1223  // If this is the first element with non-trivial overlap, set flag
1224  // Candidates will now be neighbors of elements that had non-trivial overlap
1225  // (i.e. we'll do a breadth first search now)
1226  if (!primary_elem_found)
1227  {
1228  primary_elem_found = true;
1229  primary_elem_candidates.clear();
1230  }
1231 
1232  // Add neighbors to candidate list
1233  for (auto neighbor : primary_elem_candidate->neighbor_ptr_range())
1234  {
1235  // If not valid or not on lower dimensional secondary subdomain, skip
1236  if (neighbor == nullptr || neighbor->subdomain_id() != primary_subd_id)
1237  continue;
1238  // If already processed, skip
1239  if (processed_primary_elems.count(neighbor))
1240  continue;
1241  // Otherwise, add to candidates
1242  primary_elem_candidates.insert(neighbor);
1243  }
1244 
1248  std::vector<Node *> new_nodes;
1249  for (auto pt : nodal_points)
1250  new_nodes.push_back(_mortar_segment_mesh->add_point(
1251  pt, _mortar_segment_mesh->max_node_id(), secondary_side_elem->processor_id()));
1252 
1253  // Loop through triangular elements in map
1254  for (auto el : index_range(elem_to_node_map))
1255  {
1256  // Create new triangular element
1257  std::unique_ptr<Elem> new_elem;
1258  if (elem_to_node_map[el].size() == 3)
1259  new_elem = std::make_unique<Tri3>();
1260  else
1261  mooseError("Active mortar segments only supports TRI elements, 3 nodes expected "
1262  "but: ",
1263  elem_to_node_map[el].size(),
1264  " provided.");
1265 
1266  new_elem->processor_id() = secondary_side_elem->processor_id();
1267  new_elem->subdomain_id() = secondary_side_elem->subdomain_id();
1268  new_elem->set_id(local_id_index++);
1269 
1270  // Attach newly created nodes
1271  for (auto i : index_range(elem_to_node_map[el]))
1272  new_elem->set_node(i, new_nodes[elem_to_node_map[el][i]]);
1273 
1274  // If element is smaller than tolerance, don't add to msm
1275  if (new_elem->volume() / secondary_volume < TOLERANCE)
1276  continue;
1277 
1278  // Add elements to mortar segment mesh
1279  Elem * msm_new_elem = _mortar_segment_mesh->add_elem(new_elem.release());
1280 
1281  msm_new_elem->set_extra_integer(secondary_sub_elem, sub_elem_map[el].first);
1282  msm_new_elem->set_extra_integer(primary_sub_elem, sub_elem_map[el].second);
1283 
1284  // Fill out mortar segment info
1285  MortarSegmentInfo msinfo;
1286  msinfo.secondary_elem = secondary_side_elem;
1287  msinfo.primary_elem = primary_elem_candidate;
1288 
1289  // Associate this MSM elem with the MortarSegmentInfo.
1290  _msm_elem_to_info.emplace(msm_new_elem, msinfo);
1291 
1292  // Add this mortar segment to the secondary elem to mortar segment map
1293  secondary_to_msm_element_set.insert(msm_new_elem);
1294 
1296  // Unlike for 2D, we always have a primary when building the mortar mesh so we don't
1297  // have to check for null
1299  }
1300  }
1301  // End loop through primary element candidates
1302  }
1303 
1304  for (auto sel : make_range(secondary_side_elem->n_sub_elem()))
1305  {
1306  // Check if any segments failed to project
1307  if (mortar_segment_helper[sel]->remainder() == 1.0)
1308  mooseDoOnce(
1309  mooseWarning("Some secondary elements on mortar interface were unable to identify"
1310  " a corresponding primary element; this may be expected depending on"
1311  " problem geometry but may indicate a failure of the element search"
1312  " or projection"));
1313  }
1314 
1315  if (secondary_to_msm_element_set.empty())
1316  _secondary_elems_to_mortar_segments.erase(secondary_elem_to_msm_map_it);
1317  } // End loop through secondary elements
1318  } // End loop through mortar constraint pairs
1319 
1320  _mortar_segment_mesh->cache_elem_data();
1321 
1322  // Output mortar segment mesh
1323  if (_debug)
1324  {
1325  // If element is not triangular, increment subdomain id
1326  // (ExodusII does not support mixed element types in a single subdomain)
1327  for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1328  if (msm_el->type() != TRI3)
1329  msm_el->subdomain_id()++;
1330 
1331  ExodusII_IO mortar_segment_mesh_writer(*_mortar_segment_mesh);
1332 
1333  // Default to non-HDF5 output for wider compatibility
1334  mortar_segment_mesh_writer.set_hdf5_writing(false);
1335 
1336  mortar_segment_mesh_writer.write("mortar_segment_mesh.e");
1337 
1338  // Undo increment
1339  for (const auto msm_el : _mortar_segment_mesh->active_local_element_ptr_range())
1340  if (msm_el->type() != TRI3)
1341  msm_el->subdomain_id()--;
1342  }
1343 
1345 
1346  // Print mortar segment mesh statistics
1347  if (_debug)
1348  {
1349  if (_mesh.n_processors() == 1)
1350  msmStatistics();
1351  else
1352  mooseWarning("Mortar segment mesh statistics intended for debugging purposes in serial only, "
1353  "parallel will only provide statistics for local mortar segment mesh.");
1354  }
1355 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
ElemType
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:323
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 mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition: MooseError.h:357
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
void msmStatistics()
Outputs mesh statistics for mortar segment mesh.
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.
processor_id_type n_processors() const
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.
subdomain_id_type subdomain_id() const
auto norm_sq(const T &a) -> decltype(std::norm(a))
virtual Real volume() const
IntRange< T > make_range(T beg, T end)
TRI7
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
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 292 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

293 {
295  mooseError(
296  "Must specify secondary and primary boundary ids before building node-to-elem maps.");
297 
298  // Construct nodes_to_secondary_elem_map
299  for (const auto & secondary_elem :
300  as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
301  {
302  // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
303  if (!this->_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
304  continue;
305 
306  for (const auto & nd : secondary_elem->node_ref_range())
307  {
308  std::vector<const Elem *> & vec = _nodes_to_secondary_elem_map[nd.id()];
309  vec.push_back(secondary_elem);
310  }
311  }
312 
313  // Construct nodes_to_primary_elem_map
314  for (const auto & primary_elem :
315  as_range(_mesh.active_elements_begin(), _mesh.active_elements_end()))
316  {
317  // If this is not one of the lower-dimensional primary side elements, go on to the next one.
318  if (!this->_primary_boundary_subdomain_ids.count(primary_elem->subdomain_id()))
319  continue;
320 
321  for (const auto & nd : primary_elem->node_ref_range())
322  {
323  std::vector<const Elem *> & vec = _nodes_to_primary_elem_map[nd.id()];
324  vec.push_back(primary_elem);
325  }
326  }
327 }
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:323
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 271 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

272 {
273  _mortar_segment_mesh->clear();
278  _msm_elem_to_info.clear();
279  _lower_elem_to_side_id.clear();
285  _secondary_ip_sub_ids.clear();
286  _primary_ip_sub_ids.clear();
289 }
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 1669 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

1670 {
1671  // Mark all active secondary elements
1672  std::unordered_set<const Elem *> active_local_elems;
1673 
1674  //****
1675  // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1676  // since the mortar segments are on the linearized element and comparing the volume of the
1677  // linearized element does not have the same volume as the warped element
1678  const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1679 
1680  std::unordered_map<const Elem *, Real> active_volume;
1681 
1682  // Compute fraction of elements with corresponding primary elements
1684  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1685  {
1686  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1687  const Elem * secondary_elem = msinfo.secondary_elem;
1688 
1689  active_volume[secondary_elem] += msm_elem->volume();
1690  }
1691  //****
1692 
1693  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1694  {
1695  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1696  const Elem * secondary_elem = msinfo.secondary_elem;
1697 
1698  //****
1700  if (std::abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
1701  continue;
1702  //****
1703 
1704  active_local_elems.insert(secondary_elem);
1705  }
1706 
1707  // Take complement of active elements in active local subdomain to get inactive local elements
1708  _inactive_local_lm_elems.clear();
1709  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1710  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1711  /*secondary_subd_id*/ pr.second))
1712  if (active_local_elems.find(el) == active_local_elems.end())
1713  _inactive_local_lm_elems.insert(el);
1714 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
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 1585 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

1586 {
1588  {
1590  return;
1591  }
1592 
1593  std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_active_nodes_set;
1594  const auto my_pid = _mesh.processor_id();
1595 
1596  // List of active nodes on local secondary elements
1597  std::unordered_set<dof_id_type> active_local_nodes;
1598 
1599  // Mark all active local nodes
1600  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1601  {
1602  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1603  const Elem * secondary_elem = msinfo.secondary_elem;
1604 
1605  for (auto n : make_range(secondary_elem->n_nodes()))
1606  active_local_nodes.insert(secondary_elem->node_id(n));
1607  }
1608 
1609  // Assemble list of procs that nodes contribute to
1610  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1611  {
1612  const auto secondary_subd_id = pr.second;
1613 
1614  // Loop through all elements not on my processor
1615  for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1616  {
1617  // Get processor_id
1618  const auto pid = el->processor_id();
1619 
1620  // If element is in my subdomain, skip
1621  if (pid == my_pid)
1622  continue;
1623 
1624  // If element on proc pid shares any of my active nodes, mark to send
1625  for (const auto n : make_range(el->n_nodes()))
1626  {
1627  const auto node_id = el->node_id(n);
1628  if (active_local_nodes.find(node_id) != active_local_nodes.end())
1629  proc_to_active_nodes_set[pid].insert(node_id);
1630  }
1631  }
1632  }
1633 
1634  // Send list of active nodes
1635  {
1636  // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
1637  std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_active_nodes_vector;
1638  for (const auto & proc_set : proc_to_active_nodes_set)
1639  {
1640  proc_to_active_nodes_vector[proc_set.first].reserve(proc_to_active_nodes_set.size());
1641  for (const auto node_id : proc_set.second)
1642  proc_to_active_nodes_vector[proc_set.first].push_back(node_id);
1643  }
1644 
1645  // First push data
1646  auto action_functor = [this, &active_local_nodes](const processor_id_type pid,
1647  const std::vector<dof_id_type> & sent_data)
1648  {
1649  if (pid == _mesh.processor_id())
1650  mooseError("Should not be communicating with self.");
1651  active_local_nodes.insert(sent_data.begin(), sent_data.end());
1652  };
1653  TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_active_nodes_vector, action_functor);
1654  }
1655 
1656  // Every proc has correct list of active local nodes, now take complement (list of inactive nodes)
1657  // and store to use later to zero LM DoFs on inactive nodes
1658  _inactive_local_lm_nodes.clear();
1659  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1660  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1661  /*secondary_subd_id*/ pr.second))
1662  for (const auto n : make_range(el->n_nodes()))
1663  if (active_local_nodes.find(el->node_id(n)) == active_local_nodes.end())
1664  _inactive_local_lm_nodes.insert(el->node_ptr(n));
1665 }
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:323
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 1493 of file AutomaticMortarGeneration.C.

Referenced by computeInactiveLMNodes().

1494 {
1495  // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1496  // since the mortar segments are on the linearized element and comparing the volume of the
1497  // linearized element does not have the same volume as the warped element
1498  const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1499 
1500  std::unordered_map<processor_id_type, std::set<dof_id_type>> proc_to_inactive_nodes_set;
1501  const auto my_pid = _mesh.processor_id();
1502 
1503  // List of inactive nodes on local secondary elements
1504  std::unordered_set<dof_id_type> inactive_node_ids;
1505 
1506  std::unordered_map<const Elem *, Real> active_volume{};
1507 
1508  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1509  for (const auto el : _mesh.active_subdomain_elements_ptr_range(pr.second))
1510  active_volume[el] = 0.;
1511 
1512  // Compute fraction of elements with corresponding primary elements
1513  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1514  {
1515  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1516  const Elem * secondary_elem = msinfo.secondary_elem;
1517 
1518  active_volume[secondary_elem] += msm_elem->volume();
1519  }
1520 
1521  // Mark all inactive local nodes
1522  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1523  // Loop through all elements on my processor
1524  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(pr.second))
1525  // If elem fully or partially dropped
1526  if (std::abs(active_volume[el] / el->volume() - 1.0) > tol)
1527  {
1528  // Add all nodes to list of inactive
1529  for (auto n : make_range(el->n_nodes()))
1530  inactive_node_ids.insert(el->node_id(n));
1531  }
1532 
1533  // Assemble list of procs that nodes contribute to
1534  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1535  {
1536  const auto secondary_subd_id = pr.second;
1537 
1538  // Loop through all elements not on my processor
1539  for (const auto el : _mesh.active_subdomain_elements_ptr_range(secondary_subd_id))
1540  {
1541  // Get processor_id
1542  const auto pid = el->processor_id();
1543 
1544  // If element is in my subdomain, skip
1545  if (pid == my_pid)
1546  continue;
1547 
1548  // If element on proc pid shares any of my inactive nodes, mark to send
1549  for (const auto n : make_range(el->n_nodes()))
1550  {
1551  const auto node_id = el->node_id(n);
1552  if (inactive_node_ids.find(node_id) != inactive_node_ids.end())
1553  proc_to_inactive_nodes_set[pid].insert(node_id);
1554  }
1555  }
1556  }
1557 
1558  // Send list of inactive nodes
1559  {
1560  // Pack set into vector for sending (push_parallel_vector_data doesn't like sets)
1561  std::unordered_map<processor_id_type, std::vector<dof_id_type>> proc_to_inactive_nodes_vector;
1562  for (const auto & proc_set : proc_to_inactive_nodes_set)
1563  proc_to_inactive_nodes_vector[proc_set.first].insert(
1564  proc_to_inactive_nodes_vector[proc_set.first].end(),
1565  proc_set.second.begin(),
1566  proc_set.second.end());
1567 
1568  // First push data
1569  auto action_functor = [this, &inactive_node_ids](const processor_id_type pid,
1570  const std::vector<dof_id_type> & sent_data)
1571  {
1572  if (pid == _mesh.processor_id())
1573  mooseError("Should not be communicating with self.");
1574  for (const auto pr : sent_data)
1575  inactive_node_ids.insert(pr);
1576  };
1577  TIMPI::push_parallel_vector_data(_mesh.comm(), proc_to_inactive_nodes_vector, action_functor);
1578  }
1579  _inactive_local_lm_nodes.clear();
1580  for (const auto node_id : inactive_node_ids)
1581  _inactive_local_lm_nodes.insert(_mesh.node_ptr(node_id));
1582 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:323
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().

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

Referenced by MortarInterfaceWarehouse::update().

1718 {
1719  // The dimension according to Mesh::mesh_dimension().
1720  const auto dim = _mesh.mesh_dimension();
1721 
1722  // A nodal lower-dimensional nodal quadrature rule to be used on faces.
1723  QNodal qface(dim - 1);
1724 
1725  // A map from the node id to the attached elemental normals/weights evaluated at the node. Th
1726  // length of the vector will correspond to the number of elements attached to the node. If it is a
1727  // vertex node, for a 1D mortar mesh, the vector length will be two. If it is an interior node,
1728  // the vector will be length 1. The first member of the pair is that element's normal at the node.
1729  // The second member is that element's JxW at the node
1730  std::map<dof_id_type, std::vector<std::pair<Point, Real>>> node_to_normals_map;
1731 
1733  Real sign = _periodic ? -1 : 1;
1734 
1735  // First loop over lower-dimensional secondary side elements and compute/save the outward normal
1736  // for each one. We loop over all active elements currently, but this procedure could be
1737  // parallelized as well.
1738  for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
1739  end_el = _mesh.active_elements_end();
1740  el != end_el;
1741  ++el)
1742  {
1743  const Elem * secondary_elem = *el;
1744 
1745  // If this is not one of the lower-dimensional secondary side elements, go on to the next one.
1746  if (!_secondary_boundary_subdomain_ids.count(secondary_elem->subdomain_id()))
1747  continue;
1748 
1749  // We will create an FE object and attach the nodal quadrature rule such that we can get out the
1750  // normals at the element nodes
1751  FEType nnx_fe_type(secondary_elem->default_order(), LAGRANGE);
1752  std::unique_ptr<FEBase> nnx_fe_face(FEBase::build(dim, nnx_fe_type));
1753  nnx_fe_face->attach_quadrature_rule(&qface);
1754  const std::vector<Point> & face_normals = nnx_fe_face->get_normals();
1755 
1756  const auto & JxW = nnx_fe_face->get_JxW();
1757 
1758  // Which side of the parent are we? We need to know this to know
1759  // which side to reinit.
1760  const Elem * interior_parent = secondary_elem->interior_parent();
1761  mooseAssert(interior_parent,
1762  "No interior parent exists for element "
1763  << secondary_elem->id()
1764  << ". There may be a problem with your sideset set-up.");
1765 
1766  // Map to get lower dimensional element from interior parent on secondary surface
1767  // This map can be used to provide a handle to methods in this class that need to
1768  // operate on lower dimensional elements.
1769  _secondary_element_to_secondary_lowerd_element.emplace(interior_parent->id(), secondary_elem);
1770 
1771  // Look up which side of the interior parent secondary_elem is.
1772  auto s = interior_parent->which_side_am_i(secondary_elem);
1773 
1774  // Reinit the face FE object on side s.
1775  nnx_fe_face->reinit(interior_parent, s);
1776 
1777  for (MooseIndex(secondary_elem->n_nodes()) n = 0; n < secondary_elem->n_nodes(); ++n)
1778  {
1779  auto & normals_and_weights_vec = node_to_normals_map[secondary_elem->node_id(n)];
1780  normals_and_weights_vec.push_back(std::make_pair(sign * face_normals[n], JxW[n]));
1781  }
1782  }
1783 
1784  // Note that contrary to the Bin Yang dissertation, we are not weighting by the face element
1785  // lengths/volumes. It's not clear to me that this type of weighting is a good algorithm for cases
1786  // where the face can be curved
1787  for (const auto & pr : node_to_normals_map)
1788  {
1789  // Compute normal vector
1790  const auto & node_id = pr.first;
1791  const auto & normals_and_weights_vec = pr.second;
1792 
1793  Point nodal_normal;
1794  for (const auto & norm_and_weight : normals_and_weights_vec)
1795  nodal_normal += norm_and_weight.first * norm_and_weight.second;
1796  nodal_normal = nodal_normal.unit();
1797 
1798  _secondary_node_to_nodal_normal[_mesh.node_ptr(node_id)] = nodal_normal;
1799 
1800  Point nodal_tangent_one;
1801  Point nodal_tangent_two;
1802  householderOrthogolization(nodal_normal, nodal_tangent_one, nodal_tangent_two);
1803 
1804  _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][0] = nodal_tangent_one;
1805  _secondary_node_to_hh_nodal_tangents[_mesh.node_ptr(node_id)][1] = nodal_tangent_two;
1806  }
1807 }
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.
T sign(T x)
Definition: MathUtils.h:84
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...
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 292 of file AutomaticMortarGeneration.h.

Referenced by ComputeMortarFunctor::operator()().

293  {
295  }
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 284 of file AutomaticMortarGeneration.h.

Referenced by ComputeMortarFunctor::operator()().

285  {
287  }
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 330 of file AutomaticMortarGeneration.C.

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

331 {
332  std::vector<Point> nodal_normals(secondary_elem.n_nodes());
333  for (const auto n : make_range(secondary_elem.n_nodes()))
334  nodal_normals[n] = _secondary_node_to_nodal_normal.at(secondary_elem.node_ptr(n));
335 
336  return nodal_normals;
337 }
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 383 of file AutomaticMortarGeneration.C.

384 {
385  // MetaPhysicL will check if we ran out of allocated space.
386  MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_one(0);
387  MooseUtils::SemidynamicVector<Point, 9> nodal_tangents_two(0);
388 
389  for (const auto n : make_range(secondary_elem.n_nodes()))
390  {
391  const auto & tangent_vectors =
392  libmesh_map_find(_secondary_node_to_hh_nodal_tangents, secondary_elem.node_ptr(n));
393  nodal_tangents_one.push_back(tangent_vectors[0]);
394  nodal_tangents_two.push_back(tangent_vectors[1]);
395  }
396 
397  return {{nodal_tangents_one, nodal_tangents_two}};
398 }
Utility class template for a semidynamic vector with a maximum size N and a chosen dynamic size...
Definition: MooseUtils.h:1076
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 412 of file AutomaticMortarGeneration.C.

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

414 {
415  const auto mortar_dim = _mesh.mesh_dimension() - 1;
416  const auto num_qps = xi1_pts.size();
417  const auto nodal_normals = getNodalNormals(secondary_elem);
418  std::vector<Point> normals(num_qps);
419 
420  for (const auto n : make_range(secondary_elem.n_nodes()))
421  for (const auto qp : make_range(num_qps))
422  {
423  const auto phi =
424  (mortar_dim == 1)
425  ? Moose::fe_lagrange_1D_shape(secondary_elem.default_order(), n, xi1_pts[qp](0))
426  : Moose::fe_lagrange_2D_shape(secondary_elem.type(),
427  secondary_elem.default_order(),
428  n,
429  static_cast<const TypeVector<Real> &>(xi1_pts[qp]));
430  normals[qp] += phi * nodal_normals[n];
431  }
432 
433  if (_periodic)
434  for (auto & normal : normals)
435  normal *= -1;
436 
437  return normals;
438 }
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 401 of file AutomaticMortarGeneration.C.

403 {
404  std::vector<Point> xi1_pts(oned_xi1_pts.size());
405  for (const auto qp : index_range(oned_xi1_pts))
406  xi1_pts[qp] = oned_xi1_pts[qp];
407 
408  return getNormals(secondary_elem, xi1_pts);
409 }
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 366 of file AutomaticMortarGeneration.C.

370 {
371  std::map<unsigned int, unsigned int> primary_ip_i_to_lower_primary_i;
372 
373  for (const auto i : make_range(lower_primary_elem.n_nodes()))
374  {
375  const auto & nd = lower_primary_elem.node_ref(i);
376  primary_ip_i_to_lower_primary_i[primary_elem.get_node_index(&nd)] = i;
377  }
378 
379  return primary_ip_i_to_lower_primary_i;
380 }
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 350 of file AutomaticMortarGeneration.C.

351 {
352  std::map<unsigned int, unsigned int> secondary_ip_i_to_lower_secondary_i;
353  const Elem * const secondary_ip = lower_secondary_elem.interior_parent();
354  mooseAssert(secondary_ip, "This should be non-null");
355 
356  for (const auto i : make_range(lower_secondary_elem.n_nodes()))
357  {
358  const auto & nd = lower_secondary_elem.node_ref(i);
359  secondary_ip_i_to_lower_secondary_i[secondary_ip->get_node_index(&nd)] = i;
360  }
361 
362  return secondary_ip_i_to_lower_secondary_i;
363 }
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 340 of file AutomaticMortarGeneration.C.

342 {
343  mooseAssert(_secondary_element_to_secondary_lowerd_element.count(secondary_elem_id),
344  "Map should locate secondary element");
345 
346  return _secondary_element_to_secondary_lowerd_element.at(secondary_elem_id);
347 }
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 1810 of file AutomaticMortarGeneration.C.

Referenced by computeNodalGeometry().

1813 {
1814  mooseAssert(MooseUtils::absoluteFuzzyEqual(nodal_normal.norm(), 1),
1815  "The input nodal normal should have unity norm");
1816 
1817  const Real nx = nodal_normal(0);
1818  const Real ny = nodal_normal(1);
1819  const Real nz = nodal_normal(2);
1820 
1821  // See Lopes DS, Silva MT, Ambrosio JA. Tangent vectors to a 3-D surface normal: A geometric tool
1822  // to find orthogonal vectors based on the Householder transformation. Computer-Aided Design. 2013
1823  // Mar 1;45(3):683-94. We choose one definition of h_vector and deal with special case.
1824  const Point h_vector(nx + 1.0, ny, nz);
1825 
1826  // Avoid singularity of the equations at the end of routine by providing the solution to
1827  // (nx,ny,nz)=(-1,0,0) Normal/tangent fields can be visualized by outputting nodal geometry mesh
1828  // on a spherical problem.
1829  if (std::abs(h_vector(0)) < TOLERANCE)
1830  {
1831  nodal_tangent_one(0) = 0;
1832  nodal_tangent_one(1) = 1;
1833  nodal_tangent_one(2) = 0;
1834 
1835  nodal_tangent_two(0) = 0;
1836  nodal_tangent_two(1) = 0;
1837  nodal_tangent_two(2) = -1;
1838 
1839  return;
1840  }
1841 
1842  const Real h = h_vector.norm();
1843 
1844  nodal_tangent_one(0) = -2.0 * h_vector(0) * h_vector(1) / (h * h);
1845  nodal_tangent_one(1) = 1.0 - 2.0 * h_vector(1) * h_vector(1) / (h * h);
1846  nodal_tangent_one(2) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
1847 
1848  nodal_tangent_two(0) = -2.0 * h_vector(0) * h_vector(2) / (h * h);
1849  nodal_tangent_two(1) = -2.0 * h_vector(1) * h_vector(2) / (h * h);
1850  nodal_tangent_two(2) = 1.0 - 2.0 * h_vector(2) * h_vector(2) / (h * h);
1851 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:382
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 297 of file AutomaticMortarGeneration.h.

Referenced by ComputeMortarFunctor::operator()().

297 { 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 252 of file AutomaticMortarGeneration.C.

253 {
254  if (!_debug)
255  return;
256 
257  _output_params = std::make_unique<InputParameters>(MortarNodalGeometryOutput::validParams());
258  _output_params->set<AutomaticMortarGeneration *>("_amg") = this;
259  _output_params->set<FEProblemBase *>("_fe_problem_base") = &_app.feProblem();
261  _output_params->set<std::string>(MooseBase::name_param) =
262  "mortar_nodal_geometry_" +
263  std::to_string(_primary_secondary_boundary_id_pairs.front().first) +
264  std::to_string(_primary_secondary_boundary_id_pairs.front().second) + "_" +
265  (_on_displaced ? "displaced" : "undisplaced");
266  _output_params->finalize("MortarNodalGeometryOutput");
267  _app.getOutputWarehouse().addOutput(std::make_shared<MortarNodalGeometryOutput>(*_output_params));
268 }
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
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...
Base class for MOOSE-based applications.
Definition: MooseApp.h:103
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
FEProblemBase & feProblem() const
Definition: MooseApp.C:2033
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()
const bool _on_displaced
Whether this object is on the displaced mesh.
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:2515

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

257  {
259  }
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...

◆ mortarSegmentMesh()

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

Definition at line 269 of file AutomaticMortarGeneration.h.

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

269 { 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 274 of file AutomaticMortarGeneration.h.

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

275  {
276  return _msm_elem_to_info;
277  }
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 ( )

Outputs mesh statistics for mortar segment mesh.

Definition at line 1429 of file AutomaticMortarGeneration.C.

Referenced by buildMortarSegmentMesh3d().

1430 {
1431  // Print boundary pairs
1432  Moose::out << "Mortar Interface Statistics:" << std::endl;
1433 
1434  // Count number of elements on primary and secondary sides
1435  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1436  {
1437  const auto primary_subd_id = pr.first;
1438  const auto secondary_subd_id = pr.second;
1439 
1440  // Allocate statistics vectors for primary lower, secondary lower, and msm meshes
1441  StatisticsVector<Real> primary; // primary.reserve(mesh.n_elem());
1442  StatisticsVector<Real> secondary; // secondary.reserve(mesh.n_elem());
1443  StatisticsVector<Real> msm; // msm.reserve(mortar_segment_mesh->n_elem());
1444 
1445  for (auto * el : _mesh.active_element_ptr_range())
1446  {
1447  // Add secondary and primary elem volumes to statistics vector
1448  if (el->subdomain_id() == secondary_subd_id)
1449  secondary.push_back(el->volume());
1450  else if (el->subdomain_id() == primary_subd_id)
1451  primary.push_back(el->volume());
1452  }
1453 
1454  // Note: when we allow more than one primary secondary pair will need to make
1455  // separate mortar segment mesh for each
1456  for (auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1457  {
1458  // Add msm elem volume to statistic vector
1459  msm.push_back(msm_elem->volume());
1460  }
1461 
1462  // Create table
1463  std::vector<std::string> col_names = {"mesh", "n_elems", "max", "min", "median"};
1464  std::vector<std::string> subds = {"secondary_lower", "primary_lower", "mortar_segment"};
1465  std::vector<size_t> n_elems = {secondary.size(), primary.size(), msm.size()};
1466  std::vector<Real> maxs = {secondary.maximum(), primary.maximum(), msm.maximum()};
1467  std::vector<Real> mins = {secondary.minimum(), primary.minimum(), msm.minimum()};
1468  std::vector<Real> medians = {secondary.median(), primary.median(), msm.median()};
1469 
1470  FormattedTable table;
1471  table.clear();
1472  for (auto i : index_range(subds))
1473  {
1474  table.addRow(i);
1475  table.addData<std::string>(col_names[0], subds[i]);
1476  table.addData<size_t>(col_names[1], n_elems[i]);
1477  table.addData<Real>(col_names[2], maxs[i]);
1478  table.addData<Real>(col_names[3], mins[i]);
1479  table.addData<Real>(col_names[4], medians[i]);
1480  }
1481 
1482  Moose::out << "secondary subdomain: " << secondary_subd_id
1483  << " \tprimary subdomain: " << primary_subd_id << std::endl;
1484  table.printTable(Moose::out, subds.size());
1485  }
1486 }
virtual T maximum() const
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
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 addRow(Real time)
Force a new row in the table with the passed in time.
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)

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

334  {
336  }
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 171 of file AutomaticMortarGeneration.h.

171 { return _on_displaced; }
const bool _on_displaced
Whether this object is on the displaced 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 328 of file AutomaticMortarGeneration.h.

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

328 { 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 534 of file AutomaticMortarGeneration.h.

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

535 {
536  mooseAssert(_primary_secondary_boundary_id_pairs.size() == 1,
537  "We currently only support a single boundary pair per mortar generation object");
538 
540 }
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 1865 of file AutomaticMortarGeneration.C.

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

1873 {
1874  if (!secondary_node_neighbors)
1875  secondary_node_neighbors = &libmesh_map_find(_nodes_to_secondary_elem_map, secondary_node.id());
1876  if (!primary_node_neighbors)
1877  primary_node_neighbors = &libmesh_map_find(_nodes_to_primary_elem_map, primary_node.id());
1878 
1879  std::vector<bool> primary_elems_mapped(primary_node_neighbors->size(), false);
1880 
1881  // Add entries to secondary_node_and_elem_to_xi2_primary_elem container.
1882  //
1883  // First, determine "on left" vs. "on right" orientation of the nodal neighbors.
1884  // There can be a max of 2 nodal neighbors, and we want to make sure that the
1885  // secondary nodal neighbor on the "left" is associated with the primary nodal
1886  // neighbor on the "left" and similarly for the "right". We use cross products to determine
1887  // alignment. In the below diagram, 'x' denotes a node, and connected '|' are lower dimensional
1888  // elements.
1889  // x
1890  // x |
1891  // | |
1892  // secondary x ----> x primary
1893  // | |
1894  // | x
1895  // x
1896  //
1897  // Looking at the aligned nodes, the secondary node first, if we pick the top secondary lower
1898  // dimensional element, then the cross product as written a few lines below points out of the
1899  // screen towards you. (Point in the direction of the secondary nodal normal, and then curl your
1900  // hand towards the secondary element's opposite node, then the thumb points in the direction of
1901  // the cross product). Doing the same with the aligned primary node, if we pick the top primary
1902  // element, then the cross product also points out of the screen. Because the cross products
1903  // point in the same direction (positive dot product), then we know to associate the
1904  // secondary-primary element pair. If we had picked the bottom primary element whose cross
1905  // product points into the screen, then clearly the cross products point in the opposite
1906  // direction and we don't have a match
1907  std::array<Real, 2> secondary_node_neighbor_cps, primary_node_neighbor_cps;
1908 
1909  for (const auto nn : index_range(*secondary_node_neighbors))
1910  {
1911  const Elem * const secondary_neigh = (*secondary_node_neighbors)[nn];
1912  const Point opposite = (secondary_neigh->node_ptr(0) == &secondary_node)
1913  ? secondary_neigh->point(1)
1914  : secondary_neigh->point(0);
1915  const Point cp = nodal_normal.cross(opposite - secondary_node);
1916  secondary_node_neighbor_cps[nn] = cp(2);
1917  }
1918 
1919  for (const auto nn : index_range(*primary_node_neighbors))
1920  {
1921  const Elem * const primary_neigh = (*primary_node_neighbors)[nn];
1922  const Point opposite = (primary_neigh->node_ptr(0) == &primary_node) ? primary_neigh->point(1)
1923  : primary_neigh->point(0);
1924  const Point cp = nodal_normal.cross(opposite - primary_node);
1925  primary_node_neighbor_cps[nn] = cp(2);
1926  }
1927 
1928  // Associate secondary/primary elems on matching sides.
1929  bool found_match = false;
1930  for (const auto snn : index_range(*secondary_node_neighbors))
1931  for (const auto mnn : index_range(*primary_node_neighbors))
1932  if (secondary_node_neighbor_cps[snn] * primary_node_neighbor_cps[mnn] > 0)
1933  {
1934  found_match = true;
1935  if (primary_elems_mapped[mnn])
1936  continue;
1937  primary_elems_mapped[mnn] = true;
1938 
1939  // Figure out xi^(2) value by looking at which node primary_node is
1940  // of the current primary node neighbor.
1941  const Real xi2 = (&primary_node == (*primary_node_neighbors)[mnn]->node_ptr(0)) ? -1 : +1;
1942  const auto secondary_key =
1943  std::make_pair(&secondary_node, (*secondary_node_neighbors)[snn]);
1944  const auto primary_val = std::make_pair(xi2, (*primary_node_neighbors)[mnn]);
1945  _secondary_node_and_elem_to_xi2_primary_elem.emplace(secondary_key, primary_val);
1946 
1947  // Also map in the other direction.
1948  const Real xi1 =
1949  (&secondary_node == (*secondary_node_neighbors)[snn]->node_ptr(0)) ? -1 : +1;
1950 
1951  const auto primary_key =
1952  std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[mnn]);
1953  const auto secondary_val = std::make_pair(xi1, (*secondary_node_neighbors)[snn]);
1954  _primary_node_and_elem_to_xi1_secondary_elem.emplace(primary_key, secondary_val);
1955  }
1956 
1957  if (!found_match)
1958  {
1959  // There could be coincident nodes and this might be a bad primary candidate (see
1960  // issue #21680). Instead of giving up, let's try continuing
1961  rejected_elem_candidates.insert(&candidate_element);
1962  return false;
1963  }
1964 
1965  // We need to handle the case where we've exactly projected a secondary node onto a
1966  // primary node, but our secondary node is at one of the secondary boundary face endpoints and
1967  // our primary node is not.
1968  if (secondary_node_neighbors->size() == 1 && primary_node_neighbors->size() == 2)
1969  for (const auto i : index_range(primary_elems_mapped))
1970  if (!primary_elems_mapped[i])
1971  {
1973  std::make_tuple(primary_node.id(), &primary_node, (*primary_node_neighbors)[i]),
1974  std::make_pair(1, nullptr));
1975  }
1976 
1977  return found_match;
1978 }
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 2225 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

2226 {
2227  // For each primary/secondary boundary id pair, call the
2228  // project_primary_nodes_single_pair() helper function.
2229  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
2230  projectPrimaryNodesSinglePair(pr.first, pr.second);
2231 }
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 2234 of file AutomaticMortarGeneration.C.

Referenced by projectPrimaryNodes().

2237 {
2238  // Build a Nanoflann object on the lower-dimensional secondary elements of the Mesh.
2239  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_secondary_subdomain_id);
2240  subdomain_kd_tree_t kd_tree(
2241  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
2242 
2243  // Construct the KD tree for lower-dimensional elements in the volume mesh.
2244  kd_tree.buildIndex();
2245 
2246  std::unordered_set<dof_id_type> primary_nodes_visited;
2247 
2248  for (const auto & primary_side_elem : _mesh.active_element_ptr_range())
2249  {
2250  // If this is not one of the lower-dimensional primary side elements, go on to the next one.
2251  if (primary_side_elem->subdomain_id() != lower_dimensional_primary_subdomain_id)
2252  continue;
2253 
2254  // For each node on this side, find the nearest node on the secondary side using the KDTree,
2255  // then search in nearby elements for where it projects along the nodal normal direction.
2256  for (MooseIndex(primary_side_elem->n_vertices()) n = 0; n < primary_side_elem->n_vertices();
2257  ++n)
2258  {
2259  // Get a pointer to this node.
2260  const Node * primary_node = primary_side_elem->node_ptr(n);
2261 
2262  // Get the nodal neighbors connected to this primary node.
2263  const std::vector<const Elem *> & primary_node_neighbors =
2264  _nodes_to_primary_elem_map.at(primary_node->id());
2265 
2266  // Check whether we have already successfully inverse mapped this primary node (whether during
2267  // secondary node projection or now during primary node projection) or we have already failed
2268  // to inverse map this primary node (now during primary node projection), and then skip if
2269  // either of those things is true
2270  auto primary_key =
2271  std::make_tuple(primary_node->id(), primary_node, primary_node_neighbors[0]);
2272  if (!primary_nodes_visited.insert(primary_node->id()).second ||
2274  continue;
2275 
2276  // Data structure for performing Nanoflann searches.
2277  Real query_pt[3] = {(*primary_node)(0), (*primary_node)(1), (*primary_node)(2)};
2278 
2279  // The number of results we want to get. We'll look for a
2280  // "few" nearest nodes, hopefully that is enough to let us
2281  // figure out which lower-dimensional Elem on the secondary side
2282  // we are across from.
2283  const size_t num_results = 3;
2284 
2285  // Initialize result_set and do the search.
2286  std::vector<size_t> ret_index(num_results);
2287  std::vector<Real> out_dist_sqr(num_results);
2288  nanoflann::KNNResultSet<Real> result_set(num_results);
2289  result_set.init(&ret_index[0], &out_dist_sqr[0]);
2290  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2291 
2292  // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2293  bool projection_succeeded = false;
2294 
2295  // Once we've rejected a candidate for a given
2296  // primary_node, there's no reason to check it
2297  // again.
2298  std::set<const Elem *> rejected_secondary_elem_candidates;
2299 
2300  // Loop over the closest nodes, check whether the secondary node successfully projects into
2301  // either of the closest neighbors, stop when the projection succeeds.
2302  for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2303  {
2304  // Verify that the squared distance we compute is the same as nanoflann's
2305  mooseAssert(std::abs((_mesh.point(ret_index[r]) - *primary_node).norm_sq() -
2306  out_dist_sqr[r]) <= TOLERANCE,
2307  "Lower-dimensional element squared distance verification failed.");
2308 
2309  // Get a reference to the vector of lower dimensional elements from the
2310  // nodes_to_secondary_elem_map.
2311  const std::vector<const Elem *> & secondary_elem_candidates =
2312  _nodes_to_secondary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2313 
2314  // Print the Elems connected to this node on the secondary mesh side.
2315  for (MooseIndex(secondary_elem_candidates) e = 0; e < secondary_elem_candidates.size(); ++e)
2316  {
2317  const Elem * secondary_elem_candidate = secondary_elem_candidates[e];
2318 
2319  // If we've already rejected this candidate, we don't need to check it again.
2320  if (rejected_secondary_elem_candidates.count(secondary_elem_candidate))
2321  continue;
2322 
2323  std::vector<Point> nodal_normals(secondary_elem_candidate->n_nodes());
2324  for (const auto n : make_range(secondary_elem_candidate->n_nodes()))
2325  nodal_normals[n] =
2326  _secondary_node_to_nodal_normal.at(secondary_elem_candidate->node_ptr(n));
2327 
2328  // Use equation 2.4.6 from Bin Yang's dissertation to try and solve for
2329  // the position on the secondary element where this primary came from. This
2330  // requires a Newton iteration in general.
2331  DualNumber<Real> xi1_dn{0, 1}; // initial guess
2332  auto && order = secondary_elem_candidate->default_order();
2333  unsigned int current_iterate = 0, max_iterates = 10;
2334 
2335  VectorValue<DualNumber<Real>> normals(0);
2336 
2337  // Newton iteration loop - this to converge in 1 iteration when it
2338  // succeeds, and possibly two iterations when it converges to a
2339  // xi outside the reference element. I don't know any reason why it should
2340  // only take 1 iteration -- the Jacobian is not constant in general...
2341  do
2342  {
2344  for (MooseIndex(secondary_elem_candidate->n_nodes()) n = 0;
2345  n < secondary_elem_candidate->n_nodes();
2346  ++n)
2347  {
2348  const auto phi = Moose::fe_lagrange_1D_shape(order, n, xi1_dn);
2349  x1 += phi * secondary_elem_candidate->point(n);
2350  normals += phi * nodal_normals[n];
2351  }
2352 
2353  const auto u = x1 - (*primary_node);
2354 
2355  const auto F = u(0) * normals(1) - u(1) * normals(0);
2356 
2357  if (std::abs(F) < _newton_tolerance)
2358  break;
2359 
2360  // Unlike for projection of nodal normals onto primary surfaces, we should never have a
2361  // case where the nodal normal is completely orthogonal to the secondary surface, so we
2362  // do not have to guard against F.derivatives() == 0 here
2363  Real dxi1 = -F.value() / F.derivatives();
2364 
2365  xi1_dn += dxi1;
2366 
2367  normals = 0;
2368  } while (++current_iterate < max_iterates);
2369 
2370  Real xi1 = xi1_dn.value();
2371 
2372  // Check for convergence to a valid solution... The last condition checks for obliqueness
2373  // of the projection
2374  if ((current_iterate < max_iterates) && (std::abs(xi1) <= 1. + _xi_tolerance) &&
2375  (std::abs((primary_side_elem->point(0) - primary_side_elem->point(1)).unit() *
2376  MetaPhysicL::raw_value(normals).unit()) <
2378  {
2379  if (std::abs(std::abs(xi1) - 1.) < _xi_tolerance)
2380  {
2381  // Special case: xi1=+/-1.
2382  // It is unlikely that we get here, because this primary node should already
2383  // have been mapped during the project_secondary_nodes() routine, but
2384  // there is still a chance since the tolerances are applied to
2385  // the xi coordinate and that value may be different on a primary element and a
2386  // secondary element since they may have different sizes. It's also possible that we
2387  // may reach this point if the solve has yielded a non-physical configuration such as
2388  // one block being pushed way out into space
2389  const Node & secondary_node = (xi1 < 0) ? secondary_elem_candidate->node_ref(0)
2390  : secondary_elem_candidate->node_ref(1);
2391  bool created_mortar_segment = false;
2392 
2393  // If we have failed to project this secondary node, let's try again now
2394  if (_failed_secondary_node_projections.count(secondary_node.id()))
2395  created_mortar_segment = processAlignedNodes(secondary_node,
2396  *primary_node,
2397  nullptr,
2398  &primary_node_neighbors,
2399  MetaPhysicL::raw_value(normals),
2400  *secondary_elem_candidate,
2401  rejected_secondary_elem_candidates);
2402  else
2403  rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2404 
2405  if (!created_mortar_segment)
2406  // We used to throw an exception in this scope but now that we support processing
2407  // aligned nodes within this primary node projection method, I don't see any harm in
2408  // simply rejecting the secondary element candidate in the case of failure and
2409  // continuing just as we do when projecting secondary nodes
2410  continue;
2411  }
2412  else // somewhere in the middle of the Elem
2413  {
2414  // Add entry to primary_node_and_elem_to_xi1_secondary_elem
2415  //
2416  // Note: we originally duplicated the map values for the keys (node, left_neighbor)
2417  // and (node, right_neighbor) but I don't think that should be necessary. Instead we
2418  // just do it for neighbor 0, but really maybe we don't even need to do that since
2419  // we can always look up the neighbors later given the Node... keeping it like this
2420  // helps to maintain the "symmetry" of the two containers.
2421  const Elem * neigh = primary_node_neighbors[0];
2422  for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2423  {
2424  const Node * neigh_node = neigh->node_ptr(nid);
2425  if (primary_node == neigh_node)
2426  {
2427  auto key = std::make_tuple(neigh_node->id(), neigh_node, neigh);
2428  auto val = std::make_pair(xi1, secondary_elem_candidate);
2430  }
2431  }
2432  }
2433 
2434  projection_succeeded = true;
2435  break; // out of e-loop
2436  }
2437  else
2438  {
2439  // The current primary_point is not in this Elem, so keep track of the rejects.
2440  rejected_secondary_elem_candidates.insert(secondary_elem_candidate);
2441  }
2442  } // end e-loop over candidate elems
2443 
2444  if (projection_succeeded)
2445  break; // out of r-loop
2446  } // r-loop
2447 
2448  if (!projection_succeeded && _debug)
2449  {
2450  _console << "\nFailed to find point from which primary node "
2451  << static_cast<const Point &>(*primary_node) << " was projected." << std::endl
2452  << std::endl;
2453  }
2454  } // loop over side nodes
2455  } // end loop over elements for finding where primary points would have projected from.
2456 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
Real _newton_tolerance
Newton solve tolerance for node projections.
auto raw_value(const Eigen::Map< T > &in)
Definition: EigenADReal.h:73
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
auto norm_sq(const T &a) -> decltype(std::norm(a))
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 1856 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

1857 {
1858  // For each primary/secondary boundary id pair, call the
1859  // project_secondary_nodes_single_pair() helper function.
1860  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1861  projectSecondaryNodesSinglePair(pr.first, pr.second);
1862 }
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 1981 of file AutomaticMortarGeneration.C.

Referenced by projectSecondaryNodes().

1984 {
1985  // Build the "subdomain" adaptor based KD Tree.
1986  NanoflannMeshSubdomainAdaptor<3> mesh_adaptor(_mesh, lower_dimensional_primary_subdomain_id);
1987  subdomain_kd_tree_t kd_tree(
1988  3, mesh_adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
1989 
1990  // Construct the KD tree.
1991  kd_tree.buildIndex();
1992 
1993  for (MeshBase::const_element_iterator el = _mesh.active_elements_begin(),
1994  end_el = _mesh.active_elements_end();
1995  el != end_el;
1996  ++el)
1997  {
1998  const Elem * secondary_side_elem = *el;
1999 
2000  // If this Elem is not in the current secondary subdomain, go on to the next one.
2001  if (secondary_side_elem->subdomain_id() != lower_dimensional_secondary_subdomain_id)
2002  continue;
2003 
2004  // For each node on the lower-dimensional element, find the nearest
2005  // node on the primary side using the KDTree, then
2006  // search in nearby elements for where it projects
2007  // along the nodal normal direction.
2008  for (MooseIndex(secondary_side_elem->n_vertices()) n = 0; n < secondary_side_elem->n_vertices();
2009  ++n)
2010  {
2011  const Node * secondary_node = secondary_side_elem->node_ptr(n);
2012 
2013  // Get the nodal neighbors for secondary_node, so we can check whether we've
2014  // already successfully projected it.
2015  const std::vector<const Elem *> & secondary_node_neighbors =
2016  this->_nodes_to_secondary_elem_map.at(secondary_node->id());
2017 
2018  // Check whether we've already mapped this secondary node
2019  // successfully for all of its nodal neighbors.
2020  bool is_mapped = true;
2021  for (MooseIndex(secondary_node_neighbors) snn = 0; snn < secondary_node_neighbors.size();
2022  ++snn)
2023  {
2024  auto secondary_key = std::make_pair(secondary_node, secondary_node_neighbors[snn]);
2025  if (!_secondary_node_and_elem_to_xi2_primary_elem.count(secondary_key))
2026  {
2027  is_mapped = false;
2028  break;
2029  }
2030  }
2031 
2032  // Go to the next node if this one has already been mapped.
2033  if (is_mapped)
2034  continue;
2035 
2036  // Look up the new nodal normal value in the local storage, error if not found.
2037  Point nodal_normal = _secondary_node_to_nodal_normal.at(secondary_node);
2038 
2039  // Data structure for performing Nanoflann searches.
2040  std::array<Real, 3> query_pt = {
2041  {(*secondary_node)(0), (*secondary_node)(1), (*secondary_node)(2)}};
2042 
2043  // The number of results we want to get. We'll look for a
2044  // "few" nearest nodes, hopefully that is enough to let us
2045  // figure out which lower-dimensional Elem on the primary
2046  // side we are across from.
2047  const std::size_t num_results = 3;
2048 
2049  // Initialize result_set and do the search.
2050  std::vector<size_t> ret_index(num_results);
2051  std::vector<Real> out_dist_sqr(num_results);
2052  nanoflann::KNNResultSet<Real> result_set(num_results);
2053  result_set.init(&ret_index[0], &out_dist_sqr[0]);
2054  kd_tree.findNeighbors(result_set, &query_pt[0], nanoflann::SearchParameters());
2055 
2056  // If this flag gets set in the loop below, we can break out of the outer r-loop as well.
2057  bool projection_succeeded = false;
2058 
2059  // Once we've rejected a candidate for a given secondary_node,
2060  // there's no reason to check it again.
2061  std::set<const Elem *> rejected_primary_elem_candidates;
2062 
2063  // Loop over the closest nodes, check whether
2064  // the secondary node successfully projects into
2065  // either of the closest neighbors, stop when
2066  // the projection succeeds.
2067  for (MooseIndex(result_set) r = 0; r < result_set.size(); ++r)
2068  {
2069  // Verify that the squared distance we compute is the same as nanoflann'sFss
2070  mooseAssert(std::abs((_mesh.point(ret_index[r]) - *secondary_node).norm_sq() -
2071  out_dist_sqr[r]) <= TOLERANCE,
2072  "Lower-dimensional element squared distance verification failed.");
2073 
2074  // Get a reference to the vector of lower dimensional elements from the
2075  // nodes_to_primary_elem_map.
2076  std::vector<const Elem *> & primary_elem_candidates =
2077  this->_nodes_to_primary_elem_map.at(static_cast<dof_id_type>(ret_index[r]));
2078 
2079  // Search the Elems connected to this node on the primary mesh side.
2080  for (MooseIndex(primary_elem_candidates) e = 0; e < primary_elem_candidates.size(); ++e)
2081  {
2082  const Elem * primary_elem_candidate = primary_elem_candidates[e];
2083 
2084  // If we've already rejected this candidate, we don't need to check it again.
2085  if (rejected_primary_elem_candidates.count(primary_elem_candidate))
2086  continue;
2087 
2088  // Now generically solve for xi2
2089  const auto order = primary_elem_candidate->default_order();
2090  DualNumber<Real> xi2_dn{0, 1};
2091  unsigned int current_iterate = 0, max_iterates = 10;
2092 
2093  // Newton loop
2094  do
2095  {
2097  for (MooseIndex(primary_elem_candidate->n_nodes()) n = 0;
2098  n < primary_elem_candidate->n_nodes();
2099  ++n)
2100  x2 +=
2101  Moose::fe_lagrange_1D_shape(order, n, xi2_dn) * primary_elem_candidate->point(n);
2102  const auto u = x2 - (*secondary_node);
2103  const auto F = u(0) * nodal_normal(1) - u(1) * nodal_normal(0);
2104 
2105  if (std::abs(F) < _newton_tolerance)
2106  break;
2107 
2108  if (F.derivatives())
2109  {
2110  Real dxi2 = -F.value() / F.derivatives();
2111 
2112  xi2_dn += dxi2;
2113  }
2114  else
2115  // It's possible that the secondary surface nodal normal is completely orthogonal to
2116  // the primary surface normal, in which case the derivative is 0. We know in this case
2117  // that the projection should be a failure
2118  current_iterate = max_iterates;
2119  } while (++current_iterate < max_iterates);
2120 
2121  Real xi2 = xi2_dn.value();
2122 
2123  // Check whether the projection worked. The last condition checks for obliqueness of the
2124  // projection
2125  //
2126  // We are projecting on one side first and the other side second. If we make the
2127  // tolerance bigger and remove the (5) factor we are going to continue to miss the
2128  // second projection and fall into the exception message in
2129  // projectPrimaryNodesSinglePair. What makes this modification to not fall in the
2130  // exception is that we are projecting on one side more xi than in the other. There
2131  // should be a better way of doing this by using actual distances and not parametric
2132  // coordinates. But I believe making the tolerance uniformly larger or smaller won't do
2133  // the trick here.
2134  if ((current_iterate < max_iterates) && (std::abs(xi2) <= 1. + 5 * _xi_tolerance) &&
2135  (std::abs(
2136  (primary_elem_candidate->point(0) - primary_elem_candidate->point(1)).unit() *
2137  nodal_normal) < std::cos(_minimum_projection_angle * libMesh::pi / 180)))
2138  {
2139  // If xi2 == +1 or -1 then this secondary node mapped directly to a node on the primary
2140  // surface. This isn't as unlikely as you might think, it will happen if the meshes
2141  // on the interface start off being perfectly aligned. In this situation, we need to
2142  // associate the secondary node with two different elements (and two corresponding
2143  // xi^(2) values.
2144  if (std::abs(std::abs(xi2) - 1.) <= _xi_tolerance * 5.0)
2145  {
2146  const Node * primary_node = (xi2 < 0) ? primary_elem_candidate->node_ptr(0)
2147  : primary_elem_candidate->node_ptr(1);
2148  const bool created_mortar_segment =
2149  processAlignedNodes(*secondary_node,
2150  *primary_node,
2151  &secondary_node_neighbors,
2152  nullptr,
2153  nodal_normal,
2154  *primary_elem_candidate,
2155  rejected_primary_elem_candidates);
2156 
2157  if (!created_mortar_segment)
2158  continue;
2159  }
2160  else // Point falls somewhere in the middle of the Elem.
2161  {
2162  // Add two entries to secondary_node_and_elem_to_xi2_primary_elem.
2163  for (MooseIndex(secondary_node_neighbors) nn = 0;
2164  nn < secondary_node_neighbors.size();
2165  ++nn)
2166  {
2167  const Elem * neigh = secondary_node_neighbors[nn];
2168  for (MooseIndex(neigh->n_vertices()) nid = 0; nid < neigh->n_vertices(); ++nid)
2169  {
2170  const Node * neigh_node = neigh->node_ptr(nid);
2171  if (secondary_node == neigh_node)
2172  {
2173  auto key = std::make_pair(neigh_node, neigh);
2174  auto val = std::make_pair(xi2, primary_elem_candidate);
2176  }
2177  }
2178  }
2179  }
2180 
2181  projection_succeeded = true;
2182  break; // out of e-loop
2183  }
2184  else
2185  // The current secondary_node is not in this Elem, so keep track of the rejects.
2186  rejected_primary_elem_candidates.insert(primary_elem_candidate);
2187  }
2188 
2189  if (projection_succeeded)
2190  break; // out of r-loop
2191  } // r-loop
2192 
2193  if (!projection_succeeded)
2194  {
2195  _failed_secondary_node_projections.insert(secondary_node->id());
2196  if (_debug)
2197  _console << "Failed to find primary Elem into which secondary node "
2198  << static_cast<const Point &>(*secondary_node) << ", id '"
2199  << secondary_node->id() << "', projects onto\n"
2200  << std::endl;
2201  }
2202  else if (_debug)
2203  _projected_secondary_nodes.insert(secondary_node->id());
2204  } // loop over side nodes
2205  } // end loop over lower-dimensional elements
2206 
2207  if (_distributed)
2208  {
2209  if (_debug)
2212  }
2213 
2214  if (_debug)
2215  _console << "\n"
2216  << _projected_secondary_nodes.size() << " out of "
2218  << " secondary nodes were successfully projected\n"
2219  << std::endl;
2220 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
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
auto norm_sq(const T &a) -> decltype(std::norm(a))
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 2459 of file AutomaticMortarGeneration.C.

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

2460 {
2461  auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2462  if (secondary_it == _nodes_to_secondary_elem_map.end())
2463  return {};
2464 
2465  const auto & secondary_elems = secondary_it->second;
2466  std::vector<MortarFilterIter> ret;
2467  ret.reserve(secondary_elems.size());
2468 
2469  for (const auto i : index_range(secondary_elems))
2470  {
2471  auto * const secondary_elem = secondary_elems[i];
2472  auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2473  if (msm_it == _secondary_elems_to_mortar_segments.end())
2474  // We may have removed this element key from this map
2475  continue;
2476 
2477  mooseAssert(secondary_elem->active(),
2478  "We loop over active elements when building the mortar segment mesh, so we golly "
2479  "well hope this is active.");
2480  mooseAssert(!msm_it->second.empty(),
2481  "We should have removed all secondaries from this map if they do not have any "
2482  "mortar segments associated with them.");
2483  ret.push_back(msm_it);
2484  }
2485 
2486  return ret;
2487 }
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 315 of file AutomaticMortarGeneration.h.

316  {
318  }
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 323 of file AutomaticMortarGeneration.h.

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

323 { 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 530 of file AutomaticMortarGeneration.h.

◆ MortarNodalGeometryOutput

friend class MortarNodalGeometryOutput
friend

Definition at line 529 of file AutomaticMortarGeneration.h.

Member Data Documentation

◆ _app

MooseApp& AutomaticMortarGeneration::_app
private

The Moose app.

Definition at line 350 of file AutomaticMortarGeneration.h.

Referenced by initOutput().

◆ _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(), 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(), CoarsenBlockGenerator::generate(), MeshGenerator::generateInternal(), VariableCondensationPreconditioner::getDofToCondense(), NonlinearEigen::init(), InversePowerMethod::init(), FEProblemBase::initialAdaptMesh(), DefaultMultiAppFixedPointConvergence::initialize(), SubProblem::initialSetup(), EigenExecutionerBase::inversePowerIteration(), FEProblemBase::joinAndFinalize(), TransientBase::keepGoing(), IterationAdaptiveDT::limitDTByFunction(), IterationAdaptiveDT::limitDTToPostprocessorValue(), FEProblemBase::logAdd(), EigenExecutionerBase::makeBXConsistent(), Console::meshChanged(), MooseBase::mooseDeprecated(), 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(), 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(), NonlinearSystem::solve(), FixedPointSolve::solve(), LinearSystem::solve(), LStableDirk2::solve(), LStableDirk3::solve(), ImplicitMidpoint::solve(), ExplicitTVDRK2::solve(), AStableDirk4::solve(), LStableDirk4::solve(), ExplicitRK2::solve(), TransientMultiApp::solveStep(), FixedPointSolve::solveStep(), PerfGraphLivePrint::start(), 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 511 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 499 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 527 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 443 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 408 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 517 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 427 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 399 of file AutomaticMortarGeneration.h.

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

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

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

◆ _newton_tolerance

Real AutomaticMortarGeneration::_newton_tolerance = 1e-12
private

Newton solve tolerance for node projections.

Definition at line 502 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 366 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 493 of file AutomaticMortarGeneration.h.

Referenced by initOutput(), 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 520 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 496 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 417 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 455 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 395 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 359 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 363 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh(), initOutput(), 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 412 of file AutomaticMortarGeneration.h.

Referenced by AutomaticMortarGeneration(), buildMortarSegmentMesh3d(), computeInactiveLMElems(), computeInactiveLMNodes(), computeIncorrectEdgeDroppingInactiveLMNodes(), msmStatistics(), 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 524 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 416 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 437 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 449 of file AutomaticMortarGeneration.h.

Referenced by buildMortarSegmentMesh(), buildMortarSegmentMesh3d(), clear(), 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 452 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 387 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 434 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 430 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 356 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 507 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 63 of file AutomaticMortarGeneration.h.


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