DUNE PDELab (git)

basistest.hh
1// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2// vi: set et ts=4 sw=2 sts=2:
3#ifndef DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
4#define DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
5
6#include <set>
7#include <algorithm>
8#include <string>
9#include <sstream>
10#include <map>
11#include <optional>
12
13#include <dune/common/test/testsuite.hh>
16#include <dune/common/hybridutilities.hh>
17
19
20#include <dune/functions/functionspacebases/concepts.hh>
21
22struct CheckBasisFlag {};
23struct AllowZeroBasisFunctions {};
24
25template<class T, class... S>
26struct IsContained : public std::disjunction<std::is_same<T,S>...>
27{};
28
29
30
31/*
32 * Get string identifier of element
33 */
34template<class Element, class GridView>
35std::string elementStr(const Element& element, const GridView& gridView)
36{
37 std::stringstream s;
38 s << element.type() << "#" << gridView.indexSet().index(element);
39 return s.str();
40}
41
42/*
43 * Check if two multi-indices are consecutive.
44 * This is a used by checkBasisIndexTreeConsistency()
45 */
46template<class MultiIndex>
47bool multiIndicesConsecutive(const MultiIndex& a, const MultiIndex& b)
48{
49 std::size_t i = 0;
50
51 // find largest common prefix
52 for (; (i<a.size()) and (i<b.size()) and (a[i] == b[i]); ++i)
53 {};
54
55 // if b is exhausted but a is not, then b is a strict prefix of a and does not succeed a
56 if ((i<a.size()) and (i==b.size()))
57 return false;
58
59 // if a and b are not exhausted, then the first non-common index must be an increment
60 if ((i<a.size()) and (i<b.size()))
61 {
62 if (b[i] != a[i]+1)
63 return false;
64 ++i;
65 }
66
67 // if b is not exhausted, then the following indices should be zero
68 if (i<b.size())
69 {
70 for (; i<b.size(); ++i)
71 {
72 if (b[i] != 0)
73 return false;
74 }
75 }
76 return true;
77}
78
79
80
81/*
82 * Check if given set of multi-indices is consistent, i.e.,
83 * if it induces a consistent ordered tree. This is used
84 * by checkBasisIndices()
85 */
86template<class MultiIndexSet>
87Dune::TestSuite checkBasisIndexTreeConsistency(const MultiIndexSet& multiIndexSet)
88{
89 Dune::TestSuite test("index tree consistency check");
90
91 using namespace Dune;
92
93 auto it = multiIndexSet.begin();
94 auto end = multiIndexSet.end();
95
96 // get first multi-index
97 auto lastMultiIndex = *it;
98
99 // assert that index is non-empty
100 test.require(lastMultiIndex.size()>0, "multi-index size check")
101 << "empty multi-index found";
102
103 // check if first multi-index is [0,...,0]
104 for (decltype(lastMultiIndex.size()) i = 0; i<lastMultiIndex.size(); ++i)
105 {
106 test.require(lastMultiIndex[i] == 0, "smallest index check")
107 << "smallest index contains non-zero entry " << lastMultiIndex[i] << " in position " << i;
108 }
109
110 ++it;
111 for(; it != end; ++it)
112 {
113 auto multiIndex = *it;
114
115 // assert that index is non-empty
116 test.require(multiIndex.size()>0, "multi-index size check")
117 << "empty multi-index found";
118
119 // assert that indices are consecutive
120 test.check(multiIndicesConsecutive(lastMultiIndex, multiIndex), "consecutive index check")
121 << "multi-indices " << lastMultiIndex << " and " << multiIndex << " are subsequent but not consecutive";
122
123 lastMultiIndex = multiIndex;
124 }
125
126 return test;
127}
128
129
130
131/*
132 * Check consistency of basis.size(prefix)
133 */
134template<class Basis, class MultiIndexSet>
135Dune::TestSuite checkBasisSizeConsistency(const Basis& basis, const MultiIndexSet& multiIndexSet)
136{
137 Dune::TestSuite test("index size consistency check");
138
139 // Based on the index tree, build a map that contains all possible
140 // prefixes and maps each prefix to the size (of the subsequent digit).
141 using Prefix = typename Basis::SizePrefix;
142 auto prefixSet = std::map<Prefix, std::size_t>();
143 for(const auto& index : multiIndexSet)
144 {
145 auto prefix = Prefix();
146 for (const auto& i: index)
147 {
148 prefixSet[prefix] = std::max(prefixSet[prefix], i+1);
149 prefix.push_back(i);
150 }
151 prefixSet[prefix] = 0;
152 }
153
154 // Now check for all prefixes, if the size computed from the
155 // index tree is consistent with basis.size(prefix).
156 for(const auto& [prefix, size] : prefixSet)
157 {
158 auto prefixSize = basis.size(prefix);
159 test.check(prefixSize == size, "basis.size(prefix) check")
160 << "basis.size(" << prefix << ")=" << prefixSize << ", but should be " << size;
161 }
162
163 return test;
164}
165
166
167
168/*
169 * Check indices of basis:
170 * - First store the whole index tree in a set
171 * - Check if this corresponds to a consistent index tree
172 * - Check if index tree is consistent with basis.size(prefix) and basis.dimension()
173 */
174template<class Basis>
175Dune::TestSuite checkBasisIndices(const Basis& basis)
176{
177 Dune::TestSuite test("basis index check");
178
179 using MultiIndex = typename Basis::MultiIndex;
180
181 static_assert(Dune::IsIndexable<MultiIndex>(), "MultiIndex must support operator[]");
182
183 auto compare = [](const auto& a, const auto& b) {
184 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
185 };
186
187 auto multiIndexSet = std::set<MultiIndex, decltype(compare)>{compare};
188
189 auto localView = basis.localView();
190 for (const auto& e : elements(basis.gridView()))
191 {
192 localView.bind(e);
193
194 for (decltype(localView.size()) i=0; i< localView.size(); ++i)
195 {
196 auto multiIndex = localView.index(i);
197 for(auto mi: multiIndex)
198 test.check(mi>=0)
199 << "Global multi-index contains negative entry for shape function " << i
200 << " in element " << elementStr(localView.element(), basis.gridView());
201 multiIndexSet.insert(multiIndex);
202 }
203 }
204
205 test.subTest(checkBasisIndexTreeConsistency(multiIndexSet));
206 test.subTest(checkBasisSizeConsistency(basis, multiIndexSet));
207 test.check(basis.dimension() == multiIndexSet.size())
208 << "basis.dimension() does not equal the total number of basis functions.";
209
210 return test;
211}
212
213
214
215/*
216 * Check if shape functions are not constant zero.
217 * This is called by checkLocalView().
218 */
219template<class LocalFiniteElement>
220Dune::TestSuite checkNonZeroShapeFunctions(const LocalFiniteElement& fe, std::size_t order = 5, double tol = 1e-10)
221{
222 Dune::TestSuite test;
223 static const int dimension = LocalFiniteElement::Traits::LocalBasisType::Traits::dimDomain;
224
225 auto quadRule = Dune::QuadratureRules<double, dimension>::rule(fe.type(), order);
226
227 std::vector<typename LocalFiniteElement::Traits::LocalBasisType::Traits::RangeType> values;
228 std::vector<bool> isNonZero;
229 isNonZero.resize(fe.size(), false);
230 for (const auto& qp : quadRule)
231 {
232 fe.localBasis().evaluateFunction(qp.position(), values);
233 for(std::size_t i=0; i<fe.size(); ++i)
234 isNonZero[i] = (isNonZero[i] or (values[i].infinity_norm() > tol));
235 }
236 for(std::size_t i=0; i<fe.size(); ++i)
237 test.check(isNonZero[i])
238 << "Found a constant zero basis function";
239 return test;
240}
241
242
243
244/*
245 * Check localView. This especially checks for
246 * consistency of local indices and local size.
247 */
248template<class Basis, class LocalView, class... Flags>
249Dune::TestSuite checkLocalView(const Basis& basis, const LocalView& localView, Flags... flags)
250{
251 Dune::TestSuite test(std::string("LocalView on ") + elementStr(localView.element(), basis.gridView()));
252
253 test.check(localView.size() <= localView.maxSize(), "localView.size() check")
254 << "localView.size() is " << localView.size() << " but localView.maxSize() is " << localView.maxSize();
255
256 // Count all local indices appearing in the tree.
257 std::vector<std::size_t> localIndices;
258 localIndices.resize(localView.size(), 0);
259 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& node, auto&& treePath) {
260 test.check(node.size() == node.finiteElement().size())
261 << "Size of leaf node and finite element are different.";
262 for(std::size_t i=0; i<node.size(); ++i)
263 {
264 test.check(node.localIndex(i) < localView.size())
265 << "Local index exceeds localView.size().";
266 if (node.localIndex(i) < localView.size())
267 ++(localIndices[node.localIndex(i)]);
268 }
269 });
270
271 // Check if each local index appears exactly once.
272 for(std::size_t i=0; i<localView.size(); ++i)
273 {
274 if (localIndices[i])
275 test.check(localIndices[i]>=1)
276 << "Local index " << i << " did not appear";
277 test.check(localIndices[i]<=1)
278 << "Local index " << i << " appears multiple times";
279 }
280
281 // Check that all basis functions are non-zero.
282 if (not IsContained<AllowZeroBasisFunctions, Flags...>::value)
283 {
284 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& node, auto&& treePath) {
285 test.subTest(checkNonZeroShapeFunctions(node.finiteElement()));
286 });
287 }
288
289 return test;
290}
291
292
293// Flag to enable a local continuity check for checking strong
294// continuity across an intersection within checkBasisContinuity().
295//
296// For each inside basis function this will compute the jump against
297// zero or the corresponding inside basis function. The latter is then
298// checked for being (up to a tolerance) zero on a set of quadrature points.
299struct EnableContinuityCheck
300{
301 std::size_t order_ = 5;
302 double tol_ = 1e-10;
303
304 template<class JumpEvaluator>
305 auto localJumpContinuityCheck(const JumpEvaluator& jumpEvaluator, std::size_t order, double tol) const
306 {
307 return [=](const auto& intersection, const auto& treePath, const auto& insideNode, const auto& outsideNode, const auto& insideToOutside) {
308 using Intersection = std::decay_t<decltype(intersection)>;
309 using Node = std::decay_t<decltype(insideNode)>;
310
311 std::vector<int> isContinuous(insideNode.size(), true);
312 const auto& quadRule = Dune::QuadratureRules<double, Intersection::mydimension>::rule(intersection.type(), order);
313
314 using Range = typename Node::FiniteElement::Traits::LocalBasisType::Traits::RangeType;
315 std::vector<std::vector<Range>> values;
316 std::vector<std::vector<Range>> neighborValues;
317
318 // Evaluate inside and outside basis functions.
319 values.resize(quadRule.size());
320 neighborValues.resize(quadRule.size());
321 for(std::size_t k=0; k<quadRule.size(); ++k)
322 {
323 auto pointInElement = intersection.geometryInInside().global(quadRule[k].position());
324 auto pointInNeighbor = intersection.geometryInOutside().global(quadRule[k].position());
325 insideNode.finiteElement().localBasis().evaluateFunction(pointInElement, values[k]);
326 outsideNode.finiteElement().localBasis().evaluateFunction(pointInNeighbor, neighborValues[k]);
327 }
328
329 // Check jump against outside basis function or zero.
330 for(std::size_t i=0; i<insideNode.size(); ++i)
331 {
332 for(std::size_t k=0; k<quadRule.size(); ++k)
333 {
334 auto jump = values[k][i];
335 if (insideToOutside[i].has_value())
336 jump -= neighborValues[k][insideToOutside[i].value()];
337 isContinuous[i] = isContinuous[i] and (jumpEvaluator(jump, intersection, quadRule[k].position()) < tol);
338 }
339 }
340 return isContinuous;
341 };
342 }
343
344 auto localContinuityCheck() const {
345 auto jumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
346 return jump.infinity_norm();
347 };
348 return localJumpContinuityCheck(jumpNorm, order_, tol_);
349 }
350};
351
352// Flag to enable a local normal-continuity check for checking strong
353// continuity across an intersection within checkBasisContinuity().
354//
355// For each inside basis function this will compute the normal jump against
356// zero or the corresponding inside basis function. The latter is then
357// checked for being (up to a tolerance) zero on a set of quadrature points.
358struct EnableNormalContinuityCheck : public EnableContinuityCheck
359{
360 auto localContinuityCheck() const {
361 auto normalJump = [](auto&&jump, auto&& intersection, auto&& x) -> double {
362 return jump * intersection.unitOuterNormal(x);
363 };
364 return localJumpContinuityCheck(normalJump, order_, tol_);
365 }
366};
367
368// Flag to enable a local tangential-continuity check for checking continuity
369// of tangential parts of a vector-valued basis across an intersection
370// within checkBasisContinuity().
371//
372// For each inside basis function this will compute the tangential jump against
373// zero or the corresponding outside basis function. The jump is then
374// checked for being (up to a tolerance) zero on a set of quadrature points.
375struct EnableTangentialContinuityCheck : public EnableContinuityCheck
376{
377 auto localContinuityCheck() const {
378 auto tangentialJumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
379 auto tangentialJump = jump - (jump * intersection.unitOuterNormal(x)) * intersection.unitOuterNormal(x);
380 return tangentialJump.two_norm();
381 };
382 return localJumpContinuityCheck(tangentialJumpNorm, order_, tol_);
383 }
384};
385
386// Flag to enable a center continuity check for checking continuity in the
387// center of an intersection within checkBasisContinuity().
388//
389// For each inside basis function this will compute the jump against
390// zero or the corresponding inside basis function. The latter is then
391// checked for being (up to a tolerance) zero in the center of mass
392// of the intersection.
393struct EnableCenterContinuityCheck : public EnableContinuityCheck
394{
395 template<class JumpEvaluator>
396 auto localJumpCenterContinuityCheck(const JumpEvaluator& jumpEvaluator, double tol) const
397 {
398 return [=](const auto& intersection, const auto& treePath, const auto& insideNode, const auto& outsideNode, const auto& insideToOutside) {
399 using Node = std::decay_t<decltype(insideNode)>;
400 using Range = typename Node::FiniteElement::Traits::LocalBasisType::Traits::RangeType;
401
402 std::vector<int> isContinuous(insideNode.size(), true);
403 std::vector<Range> insideValues;
404 std::vector<Range> outsideValues;
405
406 insideNode.finiteElement().localBasis().evaluateFunction(intersection.geometryInInside().center(), insideValues);
407 outsideNode.finiteElement().localBasis().evaluateFunction(intersection.geometryInOutside().center(), outsideValues);
408
409 auto centerLocal = intersection.geometry().local(intersection.geometry().center());
410
411 // Check jump against outside basis function or zero.
412 for(std::size_t i=0; i<insideNode.size(); ++i)
413 {
414 auto jump = insideValues[i];
415 if (insideToOutside[i].has_value())
416 jump -= outsideValues[insideToOutside[i].value()];
417 isContinuous[i] = isContinuous[i] and (jumpEvaluator(jump, intersection, centerLocal) < tol);
418 }
419 return isContinuous;
420 };
421 }
422
423 auto localContinuityCheck() const {
424 auto jumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
425 return jump.infinity_norm();
426 };
427 return localJumpCenterContinuityCheck(jumpNorm, tol_);
428 }
429};
430
431
432/*
433 * Check if basis functions are continuous across faces.
434 * Continuity is checked by evaluation at a set of quadrature points
435 * from a quadrature rule of given order.
436 * If two basis functions (on neighboring elements) share the same
437 * global index, their values at the quadrature points (located on
438 * their intersection) should coincide up to the given tolerance.
439 *
440 * If a basis function only appears on one side of the intersection,
441 * it should be zero on the intersection.
442 */
443template<class Basis, class LocalCheck>
444Dune::TestSuite checkBasisContinuity(const Basis& basis, const LocalCheck& localCheck)
445{
446 Dune::TestSuite test("Global continuity check of basis functions");
447
448
449 auto localView = basis.localView();
450 auto neighborLocalView = basis.localView();
451
452 for (const auto& e : elements(basis.gridView()))
453 {
454 localView.bind(e);
455 for(const auto& intersection : intersections(basis.gridView(), e))
456 {
457 if (intersection.neighbor())
458 {
459 neighborLocalView.bind(intersection.outside());
460
461 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& insideNode, auto&& treePath) {
462 const auto& outsideNode = Dune::TypeTree::child(neighborLocalView.tree(), treePath);
463
464 std::vector<std::optional<int>> insideToOutside;
465 insideToOutside.resize(insideNode.size());
466
467 // Map all inside DOFs to outside DOFs if possible
468 for(std::size_t i=0; i<insideNode.size(); ++i)
469 {
470 for(std::size_t j=0; j<outsideNode.size(); ++j)
471 {
472 if (localView.index(insideNode.localIndex(i)) == neighborLocalView.index(outsideNode.localIndex(j)))
473 {
474 // Basis function should only appear once in the neighbor element.
475 test.check(not insideToOutside[i].has_value())
476 << "Basis function " << localView.index(insideNode.localIndex(i))
477 << " appears twice in element " << elementStr(neighborLocalView.element(), basis.gridView());
478 insideToOutside[i] = j;
479 }
480 }
481 }
482
483 // Apply continuity check on given intersection with given inside/outside DOF node pair.
484 auto isContinuous = localCheck(intersection, treePath, insideNode, outsideNode, insideToOutside);
485
486 for(std::size_t i=0; i<insideNode.size(); ++i)
487 {
488 test.check(isContinuous[i])
489 << "Basis function " << localView.index(insideNode.localIndex(i))
490 << " is discontinuous across intersection of elements "
491 << elementStr(localView.element(), basis.gridView())
492 << " and " << elementStr(neighborLocalView.element(), basis.gridView());
493 }
494 });
495 }
496 }
497 }
498 return test;
499}
500
501template<class Basis, class... Flags>
502Dune::TestSuite checkConstBasis(const Basis& basis, Flags... flags)
503{
504 Dune::TestSuite test("const basis check");
505
506 using GridView = typename Basis::GridView;
507
508 // Check if basis models the GlobalBasis concept.
509 test.check(Dune::models<Dune::Functions::Concept::GlobalBasis<GridView>, Basis>(), "global basis concept check")
510 << "type passed to checkBasis() does not model the GlobalBasis concept";
511
512 // Perform all local tests.
513 auto localView = basis.localView();
514 for (const auto& e : elements(basis.gridView()))
515 {
516 localView.bind(e);
517 test.subTest(checkLocalView(basis, localView, flags...));
518 }
519
520 // Perform global index tests.
521 test.subTest(checkBasisIndices(basis));
522
523 // Perform continuity check.
524 // First capture flags in a tuple in order to iterate.
525 auto flagTuple = std::tie(flags...);
526 Dune::Hybrid::forEach(flagTuple, [&](auto&& flag) {
527 using Flag = std::decay_t<decltype(flag)>;
528 if constexpr (std::is_base_of_v<EnableContinuityCheck, Flag>)
529 test.subTest(checkBasisContinuity(basis, flag.localContinuityCheck()));
530 });
531
532 return test;
533}
534
535template<class Basis, class... Flags>
536Dune::TestSuite checkBasis(Basis& basis, Flags... flags)
537{
538 Dune::TestSuite test("basis check");
539
540 // Perform tests for a constant basis
541 test.subTest(checkConstBasis(basis,flags...));
542
543 // Check copy-construction / copy-assignable of a basis
544 {
545 Basis copy(basis);
546 test.subTest(checkConstBasis(copy,flags...));
547 copy = basis;
548 test.subTest(checkConstBasis(copy,flags...));
549 }
550
551 // Check update of gridView
552 auto gridView = basis.gridView();
553 basis.update(gridView);
554
555 return test;
556}
557
558
559
560
561#endif // DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
Grid view abstract base class.
Definition: gridview.hh:66
Intersection of a mesh entity of codimension 0 ("element") with a "neighboring" element or with the d...
Definition: intersection.hh:164
static const QuadratureRule & rule(const GeometryType &t, int p, QuadratureType::Enum qt=QuadratureType::GaussLegendre)
select the appropriate QuadratureRule for GeometryType t and order p
Definition: quadraturerules.hh:326
A Simple helper class to organize your test suite.
Definition: testsuite.hh:31
Infrastructure for concepts.
Traits for type conversions and type information.
constexpr auto models()
Check if concept is modeled by given types.
Definition: concept.hh:184
IteratorRange<... > intersections(const GV &gv, const Entity &e)
Iterates over all Intersections of an Entity with respect to the given GridView.
IteratorRange<... > elements(const GV &gv)
Iterates over all elements / cells (entities with codimension 0) of a GridView.
constexpr void forEach(Range &&range, F &&f)
Range based for loop.
Definition: hybridutilities.hh:256
constexpr auto max
Function object that returns the greater of the given values.
Definition: hybridutilities.hh:484
constexpr auto treePath(const T &... t)
Constructs a new HybridTreePath from the given indices.
Definition: treepath.hh:326
void forEachLeafNode(Tree &&tree, LeafFunc &&leafFunc)
Traverse tree and visit each leaf node.
Definition: traversal.hh:269
Dune namespace.
Definition: alignedallocator.hh:13
Type trait to determine whether an instance of T has an operator[](I), i.e. whether it can be indexed...
Definition: typetraits.hh:250
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.111.3 (Jul 15, 22:36, 2024)