Skip to content

SimulationAdaptiveGrid

Martin Hartl edited this page Dec 1, 2017 · 6 revisions

Simulation with AdaptiveGrid

The objective of this tutorial is to illustrate the general use of the AdaptiveGrid data structure and highlight when it should be preferred over a Grid. To get an overview of the data structure the reference is a good way to start: AdaptiveGrid

The codes presented in this tutorial are in pseudocode style. If you look for a full implementation that uses the AdaptiveGrid you may look at the AdaptiveGrid wave simulation sources directory. The sources feature two ways of printing the current wave simulation state. Setting the environment variable WAVE_ASCIIPLOT to any value enables an ASCII plot printed in the terminal. With the environment variable WAVE_GNUPLOT the program outputs the simulation results as CSV to the standard output which might be useful to redirect to a file (e.g. simulation_result.csv). The folder also contains a ruby script plot.rb (uses ruby gem gnuplot, csv and parallel and program gnuplot) which creates images for each simulation iteration. The script takes the CSV as input and can be executed this way: ruby plot.rb simulation_result.csv. The script animate.rb (uses ruby gem rmagick and ImageMagick image processing library) creates an animated gif of the pictures generated by plot.rb.

Overview

This tutorial covers the following steps:

Basic Simulation

To implement a basic simulation in C++ a program may have the following structure:

const int rows = 50;
const int columns = 50;
Grid<double,2> u({rows, columns});
init(u);
while(!simulation_ends) {
    simulate(u);
}

Grid u is the data structure that contains the current simulation data. After setting the initial values of the grid, the simulation is executed until a certain condition is fulfilled.

For some simulations there may be parts in the Grid that only change slightly or don't change at all. If these parts of the Grid only have minor impact on the total outcome of the simulation, the AdaptiveGrid data structure can be used to reduce the number of data points to simulate. The elements of the AdaptiveGrid data structure are cells which can switch between multiple layers of refinement to change the amount of data points. These layers can be switched at runtime to focus the computational power on regions that are more interesting or significant for the outcome of the simulation. However, the AdaptiveGrid introduces additional overhead due to the refining and coarsen of the layers.

AdaptiveGrid

If we now use the AdaptiveGrid the program may have this structure:

using TwoLayerCellConfig = CellConfig<
        2,                     // < we have a 2D adaptive grid
        layers<                // < with
            layer<2,2>         // < one 2x2 refinement layer
        >
    >;

const int rows = 50;
const int columns = 50;
AdaptiveGrid<double, TwoLayerCellConfig> u({rows / 2, columns / 2});
init(u);
while(!simulation_ends) {
    adapt(u);
    simulate(u);
}

The CellConfig describes the dimension and the size of the different layers in the AdaptiveGrid. The TwoLayerCellConfig is a two layer configuration with 2 x 2 elements for the fine layer and the implicit 1 x 1 single element layer that is always created. The number of rows and columns of the AdaptiveGrid is now half of the previous numbers because the AdaptiveGrid will have the size of the Grid if every cell in the AdaptiveGrid is using the 2 x 2 layer. However, this will lead to slightly more inaccurate results due to the less data points. Note that the AdaptiveGrid does not use less memory compared to the implementation with Grid because all layers are allocated statically.

The simulation steps now have to be adjusted to fit the AdaptiveGrid data structure. In most cases the initialization of the fine layer, which is selected by default, is useful. For the simulation loop we add a new step adapt which refines and coarsens the cells if needed.

The id function is useful to refine and coarsen a layer.

double id(double x) {
    return x;
}

If a certain cell seems to be interesting for the simulation the refine method allows us to refine the cell to enable more data points. The following C++ code refines the cell at pos in AdaptiveGrid u and sets each new value to the current cell value.

u[pos].refine(&id);

If a certain cell is no longer interesting the coarsen method allows to coarsen the cell to use a coarser layer. The following code calculates the average layer value and sets the coarser layer:

u[pos].coarsen(&id);

There is no uniform way to determine whether a cell should be refined or coarsened. For the simulations that can be found in the AdaptiveGrid wave simulation sources the difference between the old and new value decides whether the layer should be changed. For high differences the cell is likely to also have noticeable changes in the next iterations and is therefore viewed in detail.

const double threshold_refine  = 0.002;
const double threshold_coarsen = 0.001;

// compute the speed of change (~first derivation)
double change = fabs(getValue(u[pos]) - getValue(um[pos]));

// alter refinement level if necessary
if (change > threshold_refine) {
    refine(u[pos]);
} else if (change < threshold_coarsen) {
    coarsen(u[pos]);
} else {
    // stay as it is
}

Note that um contains the values of the previous iteration. The functions refine and coarsen also contain a check whether the cell is already on the desired layer.

For the simulation step the access to values of the AdaptiveGrid also has to be adjusted because the cells may have different granularity.

Clone this wiki locally