Skip to content

Commit b656aa0

Browse files
authored
Merge pull request #4221 from pbehne/variational_smoother_target_elem_cleanup
Variational smoother: target element refactor
2 parents a9f56e9 + 20df810 commit b656aa0

File tree

3 files changed

+191
-112
lines changed

3 files changed

+191
-112
lines changed

include/systems/variational_smoother_system.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ class VariationalSmootherSystem : public libMesh::FEMSystem
6767

6868
Real & get_dilation_weight() { return _dilation_weight; }
6969

70+
/**
71+
* Get the target element for a given element type.
72+
* @param type Element type
73+
* @return a std::pair containing the target element for type and the
74+
* corresponding nodes that must be kept in scope while the target element is
75+
* used.
76+
*/
77+
static std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
78+
get_target_elem(const ElemType & type);
79+
80+
/**
81+
* Get the jacobians (and determinants) of the target-to-reference element mapping.
82+
* @param target_elem Target element.
83+
* @param femcontext Context used to build mapping.
84+
* @param jacobian Vector in which to store the jacobians for each quadrature point.
85+
* @param jacobian_dets Vector in which to store the determinant of the jacobians
86+
* for each quadrature point.
87+
*/
88+
static void get_target_to_reference_jacobian(const Elem * const target_elem,
89+
const FEMContext & femcontext,
90+
std::vector<RealTensor> & jacobians,
91+
std::vector<Real> & jacobian_dets);
92+
7093
protected:
7194

7295
// System initialization
@@ -97,10 +120,10 @@ class VariationalSmootherSystem : public libMesh::FEMSystem
97120
/// The relative weight to give the dilation metric. The distortion metric is given weight 1 - _dilation_weight.
98121
Real _dilation_weight;
99122

100-
/* Map to hold target qp-dependent element inverse reference-to-target mapping
123+
/* Map to hold target qp-dependent element target-to-reference mapping
101124
* Jacobians, if any
102125
*/
103-
std::map<ElemType, std::vector<RealTensor>> _target_inverse_jacobians;
126+
std::map<ElemType, std::vector<RealTensor>> _target_jacobians;
104127
};
105128

106129
} // namespace libMesh

src/systems/variational_smoother_system.C

Lines changed: 163 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "libmesh/string_to_enum.h"
3131
#include "libmesh/utility.h"
3232
#include "libmesh/enum_to_string.h"
33+
#include <libmesh/reference_elem.h>
3334

3435
// C++ includes
3536
#include <functional> // std::reference_wrapper
@@ -102,7 +103,6 @@ void VariationalSmootherSystem::prepare_for_smoothing()
102103
this->init_context(femcontext);
103104

104105
const auto & mesh = this->get_mesh();
105-
const auto dim = mesh.mesh_dimension();
106106

107107
Real elem_averaged_det_J_sum = 0.;
108108

@@ -112,122 +112,27 @@ void VariationalSmootherSystem::prepare_for_smoothing()
112112
const auto & fe_map = femcontext.get_element_fe(0)->get_fe_map();
113113
const auto & JxW = fe_map.get_JxW();
114114

115-
std::map<ElemType, std::vector<Real>> target_elem_inverse_jacobian_dets;
115+
std::map<ElemType, std::vector<Real>> target_jacobians_dets;
116116

