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)
can be used. Any marker that implements the marker interface can be added that way.

After a marker is added using either of the above methods it will be called by a ProblemStat via

Flag markElements(AdaptInfo& adaptInfo);
Usually this gets called by a method that wraps the whole problem iteration process like 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);
This should contain the logic to decide whether an element 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);
so you do not need to bother with the details above.

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");
the corresponding initfile may look like
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:

  1. the data on the old grid is cached in a persistent container,
  2. 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 the tag::interpolation_datatransfer that works only for grids with a single GeometryType.

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 DOFVectors. 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);
to notify all attached classes about the event 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...);
After that a call to 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.