libMesh
Loading...
Searching...
No Matches
fe_orientation_test.C
Go to the documentation of this file.
1// Tests that the shape functions of a degree of freedom on a vertex, edge, or face follow only
2// that entity's orientation (Elem::positive_edge_orientation(), Elem::face_orientation()), and
3// that elements sharing a side agree on the shape functions of the degrees of freedom they share.
4
5#include "fe_test.h"
6
7#include <libmesh/enum_elem_type.h>
8#include <libmesh/enum_fe_family.h>
9#include <libmesh/enum_order.h>
10#include <libmesh/fe_map.h>
11#include <libmesh/fe_type.h>
12#include <libmesh/parallel_implementation.h>
13#include <libmesh/remote_elem.h>
14
15#include <algorithm>
16#include <cmath>
17#include <map>
18#include <memory>
19#include <utility>
20#include <vector>
21
22using namespace libMesh;
23
25enum EntityKind : unsigned int { VERTEX = 0, EDGE = 1, FACE = 2, INTERIOR = 3 };
26typedef std::pair<unsigned int, unsigned int> Entity;
27
31template <Order order, FEFamily family>
32class FEOrientationTestBase : public CppUnit::TestCase
33{
34protected:
35 std::unique_ptr<Mesh> _mesh;
36 std::string libmesh_suite_name;
37
38public:
39 void setUp()
40 {
41 _mesh = std::make_unique<Mesh>(*TestCommWorld);
42 build_mesh();
43
44 // An affine skew, so that no element symmetry masks a shape function following the wrong
45 // entity, and so that inverse_map() is exact
46 SkewFunc skew_func;
48 _mesh->complete_preparation();
49 }
50
51 void tearDown() { _mesh.reset(); }
52
54 virtual void build_mesh() = 0;
55
57 Entity node_entity(const Elem & elem, const unsigned int n)
58 {
59 if (elem.is_vertex(n))
60 return {VERTEX, n};
61
62 if (elem.is_edge(n))
63 for (const auto e : make_range(elem.n_edges()))
64 if (elem.is_node_on_edge(n, e))
65 return {EDGE, e};
66
67 if (elem.is_face(n))
68 for (const auto f : make_range(elem.n_faces()))
69 if (elem.is_node_on_side(n, f))
70 return {FACE, f};
71
72 return {INTERIOR, 0};
73 }
74
76 unsigned int entity_orientation(const Elem & elem, const Entity & entity)
77 {
78 switch (entity.first)
79 {
80 case EDGE:
81 return elem.positive_edge_orientation(entity.second);
82 case FACE:
83 return elem.face_orientation(entity.second);
84 default:
85 return 0;
86 }
87 }
88
90 {
91 LOG_UNIT_TEST;
92
93 const FEType fe_type(order, family);
94
95 // Number of comparisons between repeat visits to an entity orientation
96 unsigned int n_compared = 0;
97
98 for (auto * elem : _mesh->active_local_element_ptr_range())
99 {
100 QGauss qrule(elem->dim(), order);
101 qrule.init(*elem);
102 const std::vector<Point> & points = qrule.get_points();
103
104 // Shape function values seen, per entity and orientation index
105 std::map<Entity, std::map<unsigned int, std::vector<Real>>> seen;
106
107 // Permuting renumbers the vertices without moving them, cycling the entity orientations
108 for (const auto p : make_range(elem->n_permutations()))
109 {
110 elem->permute(p);
111
112 // Element degrees of freedom are numbered by node, then interior
113 unsigned int dof = 0;
114
115 for (const auto n : elem->node_index_range())
116 {
117 const unsigned int n_dofs = FEInterface::n_dofs_at_node(fe_type, elem, n);
118 if (!n_dofs)
119 continue;
120
121 record(seen[node_entity(*elem, n)],
122 entity_orientation(*elem, node_entity(*elem, n)),
123 shapes(fe_type, *elem, dof, n_dofs, points),
124 n_compared);
125
126 dof += n_dofs;
127 }
128
129 const unsigned int n_interior = FEInterface::n_dofs_per_elem(fe_type, elem);
130 if (n_interior)
131 record(seen[{INTERIOR, 0}], 0,
132 shapes(fe_type, *elem, dof, n_interior, points), n_compared);
133 }
134 }
135
136 // Guard against a vacuous pass
137 _mesh->comm().sum(n_compared);
138 CPPUNIT_ASSERT(n_compared > 0);
139 }
140
142 {
143 LOG_UNIT_TEST;
144
145 const FEType fe_type(order, family);
146
148 System & sys = es.add_system<System>("orientation");
149 sys.add_variable("u", fe_type);
150 es.init();
151 const DofMap & dof_map = sys.get_dof_map();
152
153 unsigned int n_compared = 0;
154
155 for (const auto round : make_range(n_rounds()))
156 {
157 // Permute by element id so that every processor agrees on the numbering of a ghosted
158 // element
159 for (auto * elem : _mesh->element_ptr_range())
160 elem->permute((round + elem->id()) % elem->n_permutations());
161
162 for (const auto * elem : _mesh->active_local_element_ptr_range())
163 {
164 std::vector<dof_id_type> dofs;
165 dof_map.dof_indices(elem, dofs);
166
167 for (const auto s : make_range(elem->n_sides()))
168 {
169 const Elem * neighbor = elem->neighbor_ptr(s);
170 if (!neighbor || neighbor == remote_elem)
171 continue;
172
173 std::vector<dof_id_type> neighbor_dofs;
174 dof_map.dof_indices(neighbor, neighbor_dofs);
175
176 std::unique_ptr<const Elem> side = elem->build_side_ptr(s);
177 QGauss side_rule(side->dim(), order);
178 side_rule.init(*side);
179
180 for (const auto & side_point : side_rule.get_points())
181 {
182 const Point physical = FEMap::map(side->dim(), side.get(), side_point);
183
184 // Map far tighter than the shape value tolerance, so mapping error cannot
185 // mask or cause a mismatch
186 const Point point =
187 FEMap::inverse_map(elem->dim(), elem, physical, map_tolerance);
188 const Point neighbor_point =
189 FEMap::inverse_map(neighbor->dim(), neighbor, physical, map_tolerance);
190
191 for (const auto i : index_range(dofs))
192 {
193 const auto it =
194 std::find(neighbor_dofs.begin(), neighbor_dofs.end(), dofs[i]);
195 if (it == neighbor_dofs.end())
196 continue;
197
198 const auto j = std::distance(neighbor_dofs.begin(), it);
199
200 LIBMESH_ASSERT_FP_EQUAL(
201 FEInterface::shape(fe_type, elem, i, point),
202 FEInterface::shape(fe_type, neighbor, j, neighbor_point),
203 TOLERANCE);
204 ++n_compared;
205 }
206 }
207 }
208 }
209 }
210
211 // Guard against a vacuous pass
212 _mesh->comm().sum(n_compared);
213 CPPUNIT_ASSERT(n_compared > 0);
214 }
215
216private:
218 static constexpr Real map_tolerance = TOLERANCE * TOLERANCE;
219
221 unsigned int n_rounds() const
222 {
223 unsigned int rounds = 0;
224 for (const auto * elem : _mesh->element_ptr_range())
225 rounds = std::max(rounds, elem->n_permutations());
226 _mesh->comm().max(rounds);
227 return rounds;
228 }
229
231 std::vector<Real> shapes(const FEType & fe_type,
232 const Elem & elem,
233 const unsigned int first,
234 const unsigned int n_dofs,
235 const std::vector<Point> & points)
236 {
237 std::vector<Real> values;
238
239 for (const auto & point : points)
240 for (const auto i : make_range(n_dofs))
241 values.push_back(FEInterface::shape(fe_type, &elem, first + i, point));
242
243 return values;
244 }
245
247 void record(std::map<unsigned int, std::vector<Real>> & seen,
248 const unsigned int orientation,
249 const std::vector<Real> & values,
250 unsigned int & n_compared)
251 {
252 const auto [entry, inserted] = seen.emplace(orientation, values);
253 if (inserted)
254 return;
255
256 CPPUNIT_ASSERT_EQUAL(entry->second.size(), values.size());
257 for (const auto k : index_range(values))
258 LIBMESH_ASSERT_FP_EQUAL(entry->second[k], values[k], TOLERANCE * TOLERANCE);
259
260 if (seen.size() > 1)
261 ++n_compared;
262 }
263};
264
268template <ElemType elem_type, Order order, FEFamily family>
269class FEOrientationTest : public FEOrientationTestBase<order, family>
270{
271public:
272 void build_mesh() override
273 {
274 const unsigned int dim = Elem::type_to_dim_map[elem_type];
275 MeshTools::Generation::build_cube(*this->_mesh, 2, 2, 2*(dim > 2),
276 0., 1., 0., 1., 0., 1., elem_type);
277 }
278};
279
286template <Order order, FEFamily family>
288{
289public:
290 void build_mesh() override
291 {
292 Mesh & mesh = *this->_mesh;
293
294 // A prism extruded along z from the triangle (0,0), (1,0), (0,1), with a hexahedron below
295 // y = 0, a prism extruded along y beyond x = 0, and a tetrahedron above z = 1
296 const std::vector<Point> points =
297 {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}, {1,0,1}, {0,1,1},
298 {0,-1,0}, {1,-1,0}, {0,-1,1}, {1,-1,1},
299 {-1,0,0}, {-1,1,0},
300 {0.25,0.25,2}};
301 for (const auto i : index_range(points))
302 mesh.add_point(points[i], i);
303
304 const std::vector<std::pair<ElemType, std::vector<dof_id_type>>> elems =
305 {{PRISM6, {0, 1, 2, 3, 4, 5}},
306 {HEX8, {6, 7, 1, 0, 8, 9, 4, 3}},
307 {PRISM6, {0, 10, 3, 2, 11, 5}},
308 {TET4, {3, 4, 5, 12}}};
309 for (const auto & [type, nodes] : elems)
310 {
311 Elem * elem = mesh.add_elem(Elem::build(type));
312 for (const auto n : index_range(nodes))
313 elem->set_node(n, mesh.node_ptr(nodes[n]));
314 }
315
318 }
319};
320
321#define ORIENTATIONTEST \
322 CPPUNIT_TEST( test_orientation_determines_shapes ); \
323 CPPUNIT_TEST( test_conformity_across_sides )
324
325#define INSTANTIATE_FEORIENTATIONTEST(elemtype, order, family) \
326 class FEOrientationTest_##family##_##order##_##elemtype : \
327 public FEOrientationTest<elemtype, order, family> { \
328 public: \
329 FEOrientationTest_##family##_##order##_##elemtype() : \
330 FEOrientationTest<elemtype, order, family>() { \
331 if (unitlog->summarized_logs_enabled()) \
332 this->libmesh_suite_name = "FEOrientationTest"; \
333 else \
334 this->libmesh_suite_name = \
335 "FEOrientationTest_" #family "_" #order "_" #elemtype; \
336 } \
337 CPPUNIT_TEST_SUITE( FEOrientationTest_##family##_##order##_##elemtype ); \
338 ORIENTATIONTEST; \
339 CPPUNIT_TEST_SUITE_END(); \
340 }; \
341 \
342 CPPUNIT_TEST_SUITE_REGISTRATION( FEOrientationTest_##family##_##order##_##elemtype )
343
344// Side-only families have no interior shape values, so they get only the conformity test
345#define INSTANTIATE_FECONFORMITYTEST(elemtype, order, family) \
346 class FEConformityTest_##family##_##order##_##elemtype : \
347 public FEOrientationTest<elemtype, order, family> { \
348 public: \
349 FEConformityTest_##family##_##order##_##elemtype() : \
350 FEOrientationTest<elemtype, order, family>() { \
351 if (unitlog->summarized_logs_enabled()) \
352 this->libmesh_suite_name = "FEConformityTest"; \
353 else \
354 this->libmesh_suite_name = \
355 "FEConformityTest_" #family "_" #order "_" #elemtype; \
356 } \
357 CPPUNIT_TEST_SUITE( FEConformityTest_##family##_##order##_##elemtype ); \
358 CPPUNIT_TEST( test_conformity_across_sides ); \
359 CPPUNIT_TEST_SUITE_END(); \
360 }; \
361 \
362 CPPUNIT_TEST_SUITE_REGISTRATION( FEConformityTest_##family##_##order##_##elemtype )
363
375
376#define INSTANTIATE_FEHYBRIDORIENTATIONTEST(order, family) \
377 class FEHybridOrientationTest_##family##_##order : \
378 public FEHybridOrientationTest<order, family> { \
379 public: \
380 FEHybridOrientationTest_##family##_##order() : \
381 FEHybridOrientationTest<order, family>() { \
382 if (unitlog->summarized_logs_enabled()) \
383 this->libmesh_suite_name = "FEHybridOrientationTest"; \
384 else \
385 this->libmesh_suite_name = \
386 "FEHybridOrientationTest_" #family "_" #order; \
387 } \
388 CPPUNIT_TEST_SUITE( FEHybridOrientationTest_##family##_##order ); \
389 ORIENTATIONTEST; \
390 CPPUNIT_TEST_SUITE_END(); \
391 }; \
392 \
393 CPPUNIT_TEST_SUITE_REGISTRATION( FEHybridOrientationTest_##family##_##order )
394
unsigned int dim
The orientation tests on a mesh mixing element types, where each pair of neighbors shares a face that...
void build_mesh() override
Builds the mesh the tests run on, before it is skewed.
The orientation tests, on a mesh that a subclass builds.
virtual void build_mesh()=0
Builds the mesh the tests run on, before it is skewed.
unsigned int entity_orientation(const Elem &elem, const Entity &entity)
The orientation index that the shape functions of entity follow.
static constexpr Real map_tolerance
The tolerance for inverse_map()
std::unique_ptr< Mesh > _mesh
Entity node_entity(const Elem &elem, const unsigned int n)
The entity of elem that owns the degrees of freedom sitting on node n.
std::vector< Real > shapes(const FEType &fe_type, const Elem &elem, const unsigned int first, const unsigned int n_dofs, const std::vector< Point > &points)
The values of shape functions first through first + n_dofs - 1 at points.
void record(std::map< unsigned int, std::vector< Real > > &seen, const unsigned int orientation, const std::vector< Real > &values, unsigned int &n_compared)
Record values for orientation, or check them against those already recorded.
unsigned int n_rounds() const
Rounds of permutation, which vary the relative numbering of shared sides.
The orientation tests on a cube of elem_type elements.
void build_mesh() override
Builds the mesh the tests run on, before it is skewed.
This class handles the numbering of degrees of freedom on a mesh.
Definition dof_map.h:181
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition dof_map.C:2203
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const =0
virtual Node *& set_node(const unsigned int i)
Definition elem.h:2688
bool positive_edge_orientation(const unsigned int i) const
Definition elem.C:4037
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:382
virtual bool is_face(const unsigned int i) const =0
virtual unsigned short dim() const =0
static const unsigned int type_to_dim_map[INVALID_ELEM]
This array maps the integer representation of the ElemType enum to the geometric dimension of the ele...
Definition elem.h:649
virtual bool is_edge(const unsigned int i) const =0
virtual unsigned int n_faces() const =0
virtual bool is_vertex(const unsigned int i) const =0
virtual bool is_node_on_edge(const unsigned int n, const unsigned int e) const =0
virtual unsigned int n_edges() const =0
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2736
unsigned int face_orientation(const unsigned int i) const
Definition elem.C:4053
This is the EquationSystems class.
virtual void init()
Initialize all the systems.
virtual System & add_system(std::string_view system_type, std::string_view name)
Add the system of type system_type named name to the systems array.
static Real shape(const unsigned int dim, const FEType &fe_t, const ElemType t, const unsigned int i, const Point &p)
static unsigned int n_dofs_at_node(const unsigned int dim, const FEType &fe_t, const ElemType t, const unsigned int n)
static unsigned int n_dofs_per_elem(const unsigned int dim, const FEType &fe_t, const ElemType t)
static Point map(const unsigned int dim, const Elem *elem, const Point &reference_point)
Definition fe_map.C:1954
static Point inverse_map(const unsigned int dim, const Elem *elem, const Point &p, const Real tolerance=TOLERANCE, const bool secure=true, const bool extra_checks=true)
Definition fe_map.C:1512
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition fe_type.h:197
virtual const Node * node_ptr(const dof_id_type i) const =0
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition mesh_base.C:824
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
virtual void all_complete_order()
Calls the range-based version of this function with a range consisting of all elements in the mesh.
Definition mesh_base.C:1823
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:41
const std::vector< Point > & get_points() const
Definition quadrature.h:156
virtual void init(const Elem &e, unsigned int p_level=invalid_uint)
Initializes the data structures for a quadrature rule for the element e.
Definition quadrature.C:65
This class implements specific orders of Gauss quadrature.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition system.C:1344
const DofMap & get_dof_map() const
Definition system.h:2417
Communicator * TestCommWorld
INSTANTIATE_FEORIENTATIONTEST(TRI6, THIRD, HIERARCHIC)
INSTANTIATE_FEHYBRIDORIENTATIONTEST(THIRD, HIERARCHIC)
EntityKind
The entity of an element that owns a degree of freedom, as a kind and an index within that kind.
std::pair< unsigned int, unsigned int > Entity
INSTANTIATE_FECONFORMITYTEST(HEX27, THIRD, SIDE_HIERARCHIC)
MeshBase & mesh
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
void redistribute(MeshBase &mesh, const FunctionBase< Real > &mapfunc)
Deterministically perturb the nodal locations.
The libMesh namespace provides an interface to certain functionality in the library.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
const RemoteElem * remote_elem
Definition remote_elem.C:57
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176