Graduation example: case 1 ========================== In this tutorial, the mesh is made up of cells at different levels which do not overlap and we want to yield a graded mesh at the end of the process. The complete example can be downloaded here: :download:`graduation case 1 <../../../demos/tutorial/graduation_case_1.cpp>` First, we need to construct an initial mesh with this property. We generate it randomly, starting by level :math:`1` in the 2D domain :math:`[0, 1] \times [0, 1]`. The principle is to randomly refine the cells at a given level and to create a new mesh. The creation of the initial mesh is done by the following code. .. code-block:: c++ auto generate_mesh(std::size_t start_level, std::size_t max_level) { constexpr std::size_t dim = 2; samurai::Box box({0, 0}, {1< ca; ca[start_level] = {start_level, box}; for(std::size_t ite = 0; ite < max_level - start_level; ++ite) { samurai::CellList cl; samurai::for_each_interval(ca, [&](std::size_t level, const auto& interval, const auto& index) { auto choice = xt::random::choice(xt::xtensor_fixed>{true, false}, interval.size()); for(int i = interval.start, ic = 0; i box({0, 0}, {1< ca; ca[start_level] = {start_level, box}; Here, we construct a :cpp:class:`samurai::CellArray` from a box. In |project|, a box is defined by its minimum and its maximum coordinates. As seen before, :cpp:class:`samurai::CellArray` contains integers describing the mesh. The relation between the space step and the level is :math:`\Delta x=\frac{1}{2^{\text{level}}}`. Recalling that our domain is :math:`[0, 1] \times [0, 1]`, then, our box starts at :math:`[0, 0]` and needs :math:`2^{\text{level}}` points to reach the maximum coordinates :math:`[1, 1]`. At the end, we assign this box to the `start_level` of the :cpp:class:`samurai::CellArray`. Now that we have the initial mesh, we can start to refine it randomly. Let us start with the inner loop. .. code-block: c++ samurai::for_each_interval(ca, [&](std::size_t level, const auto& interval, const auto& index) { auto choice = xt::random::choice(xt::xtensor_fixed>{true, false}, interval.size()); for(int i = interval.start, ic = 0; i("tag", ca); tag.fill(false); We initialize all the entries of the field `tag` to `false` meaning that all the cells are to be kept. Now we try to find an intersection as described previously using subset construction. Let us show how it is written for a given `level` and a `level_below` where `level_below < level - 1`. .. code-block:: c++ auto set = samurai::intersection(samurai::translate(ca[level], s), ca[level_below]) .on(level_below); set([&](const auto& i, const auto& index) { tag(level_below, i, index[0]) = true; }); `s` is a vector indicating how we translate the mesh. For example, if we want to translate the mesh one cell towards the right, `s` shall be equal to `{1, 0}`. `set` is the subset we try to find. Indeed, if we just write .. code-block:: c++ auto set = samurai::intersection(samurai::translate(ca[level], s), ca[level_below]); `set` will be calculated on the larger available level, namely `level`. This not what we want since we desire to tag the cell corresponding to this intersection at level `level_below`. This is why `on(level_below)` was added. If this subset exists, we want to apply a function. .. code-block:: c++ set([&](const auto& i, const auto& index) { tag(level_below, i, index[0]) = true; }); This is just a lambda function for the `operator()` of the subset which takes two parameters: `i` the interval found for this intersection and an array `index` of size `dim - 1 = 1` with the y-coordinate. The element of any field in |project| can be accessed using `field(level, i, j, k)` where `i` is an interval and `j` and `k` are integers. This operator returns a xtensor view of the field. We can now apply this kernel for different stencils and different levels of the mesh. .. code-block:: c++ std::size_t min_level = ca.min_level(); std::size_t max_level = ca.max_level(); xt::xtensor_fixed> stencil{{1, 1}, {-1, -1}, {-1, 1}, {1, -1}}; for(std::size_t level = min_level + 2; level <= max_level; ++level) { for(std::size_t level_below = min_level; level_below < level - 1; ++level_below) { for(std::size_t i = 0; i < stencil.shape()[0]; ++i) { auto s = xt::view(stencil, i); auto set = samurai::intersection(samurai::translate(ca[level], s), ca[level_below]) .on(level_below); set([&](const auto& i, const auto& index) { tag(level_below, i, index[0]) = true; }); } } } At the end of these operations, we know which cell must be refined and which cell must be kept. We can construct the new mesh using `tag` field and :cpp:class:`samurai::CellList`. .. code-block:: c++ samurai::CellList cl; samurai::for_each_cell(ca, [&](auto cell) { auto i = cell.indices[0]; auto j = cell.indices[1]; if (tag[cell]) { cl[cell.level + 1][{2*j}].add_interval({2*i, 2*i+2}); cl[cell.level + 1][{2*j + 1}].add_interval({2*i, 2*i+2}); } else { cl[cell.level][{j}].add_point(i); } }); samurai::CellArray new_ca = cl; The refinement is done for a cell at :math:`L \leq l - 2` but imagine that `L = 1` and `l = 5`. Then we would refine the cell at level `L = 1` which will transform into four cells at level `L + 1 = 2`. This is not enough to have the grading of the mesh since there is still a gap of 2 levels. Therefore, we have to iterate this process until it yields a graded mesh. The graduation procedure can be written as .. code-block:: c++ std::size_t min_level = ca.min_level(); std::size_t max_level = ca.max_level(); xt::xtensor_fixed> stencil{{1, 1}, {-1, -1}, {-1, 1}, {1, -1}}; while(true) { auto tag = samurai::make_field("tag", ca); tag.fill(false); for(std::size_t level = min_level + 2; level <= max_level; ++level) { for(std::size_t level_below = min_level; level_below < level - 1; ++level_below) { for(std::size_t i = 0; i < stencil.shape()[0]; ++i) { auto s = xt::view(stencil, i); auto set = samurai::intersection(samurai::translate(ca[level], s), ca[level_below]).on(level_below); set([&](const auto& i, const auto& index) { tag(level_below, i, index[0]) = true; }); } } } samurai::CellList cl; samurai::for_each_cell(ca, [&](auto cell) { auto i = cell.indices[0]; auto j = cell.indices[1]; if (tag[cell]) { cl[cell.level + 1][{2*j}].add_interval({2*i, 2*i+2}); cl[cell.level + 1][{2*j + 1}].add_interval({2*i, 2*i+2}); } else { cl[cell.level][{j}].add_point(i); } }); samurai::CellArray new_ca = cl; if(new_ca == ca) { break; } std::swap(ca, new_ca); } The figure below is the graded version of our initial mesh. The red cells are the cells which have been added by the graduation. .. image:: ./figures/graduation_case_1_after.png :width: 60% :align: center .. note:: If we choose to grade the mesh also along the diagonals, this can be done by considering the directions along the diagonals into the vectors of the stencils. This is .. code-block:: c++ xt::xtensor_fixed> stencil{{1, 1}, {-1, 1}, {-1, -1}, {1, -1}}; The reader can easily check that this indeed grants the good grading property also along the cartesian axis.