Adaptation
Summary
In computations using AMDiS the underlying grid can be coarsened and refined according to the needs of the simulation. This process consists of the following steps:
- elements that need to be refined or coarsened are marked,
preAdapt
prepares the grid for changes,adapt
realizes the changes according to the markings,postAdapt
performs some cleanup operations.
During this process the global basis needs to be updated since the number of grid elements and their layout changes and therefore the global indices may become invalid. This in turn means that data attached to the grid may lose its meaning and must be properly transferred from the grid before adaptation to its new version.
Markers
In the marking phase you have the option to call any number of markers and also set marks on elements manually. Note that the last marking overrides a prior mark on an element, though some implementations of markers take a previous mark into consideration when setting a new mark.
The mark m
can be either 0
(not marked), -1
(marked for coarsening) or 1
(marked for refinement).
DUNE grids only support marks for a single adaptation step, for example you cannot mark an element
to be refined twice in one adaptation step. You can however just loop the whole process until you
reached the desired grid setup.
Manual Marking
AMDiS itself does not provide any special support for manual marking, but you can use the underlying DUNE interface.
Let gridPtr
be a pointer to the grid with grid view gridView
and e
be a grid element, for example
from for (auto const& e : Dune::elements(gridView))
.
/// Set a mark m on grid element e
gridPtr->mark(m, e);
/// Return the current mark on element e
auto m = gridPtr->getMark(e);
Automatic Marking
The easiest way to use markers in AMDiS is by using a ProblemStat
instance and initfile options
for markers. Assume we have a problem named prob
setting up a marker can be done with the
initfile option prob->marker->strategy: <number>
. For further parameters consult the full list
of initfile options for Markers
.
Warning
Currently the process for automatic calculation of error estimates is not implemented and as the markers in this category require error estimates they are of limited use.
To add a marker that is not available via initfile parameters the ProblemStat
member function
template <class Marker>
void addMarker(Marker&& m)
After a marker is added using either of the above methods it will be called by a ProblemStat
via
Flag markElements(AdaptInfo& adaptInfo);
ProblemStat::oneIteration
or AdaptStationary::adapt
and does not need to be called manually.
Marker Interface
The class Marker
defines the interface for all AMDiS markers. A class that inherits from this must
define the following function:
void markElement(AdaptInfo& adaptInfo, Element const& elem);
elem
is marked with a mark m
and
then call Marker::mark(elem, m)
.
The following functions can also be overridden but have a default behaviour otherwise. Consult the implementation for details.
/// Called before traversal.
void initMarking(AdaptInfo& adaptInfo);
/// Called after traversal.
void finishMarking(AdaptInfo& adaptInfo);
/// Marking of the whole grid.
Flag markGrid(AdaptInfo& adaptInfo);
Marker Implementations
The currently implemented markers can all be found in Marker.hpp
with most of the implementation
in the file Marker.inc.hpp
.
AMDiS contains several markers that take error estimates to produce marks that will change the
grid to best minimize the errors in the next iteration. Since the error estimation mechanism is
not yet implemented we will not go into detail here. Refer to the common class EstimatorMarker
and subclasses for details.
Another predefined marker is the GridFunctionMarker
. A grid function must be provided to the
marker that returns the optimal refinement level that the grid should have at every position. This
can be used if you have a-priori information where the computationally difficult areas are - for
example the edges of a moving sphere can be set to be surrounded by a very fine mesh while the
rest of the domain can remain coarse. At each step the GridFunctionMarker
will mark the grid such
that it gets adapted towards the optimal level - refined if the current level is too low and
coarsened if it is too high.
Grid Adaptation
DUNE grids split the process of grid adaptation into three substeps: preAdapt
, adapt
and
postAdapt
. In the following we assume to have a pointer to the grid named gridPtr
with elements
e
.
In gridPtr->preAdapt()
setup work is done. If the grid manager does not detect any marks that
require the grid to coarsen this returns false
. At this stage the grid manager also considers the
markers and sets a special bool
value on every element that can be retrieved by
e.mightVanish()
. This is false only if the element will not disappear due to coarsening.
If there is any data attached to the grid this is also the stage to store it in some persistent
container as the indices may become invalid in the next step.
gridPtr->adapt()
performs the actual grid change. If at least one element was refined this returns
true
. After calling this the global basis must be updated and then the cached data can be copied
or interpolated back from the persistent container into the data containers. This process is done
by a DataTransfer
in AMDiS. As above we can retrieve information on each element if it was newly
created by refinement via a e.isNew()
.
Finally gridPtr->postAdapt()
performs cleanup and removes the isNew
markers.
AMDiS grids allow the same fine control over the adaptation procedure by giving access to all of the
functions above. On top of this a callback mechanism is employed that
automatically updates the basis in the second step and also automatically calls the DataTransfer
at every stage.
Warning
The callbacks only work when using the AMDiS wrapper classes AdaptiveGrid
and GlobalBasis
.
If you call any of the functions above directly on the underlying DUNE grid you have to manually
call the proper functions to update the basis and data vectors.
When using a ProblemStat
the whole process is wrapped in the member function
Flag adaptGrid(AdaptInfo& adaptInfo);
If you just want to simply coarsen or refine the complete grid you can use the ProblemStat
members
/// Uniform global grid coarsening by up to n level
Flag globalCoarsen(int n);
/// Uniform global refinement by n level
Flag globalRefine(int n);
Adaptation with the AdaptStationary class
The process of marking, grid adaptation and also building and solving the system as well as
computing estimates is wrapped in the class AdaptStationary
for stationary problems and
AdaptInstationary
for time-dependant problems. See the API documentation for details on using
those classes.
Warning
Currently the process for automatic calculation of error estimates is not implemented so those wrapper classes may be of limited use.
The AdaptInfo class
Many functions involved with grid adaptation can be tweaked using initfile parameters. Quite a few
of those parameters are stored in a helper class AdaptInfo
during runtime of an AMDiS program.
A full list of available options can be found in the parameter list.
All of those options are linked to the name, so if you create an object
AdaptInfo adaptInfo("adapt");
adapt->start time: 0.0
adapt->end time: 1.0
adapt->timestep: 0.1
Data Transfer
During grid adaption, the basis attached to the grid and all data attached to that basis
must be transfered to the new grid. When working with a DOFVector
this process is triggered
automatically when the grid changes, for user-defined containers you may need to call the
datatransfer manually or set up a callback.
This datatransfer happens in essentially two phases:
- the data on the old grid is cached in a persistent container,
- on the new grid either the data is simply copied from the cache, or an interpolation from the cached data is constructed to the new elements.
But, there might be other strategies to transfer the data between grid changes. It is up
to the user to choose a datatransfer strategy. This choice can be done in AMDiS, by
implementing the DataTransfer
interface, and specializing the DataTransferFactory
with
a <tag>
associated to the implement transfer routines.
The DataTransfer Interface
All DataTransfer
implementations follow the interface
/// Definition of the interface a DataTransfer class must fulfill
template <class B, class C>
struct DataTransfer
{
/// \brief Collect data that is needed before grid adaption.
/**
* \param basis The global basis before the grid is updated.
* \param container The original data before grid adaption.
* \param mightCoarsen Flag to indicate whether there are elements marked for coarsening.
**/
void preAdapt(B const& basis, C const& container, bool mightCoarsen);
/// \brief Interpolate data to new grid after grid adaption.
/**
* \param basis The global basis after the grid is updated.
* \param container The original data vector not yet updated.
**/
void adapt(B const& basis, C& container);
/// \brief Perform cleanup after grid adaption
/**
* \param container The data vector after any adaption and data transfer.
**/
void postAdapt(C& container);
};
with B
the type of the global basis and C
the type of the coefficient vector.
A class that has this interface can be stored in the type-erasure class DataTransfer
.
Additionally, all datatransfers specialize the class DataTransferFactory
:
template <class Tag>
struct DataTransferFactory
{
template <class B, class C>
static DataTransfer<B,C> create(B const& basis, C const& container);
};
Current Implementations in AMDiS
Examples of <tag>
that are currently implemented:
tag::no_adaption
: Implements the most simple datatransfer that does not transfer the values, but just resizes the data container to the updated basis size.tag::interpolation_datatransfer
: This is the default datatransfer, implementing a proper interpolation of the data from the old grid to the new grid.tag::simple_datatransfer
: A simplified version of thetag::interpolation_datatransfer
that works only for grids with a singleGeometryType
.
Changing the Data Transfer strategy
The <tag>
can be used to choose a strategy for a DOFVector
. By default the strategy
tag::interpolation_datatransfer
is set for all DOFVector
s. But, it might be that
some data does not need to be transfered, or you want to implement your own strategy,
then you can change this default:
DOFVector vec{...};
vec.setDataTransfer(<tag>);
Example
In a ProblemInstat
it might be that you don't want to interpolate the old-solution
to a new grid, since it is rewritten by default in the initTimestep(AdaptInfo)
method:
ProblemStat prob{"name", grid, lagrange<2>()};
prob.initialize(INIT_ALL);
// choose the simple data transfer for the solution vector
prob.solutionVector()->setDataTransfer(tag::simple_datatransfer{});
ProblemInstat probInstat{"name", prob};
probInstat.initilize(INIT_ALL);
// disable all interpolation for the old-solution vector
probInstat.oldSolutionVector()->setDataTransfer(tag::no_datatransfer{});
The AMDiS Callback Mechanism
As discussed in the chapters above there are certain points during the adaptation process where certain other routines must be called. For this purpose AMDiS provides callbacks on some classes involved with adaptivity.
On the one hand there are classes that are set up to signal when certain functions are called.
Those classes inherit from the Notifier<Event>
class and can call the function
notify(Event const& e);
e
happening.
On the other hand there are classes that inherit from the Observer<Event>
class, which must pass
a reference to the class they are watching to Observer
's constructor and implement the member
void updateImpl(Event e, Tags...);
notify(e)
by the observed class will automatically call the implementation of
updateImpl(e)
. By setting a tag for each implementation of updateImpl
a class can even observe
several classes and events.
Note that a Notifier
need not know what classes want to attach to it in advance. Also note that a
class can be both an Observer
and a Notifier
and can even observe an event, handle it and then
notify another class.
List of AMDiS functions using the callback mechanism
Currently there are 3 events that are being used in AMDiS.
event::preAdapt
is triggered by the AMDiS grid wrapper AdaptiveGrid
when its preAdapt
method
is called. First preAdapt
on the grid is called and then any DOFVector
or ElementVector
is
notified.
event::adapt
is triggered by AdaptiveGrid::adapt
after calling adapt
on the grid. The event is
then sent to GlobalBasis
which updates the underlying basis and then notifies any DOFVector
or
ElementVector
.
event::postAdapt
works like preAdapt
: AdaptiveGrid::postAdapt
calls postAdapt
on the grid
and then notifies any DOFVector
or ElementVector
.