Skip to content

Commit 231f9e6

Browse files
authored
Merge pull request #4218 from pbehne/variational_smoother_tri6
VariationalMeshSmoother: Support for higher order elements
2 parents d1acc0a + ead09fc commit 231f9e6

File tree

4 files changed

+310
-164
lines changed

4 files changed

+310
-164
lines changed

include/systems/variational_smoother_constraint.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class PointConstraint
5757
* @param point The point defining the constraint.
5858
* @param tol The tolerance to use for numerical comparisons.
5959
*/
60-
PointConstraint(const Point &point, const Real &tol = TOLERANCE);
60+
PointConstraint(const Point &point, const Real &tol = TOLERANCE * TOLERANCE);
6161

6262
/**
6363
* Comparison operator for ordering PointConstraint objects.
@@ -132,7 +132,7 @@ class LineConstraint
132132
* @param tol The tolerance to use for numerical comparisons.
133133
*/
134134
LineConstraint(const Point &point, const Point &direction,
135-
const Real &tol = TOLERANCE);
135+
const Real &tol = TOLERANCE * TOLERANCE);
136136

137137
/**
138138
* Comparison operator for ordering LineConstraint objects.
@@ -232,7 +232,7 @@ class PlaneConstraint
232232
* @param tol The tolerance to use for numerical comparisons.
233233
*/
234234
PlaneConstraint(const Point &point, const Point &normal,
235-
const Real &tol = TOLERANCE);
235+
const Real &tol = TOLERANCE * TOLERANCE);
236236

237237
/**
238238
* Comparison operator for ordering PlaneConstraint objects.
@@ -421,6 +421,19 @@ class VariationalSmootherConstraint : public System::Constraint
421421
*/
422422
void constrain_node_to_line(const Node & node, const Point & line_vec);
423423

