DUNE-FEM (unstable)

hdivprojection.hh
1#warning "dune/fem/operator/projection/hdivprojection.hh is deprecated and will be moved to dune-fem-dg."
2
3#ifndef DUNE_FEM_HDIV_PROJECTION_HH
4#define DUNE_FEM_HDIV_PROJECTION_HH
5
6//- Dune includes
8#include <dune/geometry/referenceelements.hh>
9
10//- Dune-fem includes
11#include <dune/fem/operator/common/spaceoperatorif.hh>
12#include <dune/fem/operator/matrix/blockmatrix.hh>
13#include <dune/fem/quadrature/caching/twistutility.hh>
14#include <dune/fem/quadrature/cachingquadrature.hh>
15#include <dune/fem/space/combinedspace.hh>
16#include <dune/fem/storage/dynamicarray.hh>
17
18// make sure higher order Lagrange works (define USE_TWISTFREE_MAPPER)
19#include <dune/fem/space/discontinuousgalerkin.hh>
20#include <dune/fem/space/lagrange.hh>
21
22namespace Dune
23{
24
25 // External Forward Declarations
26 // -----------------------------
27
28 template< int dim, class CoordCont >
29 class YaspGrid;
30
31#ifdef ENABLE_UG
32 template< int dim >
33 class UGGrid;
34#endif // #ifdef ENABLE_UG
35
36 namespace Fem
37 {
38
60 template <class DiscreteFunctionType>
61 class HdivProjection : public SpaceOperatorInterface<DiscreteFunctionType>
62 {
63 typedef typename DiscreteFunctionType::DiscreteFunctionSpaceType DiscreteFunctionSpaceType;
65 typedef typename DiscreteFunctionSpaceType::RangeType RangeType;
66 typedef typename DiscreteFunctionSpaceType::DomainType DomainType;
67 typedef typename DiscreteFunctionSpaceType::DomainFieldType DomainFieldType;
68 typedef typename DiscreteFunctionSpaceType::RangeFieldType RangeFieldType;
69 typedef typename DiscreteFunctionSpaceType::JacobianRangeType JacobianRangeType;
70 typedef typename DiscreteFunctionSpaceType::GridPartType GridPartType;
71
73 //typedef ElementQuadrature <GridPartType , 0> VolumeQuadratureType;
74
75 // face quadrature type
76 //typedef CachingQuadrature<GridPartType, 1> FaceQuadratureType;
78
79 typedef typename GridPartType :: GridType GridType;
80
81 enum { dimFaceRange = 1 };
82 enum { dimDomain = DiscreteFunctionSpaceType::dimDomain };
83 enum { dimFaceDomain = dimDomain - 1 };
84 enum { polOrdN = DiscreteFunctionSpaceType :: polynomialOrder };
85
88
89 enum { gradPolOrd = ((polOrdN - 1) < 0) ? 0 : (polOrdN - 1) };
90
91 template <class Space> struct BubbleM;
92
93 template <class FunctionSpaceImp,
94 class GridPartImp,
95 int polOrd,
96 template <class> class StorageImp,
97 template <class,class,int,template <class> class> class DiscreteFunctionSpaceImp>
98 struct BubbleM <DiscreteFunctionSpaceImp<FunctionSpaceImp,GridPartImp,polOrd,StorageImp> >
99 {
100 enum { bubblePModifier = dimDomain - 1 };
101 };
102
103 template <class FunctionSpaceImp,
104 class GridPartImp,
105 int polOrd,
106 template <class> class StorageImp>
107 struct BubbleM < LegendreDiscontinuousGalerkinSpace<FunctionSpaceImp,GridPartImp,polOrd,StorageImp> >
108 {
109 enum { bubblePModifier = 1 };
110 };
111
112 template <class DiscreteFunctionSpaceImp,
113 int N,
114 DofStoragePolicy policy>
115 struct BubbleM <CombinedSpace<DiscreteFunctionSpaceImp,N,policy> >
116 {
117 enum { bubblePModifier = BubbleM< DiscreteFunctionSpaceImp > :: bubblePModifier };
118 };
119
120 typedef BubbleM <DiscreteFunctionSpaceType> BubbleMType;
121
122#ifdef USE_TWISTFREE_MAPPER
123 // modifier for bubble pol order
124 enum { bubblePModifier = BubbleMType :: bubblePModifier };
125
126 // we need polOrd + 1 in 2d and polOrd + 2 in 3d
127 enum { bubblePolOrd = (polOrdN + bubblePModifier) };
128#else
129#ifndef NDEBUG
130#warning "Hdiv-Projection only working for polOrd = 1 (enable higher order Lagrange with -DUSE_TWISTFREE_MAPPER)"
131#endif
132 // modifier for bubble pol order
133 enum { bubblePModifier = 1 };
134
135 // limit bubble polord otherwise no compilation possible
136 enum { bubblePolOrd = (polOrdN > 1) ? 2 : (polOrdN + 1) };
137#endif
138
139 template <class Space> struct Spaces;
140
141 template <class FunctionSpaceImp,
142 class GridPartImp,
143 int polOrd,
144 template <class> class StorageImp,
145 template <class,class,int,template <class> class> class DiscreteFunctionSpaceImp>
146 struct Spaces<DiscreteFunctionSpaceImp<FunctionSpaceImp,GridPartImp,polOrd,StorageImp> >
147 {
148 //typedef LagrangeDiscreteFunctionSpace<ElSpaceType,GridPartImp,gradPolOrd,StorageImp> ElementGradientSpaceType;
149 //typedef LegendreDiscontinuousGalerkinSpace<ElSpaceType,GridPartImp,gradPolOrd,StorageImp> ElementGradientSpaceType;
150 typedef DiscreteFunctionSpaceImp<ElSpaceType,GridPartImp,gradPolOrd,StorageImp> ElementGradientSpaceType;
151 //typedef DiscontinuousGalerkinSpace<ElSpaceType,GridPartImp,gradPolOrd,StorageImp> ElementGradientSpaceType;
152 typedef DiscreteFunctionSpaceImp<FaceSpaceType,GridPartType,polOrdN,StorageImp> FaceDiscreteSpaceType;
153 //typedef LegendreDiscontinuousGalerkinSpace<FaceSpaceType,GridPartType,polOrdN,StorageImp> FaceDiscreteSpaceType;
154 //typedef DiscontinuousGalerkinSpace<FaceSpaceType,GridPartType,polOrdN,StorageImp> FaceDiscreteSpaceType;
156 };
157
158 template <class DiscreteFunctionSpaceImp,
159 int N,
160 DofStoragePolicy policy>
161 struct Spaces<CombinedSpace<DiscreteFunctionSpaceImp,N,policy> >
162 {
163 typedef typename DiscreteFunctionSpaceImp :: GridPartType GridPartImp;
164 typedef Spaces<DiscreteFunctionSpaceImp> AllSpacesType;
165 typedef typename AllSpacesType :: ElementGradientSpaceType ElementGradientSpaceType;
166 typedef typename AllSpacesType :: ElementDiscreteSpaceType ElementDiscreteSpaceType;
167 typedef typename AllSpacesType :: FaceDiscreteSpaceType FaceDiscreteSpaceType;
168 };
169
170 typedef Spaces<DiscreteFunctionSpaceType> AllSpacesType;
171 typedef typename AllSpacesType :: ElementGradientSpaceType ElementGradientSpaceType;
172 typedef typename AllSpacesType :: ElementDiscreteSpaceType ElementDiscreteSpaceType;
173 typedef typename AllSpacesType :: FaceDiscreteSpaceType FaceDiscreteSpaceType;
174
175 const DiscreteFunctionSpaceType& space_;
176 GridPartType & gridPart_;
177 const FaceDiscreteSpaceType faceSpace_;
178 const ElementDiscreteSpaceType elSpace_;
179 const ElementGradientSpaceType gradSpace_;
180
181 public:
183 [[deprecated( "Use dune-fem-dg implementation." )]]
184 HdivProjection(const DiscreteFunctionSpaceType& space) :
185 space_(space),
186 gridPart_(space.gridPart()),
187 faceSpace_( gridPart_ ),
188 elSpace_( gridPart_ ),
189 gradSpace_( gridPart_ )
190 {}
191
192 [[deprecated( "Use dune-fem-dg implementation." )]]
193 HdivProjection(const HdivProjection& org) :
194 space_(org.space_),
195 gridPart_( org.gridPart_),
196 faceSpace_( gridPart_ ),
197 elSpace_( gridPart_ ),
198 gradSpace_( gridPart_ )
199 {}
200
202 const DiscreteFunctionSpaceType& space() const
203 {
204 return space_;
205 }
206 void setTime(double) {
207 }
208 double timeStepEstimate() const {
209 return 0.;
210 }
211
213 static double normalJump(const DiscreteFunctionType &discFunc, const int polyOrder = -1 )
214 {
215 typedef typename GridPartType :: IntersectionIteratorType IntersectionIteratorType;
216 typedef typename IntersectionIteratorType :: Intersection IntersectionType;
217 typedef typename GridType :: template Codim<0> :: Entity EntityType;
218 typedef typename GridType :: Traits :: LocalIdSet LocalIdSetType;
219
220 enum { dim = GridType::dimension };
221
222 const DiscreteFunctionSpaceType &space = discFunc.space();
223 const GridPartType &gridPart = space.gridPart();
224 const int polOrd = (polyOrder <0) ? (2 * space.order() + 2) : polyOrder;
225
226 typedef typename DiscreteFunctionType::LocalFunctionType LocalFuncType;
227
228 RangeType ret (0.0);
229 RangeType neighRet (0.0);
230
231 // define type of quadrature
233
234 double sum = 0.0;
235
236 const LocalIdSetType &idSet = gridPart.grid().localIdSet();
237
238 for(const auto & en : space)
239 {
240 const LocalFuncType lf = discFunc.localFunction(en);
241
242 double localValue = 0.0;
243
244 IntersectionIteratorType endnit = gridPart.iend(en);
245 for(IntersectionIteratorType nit = gridPart.ibegin(en);
246 nit != endnit; ++nit)
247 {
248 const IntersectionType& inter=*nit;
249 // only interior faces are considered
250 if(inter.neighbor() )
251 {
252 EntityType nb = inter.outside();
253
254 if(idSet.id( en ) < idSet.id( nb ))
255 {
256 FaceQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
257 FaceQuadratureType faceQuadOuter(gridPart, inter, polOrd, FaceQuadratureType::OUTSIDE);
258
259 const LocalFuncType neighLf = discFunc.localFunction(nb);
260
261 const int quadNop = faceQuadInner.nop();
262 for (int l = 0; l < quadNop ; ++l)
263 {
264 DomainType normal =
265 inter.unitOuterNormal(faceQuadInner.localPoint(l));
266
267 lf.evaluate(faceQuadInner[l], ret);
268 neighLf.evaluate(faceQuadOuter[l], neighRet);
269
270 ret -= neighRet;
271
272 double val = ret * normal;
273 val *= faceQuadInner.weight(l);
274
275 localValue += std::abs(val);
276 }
277 }
278 }
279 } // end of intersection iterator
280 sum += std::abs(localValue);
281 }
282
283 return sum;
284 }
285
286 private:
287
288 // only works for 2d right now
289 void curl(const DomainType & arg, DomainType & dest, const int d ) const
290 {
291 if( DomainType :: dimension == 2 )
292 {
293 dest[0] = arg[1];
294 dest[1] = -arg[0];
295
296 return ;
297 }
298 else if( DomainType :: dimension == 3 )
299 {
300 if( d == 0 )
301 {
302 dest[0] = arg[1];
303 dest[1] = -arg[2];
304 dest[2] = 0;
305 return ;
306 }
307 else if ( d == 1 )
308 {
309 dest[0] = 0;
310 dest[1] = arg[2];
311 dest[2] = -arg[0];
312 return ;
313 }
314 else
315 {
316 dest[0] = -arg[2];
317 dest[1] = 0;
318 dest[2] = arg[1];
319 return ;
320 }
321 }
322 else
323 {
324 assert( false );
325 abort();
326 }
327 }
328
329 template <class EntityType,
330 class LocalFunctionType,
331 class ArrayType,
332 class MatrixType,
333 class VectorType>
334 void bubblePart(const ElementDiscreteSpaceType& space,
335 EntityType & en,
336 const LocalFunctionType & uLF, const int startRow ,
337 ArrayType& uRets,
338 MatrixType & matrix, VectorType& rhs ) const
339 {
340
341 typedef typename ElementDiscreteSpaceType :: BaseFunctionSetType BaseFunctionSetType;
342 typedef typename ElementDiscreteSpaceType :: LagrangePointSetType LagrangePointSetType;
343
344 typedef typename ElementDiscreteSpaceType :: JacobianRangeType JacobianRangeType;
345 typedef typename ElementDiscreteSpaceType :: DomainType DomainType;
346
347 enum { dim = GridType::dimension };
348
349 const LagrangePointSetType& lagrangePointSet = space.lagrangePointSet( en );
350 const BaseFunctionSetType bSet = space.baseFunctionSet( en );
351 const int polOrd = 2 * space.order(); // + 2;
352
353 const int cols = uLF.numDofs();
354 assert( uRets.size() == (unsigned int)cols );
355
356 VolumeQuadratureType quad (en,polOrd);
357 DomainType result;
358 std::vector< JacobianRangeType > valTmpVec( bSet.size() );
359 DomainType bVal;
360 DomainType aVal;
361
362 // get geometry
363 typedef typename EntityType :: Geometry Geometry ;
364 const Geometry& geo = en.geometry();
365 // get geometry type
366 const GeometryType& type = geo.type();
367 const int bubbleOffset = (type.isSimplex()) ? 0 : baseFunctionOffset( 0 );
368
369 // type of jacobian inverse
370 enum { cdim = Geometry :: coorddimension };
371 enum { mydim = Geometry :: mydimension };
372 typedef typename Geometry::JacobianInverseTransposed JacobianInverseType;
373
374 // get number of dofs for codim 0 (skip first for)
375 const int enDofs = numberOfBubbles( lagrangePointSet.numDofs( 0 ), type ,
376 cols, startRow );
377 const int bubbleMod = bubbleModifier( mydim );
378
379 const int quadNop = quad.nop();
380 for (int l = 0; l < quadNop ; ++l)
381 {
382 // get jacobian inverse
383 const JacobianInverseType& inv = geo.jacobianInverseTransposed( quad.point(l) );
384
385 // get integration element
386 const double intel = quad.weight(l) *
387 geo.integrationElement(quad.point(l));
388
389 // evaluate u
390 uLF.evaluate(quad[l], result);
391
392 // evaluate base functions of u
393 uLF.baseFunctionSet().evaluateAll( quad[l], uRets );
394
395 // evaluate gradients
396 bSet.jacobianAll( quad[l], inv, valTmpVec );
397
398 // for all bubble functions
399 for( int i = 0 ; i<enDofs; i += bubbleMod )
400 {
401 // we might have other row
402 int row = startRow + i;
403
404 // map to lagrange base function number
405 const int localBaseFct = ((int) i/bubbleMod) + bubbleOffset;
406 // get dof number of 'localBaseFct' dof on codim 0 subentity 0
407 const int baseFct = lagrangePointSet.entityDofNumber( 0, 0, localBaseFct );
408
409 // evaluate gradient
410 JacobianRangeType& val = valTmpVec[ baseFct ];
411
412 //apply inverse
413 //inv.mv( valTmp[0], val[0] );
414
415 for(int d = 0; d<bubbleMod; ++d )
416 {
417 // apply curl
418 curl(val[0], aVal, d );
419
420 double r = aVal * result;
421 r *= intel;
422 rhs[row] += r;
423
424 // for cols make matrix
425 for(int j=0; j<cols; ++j)
426 {
427 double t = aVal * uRets[ j ];
428 t *= intel;
429 matrix[ row ][ j ] += t;
430 }
431
432 // increase row
433 ++row;
434 }
435 }
436 }
437 }
438
439 template <class EntityType, class LocalFunctionType, class ArrayType,
440 class MatrixType, class VectorType>
441 void gradientPart(const ElementGradientSpaceType & space,
442 EntityType & en,
443 const LocalFunctionType & uLF, const int startRow ,
444 ArrayType& uRets, MatrixType& matrix, VectorType& rhs ) const
445 {
446 typedef typename ElementGradientSpaceType :: BaseFunctionSetType BaseFunctionSetType;
447 const BaseFunctionSetType bSet = space.baseFunctionSet( en );
448 int polOrd = 2 * space.order() + 1;
449
450 const int localRows = gradientBaseFct( bSet );
451 const int cols = uLF.numDofs();
452
453 VolumeQuadratureType quad (en,polOrd);
454
455 RangeType result;
456 RangeType uPhi;
457
458 typedef typename ElementGradientSpaceType :: JacobianRangeType GradJacobianRangeType;
459 std::vector< GradJacobianRangeType > gradTmpVec( bSet.size() );
460 GradJacobianRangeType gradPhi;
461
462 typedef typename EntityType :: Geometry Geometry ;
463 const Geometry& geo = en.geometry();
464
465 enum { cdim = Geometry :: coorddimension };
466 enum { mydim = Geometry :: mydimension };
467 typedef typename Geometry::JacobianInverseTransposed JacobianInverseType;
468
469 const int quadNop = quad.nop();
470 for (int l = 0; l < quadNop ; ++l)
471 {
472 // get jacobian inverse
473 const JacobianInverseType& inv = geo.jacobianInverseTransposed( quad.point( l ) );
474
475 // get integration element
476 const double intel = quad.weight(l) *
477 geo.integrationElement( quad.point( l ) );
478
479 // evaluate uLF
480 uLF.evaluate(quad[l], result);
481
482 // evaluate base function on quadrature point
483 uLF.baseFunctionSet().evaluateAll( quad[l], uRets );
484
485 // evaluate gradient (skip first function because this function
486 // is constant and the gradient therefore 0 )
487 bSet.jacobianAll( quad[l], inv, gradTmpVec );
488
489 // apply jacobian Inverse
490 for(int i=0; i<localRows; ++i)
491 {
492 // we might have other row
493 const int row = startRow + i;
494
495 // evaluate gradient (skip first function because this function
496 // is constant and the gradient therefore 0 )
497 GradJacobianRangeType& gradPhi = gradTmpVec[ baseFunctionOffset( i ) ];
498
499 const double uDGVal = result * gradPhi[0];
500 rhs[row] += uDGVal * intel;
501
502 // for cols make matrix
503 for(int j=0; j<cols; ++j)
504 {
505 //uLF.baseFunctionSet().evaluate(j, quad[l], uPhi);
506 //const double val = uPhi * gradPhi[0];
507 const double val = uRets[ j ] * gradPhi[0];
508 matrix[row][j] += val * intel;
509 }
510 }
511 }
512 }
513
514 template <class FaceBSetType, class GridType>
515 struct GetSubBaseFunctionSet
516 {
517 template <class EntityType, class SpaceType>
518 static inline FaceBSetType faceBaseSet(const EntityType& en, const SpaceType& space)
519 {
520 return space.baseFunctionSet( (en.template subEntity<1> (0) )->type() );
521 }
522 };
523
524 template <class FaceBSetType, int dim, class CoordCont>
525 struct GetSubBaseFunctionSet< FaceBSetType, YaspGrid< dim, CoordCont > >
526 {
527 template <class EntityType, class SpaceType>
528 static inline FaceBSetType faceBaseSet(const EntityType& en, const SpaceType& space)
529 {
530 return space.baseFunctionSet( GeometryTypes::cube(dim-1) );
531 }
532 };
533
534#ifdef ENABLE_UG
535 template <class FaceBSetType, int dim>
536 struct GetSubBaseFunctionSet<FaceBSetType, UGGrid<dim> >
537 {
538 template <class EntityType, class SpaceType>
539 static inline FaceBSetType faceBaseSet(const EntityType& en, const SpaceType& space)
540 {
541 const GeometryType geoType (en.geometry().type().basicType(),dim-1);
542 return space.baseFunctionSet( geoType );
543 }
544 };
545#endif
546
547 enum { gradFuncOffset = 1 };
548 template <class GradBaseFunctionSet>
549 int gradientBaseFct(const GradBaseFunctionSet& gradSet) const
550 {
551 return (gradPolOrd <= 0) ? 0 : gradSet.size() - gradFuncOffset;
552 }
553
554 int baseFunctionOffset(const int i) const
555 {
556 return i + gradFuncOffset;
557 }
558
559 int numberOfBubbles( const int bubbles , const GeometryType& type,
560 const int allDofs, const int allOther ) const
561 {
562 /*
563 // for hexahedrons this is different
564 if( type.isHexahedron() )
565 {
566 return (bubbleModifier( type.dim() )) * (bubbles - gradFuncOffset) - 1;
567 }
568 else
569 */
570 {
571 // the rest is padded with bubble functions
572 const int numBubble = allDofs - allOther ;
573 return (numBubble > 0) ? numBubble : 0;
574 }
575 }
576
577 int bubbleModifier( const int dim ) const
578 {
579 // return 1 for 2d and 3 for 3d
580 return (dim - 2) * (dim - 1) + 1;
581 }
582
584 void project(const DiscreteFunctionType &uDG,
585 DiscreteFunctionType & velo ) const
586 {
588 enum { localBlockSize = FunctionSpaceType::localBlockSize };
589
590 typedef typename FunctionSpaceType::GridType GridType;
591 typedef typename FunctionSpaceType::GridPartType GridPartType;
592 typedef typename FunctionSpaceType::IteratorType Iterator;
593
594 typedef typename FunctionSpaceType::RangeFieldType RangeFieldType;
595 typedef typename FunctionSpaceType::RangeType RangeType;
596
597 enum { dim = GridType::dimension };
598 typedef typename GridType :: ctype coordType;
599
600 const FunctionSpaceType& space = uDG.space();
601
602 // for polOrd 0 this is not working
603 // then just copy the function
604 if(space.order() < 1 )
605 {
606 velo.assign( uDG );
607 return ;
608 }
609
610 const int polOrd = 2 * space.order() + 2;
611
612 typedef typename FaceDiscreteSpaceType :: BaseFunctionSetType FaceBSetType ;
613
614 typedef typename ElementGradientSpaceType :: BaseFunctionSetType GradientBaseSetType ;
615
616 typedef typename DiscreteFunctionType::LocalFunctionType LocalFuncType;
617
618 Iterator start = space.begin();
619 // if empty grid, do nothing
620 if( start == space.end() ) return;
621
622 // only working for spaces with one element type
623 if( space.multipleGeometryTypes() )
624 {
625 if( space.indexSet().geomTypes(0).size() > 1)
626 {
627 assert( space.indexSet().geomTypes(0).size() == 1 );
628 DUNE_THROW(NotImplemented,"H-div projection not implemented for hybrid grids");
629 }
630 }
631
632 const GeometryType startType = start->type();
633
634 // only working for spaces with one element type
635 if( startType.isHexahedron() && space.order() > 1 )
636 {
637 assert( ! startType.isHexahedron() || space.order() <= 1 );
638 DUNE_THROW(NotImplemented,"H-div projection not implemented for p > 1 on hexas! ");
639 }
640
641 const int desiredOrder = space.order() + bubblePModifier;
642 // check Lagrange space present
643 if( bubblePolOrd != desiredOrder )
644 {
645 assert( bubblePolOrd == desiredOrder );
646 DUNE_THROW(NotImplemented,"H-div projection not working for "
647 << space.order() << " when LagrangeSpace of order "<< desiredOrder << " is missing");
648 }
649
650 // colums are dofs of searched function
651 LocalFuncType lf = uDG.localFunction(*start);
652 const int numDofs = lf.numDofs();
653 //std::cout << numDofs << " numDofs \n";
654
655 const FaceBSetType faceSet =
656 GetSubBaseFunctionSet<FaceBSetType,GridType>::faceBaseSet( *start , faceSpace_ );
657 // number of dofs on faces
658 const int numFaceDofs = faceSet.size();
659
660 const GradientBaseSetType gradSet = gradSpace_.baseFunctionSet(*start);
661 // in case of linear space the is zero
662 const int numGradDofs = gradientBaseFct( gradSet );
663
664 const auto& refElem = Dune::ReferenceElements< coordType, dim >::general( startType );
665
666 // get number of faces
667 const int overallFaceDofs = numFaceDofs * refElem.size(1);
668
669 // get all element dofs from Lagrange space
670 const int numBubbleDofs = (space.order() <= 1) ? 0 :
671 numberOfBubbles( elSpace_.lagrangePointSet( *start ).numDofs ( 0 ) , startType,
672 numDofs, overallFaceDofs + numGradDofs );
673
674 const int rows = (overallFaceDofs + numGradDofs + numBubbleDofs);
675
676 // number of columns
677 const int cols = numDofs;
678
679 DynamicArray< RangeFieldType > rets(numDofs);
680 DynamicArray< RangeType > uRets(numDofs);
681
682 typedef FieldMatrix<RangeFieldType,localBlockSize,localBlockSize> FieldMatrixType;
683
684 // resulting system matrix
685 FieldMatrixType inv;
686
687 typedef FieldVector<RangeFieldType,localBlockSize> VectorType;
688 VectorType fRhs(0.0);
689
690 assert( numDofs == localBlockSize );
691 if( numDofs != localBlockSize )
692 {
693 DUNE_THROW(InvalidStateException,"wrong sizes ");
694 }
695
696 // flag to say whether we have non-symetric or symetric situation
697 const bool nonSymetric = (cols != rows);
698
699 // matrix type
700 typedef Fem::DenseMatrix<double> MatrixType;
701 MatrixType matrix(rows,cols);
702 typedef typename MatrixType :: RowType RowType;
703 // matrix type
704 MatrixType fakeMatrix(cols,cols);
705 RowType rhs(rows,0.0);
706 RowType fakeRhs(numDofs,0.0);
707
708 // iterate over grid
709 for(const auto & en : space)
710 {
711 if( nonSymetric )
712 {
713 // reset values
714 matrix = 0.0;
715
716 // reset rhs
717 for(int i=0; i<rows; ++i)
718 {
719 rhs[i] = 0.0;
720 }
721
722 // fill non-symetric matrix
723 fillMatrix(gridPart_,en,uDG,
724 faceSpace_,
725 gradSpace_, overallFaceDofs,
726 elSpace_, rows - numBubbleDofs,
727 polOrd,numDofs,numFaceDofs,
728 rets,uRets, matrix,rhs);
729
730 // apply least square
731 matrix.multTransposed(rhs, fakeRhs);
732 fakeMatrix.multiply_AT_A(matrix);
733
734 // copy values
735 for(int i=0; i<numDofs; ++i)
736 {
737 fRhs[i] = fakeRhs[i];
738 for(int j=0; j<numDofs; ++j)
739 {
740 inv[i][j] = fakeMatrix[i][j];
741 }
742 }
743 }
744 else
745 {
746 // reset values
747 inv = 0.0;
748 // reset rhs
749 fRhs = 0.0;
750
751 assert( cols == rows );
752 // fill inv and fRhs directly
753 fillMatrix(gridPart_,en,uDG,
754 faceSpace_,
755 gradSpace_, rows - numBubbleDofs - numGradDofs,
756 elSpace_, rows - numBubbleDofs,
757 polOrd,numDofs,numFaceDofs,
758 rets, uRets, inv,fRhs);
759 }
760
761 // set new values to new velocity function
762 {
763 LocalFuncType veloLF = velo.localFunction( en );
764
765 // solve linear system
766 luSolve( inv, fRhs );
767 const VectorType& x = fRhs ;
768
769 for(int i=0; i<localBlockSize; ++i)
770 {
771 veloLF[ i ] = x[ i ];
772 }
773 }
774 }
775 }
776
777 template <class GridPartType,
778 class EntityType,
779 class ArrayType,
780 class Array2Type,
781 class MatrixType,
782 class VectorType>
783 void fillMatrix(const GridPartType& gridPart,
784 const EntityType& en,
785 const DiscreteFunctionType& uDG,
786 const FaceDiscreteSpaceType& faceSpace,
787 const ElementGradientSpaceType& gradSpace, const int startGradDofs,
788 const ElementDiscreteSpaceType& elSpace, const int startBubbleDofs,
789 const int polOrd, const int numDofs, const int numFaceDofs,
790 ArrayType& rets, Array2Type& uRets,
791 MatrixType& matrix, VectorType& rhs) const
792 {
793 typedef typename GridPartType :: IntersectionIteratorType IntersectionIteratorType;
794 typedef typename IntersectionIteratorType::Intersection IntersectionType;
795
796 typedef typename FaceDiscreteSpaceType :: BaseFunctionSetType FaceBSetType ;
797 typedef typename FaceDiscreteSpaceType :: RangeType FaceRangeType;
798 FaceRangeType faceVal;
799
800 typedef typename DiscreteFunctionSpaceType::RangeType RangeType;
801 RangeType ret (0.0);
802 RangeType neighRet (0.0);
803 RangeType uPhi (0.0);
804
805 typedef typename DiscreteFunctionType :: LocalFunctionType LocalFuncType ;
806 typedef typename DiscreteFunctionType :: DiscreteFunctionSpaceType
807 :: BaseFunctionSetType BaseFunctionSetType;
808
809 // get uDg local on entity
810 const LocalFuncType uLF = uDG.localFunction(en);
811
812 // get base functions set
813 const BaseFunctionSetType & bSet = uLF.baseFunctionSet();
814
815 // iterate over intersections
816 IntersectionIteratorType endnit = gridPart.iend(en);
817 for(IntersectionIteratorType nit = gridPart.ibegin(en);
818 nit != endnit; ++nit)
819 {
820 const IntersectionType& inter=*nit;
821 // get base function set of face
822 const FaceBSetType &faceSet = faceSpace.baseFunctionSet( inter.type() );
823
824 const int firstRow = inter.indexInInside() * numFaceDofs;
825
826 // only interior faces are considered
827 if(inter.neighbor())
828 {
829 // get neighbor entity
830 EntityType nb = inter.outside();
831
832 // get local function of neighbor
833 const LocalFuncType uNeighLf = uDG.localFunction(nb);
834
835 //typedef TwistUtility<GridType> TwistUtilityType;
836 // for conforming situations apply Quadrature given
837 //if( TwistUtilityType::conforming(gridPart.grid(),inter) )
838 if( inter.conforming() )
839 {
840 // create quadratures
841 FaceQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
842 FaceQuadratureType faceQuadOuter(gridPart, inter, polOrd, FaceQuadratureType::OUTSIDE);
843
844 applyLocalNeighbor(nit,faceQuadInner,faceQuadOuter,
845 bSet,faceSet, uLF, uNeighLf,
846 firstRow, numFaceDofs,
847 rets,
848 ret,neighRet,faceVal,
849 matrix, rhs);
850 }
851 else
852 {
853 // type of quadrature for non-conforming intersections
854 typedef typename FaceQuadratureType ::
855 NonConformingQuadratureType NonConformingQuadratureType;
856 // create quadratures
857 NonConformingQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
858 NonConformingQuadratureType faceQuadOuter(gridPart, inter, polOrd, FaceQuadratureType::OUTSIDE);
859
860 applyLocalNeighbor(nit,faceQuadInner,faceQuadOuter,
861 bSet,faceSet, uLF, uNeighLf,
862 firstRow, numFaceDofs,
863 rets,
864 ret,neighRet,faceVal,
865 matrix, rhs);
866
867 }
868 }
869
870 // only interior faces are considered
871 if(inter.boundary())
872 {
873 // create quadrature
874 FaceQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
875 const int quadNop = faceQuadInner.nop();
876
877 std::vector< RangeType > uPhiVec( numDofs );
878 std::vector< FaceRangeType > faceValVec( numFaceDofs );
879
880 for (int l = 0; l < quadNop ; ++l)
881 {
882 DomainType unitNormal =
883 inter.integrationOuterNormal(faceQuadInner.localPoint(l));
884
885 const double faceVol = unitNormal.two_norm();
886 unitNormal *= 1.0/faceVol;
887
888 // get integration weight
889 const double intel = faceVol * faceQuadInner.weight(l);
890
891 // evaluate function
892 uLF.evaluate(faceQuadInner[l], ret);
893
894 RangeFieldType val = ret * unitNormal;
895 val *= intel;
896
897 bSet.evaluateAll( faceQuadInner[l], uPhiVec );
898
899 // evaluate base functions
900 for(int i=0; i<numDofs; ++i)
901 {
902 rets[i] = uPhiVec[ i ] * unitNormal;
903 rets[i] *= intel;
904 }
905
906 faceSet.evaluateAll( faceQuadInner[ l ], faceValVec );
907
908 int row = firstRow;
909 for(int j=0; j<numFaceDofs; ++j, ++row)
910 {
911 FaceRangeType& faceVal = faceValVec[ j ];
912 rhs[row] += val*faceVal[0];
913
914 for(int i=0; i<numDofs; ++i)
915 {
916 matrix[row][i] += (faceVal[0] * rets[i]);
917 }
918 }
919 }
920 }
921 }
922
923 // add gradient part
924 if( gradPolOrd > 0 )
925 {
926 gradientPart(gradSpace, en, uLF, startGradDofs, uRets, matrix, rhs );
927 }
928
929 // add bubble part
930 if( bubblePolOrd - bubblePModifier > 1 )
931 {
932 bubblePart(elSpace, en, uLF, startBubbleDofs, uRets, matrix, rhs);
933 }
934
935 // printMatrix( matrix );
936 }
937
938 template <class MatrixType>
939 void printMatrix(const MatrixType& matrix) const
940 {
941 std::cout << "Print Matrix \n";
942 for(size_t row = 0; row < matrix.N(); ++row)
943 {
944 std::cout << row << ": ";
945 for(size_t col = 0; col< matrix.M(); ++col)
946 {
947 if( std::abs( matrix[row][col] ) < 1e-12 )
948 std::cout << "0 ";
949 else
950 std::cout << matrix[row][col] << " ";
951 }
952 std::cout << std::endl;
953 }
954 std::cout << "Finished print Matrix \n";
955 }
956
957 template <class IntersectionIteratorType,
958 class QuadratureType,
959 class BaseFunctionSetType,
960 class FaceBaseFunctionSetType,
961 class LocalFunctionType,
962 class ArrayType,
963 class RangeType,
964 class FaceRangeType,
965 class MatrixType,
966 class RHSType>
967 static void applyLocalNeighbor(const IntersectionIteratorType& nit,
968 const QuadratureType& faceQuadInner,
969 const QuadratureType& faceQuadOuter,
970 const BaseFunctionSetType& bSet,
971 const FaceBaseFunctionSetType& faceSet,
972 const LocalFunctionType& uLF,
973 const LocalFunctionType& uNeighLf,
974 const int firstRow,
975 const int numFaceDofs,
976 ArrayType& rets,
977 RangeType& ret, RangeType& neighRet,
978 FaceRangeType& faceVal,
979 MatrixType& matrix,
980 RHSType& rhs)
981 {
982 const typename IntersectionIteratorType::Intersection& inter = *nit;
983 const int quadNop = faceQuadInner.nop();
984 const int numDofs = uLF.numDofs();
985
986 std::vector< RangeType > phiVec( numDofs );
987 std::vector< FaceRangeType > facePhiVec( numFaceDofs );
988
989 for (int l = 0; l < quadNop ; ++l)
990 {
991 DomainType unitNormal =
992 inter.integrationOuterNormal(faceQuadInner.localPoint(l));
993
994 // get unit outer normal
995 const double faceVol = unitNormal.two_norm();
996 unitNormal *= 1.0/faceVol;
997
998 // integration weight
999 const double intel = faceVol * faceQuadInner.weight(l);
1000
1001 // evaluate dg velocity
1002 uLF.evaluate(faceQuadInner[l], ret);
1003 uNeighLf.evaluate(faceQuadOuter[l], neighRet);
1004
1005 // take mean value
1006 ret += neighRet;
1007 ret *= 0.5;
1008
1009 RangeFieldType val = ret * unitNormal;
1010 val *= intel;
1011
1012 bSet.evaluateAll( faceQuadInner[l], phiVec );
1013
1014 // evaluate base functions
1015 for(int i=0; i<numDofs; ++i)
1016 {
1017 rets[i] = phiVec[ i ] * unitNormal;
1018 rets[i] *= intel;
1019 }
1020
1021 // evaluate all basis functions
1022 faceSet.evaluateAll( faceQuadInner[ l ], facePhiVec );
1023
1024 int row = firstRow;
1025 for(int j=0; j<numFaceDofs; ++j, ++row )
1026 {
1027 FaceRangeType& faceVal = facePhiVec[ j ];
1028
1029 rhs[row] += val * faceVal[0];
1030
1031 for(int i=0; i<numDofs; ++i)
1032 {
1033 matrix[row][i] += (faceVal[0] * rets[i]);
1034 }
1035 }
1036 }
1037 }
1038
1039 public:
1041 virtual void operator () (const DiscreteFunctionType &arg,
1042 DiscreteFunctionType& dest) const
1043 {
1044 // apply H-div projection
1045 project(arg,dest);
1046 }
1047
1048 template <class AdaptationType>
1049 static void estimator(const DiscreteFunctionType &velo,
1050 AdaptationType& adaptation)
1051 {
1052 typedef typename DiscreteFunctionType::LocalFunctionType LocalFuncType;
1053 typedef typename GridType :: Traits :: LocalIdSet LocalIdSetType;
1054
1055 enum { dim = GridType :: dimension };
1056 typedef typename GridType :: template Codim<0> :: Entity EntityType;
1057
1058 const DiscreteFunctionSpaceType& space = velo.space();
1059 GridPartType & gridPart = space.gridPart();
1060 int polOrd = space.order() + 1;
1061
1062 // get local id set
1063 const LocalIdSetType& localIdSet = gridPart.grid().localIdSet();
1064
1065 // define type of face quadrature
1066 typedef CachingQuadrature<GridPartType,1> FaceQuadratureType;
1067
1068 for(const auto & en : space)
1069 {
1070 const LocalFuncType uLF = velo.localFunction(en);
1071 const double enVol = en.geometry().volume();
1072
1073 typedef typename GridPartType :: IntersectionIteratorType IntersectionIteratorType;
1074 typedef typename IntersectionIteratorType :: Intersection IntersectionType;
1075 IntersectionIteratorType endnit = gridPart.iend(en);
1076 for(IntersectionIteratorType nit = gridPart.ibegin(en);
1077 nit != endnit; ++nit)
1078 {
1079 const IntersectionType& inter=*nit;
1080 double enError = 0.0;
1081 // only interior faces are considered
1082 if(inter.neighbor())
1083 {
1084 EntityType nb = inter.outside();
1085 const double enVol_nbVol = 0.5 * (enVol + nb.geometry().volume());
1086
1087#if HAVE_MPI
1088 // get partition type
1089 const bool interiorEntity = (nb.partitionType() == InteriorEntity);
1090#else
1091 const bool interiorEntity = true;
1092#endif
1093 if( (localIdSet.id( en ) < localIdSet.id( nb ))
1094#if HAVE_MPI
1095 || ( ! interiorEntity )
1096#endif
1097 )
1098 {
1099 const LocalFuncType uNeighLf = velo.localFunction(nb);
1100
1101 //typedef TwistUtility<GridType> TwistUtilityType;
1102 // for conforming situations apply Quadrature given
1103 //if( TwistUtilityType::conforming(gridPart.grid(),inter) )
1104 if( inter.conforming() )
1105 {
1106 // create quadratures
1107 FaceQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
1108 FaceQuadratureType faceQuadOuter(gridPart, inter, polOrd, FaceQuadratureType::OUTSIDE);
1109
1110 applyLocalNeighEstimator(nit,nb,faceQuadInner,faceQuadOuter,
1111 uLF, uNeighLf, enVol_nbVol, interiorEntity,
1112 enError, adaptation);
1113 }
1114 else
1115 {
1116 // type of quadrature for non-conforming intersections
1117 typedef typename FaceQuadratureType ::
1118 NonConformingQuadratureType NonConformingQuadratureType;
1119 // create quadratures
1120 NonConformingQuadratureType faceQuadInner(gridPart, inter, polOrd, FaceQuadratureType::INSIDE);
1121 NonConformingQuadratureType faceQuadOuter(gridPart, inter, polOrd, FaceQuadratureType::OUTSIDE);
1122
1123 applyLocalNeighEstimator(nit,nb,faceQuadInner,faceQuadOuter,
1124 uLF, uNeighLf, enVol_nbVol, interiorEntity,
1125 enError, adaptation);
1126 }
1127
1128 } // end enId < nbId
1129 } // end neighbor
1130
1131 if(enError > 0.0)
1132 {
1133 adaptation.addToLocalIndicator( en , enError );
1134 }
1135 } // end intersection iterator
1136 }
1137 }
1138
1139 private:
1140 template <class IntersectionIteratorType,
1141 class EntityType,
1142 class QuadratureType,
1143 class LocalFunctionType,
1144 class AdaptationType>
1145 static inline void applyLocalNeighEstimator(const IntersectionIteratorType& nit,
1146 const EntityType& nb,
1147 const QuadratureType& faceQuadInner,
1148 const QuadratureType& faceQuadOuter,
1149 const LocalFunctionType& uLF,
1150 const LocalFunctionType& uNeighLf,
1151 const double enVol_nbVol,
1152 const bool interiorEntity,
1153 double& enError,
1154 AdaptationType& adaptation)
1155 {
1156 const typename IntersectionIteratorType::Intersection& inter=*nit;
1157 enum { dim = GridType :: dimension };
1158 RangeType jump;
1159 RangeType neighRet;
1160 double nbError = 0.0;
1161
1162 const int quadNop = faceQuadInner.nop();
1163 for (int l = 0; l < quadNop ; ++l)
1164 {
1165 DomainType unitNormal =
1166 inter.integrationOuterNormal(faceQuadInner.localPoint(l));
1167
1168 double faceVol = unitNormal.two_norm();
1169 unitNormal *= 1.0/faceVol;
1170
1171 // in case of power != 0
1172 if(dim > 2)
1173 {
1174 const double power = (1.0/(dim-1));
1175 faceVol = pow(faceVol, power);
1176 }
1177
1178 // evaluate | (u_l * n_l) + (u_r * n_r) | = | (u_l - u_r) * n_l |
1179 uLF.evaluate(faceQuadInner[l], jump);
1180 uNeighLf.evaluate(faceQuadOuter[l], neighRet);
1181
1182 // get difference
1183 jump -= neighRet;
1184
1185 const double weight = (enVol_nbVol) * faceQuadInner.weight(l);
1186
1187 double error = weight * SQR(jump * unitNormal);
1188 error = std::abs( error );
1189
1190 enError += error;
1191 nbError += error;
1192 }
1193
1194 if( (nbError > 0.0)
1195#if HAVE_MPI
1196 // only add neihgbor on interior entities
1197 && (interiorEntity)
1198#endif
1199 )
1200 {
1201 adaptation.addToLocalIndicator( nb , nbError );
1202 }
1203 }
1204
1205 // LU decomposition of matrix (matrix and b are overwritten)
1206 //
1207 // param[inout] a Matrix that LU decomposition is calculated for
1208 // param[in] b right hand side
1209 // param[out] solution solution of linear system
1210 template <class MatrixType, class VectorType>
1211 void luSolve(MatrixType& a,
1212 VectorType& x) const
1213 {
1214 typedef typename VectorType :: field_type ctype;
1215 enum { n = VectorType :: dimension };
1216
1217 // make sure we got the right dimensions
1218 assert( a.N() == a.M() );
1219 assert( a.N() == n );
1220
1221 // pivot storage
1222 int p[ n-1 ];
1223
1224 for(int i=0; i<n-1; ++i)
1225 {
1226 // initialize
1227 p[i] = i;
1228
1229 // Pivot search
1230 ctype max_abs = 0;
1231 for(int k=i; k<n; ++k)
1232 {
1233 if ( std::abs(a[k][i]) > max_abs )
1234 {
1235 max_abs = fabs(a[k][i]);
1236 p[i] = k;
1237 }
1238 }
1239
1240 if( p[ i ] != i )
1241 {
1242 // toggle row i with row argmax=p[i]
1243 for(int j=0; j<n; ++j)
1244 {
1245 const ctype tmp = a[ p[i] ][j];
1246 a[ p[i] ][j] = a[i][j];
1247 a[i][j] = tmp;
1248 }
1249 }
1250
1251 // elimination
1252 for(int k=i+1; k<n; ++k)
1253 {
1254 const ctype lambda = a[k][i] / a[i][i];
1255 a[k][i] = lambda;
1256 for(int j=i+1; j<n; ++j) a[k][j] -= lambda * a[i][j];
1257 }
1258 }
1259
1260 // 1. x = Px_old, permutation with right hand side
1261 for(int i=0; i<n-1; ++i)
1262 {
1263 const ctype tmp = x[ i ];
1264 x[ i ] = x[ p[ i ] ];
1265 x[ p[ i ] ] = tmp;
1266 }
1267
1268 // 1. Lx = x_old, forward loesen
1269 for(int i=0; i<n; ++i)
1270 {
1271 ctype dot = 0;
1272 for(int j=0; j<i; ++j) dot += a[i][j] * x[j];
1273 x[i] -= dot;
1274 }
1275
1276 // 2. Ux = x_old, backward solve
1277 for(int i=n-1; i>=0; --i)
1278 {
1279 ctype dot = 0;
1280 for(int j=i+1; j<n; ++j) dot += a[i][j] * x[j];
1281 x[i] = (x[i] - dot) / a[i][i];
1282 }
1283 }
1284 };
1285
1286 } // namespace Fem
1287
1288} // namespace Dune
1289#endif // #ifndef DUNE_FEM_HDIV_PROJECTION_HH
BaseType::LocalFunctionType LocalFunctionType
type of local functions
Definition: discretefunction.hh:639
void assign(const DiscreteFunctionInterface< DFType > &g)
Definition: discretefunction_inline.hh:132
LocalFunctionType localFunction(const EntityType &entity)
obtain a local function for an entity (read-write)
Definition: discretefunction.hh:716
quadrature on the codim-0 reference element
Definition: elementquadrature.hh:58
FunctionSpaceTraits::RangeType RangeType
Type of range vector (using type of range field) has a Dune::FieldVector type interface.
Definition: functionspaceinterface.hh:71
FunctionSpaceTraits::RangeFieldType RangeFieldType
Intrinsic type used for values in the range field (usually a double)
Definition: functionspaceinterface.hh:63
A vector valued function space.
Definition: functionspace.hh:60
H-div Projection for discontinuous discrete functions. The projection is described in detail in:
Definition: hdivprojection.hh:62
void setTime(double)
set time for operators
Definition: hdivprojection.hh:206
double timeStepEstimate() const
estimate maximum time step
Definition: hdivprojection.hh:208
static double normalJump(const DiscreteFunctionType &discFunc, const int polyOrder=-1)
return sum of jumps of discrete function normal to intersection
Definition: hdivprojection.hh:213
const DiscreteFunctionSpaceType & space() const
return reference to space
Definition: hdivprojection.hh:202
HdivProjection(const DiscreteFunctionSpaceType &space)
constructor taking space
Definition: hdivprojection.hh:184
virtual void operator()(const DiscreteFunctionType &arg, DiscreteFunctionType &dest) const
application operator projection arg to H-div space
Definition: hdivprojection.hh:1041
Lagrange discrete function space.
Definition: space.hh:131
int numDofs() const
obtain the number of local DoFs
Definition: localfunction.hh:351
interface for time evolution operators
Definition: spaceoperatorif.hh:38
forward declaration
Definition: discretefunction.hh:51
const DiscreteFunctionSpaceType & space() const
obtain a reference to the corresponding DiscreteFunctionSpace
Definition: discretefunction.hh:709
TupleDiscreteFunctionSpace< typename DiscreteFunctions::DiscreteFunctionSpaceType ... > DiscreteFunctionSpaceType
type for the discrete function space this function lives in
Definition: discretefunction.hh:69
Implementation::JacobianInverseTransposed JacobianInverseTransposed
type of jacobian inverse transposed
Definition: geometry.hh:120
static constexpr int coorddimension
dimension of embedding coordinate system
Definition: geometry.hh:97
static constexpr int mydimension
geometry dimension
Definition: geometry.hh:94
Definition of the DUNE_NO_DEPRECATED_* macros.
GeometryType
Type representing VTK's entity geometry types.
Definition: common.hh:132
auto dot(const A &a, const B &b) -> typename std::enable_if< IsNumber< A >::value &&!IsVector< A >::value &&!std::is_same< typename FieldTraits< A >::field_type, typename FieldTraits< A >::real_type > ::value, decltype(conj(a) *b)>::type
computes the dot product for fundamental data types according to Petsc's VectDot function: dot(a,...
Definition: dotproduct.hh:42
#define DUNE_THROW(E, m)
Definition: exceptions.hh:218
@ InteriorEntity
all interior entities
Definition: gridenums.hh:31
constexpr GeometryType cube(unsigned int dim)
Returns a GeometryType representing a hypercube of dimension dim.
Definition: type.hh:462
Dune namespace.
Definition: alignedallocator.hh:13
constexpr Base power(Base m, Exponent p)
Power method for integer exponents.
Definition: math.hh:75
static const ReferenceElement & general(const GeometryType &type)
get general reference elements
Definition: referenceelements.hh:156
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.111.3 (Jul 27, 22:29, 2024)