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

Referenced by buildMortarSegmentMesh(), and buildMortarSegmentMesh3d().

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

Referenced by MortarInterfaceWarehouse::update().

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

Referenced by MortarInterfaceWarehouse::update().

1674 {
1675  // Mark all active secondary elements
1676  std::unordered_set<const Elem *> active_local_elems;
1677 
1678  //****
1679  // Note that in 3D our trick to check whether an element has edge dropping needs loose tolerances
1680  // since the mortar segments are on the linearized element and comparing the volume of the
1681  // linearized element does not have the same volume as the warped element
1682  const Real tol = (dim() == 3) ? 0.1 : TOLERANCE;
1683 
1684  std::unordered_map<const Elem *, Real> active_volume;
1685 
1686  // Compute fraction of elements with corresponding primary elements
1688  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1689  {
1690  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1691  const Elem * secondary_elem = msinfo.secondary_elem;
1692 
1693  active_volume[secondary_elem] += msm_elem->volume();
1694  }
1695  //****
1696 
1697  for (const auto msm_elem : _mortar_segment_mesh->active_local_element_ptr_range())
1698  {
1699  const MortarSegmentInfo & msinfo = _msm_elem_to_info.at(msm_elem);
1700  const Elem * secondary_elem = msinfo.secondary_elem;
1701 
1702  //****
1704  if (abs(active_volume[secondary_elem] / secondary_elem->volume() - 1.0) > tol)
1705  continue;
1706  //****
1707 
1708  active_local_elems.insert(secondary_elem);
1709  }
1710 
1711  // Take complement of active elements in active local subdomain to get inactive local elements
1712  _inactive_local_lm_elems.clear();
1713  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1714  for (const auto el : _mesh.active_local_subdomain_elements_ptr_range(
1715  /*secondary_subd_id*/ pr.second))
1716  if (active_local_elems.find(el) == active_local_elems.end())
1717  _inactive_local_lm_elems.insert(el);
1718 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
std::unordered_set< const Elem * > _inactive_local_lm_elems
List of inactive lagrange multiplier nodes (for elemental variables)
std::unordered_map< const Elem *, MortarSegmentInfo > _msm_elem_to_info
Map between Elems in the mortar segment mesh and their info structs.
const bool _correct_edge_dropping
Flag to enable regressed treatment of edge dropping where all LM DoFs on edge dropping element are st...
const Elem * secondary_elem
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Holds xi^(1), xi^(2), and other data for a given mortar segment.
virtual SimpleRange< element_iterator > active_local_subdomain_elements_ptr_range(subdomain_id_type sid)=0
virtual Real volume() const
MeshBase & _mesh
Reference to the mesh stored in equation_systems.
std::vector< std::pair< SubdomainID, SubdomainID > > _primary_secondary_subdomain_id_pairs
A list of primary/secondary subdomain id pairs corresponding to each side of the mortar interface...
std::unique_ptr< MeshBase > _mortar_segment_mesh
1D Mesh of mortar segment elements which gets built by the call to build_mortar_segment_mesh().

◆ computeInactiveLMNodes()

void AutomaticMortarGeneration::computeInactiveLMNodes ( )

Get list of secondary nodes that don't contribute to interaction with any primary element.

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

Definition at line 1589 of file AutomaticMortarGeneration.C.

Referenced by MortarInterfaceWarehouse::update().

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

Referenced by computeInactiveLMNodes().

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

Referenced by MortarInterfaceWarehouse::update().

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

Referenced by computeNodalGeometry().

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

◆ incorrectEdgeDropping()

bool AutomaticMortarGeneration::incorrectEdgeDropping ( ) const
inline

Definition at line 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:2043
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:2525

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

Referenced by buildMortarSegmentMesh3d().

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

Referenced by projectPrimaryNodesSinglePair(), and projectSecondaryNodesSinglePair().

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

Referenced by MortarInterfaceWarehouse::update().

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

Referenced by projectPrimaryNodes().

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

Referenced by MortarInterfaceWarehouse::update().

1863 {
1864  // For each primary/secondary boundary id pair, call the
1865  // project_secondary_nodes_single_pair() helper function.
1866  for (const auto & pr : _primary_secondary_subdomain_id_pairs)
1867  projectSecondaryNodesSinglePair(pr.first, pr.second);
1868 }
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 1987 of file AutomaticMortarGeneration.C.

Referenced by projectSecondaryNodes().

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

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

2469 {
2470  auto secondary_it = _nodes_to_secondary_elem_map.find(node.id());
2471  if (secondary_it == _nodes_to_secondary_elem_map.end())
2472  return {};
2473 
2474  const auto & secondary_elems = secondary_it->second;
2475  std::vector<MortarFilterIter> ret;
2476  ret.reserve(secondary_elems.size());
2477 
2478  for (const auto i : index_range(secondary_elems))
2479  {
2480  auto * const secondary_elem = secondary_elems[i];
2481  auto msm_it = _secondary_elems_to_mortar_segments.find(secondary_elem->id());
2482  if (msm_it == _secondary_elems_to_mortar_segments.end())
2483  // We may have removed this element key from this map
2484  continue;
2485 
2486  mooseAssert(secondary_elem->active(),
2487  "We loop over active elements when building the mortar segment mesh, so we golly "
2488  "well hope this is active.");
2489  mooseAssert(!msm_it->second.empty(),
2490  "We should have removed all secondaries from this map if they do not have any "
2491  "mortar segments associated with them.");
2492  ret.push_back(msm_it);
2493  }
2494 
2495  return ret;
2496 }
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(), WebServerControl::outputMessage(), Console::outputPostprocessors(), PseudoTimestep::outputPseudoTimestep(), Console::outputReporters(), DefaultMultiAppFixedPointConvergence::outputResidualNorm(), Console::outputScalarVariables(), Console::outputSystemInformation(), FEProblemBase::possiblyRebuildGeomSearchPatches(), EigenExecutionerBase::postExecute(), AB2PredictorCorrector::postSolve(), ActionWarehouse::printActionDependencySets(), BlockRestrictionDebugOutput::printBlockRestrictionMap(), SolutionInvalidity::printDebug(), EigenExecutionerBase::printEigenvalue(), SecantSolve::printFixedPointConvergenceHistory(), SteffensenSolve::printFixedPointConvergenceHistory(), PicardSolve::printFixedPointConvergenceHistory(), FixedPointSolve::printFixedPointConvergenceReason(), PerfGraphLivePrint::printLiveMessage(), MaterialPropertyDebugOutput::printMaterialMap(), PerfGraphLivePrint::printStats(), NEML2Action::printSummary(), projectPrimaryNodesSinglePair(), projectSecondaryNodesSinglePair(), CoarsenBlockGenerator::recursiveCoarsen(), SolutionTimeAdaptiveDT::rejectStep(), MultiApp::restore(), FEProblemBase::restoreMultiApps(), FEProblemBase::restoreSolutions(), NonlinearSystemBase::setInitialSolution(), MooseApp::setupOptions(), Checkpoint::shouldOutput(), SubProblem::showFunctorRequestors(), SubProblem::showFunctors(), FullSolveMultiApp::showStatusMessage(), EigenProblem::solve(), FEProblemSolve::solve(), FixedPointSolve::solve(), NonlinearSystem::solve(), LinearSystem::solve(), LStableDirk2::solve(), LStableDirk3::solve(), ImplicitMidpoint::solve(), ExplicitTVDRK2::solve(), AStableDirk4::solve(), LStableDirk4::solve(), ExplicitRK2::solve(), TransientMultiApp::solveStep(), FixedPointSolve::solveStep(), PerfGraphLivePrint::start(), WebServerControl::startServer(), AB2PredictorCorrector::step(), NonlinearEigen::takeStep(), MFEMTransient::takeStep(), TransientBase::takeStep(), TerminateChainControl::terminate(), SubProblem::timestepSetup(), FEProblemBase::updateMeshXFEM(), Convergence::verboseOutput(), Console::writeTimestepInformation(), Console::writeVariableNorms(), and FEProblemBase::~FEProblemBase().

◆ _correct_edge_dropping

const bool AutomaticMortarGeneration::_correct_edge_dropping
private

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

Definition at line 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: