Skip to content

Commit cdd847c

Browse files
authored
Merge pull request #4287 from pbehne/variational_smoother_tets
Variational smoother: tet support
2 parents 698fa66 + 5376a24 commit cdd847c

File tree

6 files changed

+412
-57
lines changed

6 files changed

+412
-57
lines changed

include/mesh/mesh_tools.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,19 @@ void find_nodal_neighbors(const MeshBase & mesh,
347347
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
348348
std::vector<const Node *> & neighbors);
349349

350+
/**
351+
* Given a mesh and a node in the mesh, the vector will be filled with
352+
* every node directly attached to the given one. IF NO nodal neighbors are found,
353+
* all nodes on the containing side are treated as neighbors. This is useful
354+
* when the node does not lie on an edge, such as the central face node in
355+
* HEX27, TET14, etc. elements.
356+
*/
357+
void find_nodal_or_face_neighbors(
358+
const MeshBase & mesh,
359+
const Node & node,
360+
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
361+
std::vector<const Node *> & neighbors);
362+
350363
/**
351364
* Given a mesh hanging_nodes will be filled with an associative array keyed off the
352365
* global id of all the hanging nodes in the mesh. It will hold an array of the

include/systems/variational_smoother_constraint.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -433,19 +433,6 @@ class VariationalSmootherConstraint : public System::Constraint
433433
*/
434434
void constrain_node_to_line(const Node & node, const Point & line_vec);
435435

436-
/**
437-
* Given a mesh and a node in the mesh, the vector will be filled with
438-
* every node directly attached to the given one. IF NO neighbors are found,
439-
* all nodes on the containing side are treated as neighbors. This is useful
440-
* when the node does not lie on an edge, such as the central face node in
441-
* HEX27 elements.
442-
*/
443-
static void find_nodal_or_face_neighbors(
444-
const MeshBase & mesh,
445-
const Node & node,
446-
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
447-
std::vector<const Node *> & neighbors);
448-
449436
/**
450437
* Determines whether two neighboring nodes share a common boundary id.
451438
* @param boundary_node The first of the two prospective nodes.

src/mesh/mesh_tools.C

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,45 @@ void find_nodal_neighbors(const MeshBase &,
10611061
find_nodal_neighbors_helper(node.id(), node_to_elem_vec, neighbors);
10621062
}
10631063

1064+
void find_nodal_or_face_neighbors(
1065+
const MeshBase & mesh,
1066+
const Node & node,
1067+
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
1068+
std::vector<const Node *> & neighbors)
1069+
{
1070+
// Find all the nodal neighbors... that is the nodes directly connected
1071+
// to this node through one edge.
1072+
find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
1073+
1074+
// If no neighbors are found, use all nodes on the containing side as
1075+
// neighbors.
1076+
if (!neighbors.size())
1077+
{
1078+
// Grab the element containing node
1079+
const auto * elem = libmesh_map_find(nodes_to_elem_map, node.id()).front();
1080+
// Find the element side containing node
1081+
for (const auto &side : elem->side_index_range())
1082+
{
1083+
const auto &nodes_on_side = elem->nodes_on_side(side);
1084+
const auto it =
1085+
std::find_if(nodes_on_side.begin(), nodes_on_side.end(), [&](auto local_node_id) {
1086+
return elem->node_id(local_node_id) == node.id();
1087+
});
1088+
1089+
if (it != nodes_on_side.end())
1090+
{
1091+
for (const auto &local_node_id : nodes_on_side)
1092+
// No need to add node itself as a neighbor
1093+
if (const auto *node_ptr = elem->node_ptr(local_node_id);
1094+
*node_ptr != node)
1095+
neighbors.push_back(node_ptr);
1096+
break;
1097+
}
1098+
}
1099+
}
1100+
libmesh_assert(neighbors.size());
1101+
}
1102+
10641103

10651104

10661105
void find_hanging_nodes_and_parents(const MeshBase & mesh,

src/systems/variational_smoother_constraint.C

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -562,45 +562,6 @@ void VariationalSmootherConstraint::constrain_node_to_line(const Node & node,
562562
}
563563
}
564564

565-
void VariationalSmootherConstraint::find_nodal_or_face_neighbors(
566-
const MeshBase & mesh,
567-
const Node & node,
568-
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
569-
std::vector<const Node *> & neighbors)
570-
{
571-
// Find all the nodal neighbors... that is the nodes directly connected
572-
// to this node through one edge.
573-
MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
574-
575-
// If no neighbors are found, use all nodes on the containing side as
576-
// neighbors.
577-
if (!neighbors.size())
578-
{
579-
// Grab the element containing node
580-
const auto * elem = libmesh_map_find(nodes_to_elem_map, node.id()).front();
581-
// Find the element side containing node
582-
for (const auto &side : elem->side_index_range())
583-
{
584-
const auto &nodes_on_side = elem->nodes_on_side(side);
585-
const auto it =
586-
std::find_if(nodes_on_side.begin(), nodes_on_side.end(), [&](auto local_node_id) {
587-
return elem->node_id(local_node_id) == node.id();
588-
});
589-
590-
if (it != nodes_on_side.end())
591-
{
592-
for (const auto &local_node_id : nodes_on_side)
593-
// No need to add node itself as a neighbor
594-
if (const auto *node_ptr = elem->node_ptr(local_node_id);
595-
*node_ptr != node)
596-
neighbors.push_back(node_ptr);
597-
break;
598-
}
599-
}
600-
}
601-
libmesh_assert(neighbors.size());
602-
}
603-
604565
// Utility function to determine whether two nodes share a boundary ID.
605566
// The motivation for this is that a sliding boundary node on a triangular
606567
// element can have a neighbor boundary node in the same element that is not
@@ -671,7 +632,7 @@ VariationalSmootherConstraint::get_neighbors_for_subdomain_constraint(
671632
// to this node through one edge, or if none exists, use other nodes on the
672633
// containing face
673634
std::vector<const Node *> neighbors;
674-
VariationalSmootherConstraint::find_nodal_or_face_neighbors(
635+
MeshTools::find_nodal_or_face_neighbors(
675636
mesh, node, nodes_to_elem_map, neighbors);
676637

677638
// Each constituent set corresponds to neighbors sharing a face on the
@@ -757,7 +718,7 @@ VariationalSmootherConstraint::get_neighbors_for_boundary_constraint(
757718
// to this node through one edge, or if none exists, use other nodes on the
758719
// containing face
759720
std::vector<const Node *> neighbors;
760-
VariationalSmootherConstraint::find_nodal_or_face_neighbors(
721+
MeshTools::find_nodal_or_face_neighbors(
761722
mesh, node, nodes_to_elem_map, neighbors);
762723

763724
// Each constituent set corresponds to neighbors sharing a face on the

src/systems/variational_smoother_system.C

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ VariationalSmootherSystem::get_target_elem(const ElemType & type)
923923
const auto ref_vol = target_elem->reference_elem()->volume();
924924

925925
// Update the nodes of the target element, depending on type
926+
const Real sqrt_2 = std::sqrt(Real(2));
926927
const Real sqrt_3 = std::sqrt(Real(3));
927928
std::vector<std::unique_ptr<Node>> owned_nodes;
928929

@@ -1055,7 +1056,6 @@ VariationalSmootherSystem::get_target_elem(const ElemType & type)
10551056
// Solving for s: s = (3 sqrt(2) v)^(1/3), where v is the volume of the
10561057
// non-optimal reference element.
10571058

1058-
const Real sqrt_2 = std::sqrt(Real(2));
10591059
// Side length that preserves the volume of the reference element
10601060
const auto side_length = std::pow(3. * sqrt_2 * ref_vol, 1. / 3.);
10611061
// Pyramid height with the property that all faces are equilateral triangles
@@ -1110,6 +1110,67 @@ VariationalSmootherSystem::get_target_elem(const ElemType & type)
11101110

11111111
} // if Pyramid
11121112

1113+
// Elems deriving from Tet
1114+
else if (type_str.compare(0, 3, "TET") == 0)
1115+
{
1116+
1117+
// The ideal target element is a a regular tet with equilateral
1118+
// triangles for all faces, with volume equal to the volume of the
1119+
// reference element.
1120+
1121+
// The volume of a tet is given by v = b * h / 3, where b is the area of
1122+
// the base face and h is the height of the apex node. The area of an
1123+
// equilateral triangle with side length s is b = sqrt(3) s^2 / 4.
1124+
// For all faces to have side length s, the height of the apex node is
1125+
// h = sqrt(2/3) * s. Then the volume is v = sqrt(2) * s^3 / 12.
1126+
// Solving for s, the side length that will preserve the volume of the
1127+
// reference element is s = (6 * sqrt(2) * v)^(1/3), where v is the volume
1128+
// of the non-optimal reference element (i.e., a right tet).
1129+
1130+
// Side length that preserves the volume of the reference element
1131+
const auto side_length = std::cbrt(6. * sqrt_2 * ref_vol);
1132+
// tet height with the property that all faces are equilateral triangles
1133+
const auto target_height = sqrt_2 / sqrt_3 * side_length;
1134+
1135+
const auto & s = side_length;
1136+
const auto & h = target_height;
1137+
1138+
// For regular tet
1139+
// x y z node_id
1140+
owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
1141+
owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
1142+
owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
1143+
owned_nodes.emplace_back(Node::build(Point(0.5 * s, sqrt_3 / 6. * s, h), 3));
1144+
1145+
if (type == TET10 || type == TET14)
1146+
{
1147+
const auto & on = owned_nodes;
1148+
// Define the edge midpoint nodes of the tet
1149+
1150+
// Base node to base node midpoint nodes
1151+
owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 4));
1152+
owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 5));
1153+
owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 6));
1154+
// Base node to apex node midpoint nodes
1155+
owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 7));
1156+
owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[3]) / 2.), 8));
1157+
owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3]) / 2.), 9));
1158+
1159+
if (type == TET14)
1160+
{
1161+
// Define the face midpoint nodes of the tet
1162+
owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 10));
1163+
owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3]) / 3.), 11));
1164+
owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[3]) / 3.), 12));
1165+
owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3]) / 3.), 13));
1166+
}
1167+
}
1168+
1169+
else if (type != TET4)
1170+
libmesh_error_msg("Unsupported tet element: " << type_str);
1171+
1172+
} // if Tet
1173+
11131174
// Set the target_elem equal to the reference elem
11141175
else
11151176
for (const auto & node : target_elem->reference_elem()->node_ref_range())

0 commit comments

Comments
 (0)