Skip to content
Martin Hartl edited this page Jan 2, 2018 · 2 revisions

Unstructured Multi-Grid Meshes

The objective of this tutorial is to illustrate the general use of the Mesh data structure. To get an overview of the data structure the reference Mesh is a good way to start. It is also recommended to take a look at the VCycle operator since it is also used in this tutorial.

This tutorial only shows the important parts of an actual implementation. If you look for the full implementation that uses the Mesh data structure you can look at the Mesh simulation sources directory.

For all following code examples we will use the namespace:

using namespace allscale::api::user;

Overview

This tutorial covers the following steps:

MeshBuilder

We first define the types of the nodes in the mesh. When building the mesh we create nodes of these types and link them. MeshProperties allow to associate a value to each node of a certain kind on each level of a mesh.

// nodes of our mesh
struct Cell {};
struct Face {};
struct Node {};

struct LeftBoundaryFace {};
struct RightBoundaryFace {};

Next we define edges to connect nodes on the same level. These edges inherit from allscale::api::user::data::edge<NodeKindA,NodeKindB>. The NodeKinds are of the node types we defined before. For the simulation we define the following edges:

// edges
struct Face_2_Cell_Left  : public data::edge<Face,Cell> {};
struct Face_2_Cell_Right : public data::edge<Face,Cell> {};

struct BoundaryFace_2_Cell_Left  : public data::edge<RightBoundaryFace,Cell> {};
struct BoundaryFace_2_Cell_Right : public data::edge<LeftBoundaryFace,Cell> {};
struct Cell_2_Face_Left  : public data::edge<Cell,Face> {};
struct Cell_2_Face_Right : public data::edge<Cell,Face> {};

struct Node_2_Cell : public data::edge<Node,Cell> {};
struct Face_2_Node : public data::edge<Face,Node> {};

In the example code above the Face_2_Cell_Left edge connects a Face with a Cell.

The hierarchies inherit from allscale::api::user::data::hierarchy<NodeKindA,NodeKindB>. They are similar to the edges but connect nodes on adjacent levels instead of the same level.

// hierarchies
struct Cell_2_Child : public data::hierarchy<Cell,Cell> {};
struct Face_2_Child : public data::hierarchy<Face,Face> {};
struct LeftBoundaryFace_2_Child  : public data::hierarchy<LeftBoundaryFace,LeftBoundaryFace> {};
struct RightBoundaryFace_2_Child : public data::hierarchy<RightBoundaryFace,RightBoundaryFace> {};

The hierarchy Cell_2_Child connects a Cell with a Cell on adjacent levels.

The MeshBuilder takes the nodes, edges and hierarchies and number of levels as template arguments:

template<unsigned levels = 1>
using MeshBuilder = data::MeshBuilder<
    data::nodes<
        Cell,
        Face,
        Node,
        LeftBoundaryFace,
        RightBoundaryFace
    >,
    data::edges<
        Face_2_Cell_Left,
        Face_2_Cell_Right,
        BoundaryFace_2_Cell_Left,
        BoundaryFace_2_Cell_Right,
        Node_2_Cell,
        Face_2_Node,
        Cell_2_Face_Left,
        Cell_2_Face_Right
    >,
    data::hierarchies<
        Cell_2_Child,
        LeftBoundaryFace_2_Child,
        RightBoundaryFace_2_Child
    >,
    levels
>;

The MeshBuilder provides the utility to build a Mesh data structure. The code below creates a new node of type Cell and a new node of type Face on level 0. Both calls return a NodeRef to the newly created node.

auto cellRef = builder.template create<Cell, 0>();
auto faceRef = builder.template create<Face, 0>();

The created nodes can be connected using the link method. Given the edge kind as template argument and the node references as method arguments the two nodes are linked using the link method.

builder.link<Face_2_Cell_Left>(faceRef, cellRef);

The above code creates an edge between the nodes faceRef and cellRef. Note that the method arguments have to be in the right order. The link method can also be used to link nodes on adjacent levels (i.e. hierarchies).

In the tutorial sources we build a tube with a given number of cells. Going up one level in the hierarchy will halve the number of cells compared to the lower level. If you are interested in the implementation details take a look at the TubeLayerBuilder class in the Mesh simulation sources directory.

If all needed nodes are created and linked the Mesh data structure can be created:

Mesh mesh = std::move(builder).build();

If the MeshBuilder isn't used further it is desireable to call the build method on a rvalue reference.

MeshProperties

The mesh properties help to associate values to a certain kind of node on each level of the mesh. For the simulation we define the following mesh properties:

using value_t = double;

struct CellVolume : public data::mesh_property<Cell,value_t> {};
struct CellCenter : public data::mesh_property<Cell,Point> {};

struct FaceSurface : public data::mesh_property<Face,value_t> {};

struct LeftBoundaryFaceSurface : public data::mesh_property<LeftBoundaryFace,value_t> {};
struct LeftBoundaryFaceCenter  : public data::mesh_property<LeftBoundaryFace,Point> {};

