DUNE-FEM (unstable)

galerkin.hh
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file LICENSE.md in module root
2// SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception
3// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
4// vi: set et ts=4 sw=2 sts=2:
5#ifndef DUNE_GALERKIN_HH
6#define DUNE_GALERKIN_HH
7
8#include "aggregates.hh"
9#include "pinfo.hh"
12#include <set>
13#include <limits>
14#include <algorithm>
15
16namespace Dune
17{
18 namespace Amg
19 {
31 template<class T>
32 struct OverlapVertex
33 {
37 typedef T Aggregate;
38
42 typedef T Vertex;
43
48
53 };
54
55
56
61 template<class M>
63 {
64 public:
70 SparsityBuilder(M& matrix);
71
72 void insert(const typename M::size_type& index);
73
74 void operator++();
75
76 std::size_t minRowSize();
77
78 std::size_t maxRowSize();
79
80 std::size_t sumRowSize();
81 std::size_t index()
82 {
83 return row_.index();
84 }
85 private:
87 typename M::CreateIterator row_;
89 std::size_t minRowSize_;
91 std::size_t maxRowSize_;
92 std::size_t sumRowSize_;
93#ifdef DUNE_ISTL_WITH_CHECKING
94 bool diagonalInserted;
95#endif
96 };
97
98 class BaseGalerkinProduct
99 {
100 public:
109 template<class M, class V, class I, class O>
110 void calculate(const M& fine, const AggregatesMap<V>& aggregates, M& coarse,
111 const I& pinfo, const O& copy);
112
113 };
114
115 template<class T>
116 class GalerkinProduct
117 : public BaseGalerkinProduct
118 {
119 public:
120 typedef T ParallelInformation;
121
131 template<class G, class V, class Set>
132 typename G::MutableMatrix* build(G& fineGraph, V& visitedMap,
133 const ParallelInformation& pinfo,
135 const typename G::Matrix::size_type& size,
136 const Set& copy);
137 private:
138
145 template<class G, class I, class Set>
146 const OverlapVertex<typename G::VertexDescriptor>*
147 buildOverlapVertices(const G& graph, const I& pinfo,
149 const Set& overlap,
150 std::size_t& overlapCount);
151
152 template<class A>
153 struct OVLess
154 {
155 bool operator()(const OverlapVertex<A>& o1, const OverlapVertex<A>& o2)
156 {
157 return *o1.aggregate < *o2.aggregate;
158 }
159 };
160 };
161
162 template<>
163 class GalerkinProduct<SequentialInformation>
164 : public BaseGalerkinProduct
165 {
166 public:
176 template<class G, class V, class Set>
177 typename G::MutableMatrix* build(G& fineGraph, V& visitedMap,
178 const SequentialInformation& pinfo,
179 const AggregatesMap<typename G::VertexDescriptor>& aggregates,
180 const typename G::Matrix::size_type& size,
181 const Set& copy);
182 };
183
184 struct BaseConnectivityConstructor
185 {
186 template<class R, class G, class V>
187 static void constructOverlapConnectivity(R& row, G& graph, V& visitedMap,
188 const AggregatesMap<typename G::VertexDescriptor>& aggregates,
189 const OverlapVertex<typename G::VertexDescriptor>*& seed,
190 const OverlapVertex<typename G::VertexDescriptor>* overlapEnd);
191
195 template<class R, class G, class V>
196 static void constructNonOverlapConnectivity(R& row, G& graph, V& visitedMap,
197 const AggregatesMap<typename G::VertexDescriptor>& aggregates,
198 const typename G::VertexDescriptor& seed);
199
200
204 template<class G, class S, class V>
206 {
207 public:
211 typedef G Graph;
215 typedef typename Graph::ConstEdgeIterator ConstEdgeIterator;
216
220 typedef S Set;
221
225 typedef V VisitedMap;
226
230 typedef typename Graph::VertexDescriptor Vertex;
231
239 ConnectedBuilder(const AggregatesMap<Vertex>& aggregates, Graph& graph,
240 VisitedMap& visitedMap, Set& connected);
241
246 void operator()(const ConstEdgeIterator& edge);
247
248 private:
252 const AggregatesMap<Vertex>& aggregates_;
253
254 Graph& graph_;
255
259 VisitedMap& visitedMap_;
260
264 Set& connected_;
265 };
266
267 };
268
269 template<class G, class T>
270 struct ConnectivityConstructor : public BaseConnectivityConstructor
271 {
272 typedef typename G::VertexDescriptor Vertex;
273
274 template<class V, class O, class R>
275 static void examine(G& graph,
276 V& visitedMap,
277 const T& pinfo,
278 const AggregatesMap<Vertex>& aggregates,
279 const O& overlap,
280 const OverlapVertex<Vertex>* overlapVertices,
281 const OverlapVertex<Vertex>* overlapEnd,
282 R& row);
283 };
284
285 template<class G>
286 struct ConnectivityConstructor<G,SequentialInformation> : public BaseConnectivityConstructor
287 {
288 typedef typename G::VertexDescriptor Vertex;
289
290 template<class V, class R>
291 static void examine(G& graph,
292 V& visitedMap,
293 const SequentialInformation& pinfo,
294 const AggregatesMap<Vertex>& aggregates,
295 R& row);
296 };
297
298 template<class T>
299 struct DirichletBoundarySetter
300 {
301 template<class M, class O>
302 static void set(M& coarse, const T& pinfo, const O& copy);
303 };
304
305 template<>
306 struct DirichletBoundarySetter<SequentialInformation>
307 {
308 template<class M, class O>
309 static void set(M& coarse, const SequentialInformation& pinfo, const O& copy);
310 };
311
312 template<class R, class G, class V>
313 void BaseConnectivityConstructor::constructNonOverlapConnectivity(R& row, G& graph, V& visitedMap,
315 const typename G::VertexDescriptor& seed)
316 {
317 assert(row.index()==aggregates[seed]);
318 row.insert(aggregates[seed]);
319 ConnectedBuilder<G,R,V> conBuilder(aggregates, graph, visitedMap, row);
320 typedef typename G::VertexDescriptor Vertex;
321 typedef std::allocator<Vertex> Allocator;
322 typedef SLList<Vertex,Allocator> VertexList;
323 typedef typename AggregatesMap<Vertex>::DummyEdgeVisitor DummyVisitor;
324 VertexList vlist;
325 DummyVisitor dummy;
326 aggregates.template breadthFirstSearch<true,false>(seed,aggregates[seed], graph, vlist, dummy,
327 conBuilder, visitedMap);
328 }
329
330 template<class R, class G, class V>
331 void BaseConnectivityConstructor::constructOverlapConnectivity(R& row, G& graph, V& visitedMap,
333 const OverlapVertex<typename G::VertexDescriptor>*& seed,
334 const OverlapVertex<typename G::VertexDescriptor>* overlapEnd)
335 {
336 ConnectedBuilder<G,R,V> conBuilder(aggregates, graph, visitedMap, row);
337 const typename G::VertexDescriptor aggregate=*seed->aggregate;
338
339 if (row.index()==*seed->aggregate) {
340 while(seed != overlapEnd && aggregate == *seed->aggregate) {
341 row.insert(*seed->aggregate);
342 // Walk over all neighbours and add them to the connected array.
343 visitNeighbours(graph, seed->vertex, conBuilder);
344 // Mark vertex as visited
345 put(visitedMap, seed->vertex, true);
346 ++seed;
347 }
348 }
349 }
350
351 template<class G, class S, class V>
353 Graph& graph, VisitedMap& visitedMap,
354 Set& connected)
355 : aggregates_(aggregates), graph_(graph), visitedMap_(visitedMap), connected_(connected)
356 {}
357
358 template<class G, class S, class V>
360 {
361 const Vertex& vertex = aggregates_[edge.target()];
364 connected_.insert(vertex);
365 }
366
367 template<class T>
368 template<class G, class I, class Set>
369 const OverlapVertex<typename G::VertexDescriptor>*
370 GalerkinProduct<T>::buildOverlapVertices(const G& graph, const I& pinfo,
372 const Set& overlap,
373 std::size_t& overlapCount)
374 {
375 // count the overlap vertices.
376 typedef typename G::ConstVertexIterator ConstIterator;
377 typedef typename I::GlobalLookupIndexSet GlobalLookup;
378 typedef typename GlobalLookup::IndexPair IndexPair;
379
380 const ConstIterator end = graph.end();
381 overlapCount = 0;
382
383 const GlobalLookup& lookup=pinfo.globalLookup();
384
385 for(ConstIterator vertex=graph.begin(); vertex != end; ++vertex) {
386 const IndexPair* pair = lookup.pair(*vertex);
387
388 if(pair!=0 && overlap.contains(pair->local().attribute()))
389 ++overlapCount;
390 }
391 // Allocate space
392 typedef typename G::VertexDescriptor Vertex;
393
394 OverlapVertex<Vertex>* overlapVertices = new OverlapVertex<Vertex>[overlapCount=0 ? 1 : overlapCount];
395 if(overlapCount==0)
396 return overlapVertices;
397
398 // Initialize them
399 overlapCount=0;
400 for(ConstIterator vertex=graph.begin(); vertex != end; ++vertex) {
401 const IndexPair* pair = lookup.pair(*vertex);
402
403 if(pair!=0 && overlap.contains(pair->local().attribute())) {
404 overlapVertices[overlapCount].aggregate = &aggregates[pair->local()];
405 overlapVertices[overlapCount].vertex = pair->local();
406 ++overlapCount;
407 }
408 }
409
410 dverb << overlapCount<<" overlap vertices"<<std::endl;
411
412 std::sort(overlapVertices, overlapVertices+overlapCount, OVLess<Vertex>());
413 // due to the sorting the isolated aggregates (to be skipped) are at the end.
414
415 return overlapVertices;
416 }
417
418 template<class G, class T>
419 template<class V, class O, class R>
420 void ConnectivityConstructor<G,T>::examine(G& graph,
421 V& visitedMap,
422 const T& pinfo,
423 const AggregatesMap<Vertex>& aggregates,
424 const O& overlap,
425 const OverlapVertex<Vertex>* overlapVertices,
426 const OverlapVertex<Vertex>* overlapEnd,
427 R& row)
428 {
429 typedef typename T::GlobalLookupIndexSet GlobalLookup;
430 const GlobalLookup& lookup = pinfo.globalLookup();
431
432 typedef typename G::VertexIterator VertexIterator;
433
434 VertexIterator vend=graph.end();
435
436#ifdef DUNE_ISTL_WITH_CHECKING
437 std::set<Vertex> examined;
438#endif
439
440 // The aggregates owned by the process have lower local indices
441 // then those not owned. We process them in the first pass.
442 // They represent the rows 0, 1, ..., n of the coarse matrix
443 for(VertexIterator vertex = graph.begin(); vertex != vend; ++vertex)
444 if(!get(visitedMap, *vertex)) {
445 // In the first pass we only process owner nodes
446 typedef typename GlobalLookup::IndexPair IndexPair;
447 const IndexPair* pair = lookup.pair(*vertex);
448 if(pair==0 || !overlap.contains(pair->local().attribute())) {
449#ifdef DUNE_ISTL_WITH_CHECKING
450 assert(examined.find(aggregates[*vertex])==examined.end());
451 examined.insert(aggregates[*vertex]);
452#endif
453 constructNonOverlapConnectivity(row, graph, visitedMap, aggregates, *vertex);
454
455 // only needed for ALU
456 // (ghosts with same global id as owners on the same process)
457 if (SolverCategory::category(pinfo) == static_cast<int>(SolverCategory::nonoverlapping)) {
458 if(overlapVertices != overlapEnd) {
459 if(*overlapVertices->aggregate!=AggregatesMap<Vertex>::ISOLATED) {
460 constructOverlapConnectivity(row, graph, visitedMap, aggregates, overlapVertices, overlapEnd);
461 }
462 else{
463 ++overlapVertices;
464 }
465 }
466 }
467 ++row;
468 }
469 }
470
471 dvverb<<"constructed "<<row.index()<<" non-overlapping rows"<<std::endl;
472
473 // Now come the aggregates not owned by use.
474 // They represent the rows n+1, ..., N
475 while(overlapVertices != overlapEnd)
476 if(*overlapVertices->aggregate!=AggregatesMap<Vertex>::ISOLATED) {
477
478#ifdef DUNE_ISTL_WITH_CHECKING
479 typedef typename GlobalLookup::IndexPair IndexPair;
480 const IndexPair* pair = lookup.pair(overlapVertices->vertex);
481 assert(pair!=0 && overlap.contains(pair->local().attribute()));
482 assert(examined.find(aggregates[overlapVertices->vertex])==examined.end());
483 examined.insert(aggregates[overlapVertices->vertex]);
484#endif
485 constructOverlapConnectivity(row, graph, visitedMap, aggregates, overlapVertices, overlapEnd);
486 ++row;
487 }else{
488 ++overlapVertices;
489 }
490 }
491
492 template<class G>
493 template<class V, class R>
494 void ConnectivityConstructor<G,SequentialInformation>::examine(G& graph,
495 V& visitedMap,
496 [[maybe_unused]] const SequentialInformation& pinfo,
497 const AggregatesMap<Vertex>& aggregates,
498 R& row)
499 {
500 typedef typename G::VertexIterator VertexIterator;
501
502 VertexIterator vend=graph.end();
503 for(VertexIterator vertex = graph.begin(); vertex != vend; ++vertex) {
504 if(!get(visitedMap, *vertex)) {
505 constructNonOverlapConnectivity(row, graph, visitedMap, aggregates, *vertex);
506 ++row;
507 }
508 }
509
510 }
511
512 template<class M>
514 : row_(matrix.createbegin()),
515 minRowSize_(std::numeric_limits<std::size_t>::max()),
516 maxRowSize_(0), sumRowSize_(0)
517 {
518#ifdef DUNE_ISTL_WITH_CHECKING
519 diagonalInserted = false;
520#endif
521 }
522 template<class M>
524 {
525 return maxRowSize_;
526 }
527 template<class M>
528 std::size_t SparsityBuilder<M>::minRowSize()
529 {
530 return minRowSize_;
531 }
532
533 template<class M>
534 std::size_t SparsityBuilder<M>::sumRowSize()
535 {
536 return sumRowSize_;
537 }
538 template<class M>
539 void SparsityBuilder<M>::operator++()
540 {
541 sumRowSize_ += row_.size();
542 minRowSize_=std::min(minRowSize_, row_.size());
543 maxRowSize_=std::max(maxRowSize_, row_.size());
544 ++row_;
545#ifdef DUNE_ISTL_WITH_CHECKING
546 assert(diagonalInserted);
547 diagonalInserted = false;
548#endif
549 }
550
551 template<class M>
552 void SparsityBuilder<M>::insert(const typename M::size_type& index)
553 {
554 row_.insert(index);
555#ifdef DUNE_ISTL_WITH_CHECKING
556 diagonalInserted = diagonalInserted || row_.index()==index;
557#endif
558 }
559
560 template<class T>
561 template<class G, class V, class Set>
562 typename G::MutableMatrix*
563 GalerkinProduct<T>::build(G& fineGraph, V& visitedMap,
564 const ParallelInformation& pinfo,
566 const typename G::Matrix::size_type& size,
567 const Set& overlap)
568 {
569 typedef OverlapVertex<typename G::VertexDescriptor> OverlapVertex;
570
571 std::size_t count;
572
573 const OverlapVertex* overlapVertices = buildOverlapVertices(fineGraph,
574 pinfo,
575 aggregates,
576 overlap,
577 count);
578 typedef typename G::MutableMatrix M;
579 M* coarseMatrix = new M(size, size, M::row_wise);
580
581 // Reset the visited flags of all vertices.
582 // As the isolated nodes will be skipped we simply mark them as visited
583
584 typedef typename G::VertexIterator Vertex;
585 Vertex vend = fineGraph.end();
586 for(Vertex vertex = fineGraph.begin(); vertex != vend; ++vertex) {
589 }
590
591 typedef typename G::MutableMatrix M;
592 SparsityBuilder<M> sparsityBuilder(*coarseMatrix);
593
594 ConnectivityConstructor<G,T>::examine(fineGraph, visitedMap, pinfo,
595 aggregates, overlap,
596 overlapVertices,
597 overlapVertices+count,
598 sparsityBuilder);
599
600 dinfo<<pinfo.communicator().rank()<<": Matrix ("<<coarseMatrix->N()<<"x"<<coarseMatrix->M()<<" row: min="<<sparsityBuilder.minRowSize()<<" max="
601 <<sparsityBuilder.maxRowSize()<<" avg="
602 <<static_cast<double>(sparsityBuilder.sumRowSize())/coarseMatrix->N()
603 <<std::endl;
604
605 delete[] overlapVertices;
606
607 return coarseMatrix;
608 }
609
610 template<class G, class V, class Set>
611 typename G::MutableMatrix*
612 GalerkinProduct<SequentialInformation>::build(G& fineGraph, V& visitedMap,
613 const SequentialInformation& pinfo,
615 const typename G::Matrix::size_type& size,
616 [[maybe_unused]] const Set& overlap)
617 {
618 typedef typename G::MutableMatrix M;
619 M* coarseMatrix = new M(size, size, M::row_wise);
620
621 // Reset the visited flags of all vertices.
622 // As the isolated nodes will be skipped we simply mark them as visited
623
624 typedef typename G::VertexIterator Vertex;
625 Vertex vend = fineGraph.end();
626 for(Vertex vertex = fineGraph.begin(); vertex != vend; ++vertex) {
629 }
630
631 SparsityBuilder<M> sparsityBuilder(*coarseMatrix);
632
633 ConnectivityConstructor<G,SequentialInformation>::examine(fineGraph, visitedMap, pinfo,
634 aggregates, sparsityBuilder);
635 dinfo<<"Matrix row: min="<<sparsityBuilder.minRowSize()<<" max="
636 <<sparsityBuilder.maxRowSize()<<" average="
637 <<static_cast<double>(sparsityBuilder.sumRowSize())/coarseMatrix->N()<<std::endl;
638 return coarseMatrix;
639 }
640
641 template<class M, class V, class P, class O>
642 void BaseGalerkinProduct::calculate(const M& fine, const AggregatesMap<V>& aggregates, M& coarse,
643 const P& pinfo, [[maybe_unused]] const O& copy)
644 {
645 coarse = static_cast<typename M::field_type>(0);
646
647 typedef typename M::ConstIterator RowIterator;
648 RowIterator endRow = fine.end();
649
650 for(RowIterator row = fine.begin(); row != endRow; ++row)
651 if(aggregates[row.index()] != AggregatesMap<V>::ISOLATED) {
652 assert(aggregates[row.index()]!=AggregatesMap<V>::UNAGGREGATED);
653 typedef typename M::ConstColIterator ColIterator;
654 ColIterator endCol = row->end();
655
656 for(ColIterator col = row->begin(); col != endCol; ++col)
657 if(aggregates[col.index()] != AggregatesMap<V>::ISOLATED) {
658 assert(aggregates[row.index()]!=AggregatesMap<V>::UNAGGREGATED);
659 coarse[aggregates[row.index()]][aggregates[col.index()]]+=*col;
660 }
661 }
662
663 // get the right diagonal matrix values on copy lines from owner processes
664 typedef typename M::block_type BlockType;
665 std::vector<BlockType> rowsize(coarse.N(),BlockType(0));
666 for (RowIterator row = coarse.begin(); row != coarse.end(); ++row)
667 rowsize[row.index()]=coarse[row.index()][row.index()];
668 pinfo.copyOwnerToAll(rowsize,rowsize);
669 for (RowIterator row = coarse.begin(); row != coarse.end(); ++row)
670 coarse[row.index()][row.index()] = rowsize[row.index()];
671
672 // don't set dirichlet boundaries for copy lines to make novlp case work,
673 // the preconditioner yields slightly different results now.
674
675 // Set the dirichlet border
676 //DirichletBoundarySetter<P>::template set<M>(coarse, pinfo, copy);
677
678 }
679
680 template<class T>
681 template<class M, class O>
682 void DirichletBoundarySetter<T>::set(M& coarse, const T& pinfo, const O& copy)
683 {
684 typedef typename T::ParallelIndexSet::const_iterator ConstIterator;
685 ConstIterator end = pinfo.indexSet().end();
686 typedef typename M::block_type Block;
687 Block identity=Block(0.0);
688 for(typename Block::RowIterator b=identity.begin(); b != identity.end(); ++b)
689 b->operator[](b.index())=1.0;
690
691 for(ConstIterator index = pinfo.indexSet().begin();
692 index != end; ++index) {
693 if(copy.contains(index->local().attribute())) {
694 typedef typename M::ColIterator ColIterator;
695 typedef typename M::row_type Row;
696 Row row = coarse[index->local()];
697 ColIterator cend = row.find(index->local());
698 ColIterator col = row.begin();
699 for(; col != cend; ++col)
700 *col = 0;
701
702 cend = row.end();
703
704 assert(col != cend); // There should be a diagonal entry
705 *col = identity;
706
707 for(++col; col != cend; ++col)
708 *col = 0;
709 }
710 }
711 }
712
713 template<class M, class O>
714 void DirichletBoundarySetter<SequentialInformation>::set(M& coarse,
715 const SequentialInformation& pinfo,
716 const O& overlap)
717 {}
718
719 } // namespace Amg
720} // namespace Dune
721#endif
Provides classes for the Coloring process of AMG.
Class providing information about the mapping of the vertices onto aggregates.
Definition: aggregates.hh:560
Visitor for identifying connected aggregates during a breadthFirstSearch.
Definition: galerkin.hh:206
Functor for building the sparsity pattern of the matrix using examineConnectivity.
Definition: galerkin.hh:63
A pair consisting of a global and local index.
Definition: indexset.hh:85
A single linked list.
Definition: sllist.hh:44
Classes for building sets out of enumeration values.
LocalIndex & local()
Get the local index.
constexpr GeometryType vertex
GeometryType representing a vertex.
Definition: type.hh:492
constexpr auto max
Function object that returns the greater of the given values.
Definition: hybridutilities.hh:484
G Graph
The type of the graph.
Definition: galerkin.hh:211
void operator()(const ConstEdgeIterator &edge)
Process an edge pointing to another aggregate.
Definition: galerkin.hh:359
T Aggregate
The aggregate descriptor.
Definition: galerkin.hh:37
SparsityBuilder(M &matrix)
Constructor.
Definition: galerkin.hh:513
ConnectedBuilder(const AggregatesMap< Vertex > &aggregates, Graph &graph, VisitedMap &visitedMap, Set &connected)
Constructor.
Definition: galerkin.hh:352
Graph::ConstEdgeIterator ConstEdgeIterator
The constant edge iterator.
Definition: galerkin.hh:215
T Vertex
The vertex descriptor.
Definition: galerkin.hh:42
V VisitedMap
The type of the map for marking vertices as visited.
Definition: galerkin.hh:225
int visitNeighbours(const G &graph, const typename G::VertexDescriptor &vertex, V &visitor)
Visit all neighbour vertices of a vertex in a graph.
static const Vertex ISOLATED
Identifier of isolated vertices.
Definition: aggregates.hh:571
S Set
The type of the connected set.
Definition: galerkin.hh:220
Aggregate * aggregate
The aggregate the vertex belongs to.
Definition: galerkin.hh:47
Vertex vertex
The vertex descriptor.
Definition: galerkin.hh:52
Graph::VertexDescriptor Vertex
The vertex descriptor of the graph.
Definition: galerkin.hh:230
void calculate(const M &fine, const AggregatesMap< V > &aggregates, M &coarse, const I &pinfo, const O &copy)
Calculate the galerkin product.
DVVerbType dvverb(std::cout)
stream for very verbose output.
Definition: stdstreams.hh:96
DInfoType dinfo(std::cout)
Stream for informative output.
Definition: stdstreams.hh:141
DVerbType dverb(std::cout)
Singleton of verbose debug stream.
Definition: stdstreams.hh:117
Dune namespace.
Definition: alignedallocator.hh:13
constexpr std::integral_constant< std::size_t, sizeof...(II)> size(std::integer_sequence< T, II... >)
Return the size of the sequence.
Definition: integersequence.hh:75
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
STL namespace.
An stl-compliant pool allocator.
@ nonoverlapping
Category for non-overlapping solvers.
Definition: solvercategory.hh:27
static Category category(const OP &op, decltype(op.category()) *=nullptr)
Helperfunction to extract the solver category either from an enum, or from the newly introduced virtu...
Definition: solvercategory.hh:34
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.111.3 (Nov 12, 23:30, 2024)