424+
/**
425+
* Given a mesh and a node in the mesh, the vector will be filled with
426+
* every node directly attached to the given one. IF NO neighbors are found,
427+
* all nodes on the containing side are treated as neighbors. This is useful
428+
* when the node does not lie on an edge, such as the central face node in
429+
* HEX27 elements.
430+
*/
431+
static void find_nodal_or_face_neighbors(
432+
const MeshBase & mesh,
433+
const Node & node,
434+
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
435+
std::vector<const Node *> & neighbors);
436+
424437
/**
425438
* Determines whether two neighboring nodes share a common boundary id.
426439
* @param boundary_node The first of the two prospective nodes.

src/systems/variational_smoother_constraint.C

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,45 @@ void VariationalSmootherConstraint::constrain_node_to_line(const Node & node,
521521
}
522522
}
523523

524+
void VariationalSmootherConstraint::find_nodal_or_face_neighbors(
525+
const MeshBase & mesh,
526+
const Node & node,
527+
const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map,
528+
std::vector<const Node *> & neighbors)
529+
{
530+
// Find all the nodal neighbors... that is the nodes directly connected
531+
// to this node through one edge.
532+
MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
533+
534+
// If no neighbors are found, use all nodes on the containing side as
535+
// neighbors.
536+
if (!neighbors.size())
537+
{
538+
// Grab the element containing node
539+
const auto * elem = libmesh_map_find(nodes_to_elem_map, node.id()).front();
540+
// Find the element side containing node
541+
for (const auto &side : elem->side_index_range())
542+
{
543+
const auto &nodes_on_side = elem->nodes_on_side(side);
544+
const auto it =
545+
std::find_if(nodes_on_side.begin(), nodes_on_side.end(), [&](auto local_node_id) {
546+
return elem->node_id(local_node_id) == node.id();
547+
});
548+
549+
if (it != nodes_on_side.end())
550+
{
551+
for (const auto &local_node_id : nodes_on_side)
552+
// No need to add node itself as a neighbor
553+
if (const auto *node_ptr = elem->node_ptr(local_node_id);
554+
*node_ptr != node)
555+
neighbors.push_back(node_ptr);
556+
break;
557+
}
558+
}
559+
}
560+
libmesh_assert(neighbors.size());
561+
}
562+
524563
// Utility function to determine whether two nodes share a boundary ID.
525564
// The motivation for this is that a sliding boundary node on a triangular
526565
// element can have a neighbor boundary node in the same element that is not
@@ -588,9 +627,11 @@ VariationalSmootherConstraint::get_neighbors_for_subdomain_constraint(
588627
{
589628

590629
// Find all the nodal neighbors... that is the nodes directly connected
591-
// to this node through one edge
630+
// to this node through one edge, or if none exists, use other nodes on the
631+
// containing face
592632
std::vector<const Node *> neighbors;
593-
MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
633+
VariationalSmootherConstraint::find_nodal_or_face_neighbors(
634+
mesh, node, nodes_to_elem_map, neighbors);
594635

595636
// Each constituent set corresponds to neighbors sharing a face on the
596637
// subdomain boundary
@@ -600,8 +641,8 @@ VariationalSmootherConstraint::get_neighbors_for_subdomain_constraint(
600641
{
601642
// Determine whether the neighbor is on the subdomain boundary
602643
// First, find the common elements that both node and neigh belong to
603-
const auto & elems_containing_node = nodes_to_elem_map.at(node.id());
604-
const auto & elems_containing_neigh = nodes_to_elem_map.at(neigh->id());
644+
const auto & elems_containing_node = libmesh_map_find(nodes_to_elem_map, node.id());
645+
const auto & elems_containing_neigh = libmesh_map_find(nodes_to_elem_map, neigh->id());
605646
const Elem * common_elem = nullptr;
606647
for (const auto * neigh_elem : elems_containing_neigh)
607648
{
@@ -672,9 +713,11 @@ VariationalSmootherConstraint::get_neighbors_for_boundary_constraint(
672713
{
673714

674715
// Find all the nodal neighbors... that is the nodes directly connected
675-
// to this node through one edge
716+
// to this node through one edge, or if none exists, use other nodes on the
717+
// containing face
676718
std::vector<const Node *> neighbors;
677-
MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
719+
VariationalSmootherConstraint::find_nodal_or_face_neighbors(
720+
mesh, node, nodes_to_elem_map, neighbors);
678721

679722
// Each constituent set corresponds to neighbors sharing a face on the
680723
// boundary
@@ -689,8 +732,8 @@ VariationalSmootherConstraint::get_neighbors_for_boundary_constraint(
689732

690733
// Determine whether nodes share a common boundary id
691734
// First, find the common element that both node and neigh belong to
692-
const auto & elems_containing_node = nodes_to_elem_map.at(node.id());
693-
const auto & elems_containing_neigh = nodes_to_elem_map.at(neigh->id());
735+
const auto & elems_containing_node = libmesh_map_find(nodes_to_elem_map, node.id());
736+
const auto & elems_containing_neigh = libmesh_map_find(nodes_to_elem_map, neigh->id());
694737
const Elem * common_elem = nullptr;
695738
for (const auto * neigh_elem : elems_containing_neigh)
696739
{

src/systems/variational_smoother_system.C

Lines changed: 102 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "libmesh/elem.h"
2121
#include "libmesh/face_tri3.h"
22+
#include "libmesh/face_tri6.h"
2223
#include "libmesh/fe_base.h"
2324
#include "libmesh/fe_interface.h"
2425
#include "libmesh/fem_context.h"
@@ -113,90 +114,121 @@ void VariationalSmootherSystem::prepare_for_smoothing()
113114
std::map<ElemType, std::vector<Real>> target_elem_inverse_jacobian_dets;
114115

115116
for (const auto * elem : mesh.active_local_element_ptr_range())
116-
{
117-
femcontext.pre_fe_reinit(*this, elem);
118-
femcontext.elem_fe_reinit();
119-
120-
// Add target element info, if applicable
121-
if (_target_inverse_jacobians.find(elem->type()) == _target_inverse_jacobians.end())
122117
{
123-
// Create FEMap to compute target_element mapping information
124-
FEMap fe_map_target;
125-
126-
// pre-request mapping derivatives
127-
const auto & dxyzdxi = fe_map_target.get_dxyzdxi();
128-
const auto & dxyzdeta = fe_map_target.get_dxyzdeta();
129-
// const auto & dxyzdzeta = fe_map_target.get_dxyzdzeta();
118+
femcontext.pre_fe_reinit(*this, elem);
119+
femcontext.elem_fe_reinit();
130120

131-
const auto & qrule_points = femcontext.get_element_qrule().get_points();
132-
const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
133-
const auto nq_points = femcontext.get_element_qrule().n_points();
134-
135-
// If the target element is the reference element, Jacobian matrix is
136-
// identity, det of inverse is 1. This will only be overwritten if a
137-
// different target elemen is explicitly specified.
138-
target_elem_inverse_jacobian_dets[elem->type()] =
139-
std::vector<Real>(nq_points, 1.0);
140-
141-
switch (elem->type())
121+
// Add target element info, if applicable
122+
if (_target_inverse_jacobians.find(elem->type()) == _target_inverse_jacobians.end())
142123
{
143-
case TRI3:
124+
// Create FEMap to compute target_element mapping information
125+
FEMap fe_map_target;
126+
127+
// pre-request mapping derivatives
128+
const auto & dxyzdxi = fe_map_target.get_dxyzdxi();
129+
const auto & dxyzdeta = fe_map_target.get_dxyzdeta();
130+
// const auto & dxyzdzeta = fe_map_target.get_dxyzdzeta();
131+
132+
const auto & qrule_points = femcontext.get_element_qrule().get_points();
133+
const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
134+
const auto nq_points = femcontext.get_element_qrule().n_points();
135+
136+
// If the target element is the reference element, Jacobian matrix is
137+
// identity, det of inverse is 1. This will only be overwritten if a
138+
// different target elemen is explicitly specified.
139+
target_elem_inverse_jacobian_dets[elem->type()] =
140+
std::vector<Real>(nq_points, 1.0);
141+
142+
// Elems deriving from Tri
143+
if (elem->type() == TRI3 || elem->type() == TRI6)
144144
{
145-
// Build target element: an equilateral triangle
146-
Tri3 target_elem;
147145

148-
// equilateral triangle side length that preserves area of reference
149-
// element
146+
// The target element will be an equilateral triangle with area equal to
147+
// the area of the reference element.
148+
149+
// First, let's define the nodal locations of the vertices
150150
const Real sqrt_3 = std::sqrt(3.);
151-
const auto ref_volume = target_elem.reference_elem()->volume();
152-
const auto s = std::sqrt(4. / sqrt_3 * ref_volume);
151+
152+
std::vector<Point> equilateral_points{
153+
Point(0., 0.),
154+
Point(1., 0.),
155+
Point(0.5, 0.5 * sqrt_3)
156+
};
157+
158+
// Target element
159+
const auto target_elem = Elem::build(elem->type());
160+
161+
// Area of the reference element
162+
const auto ref_area = target_elem->reference_elem()->volume();
163+
164+
switch (elem->type())
165+
{
166+
case TRI3:
167+
{
168+
// Nothing to do here, vertices already defined in equilateral_points
169+
break;
170+
}
171+
172+
case TRI6:
173+
{
174+
// Define the midpoint nodes of the equilateral triangle
175+
equilateral_points.emplace_back(0.50, 0. );
176+
equilateral_points.emplace_back(0.75, 0.25 * sqrt_3);
177+
equilateral_points.emplace_back(0.25, 0.25 * sqrt_3);
178+
179+
break;
180+
}
181+
182+
default:
183+
libmesh_error_msg("Unsupported triangular element!");
184+
break;
185+
}
186+
187+
// Equilateral triangle side length preserving area of the reference element
188+
const auto side_length = std::sqrt(4. / sqrt_3 * ref_area);
189+
190+
std::vector<std::unique_ptr<Node>> owned_nodes;
153191

154192
// Set nodes of target element to form an equilateral triangle
155-
Node node_0 = Node(0., 0.);
156-
Node node_1 = Node(s, 0.);
157-
Node node_2 = Node(0.5 * s, 0.5 * s * sqrt_3);
158-
target_elem.set_node(0, &node_0);
159-
target_elem.set_node(1, &node_1);
160-
target_elem.set_node(2, &node_2);
193+
for (const dof_id_type node_id : index_range(equilateral_points))
194+
{
195+
// Scale the nodal positions to conserve area, store the pointer to keep it alive
196+
owned_nodes.emplace_back(
197+
Node::build(equilateral_points[node_id] * side_length, node_id));
198+
target_elem->set_node(node_id, owned_nodes.back().get());
199+
}
161200

162201
// build map
163-
fe_map_target.init_reference_to_physical_map(dim, qrule_points,
164-
&target_elem);
165-
fe_map_target.compute_map(dim, qrule_weights, &target_elem,
166-
/*d2phi=*/false);
202+
fe_map_target.init_reference_to_physical_map(dim, qrule_points, target_elem.get());
203+
fe_map_target.compute_map(dim, qrule_weights, target_elem.get(), /*d2phi=*/false);
167204

