DUNE-FEM (unstable)

blockmapper.hh
1 #ifndef DUNE_FEM_HPDG_SPACE_DISCONTINUOUSGALERKIN_BLOCKMAPPER_HH
2 #define DUNE_FEM_HPDG_SPACE_DISCONTINUOUSGALERKIN_BLOCKMAPPER_HH
3 
4 #include <cassert>
5 #include <cstddef>
6 
7 #include <algorithm>
8 #include <functional>
9 #include <limits>
10 #include <type_traits>
11 #include <utility>
12 #include <vector>
13 
15 
16 #include <dune/geometry/dimension.hh>
17 
18 #include <dune/grid/common/gridenums.hh>
19 #include <dune/grid/utility/persistentcontainer.hh>
20 
21 #include <dune/fem/gridpart/common/gridpart.hh>
22 #include <dune/fem/io/streams/streams.hh>
23 #include <dune/fem/space/common/dofmanager.hh>
24 #include <dune/fem/space/mapper/dofmapper.hh>
25 
26 #include <dune/fem/space/common/capabilities.hh>
27 
28 #include "datahandle.hh"
29 #include "localdofstorage.hh"
30 
31 namespace Dune
32 {
33 
34  namespace Fem
35  {
36 
37  namespace hpDG
38  {
39 
40  // Internal forward declaration
41  // ----------------------------
42 
43  template< class GridPart, class LocalKeys >
44  struct DiscontinuousGalerkinBlockMapper;
45 
46 
47 
48 #ifndef DOXYGEN
49 
50  // DiscontinuousGalerkinBlockMapperTraits
51  // --------------------------------------
52 
53  template< class GridPart, class LocalKeys >
54  struct DiscontinuousGalerkinBlockMapperTraits
55  {
56  using DofMapperType = DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys >;
57  using ElementType = typename GridPart::template Codim< 0 >::EntityType;
58  using SizeType = std::size_t;
59  };
60 
61 #endif // #ifndef DOXYGEN
62 
63 
64 
65  // DiscontinuousGalerkinBlockMapper
66  // --------------------------------
67 
99  template< class GridPart, class LocalKeys >
101  : public AdaptiveDofMapper< DiscontinuousGalerkinBlockMapperTraits< GridPart, LocalKeys > >
102  {
105 
106  public:
108  using SizeType = typename BaseType::SizeType;
111 
113  using GridPartType = GridPart;
116 
118  using LocalKeysType = LocalKeys;
120  using KeyType = typename LocalKeysType::KeyType;
121 
122  private:
123  using DataHandleType = DataHandle< ThisType >;
124  friend class DataHandle< DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys > >;
125 
126  struct Reserve;
127  struct Resize;
128 
129  using LocalDofStorageType = LocalDofStorage< GlobalKeyType >;
130 
131  using GridType = typename GridPartType::GridType;
132  using GridElementType = typename GridType::template Codim< 0 >::Entity;
133 
134  public:
137 
142  template< class Function >
144  const LocalKeysType &localKeys,
145  const KeyType &value, Function function )
146  : gridPart_( gridPart ),
147  localKeys_( localKeys ),
148  key_( value ),
149  keys_( gridPart_.get().grid(), 0, std::make_pair( key_, key_ ) ),
150  size_( 0u ),
151  dofs_( gridPart_.get().grid(), 0 ),
152  dofManager_( DofManagerType::instance( gridPart.grid() ) )
153  {
154  auto first = gridPart.template begin< 0, All_Partition >();
155  auto last = gridPart.template end< 0, All_Partition >();
156  for( ; first != last; ++first )
157  {
158  const ElementType &element = *first;
159  const KeyType key = function( element );
160 
161  const GridElementType &gridElement = gridEntity( element );
162  keys_[ gridElement ] = std::make_pair( key, key );
163 
164  auto &dofs = dofs_[ gridElement ];
165  resize( dofs, localKeys.blocks( gridElement.type(), key ) );
166  }
167 
168  dofManager_.get().addIndexSet( *this );
169  }
170 
171  DiscontinuousGalerkinBlockMapper ( const GridPartType &gridPart,
172  const LocalKeysType &localKeys,
173  const KeyType &value )
174  : DiscontinuousGalerkinBlockMapper( gridPart, localKeys, value, [&value]( const ElementType & ){ return value; } )
175  {}
176 
184  DiscontinuousGalerkinBlockMapper ( const ThisType & ) = delete;
185 
187  DiscontinuousGalerkinBlockMapper ( ThisType && ) = default;
188 
190  ThisType &operator= ( const ThisType & ) = delete;
191 
193  ThisType &operator= ( ThisType && ) = default;
194 
197 #ifndef DOXYGEN
198 
200  {
201  dofManager_.get().removeIndexSet( *this );
202  }
203 
204 #endif // #ifndef DOXYGEN
205 
211  SizeType size () const { return size_; }
212 
214  int maxNumDofs () const { return localKeys().maxBlocks(); }
215 
217  SizeType numDofs ( const ElementType &element ) const
218  {
219  return numDofs( element, Codim< ElementType::codimension >() );
220  }
221 
223  template< class Entity >
224  SizeType numEntityDofs ( const Entity &entity ) const
225  {
226  return numDofs( entity, Codim< Entity::codimension >() );
227  }
228 
229  [[deprecated("Use onSubEntity method with char vector instead")]]
230  void onSubEntity ( const ElementType &element, int i, int c, std::vector< bool > &indices ) const
231  {
232  std::vector< char > _idx;
233  onSubEntity(element, i, c, _idx);
234  indices.resize( _idx.size() );
235  for (std::size_t i=0; i<_idx.size();++i)
236  _idx[i] = indices[i] > 0;
237  }
238  // this method returns which local dofs are attached to the given subentity.
239  // indices[locDofNr] =
240  // 0 : not attached, not equal to 0 : attached
241  // (so this method can still be used in the way the deprecated method was).
242  // New: In case the dof can be associated to a component of the
243  // space, the value returned is that component+1. In other
244  // cases (normal velocity for RT for example) the value is -1).
245  // So indices[i] is in [-1,dimRange+1]
246  void onSubEntity ( const ElementType &element, int i, int c, std::vector< char > &indices ) const
247  {
248  indices.resize( numDofs(element) );
249  if (c == 0)
250  std::fill(indices.begin(),indices.end(),1);
251  else
252  std::fill(indices.begin(),indices.end(),0);
253  }
254 
256  static constexpr bool contains ( const int codim ) { return (codim == 0); }
257 
259  static constexpr bool fixedDataSize ( int codim ) { return (codim != 0); }
260 
262  template< class Function >
263  void mapEach ( const ElementType &element, Function function ) const
264  {
265  mapEach( element, function, Codim< ElementType::codimension >() );
266  }
267 
269  template< class Entity, class Function >
270  void mapEachEntityDof ( const Entity &entity, Function function ) const
271  {
272  mapEach( entity, function, Codim< Entity::codimension >() );
273  }
274 
276  SizeType numberOfHoles ( const int block ) const
277  {
278  assert( block == 0 );
279  return indices_.size();
280  }
281 
283  GlobalKeyType oldIndex ( const int hole, const int block ) const
284  {
285  assert( block == 0 );
286  GlobalKeyType dof = indices_[ hole ].first;
288  return std::move( dof );
289  }
290 
292  GlobalKeyType newIndex ( const int hole, const int block ) const
293  {
294  assert( block == 0 );
295  return indices_[ hole ].second;
296  }
297 
299  static constexpr bool consecutive () { return true; }
300 
302  static constexpr SizeType oldOffSet ( const int block ) { return 0u; }
303 
305  static constexpr SizeType offSet ( const int block ) { return 0u; }
306 
308  static constexpr SizeType numBlocks () { return 1u; }
309 
317  template< class Element >
318  typename std::enable_if< (std::is_same< Element, ElementType >::value || std::is_same< Element, GridElementType >::value), const KeyType & >::type
319  key ( const Element &element ) const
320  {
321  return keys_[ gridEntity( element ) ].first;
322  }
323 
325  void mark ( const KeyType &key, const ElementType &element )
326  {
327  assert( gridPart().indexSet().contains( element ) );
328  keys_[ gridEntity( element ) ].second = key;
329  }
330 
332  KeyType getMark ( const ElementType &element ) const
333  {
334  assert( gridPart().indexSet().contains( element ) );
335  return keys_[ gridEntity( element ) ].second;
336  }
337 
339  template< class Function >
340  bool adapt ( Function function );
341 
343  bool adapt ()
344  {
345  auto function = []( const ElementType &,
346  const KeyType &, const KeyType &,
347  const std::vector< std::size_t > &,
348  const std::vector< std::size_t > & )
349  {};
350  return adapt( function );
351  }
352 
360  const DofManagerType &dofManager () const { return dofManager_.get(); }
361 
363  void insertEntity ( const GridElementType &gridElement )
364  {
365  resize( dofs_ );
366  resize( keys_, std::make_pair( key_, key_ ) );
367 
368  resize( dofs_[ gridElement ], localKeys().blocks( gridElement.type(), key( gridElement ) ) );
369  }
370 
372  void removeEntity ( const GridElementType &gridElement )
373  {
374  auto &dofs = dofs_[ gridElement ];
375 
376  size_ = dofs.reserve( 0u, Reserve( size_ ) );
377  Resize function( holes_ );
378  for( auto dof : dofs )
379  function( dof );
380  }
381 
383  void insertNewEntity ( const GridElementType &gridElement )
384  {
385  if( gridElement.isNew() )
386  {
387  resize( dofs_[ gridElement ], localKeys().blocks( gridElement.type(), key( gridElement ) ) );
388  assert( gridElement.hasFather() );
389  const GridElementType &father = gridElement.father();
390  insertNewEntity( father );
391  removeEntity( father );
392  }
393  else
394  {
395  auto &dofs = dofs_[ gridElement ];
396  resize( dofs, localKeys().blocks( gridElement.type(), key( gridElement ) ) );
397  for( auto dof : dofs )
398  {
399  auto iterator = std::lower_bound( holes_.begin(), holes_.end(), dof );
400  if( iterator != holes_.end() && *iterator == dof )
401  holes_.erase( iterator );
402  }
403  }
404  }
405 
406  void resize ()
407  {
408  resize( dofs_ );
409  resize( keys_, std::make_pair( key_, key_ ) );
410 
411  auto first = gridPart().template begin< 0, All_Partition >();
412  auto last = gridPart().template end< 0, All_Partition >();
413  for( ; first != last; ++first )
414  {
415  const ElementType &element = *first;
416  insertNewEntity( gridEntity( element ) );
417  }
418 #if 0
419  auto &dofs = dofs_[ gridEntity( element ) ];
420  resize( dofs, localKeys().blocks( element.type(), key( element ) ) );
421 #endif
422  }
423 
425  bool compress ();
426 
428  template< class Traits >
430  {
431  DUNE_THROW( NotImplemented, "Method write() not implemented yet" );
432  }
433 
435  template< class Traits >
437  {
438  DUNE_THROW( NotImplemented, "Method read() not implemented yet" );
439  }
440 
442  void backup () const
443  {
444  DUNE_THROW( NotImplemented, "Method backup() not implemented" );
445  }
446 
448  void restore ()
449  {
450  DUNE_THROW( NotImplemented, "Method restore() not implemented" );
451  }
452 
453  private:
454  bool compressed () const { return holes_.empty(); }
455 
456  template< class Element, class Function >
457  void mapEach ( const Element &element, Function function, Codim< 0 > ) const
458  {
459  const auto &dofs = dofs_[ gridEntity( element ) ];
460  std::size_t size = dofs.size();
461  for( std::size_t i = 0; i < size; ++i )
462  function( i, dofs[ i ] );
463  }
464 
465  template< class Entity, class Function, int codim >
466  void mapEach ( const Entity &entity, Function function, Codim< codim > ) const
467  {}
468 
469  template< class Element >
470  SizeType numDofs ( const Element &element, Codim< 0 > ) const
471  {
472  return dofs_[ gridEntity( element ) ].size();
473  }
474 
475  template< class Entity, int codim >
476  SizeType numDofs ( const Entity &entity, Codim< codim > ) const
477  {
478  return 0u;
479  }
480 
481  void resize ( LocalDofStorageType &dofs, SizeType n )
482  {
483  size_ = dofs.reserve( n, Reserve( size_ ) );
484  dofs.resize( Resize( holes_ ) );
485  }
486 
487  template< class T >
488  static void resize ( PersistentContainer< GridType, T > &container, const T &value = T() )
489  {
490  container.resize( value );
491  container.shrinkToFit();
492  }
493 
494  const GridPartType &gridPart () const { return gridPart_.get(); }
495 
496  const LocalKeysType &localKeys () const { return localKeys_.get(); }
497 
498  std::reference_wrapper< const GridPartType > gridPart_;
499  std::reference_wrapper< const LocalKeysType > localKeys_;
500 
501  const KeyType key_;
502  PersistentContainer< GridType, std::pair< KeyType, KeyType > > keys_;
503 
504  SizeType size_;
506 
507  std::vector< GlobalKeyType > holes_;
508  std::vector< std::pair< GlobalKeyType, GlobalKeyType > > indices_;
509 
510  std::reference_wrapper< DofManagerType > dofManager_;
511  };
512 
513 
514 
515  // DiscontinuousGalerkinBlockMapper::Reserve
516  // -----------------------------------------
517 
518  template< class GridPart, class LocalKeys >
519  struct DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys >::Reserve
520  {
521  explicit Reserve ( SizeType &size ) : size_( size ) {}
522 
523  GlobalKeyType operator() () { return size_++; }
524 
525  operator SizeType () const { return size_; }
526 
527  private:
528  SizeType &size_;
529  };
530 
531 
532 
533  // DiscontinuousGalerkinBlockMapper::Resize
534  // ----------------------------------------
535 
536  template< class GridPart, class LocalKeys >
537  struct DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys >::Resize
538  {
539  explicit Resize ( std::vector< GlobalKeyType > &holes )
540  : holes_( holes )
541  {
542  assert( std::is_sorted( holes_.begin(), holes_.end() ) );
543  }
544 
545  ~Resize ()
546  {
547  assert( std::is_sorted( holes_.begin(), holes_.end() ) );
548  }
549 
550  void operator() ( const GlobalKeyType &dof )
551  {
552  auto iterator = std::lower_bound( holes_.begin(), holes_.end(), dof );
553  if( iterator == holes_.end() || *iterator != dof )
554  holes_.insert( iterator, dof );
555  }
556 
557  private:
558  std::vector< GlobalKeyType > &holes_;
559  };
560 
561 
562 
563  // DiscontinuousGalerkinBlockMapper::adapt
564  // ---------------------------------------
565 
566  template< class GridPart, class LocalKeys >
567  template< class Function >
569  {
570  DataHandleType dataHandle( *this );
571  gridPart().communicate( dataHandle, InteriorBorder_All_Interface, ForwardCommunication );
572 
573  std::vector< std::size_t > origin, destination;
574  origin.reserve( maxNumDofs() );
575  destination.reserve( maxNumDofs() );
576 
577  std::size_t count = 0;
578 
579  auto first = gridPart().template begin< 0, All_Partition >();
580  auto last = gridPart().template end< 0, All_Partition >();
581  for( ; first != last; ++first )
582  {
583  const ElementType &element = *first;
584  const GridElementType &gridElement = gridEntity( element );
585  auto &keys = keys_[ gridElement ];
586  if( keys.first == keys.second )
587  continue;
588 
589  // remember old state
590  const KeyType prior = keys.first;
591  auto &dofs = dofs_[ gridElement ];
592  origin.resize( dofs.size() );
593  std::copy( dofs.begin(), dofs.end(), origin.begin() );
594 
595  // reset to new state
596  keys.first = keys.second;
597  resize( dofs, localKeys().blocks( gridElement.type(), keys.first ) );
598 
599  // call function
600  destination.resize( dofs.size() );
601  std::copy( dofs.begin(), dofs.end(), destination.begin() );
602  function( element, prior, keys.first, origin, destination );
603 
604  ++count;
605  }
606  return (count > 0u);
607  }
608 
609 
610 
611  // DiscontinuousGalerkinBlockMapper::compress
612  // ------------------------------------------
613 
614  template< class GridPart, class LocalKeys >
616  {
617  // resize persistent containers
618  resize( dofs_ );
619  resize( keys_, std::make_pair( key_, key_ ) );
620 
621  for( auto &dofs : dofs_ )
622  dofs.resize( Resize( holes_ ) );
623 
624  // check if compress is necessary
625  if( holes_.empty() )
626  {
627  indices_.clear();
628  return false;
629  }
630 
631  // get number of dofs after compress
632  assert( size_ >= holes_.size() );
633  size_ -= holes_.size();
634 
635  // remove trailing indices
636  auto iterator = std::lower_bound( holes_.begin(), holes_.end(), size_ );
637  holes_.erase( iterator, holes_.end() );
638 
639  // initialize vector of old and new indices
640  auto assign = []( GlobalKeyType newIndex ) -> std::pair< GlobalKeyType, GlobalKeyType >
641  {
643  return std::make_pair( oldIndex, newIndex );
644  };
645  indices_.resize( holes_.size() );
646  std::transform( holes_.begin(), holes_.end(), indices_.begin(), assign );
647 
648  // update local dof storages
649  std::size_t hole = 0u;
650  for( auto &dofs : dofs_ )
651  {
652  for( auto &dof : dofs )
653  {
654  if( dof >= size_ )
655  {
656  auto &indices = indices_.at( hole++ );
657  indices.first = dof;
658  dof = indices.second;
659  }
660  }
661  }
662 
663  assert( hole == holes_.size() );
664  holes_.clear();
665 
666  // replace all but markers currently used by default value
667  PersistentContainer< GridType, std::pair< KeyType, KeyType > > tmp( gridPart().grid(), 0, std::make_pair( key_, key_ ) );
668  auto first = gridPart().template begin< 0, All_Partition >();
669  auto last = gridPart().template end< 0, All_Partition >();
670  for( ; first != last; ++first )
671  {
672  const ElementType &element = *first;
673  const GridElementType &gridElement = gridEntity( element );
674  tmp[ gridElement ] = keys_[ gridElement ];
675  }
676  keys_.swap( tmp );
677 
678  return true;
679  }
680 
681  } // namespace hpDG
682 
683  namespace Capabilities
684  {
685  // isConsecutiveIndexSet
686  // ---------------------
687 
688  template< class GridPart, class LocalKeys >
689  struct isConsecutiveIndexSet< hpDG::DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys > >
690  {
691  static const bool v = true;
692  };
693 
694  // isAdaptiveDofMapper
695  // -------------------
696 
697  template< class GridPart, class LocalKeys >
698  struct isAdaptiveDofMapper< hpDG::DiscontinuousGalerkinBlockMapper< GridPart, LocalKeys > >
699  {
700  static const bool v = true;
701  };
702 
703  } // namespace Capabilities
704 
705  } // namespace Fem
706 
707 } // namespace Dune
708 
709 #endif // #ifndef DUNE_FEM_HPDG_SPACE_DISCONTINUOUSGALERKIN_BLOCKMAPPER_HH
Wrapper class for entities.
Definition: entity.hh:66
Extended interface for adaptive DoF mappers.
Definition: dofmapper.hh:219
SizeType GlobalKeyType
at the moment this should be similar to SizeType
Definition: dofmapper.hh:230
BaseType::SizeType SizeType
type of size integer
Definition: dofmapper.hh:227
Definition: dofmanager.hh:786
Traits::ElementType ElementType
type of codimension 0 entities
Definition: dofmapper.hh:54
Abstract class representing a function.
Definition: function.hh:50
abstract interface for an input stream
Definition: streams.hh:190
abstract interface for an output stream
Definition: streams.hh:48
Default exception for dummy implementations.
Definition: exceptions.hh:263
A few common exception classes.
#define DUNE_THROW(E, m)
Definition: exceptions.hh:218
@ ForwardCommunication
communicate as given in InterfaceType
Definition: gridenums.hh:171
@ InteriorBorder_All_Interface
send interior and border, receive all entities
Definition: gridenums.hh:88
concept Entity
Model of a grid entity.
Definition: entity.hh:107
constexpr auto max
Function object that returns the greater of the given values.
Definition: hybridutilities.hh:484
Dune namespace.
Definition: alignedallocator.hh:13
void assign(T &dst, const T &src, bool mask)
masked Simd assignment (scalar version)
Definition: simd.hh:447
constexpr auto get(std::integer_sequence< T, II... >, std::integral_constant< std::size_t, pos >={})
Return the entry at position pos of the given sequence.
Definition: integersequence.hh:22
Static tag representing a codimension.
Definition: dimension.hh:24
An -adaptive Dune::Fem::DofMapper.
Definition: blockmapper.hh:102
void backup() const
this mapper has no I/O capabilities
Definition: blockmapper.hh:442
static constexpr SizeType oldOffSet(const int block)
return 0 (this mapper has no offset)
Definition: blockmapper.hh:302
SizeType size() const
return number of dofs
Definition: blockmapper.hh:211
GlobalKeyType newIndex(const int hole, const int block) const
return new index of given hole during compression
Definition: blockmapper.hh:292
typename LocalKeysType::KeyType KeyType
key type
Definition: blockmapper.hh:120
static constexpr bool consecutive()
return true (this mapper yields a consecutive DOF numbering)
Definition: blockmapper.hh:299
GridPart GridPartType
grid part type
Definition: blockmapper.hh:113
void write(OutStreamInterface< Traits > &)
this mapper has no I/O capabilities
Definition: blockmapper.hh:429
void mapEachEntityDof(const Entity &entity, Function function) const
map local dof to global key
Definition: blockmapper.hh:270
DiscontinuousGalerkinBlockMapper(const ThisType &)=delete
copy constructor
void mapEach(const ElementType &element, Function function) const
map local dof to global key
Definition: blockmapper.hh:263
LocalKeys LocalKeysType
basis function sets type
Definition: blockmapper.hh:118
ThisType & operator=(const ThisType &)=delete
assignment operator
SizeType numEntityDofs(const Entity &entity) const
return number of dofs for given element
Definition: blockmapper.hh:224
typename BaseType::ElementType ElementType
element type
Definition: blockmapper.hh:115
void insertNewEntity(const GridElementType &gridElement)
add DOFs for new element
Definition: blockmapper.hh:383
bool adapt()
please doc me
Definition: blockmapper.hh:343
void insertEntity(const GridElementType &gridElement)
add DOFs for element
Definition: blockmapper.hh:363
static constexpr bool fixedDataSize(int codim)
return true if number of dofs is fixed for given codimension
Definition: blockmapper.hh:259
const DofManagerType & dofManager() const
return DOF manager
Definition: blockmapper.hh:360
KeyType getMark(const ElementType &element) const
get key to be assigned to an entity after next call to adapt()
Definition: blockmapper.hh:332
bool compress()
compress DOF mapping
Definition: blockmapper.hh:615
DiscontinuousGalerkinBlockMapper(ThisType &&)=default
move constructor
typename BaseType::GlobalKeyType GlobalKeyType
global key type
Definition: blockmapper.hh:110
void read(InStreamInterface< Traits > &)
this mapper has no I/O capabilities
Definition: blockmapper.hh:436
SizeType numDofs(const ElementType &element) const
return number of dofs for given element
Definition: blockmapper.hh:217
int maxNumDofs() const
return upper bound for number of dofs
Definition: blockmapper.hh:214
SizeType numberOfHoles(const int block) const
return number of holes during compression
Definition: blockmapper.hh:276
static constexpr bool contains(const int codim)
return true if dofs are associated to codimension
Definition: blockmapper.hh:256
void removeEntity(const GridElementType &gridElement)
mark DOFs for removal
Definition: blockmapper.hh:372
std::enable_if<(std::is_same< Element, ElementType >::value||std::is_same< Element, GridElementType >::value), const KeyType & >::type key(const Element &element) const
get key currently assigned to an entity
Definition: blockmapper.hh:319
GlobalKeyType oldIndex(const int hole, const int block) const
return old index of given hole during compression
Definition: blockmapper.hh:283
static constexpr SizeType offSet(const int block)
return 0 (this mapper has no offset)
Definition: blockmapper.hh:305
void restore()
this mapper has no I/O capabilities
Definition: blockmapper.hh:448
void mark(const KeyType &key, const ElementType &element)
set key to be assigned to an entity after next call to adapt()
Definition: blockmapper.hh:325
static constexpr SizeType numBlocks()
return 1 (this mapper has one block)
Definition: blockmapper.hh:308
typename BaseType::SizeType SizeType
size type
Definition: blockmapper.hh:108
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.80.0 (May 16, 22:29, 2024)