struct RightBoundaryFaceSurface : public data::mesh_property<RightBoundaryFace,value_t> {};
struct RightBoundaryFaceCenter  : public data::mesh_property<RightBoundaryFace,Point> {};

struct NodePosition : public data::mesh_property<Node,Point> {};

The above code creates eight different properties. Each property associates a value to every node of a certain kind. e.g. the property CellVolume associates a value_t (double) to each Cell on each level.

To receive the MeshProperties object we call the createProperties method of the Mesh data structure:

auto properties = mesh.createProperties<CellVolume,
    CellCenter,
    FaceSurface,
    LeftBoundaryFaceSurface,
    LeftBoundaryFaceCenter,
    RightBoundaryFaceSurface,
    RightBoundaryFaceCenter,
    NodePosition>();

After creating the MeshProperties data structure the values of each property can be accessed. The get method of the MeshProperties data structure provides access to the values on a given layer:

auto& data = properties.get<RightBoundaryFaceSurface,0>();
data[noderef]; // access the value for noderef

The above code returns MeshData which provides access by NodeRef using the subscript operator to get the specific value for a node.

There is also a way to directly access a value of a node by passing the NodeRef as argument to the get method:

properties.get<RightBoundaryFaceSurface,0>(noderef);

Mesh

The Mesh is the data structure that holds the topological information of a hierarchical mesh. It provides methods to iterate over given node types and levels in sequential or in parallel order. The code below sets the property CellVolume to 1:

auto& cellVolume = properties.template get<CellVolume>();
mesh.forAll<Cell>([&](auto& c) {
    // do something with cell c
    // e.g. update a property value of c
    cellVolume[c] = 1;
});

Note that the above code only updates the value of all nodes of kind Cell on level 0 because the forAll method takes a second template argument to select the level which is implicitly 0 if no argument is given. The method pforAll is the parallel version of forAll.

Further there are methods to access neighbor nodes depending on the structure we defined in the MeshBuilder by linking nodes.

// get a NodeList of all sinks for faceRef and edge "Face_2_Cell_Left"
auto nodes = mesh.getSinks<Face_2_Cell_Left>(faceRef);

// get the NodeRef of the only sink for faceRef and edge "Face_2_Cell_Left"
auto node = mesh.getSink<Face_2_Cell_Left>(faceRef);

// get a NodeList of all sources for cellRef and edge "Face_2_Cell_Left"
auto nodes = mesh.getSources<Face_2_Cell_Left>(cellRef);

// get the NodeRef of the only source for cellRef and edge "Face_2_Cell_Left"
auto node = mesh.getSource<Face_2_Cell_Left>(cellRef);

// neighbor methods return cell nodes because a face node is given as the argument
// in general these methods return the result of
// getSink(s) or getSource(s) depending on the method argument type

auto nodes = mesh.getNeighbors<Face_2_Cell_Left>(faceRef);

auto node = mesh.getNeighbor<Face_2_Cell_Left>(faceRef);

Just like for linked nodes on the same level there are also access methods for adjacent levels.

// returns a NodeList of children of c for hierarchy "Cell_2_Child"
auto children = mesh.template getChildren<Cell_2_Child>(c);

// returns the parent NodeRef of c for hierarchy "Cell_2_Child"
auto parent = mesh.template getParent<Cell_2_Child>(c);

In addition to MeshProperties the method createNodeData() creates values for each node on a single level.

auto& data = mesh.createNodeData<Cell,value_t,0>();
data[c]; //access data for NodeRef c

The method returns MeshData which can be accessed with the subscript operator with a NodeRef as the argument. The above code creates a double for each Cell on level 0. As for the previous methods the level can be omitted if it is 0.

VCycle

A single iteration of the V-cycle computes new values on each hierarchical level starting at the bottom, going to the top and back down afterwards.

// -- VCycle stage --
template<typename Mesh, unsigned Level>
struct TemperatureStage {
    void computeFineToCoarse() {
        // compute stage while going up the hierarchy
        // ...
    }

    void computeCoarseToFine() {
        // compute stage while going down the hierarchy
        // ...
    }

    void restrictFrom(TemperatureStage<Mesh,Level-1>& childStage) {
        // go from child stage to this stage
        // ...
    }

    void prolongateTo(TemperatureStage<Mesh,Level-1>& childStage) {
        // go from this stage to child stage
        // ...
    }
}

One iteration of the V-cycle starts at the lowest level of the mesh hierarchy and calls the computeFineToCoarse method, goes up one level and calls the restrictFrom method. These calls are repeated until the top of the hierarchy is reached. If the top level of the hierarchy is reached the VCycle goes down the hierarchy by calling prolongateTo followed by computeCoarseToFine repeatedly until the bottom level is reached. When the bottom level is reached one iteration of the V-cycle was done. For a detailed implementation of the TemperatureStage you should again take a look at the Mesh simulation sources directory.

using vcycle_type = algorithm::VCycle<
    TemperatureStage,
    decltype(mesh)
>;

vcycle_type vcycle(mesh);

// run 10 iterations
vcycle.run(10);

The above code runs ten iterations of our TemperatureStage V-cycle on our mesh.

Clone this wiki locally