117117
for (const auto * elem : mesh.active_local_element_ptr_range())
118118
{
119119
femcontext.pre_fe_reinit(*this, elem);
120120
femcontext.elem_fe_reinit();
121121

122122
// Add target element info, if applicable
123-
if (_target_inverse_jacobians.find(elem->type()) == _target_inverse_jacobians.end())
123+
if (_target_jacobians.find(elem->type()) == _target_jacobians.end())
124124
{
125-
// Create FEMap to compute target_element mapping information
126-
FEMap fe_map_target;
127-
128-
// pre-request mapping derivatives
129-
const auto & dxyzdxi = fe_map_target.get_dxyzdxi();
130-
const auto & dxyzdeta = fe_map_target.get_dxyzdeta();
131-
// const auto & dxyzdzeta = fe_map_target.get_dxyzdzeta();
132-
133-
const auto & qrule_points = femcontext.get_element_qrule().get_points();
134-
const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
135-
const auto nq_points = femcontext.get_element_qrule().n_points();
136-
137-
// If the target element is the reference element, Jacobian matrix is
138-
// identity, det of inverse is 1. This will only be overwritten if a
139-
// different target elemen is explicitly specified.
140-
target_elem_inverse_jacobian_dets[elem->type()] =
141-
std::vector<Real>(nq_points, 1.0);
142-
143-
const auto type_str = Utility::enum_to_string(elem->type());
144-
145-
// Elems deriving from Tri
146-
if (type_str.compare(0, 3, "TRI") == 0)
147-
{
148-
149-
// The target element will be an equilateral triangle with area equal to
150-
// the area of the reference element.
151-
152-
// First, let's define the nodal locations of the vertices
153-
const Real sqrt_3 = std::sqrt(3.);
154-
155-
std::vector<Point> equilateral_points{
156-
Point(0., 0.),
157-
Point(1., 0.),
158-
Point(0.5, 0.5 * sqrt_3)
159-
};
160-
161-
// Target element
162-
const auto target_elem = Elem::build(elem->type());
163-
164-
// Area of the reference element
165-
const auto ref_area = target_elem->reference_elem()->volume();
166-
167-
switch (elem->type())
168-
{
169-
case TRI3:
170-
{
171-
// Nothing to do here, vertices already defined in equilateral_points
172-
break;
173-
}
174-
175-
case TRI6:
176-
{
177-
// Define the midpoint nodes of the equilateral triangle
178-
equilateral_points.emplace_back(0.50, 0. );
179-
equilateral_points.emplace_back(0.75, 0.25 * sqrt_3);
180-
equilateral_points.emplace_back(0.25, 0.25 * sqrt_3);
181-
182-
break;
183-
}
184-
185-
default:
186-
libmesh_error_msg("Unsupported triangular element: " << type_str);
187-
break;
188-
}
189-
190-
// Equilateral triangle side length preserving area of the reference element
191-
const auto side_length = std::sqrt(4. / sqrt_3 * ref_area);
192-
193-
std::vector<std::unique_ptr<Node>> owned_nodes;
194-
195-
// Set nodes of target element to form an equilateral triangle
196-
for (const dof_id_type node_id : index_range(equilateral_points))
197-
{
198-
// Scale the nodal positions to conserve area, store the pointer to keep it alive
199-
owned_nodes.emplace_back(
200-
Node::build(equilateral_points[node_id] * side_length, node_id));
201-
target_elem->set_node(node_id, owned_nodes.back().get());
202-
}
203-
204-
// build map
205-
fe_map_target.init_reference_to_physical_map(dim, qrule_points, target_elem.get());
206-
fe_map_target.compute_map(dim, qrule_weights, target_elem.get(), /*d2phi=*/false);
207-
208-
// Yes, triangle-to-triangle mappings have constant Jacobians, but we
209-
// will keep things general for now
210-
_target_inverse_jacobians[target_elem->type()] =
211-
std::vector<RealTensor>(nq_points);
212-
for (const auto qp : make_range(nq_points))
213-
{
214-
const RealTensor H_inv =
215-
RealTensor(dxyzdxi[qp](0), dxyzdeta[qp](0), 0, dxyzdxi[qp](1),
216-
dxyzdeta[qp](1), 0, 0, 0, 1)
217-
.inverse();
218-
219-
_target_inverse_jacobians[target_elem->type()][qp] = H_inv;
220-
target_elem_inverse_jacobian_dets[target_elem->type()][qp] =
221-
H_inv.det();
222-
}
223-
}// if Tri
125+
const auto [target_elem, target_nodes] = get_target_elem(elem->type());
126+
get_target_to_reference_jacobian(target_elem.get(),
127+
femcontext,
128+
_target_jacobians[elem->type()],
129+
target_jacobians_dets[elem->type()]);
224130
}// if find == end()
225131

226132
// Reference volume computation
227133
Real elem_integrated_det_J = 0.;
228134
for (const auto qp : index_range(JxW))
229-
elem_integrated_det_J +=
230-
JxW[qp] * target_elem_inverse_jacobian_dets[elem->type()][qp];
135+
elem_integrated_det_J += JxW[qp] * target_jacobians_dets[elem->type()][qp];
231136
const auto ref_elem_vol = elem->reference_elem()->volume();
232137
elem_averaged_det_J_sum += elem_integrated_det_J / ref_elem_vol;
233138

@@ -378,9 +283,8 @@ bool VariationalSmootherSystem::element_time_derivative (bool request_jacobian,
378283
}
379284

380285
// Apply any applicable target element transformations
381-
if (_target_inverse_jacobians.find(elem.type()) !=
382-
_target_inverse_jacobians.end())
383-
S = S * _target_inverse_jacobians[elem.type()][qp];
286+
if (_target_jacobians.find(elem.type()) != _target_jacobians.end())
287+
S = S * _target_jacobians[elem.type()][qp];
384288

385289
// Compute quantities needed for the smoothing algorithm
386290

@@ -724,4 +628,156 @@ bool VariationalSmootherSystem::element_time_derivative (bool request_jacobian,
724628
return request_jacobian;
725629
}
726630

631+
std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
632+
VariationalSmootherSystem::get_target_elem(const ElemType & type)
633+
{
634+
// Build target element
635+
auto target_elem = Elem::build(type);
636+
637+
// Volume of reference element
638+
const auto ref_vol = target_elem->reference_elem()->volume();
639+
640+
// Update the nodes of the target element, depending on type
641+
const Real sqrt_3 = std::sqrt(Real(3));
642+
std::vector<std::unique_ptr<Node>> owned_nodes;
643+
644+
const auto type_str = Utility::enum_to_string(type);
645+
646+
// Elems deriving from Tri
647+
if (type_str.compare(0, 3, "TRI") == 0)
648+
{
649+
650+
// The target element will be an equilateral triangle with area equal to
651+
// the area of the reference element.
652+
653+
// Equilateral triangle side length preserving area of the reference element
654+
const auto side_length = std::sqrt(4. / sqrt_3 * ref_vol);
655+
656+
// Define the nodal locations of the vertices
657+
const auto & s = side_length;
658+
// x y node_id
659+
owned_nodes.emplace_back(Node::build(Point(0., 0.), 0));
660+
owned_nodes.emplace_back(Node::build(Point(s, 0.), 1));
661+
owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s), 2));
662+
663+
switch (type)
664+
{
665+
case TRI3: {
666+
// Nothing to do here, vertices already added above
667+
break;
668+
}
669+
670+
case TRI6: {
671+
// Define the midpoint nodes of the equilateral triangle
672+
// x y node_id
673+
owned_nodes.emplace_back(Node::build(Point(0.50 * s, 0.00), 3));
674+
owned_nodes.emplace_back(Node::build(Point(0.75 * s, 0.25 * sqrt_3 * s), 4));
675+
owned_nodes.emplace_back(Node::build(Point(0.25 * s, 0.25 * sqrt_3 * s), 5));
676+
677+
break;
678+
}
679+
680+
default:
681+
libmesh_error_msg("Unsupported triangular element: " << type_str);
682+
break;
683+
}
684+
} // if Tri
685+
686+
687+
// Set the target_elem equal to the reference elem
688+
else
689+
for (const auto & node : target_elem->reference_elem()->node_ref_range())
690+
owned_nodes.emplace_back(Node::build(node, node.id()));
691+
692+
// Set nodes of target element
693+
for (const auto & node_ptr : owned_nodes)
694+
target_elem->set_node(node_ptr->id(), node_ptr.get());
695+
696+
libmesh_assert(relative_fuzzy_equals(target_elem->volume(), ref_vol));
697+
698+
return std::make_pair(std::move(target_elem), std::move(owned_nodes));
699+
}
700+
701+
void VariationalSmootherSystem::get_target_to_reference_jacobian(
702+
const Elem * const target_elem,
703+
const FEMContext & femcontext,
704+
std::vector<RealTensor> & jacobians,
705+
std::vector<Real> & jacobian_dets)
706+
{
707+
708+
const auto dim = target_elem->dim();
709+
710+
const auto & qrule_points = femcontext.get_element_qrule().get_points();
711+
const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
712+
const auto nq_points = femcontext.get_element_qrule().n_points();
713+
714+
// If the target element is the reference element, Jacobian matrix is
715+
// identity, det of inverse is 1. These will only be overwritten if a
716+
// different target element is explicitly specified.
717+
jacobians = std::vector<RealTensor>(nq_points, RealTensor(
718+
1., 0., 0.,
719+
0., 1., 0.,
720+
0., 0., 1.));
721+
jacobian_dets = std::vector<Real>(nq_points, 1.0);
722+
723+
// Don't use "if (*target_elem == *(target_elem->reference_elem()))" here, it
724+
// only compares global node ids, not the node locations themselves.
725+
bool target_equals_reference = true;
726+
const auto * ref_elem = target_elem->reference_elem();
727+
for (const auto local_id : make_range(target_elem->n_nodes()))
728+
target_equals_reference &= target_elem->node_ref(local_id) == ref_elem->node_ref(local_id);
729+
if (target_equals_reference)
730+
return;
731+
732+
// Create FEMap to compute target_element mapping information
733+
FEMap fe_map_target;
734+
735+
// pre-request mapping derivatives
736+
const auto & dxyzdxi = fe_map_target.get_dxyzdxi();
737+
const auto & dxyzdeta = fe_map_target.get_dxyzdeta();
738+
const auto & dxyzdzeta = fe_map_target.get_dxyzdzeta();
739+
740+
// build map
741+
fe_map_target.init_reference_to_physical_map(dim, qrule_points, target_elem);
742+
fe_map_target.compute_map(dim, qrule_weights, target_elem, /*d2phi=*/false);
743+
744+
for (const auto qp : make_range(nq_points))
745+
{
746+
// We use Larisa's H notation to denote the reference-to-target jacobian
747+
RealTensor H;
748+
switch (dim)
749+
{
750+
case 1: {
751+
H = RealTensor(
752+
dxyzdxi[qp](0), 0., 0.,
753+
0., 1., 0.,
754+
0., 0., 1.);
755+
756+
break;
757+
}
758+
case 2: {
759+
H = RealTensor(
760+
dxyzdxi[qp](0), dxyzdeta[qp](0), 0.,
761+
dxyzdxi[qp](1), dxyzdeta[qp](1), 0.,
762+
0., 0., 1.);
763+
764+
break;
765+
}
766+
case 3: {
767+
H = RealTensor(dxyzdxi[qp], dxyzdeta[qp], dxyzdzeta[qp]).transpose();
768+
769+
break;
770+
}
771+
772+
default:
773+
libmesh_error_msg("Unsupported mesh dimension " << dim);
774+
}
775+
776+
// The target-to-reference jacobian is the inverse of the
777+
// reference-to-target jacobian
778+
jacobians[qp] = H.inverse();
779+
jacobian_dets[qp] = jacobians[qp].det();
780+
}
781+
}
782+
727783
} // namespace libMesh

tests/mesh/mesh_smoother_test.C

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class SquareToParallelogram : public FunctionBase<Real>
137137
{
138138
output.resize(3);
139139
output(0) = p(0) - 0.5 * p(1);
140-
output(1) = 0.5 * std::sqrt(3) * p(1);
140+
output(1) = 0.5 * std::sqrt(Real(3)) * p(1);
141141
output(2) = 0;
142142
}
143143
};
@@ -160,8 +160,8 @@ class ParallelogramToSquare : public FunctionBase<Real>
160160
void operator()(const Point & p, const Real, DenseVector<Real> & output)
161161
{
162162
output.resize(3);
163-
output(0) = p(0) + p(1) / std::sqrt(3.);
164-
output(1) = (2. / std::sqrt(3)) * p(1);
163+
output(0) = p(0) + p(1) / std::sqrt(Real(3));
164+
output(1) = (2. / std::sqrt(Real(3))) * p(1);
165165
output(2) = 0;
166166
}
167167
};

0 commit comments

Comments
 (0)