-
Notifications
You must be signed in to change notification settings - Fork 5
SimulationAdaptiveGrid
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
.
This tutorial covers the following steps:
- Basic Simulation - how a simulation application can be structured
-
AdaptiveGrid - using the
AdaptiveGrid
to speed up the 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.
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.
Part of the AllScale project - http://www.allscale.eu