168205
// Yes, triangle-to-triangle mappings have constant Jacobians, but we
169206
// will keep things general for now
170-
_target_inverse_jacobians[target_elem.type()] =
207+
_target_inverse_jacobians[target_elem->type()] =
171208
std::vector<RealTensor>(nq_points);
172209
for (const auto qp : make_range(nq_points))
173-
{
174-
const RealTensor H_inv =
175-
RealTensor(dxyzdxi[qp](0), dxyzdeta[qp](0), 0, dxyzdxi[qp](1),
176-
dxyzdeta[qp](1), 0, 0, 0, 1)
177-
.inverse();
178-
179-
_target_inverse_jacobians[target_elem.type()][qp] = H_inv;
180-
target_elem_inverse_jacobian_dets[target_elem.type()][qp] =
181-
H_inv.det();
182-
}
183-
184-
break;
185-
}
186-
187-
default:
188-
break;
189-
}
190-
}
191-
192-
Real elem_integrated_det_J(0.);
193-
for (const auto qp : index_range(JxW))
194-
elem_integrated_det_J +=
195-
JxW[qp] * target_elem_inverse_jacobian_dets[elem->type()][qp];
196-
const auto ref_elem_vol = elem->reference_elem()->volume();
197-
elem_averaged_det_J_sum += elem_integrated_det_J / ref_elem_vol;
198-
199-
} // for elem
210+
{
211+
const RealTensor H_inv =
212+
RealTensor(dxyzdxi[qp](0), dxyzdeta[qp](0), 0, dxyzdxi[qp](1),
213+
dxyzdeta[qp](1), 0, 0, 0, 1)
214+
.inverse();
215+
216+
_target_inverse_jacobians[target_elem->type()][qp] = H_inv;
217+
target_elem_inverse_jacobian_dets[target_elem->type()][qp] =
218+
H_inv.det();
219+
}
220+
}// if Tri
221+
}// if find == end()
222+
223+
// Reference volume computation
224+
Real elem_integrated_det_J = 0.;
225+
for (const auto qp : index_range(JxW))
226+
elem_integrated_det_J +=
227+
JxW[qp] * target_elem_inverse_jacobian_dets[elem->type()][qp];
228+
const auto ref_elem_vol = elem->reference_elem()->volume();
229+
elem_averaged_det_J_sum += elem_integrated_det_J / ref_elem_vol;
230+
231+
} // for elem
200232

201233
// Get contributions from elements on other processors
202234
mesh.comm().sum(elem_averaged_det_J_sum);
@@ -395,7 +427,7 @@ bool VariationalSmootherSystem::element_time_derivative (bool request_jacobian,
395427
const Real chi_prime = 0.5 * (1. + det / sqrt_term);
396428
const Real chi_prime_sq = chi_prime * chi_prime;
397429
// d2chi(x) / dx2
398-
const Real chi_2prime = 0.5 * (1. / sqrt_term - det_sq / std::pow(sqrt_term, 3));
430+
const Real chi_2prime = 0.5 * (1. / sqrt_term - det_sq / Utility::pow<3>(sqrt_term));
399431

400432
// Distortion metric (beta)
401433
//const Real beta = trace_powers[0] / chi;

0 commit comments

Comments
 (0)