From 75dfff577e1bc3e9334f164c7201840174bb2319 Mon Sep 17 00:00:00 2001
From: Felix Schindler <felix.schindler@wwu.de>
Date: Thu, 11 Oct 2018 23:03:16 +0200
Subject: [PATCH] [examples] add adaptive interpolation

---
 examples/adaptive-interpolation.cc | 303 +++++++++++++++++++++++++++++
 1 file changed, 303 insertions(+)
 create mode 100644 examples/adaptive-interpolation.cc

diff --git a/examples/adaptive-interpolation.cc b/examples/adaptive-interpolation.cc
new file mode 100644
index 000000000..75379e113
--- /dev/null
+++ b/examples/adaptive-interpolation.cc
@@ -0,0 +1,303 @@
+// This file is part of the dune-gdt project:
+//   https://github.com/dune-community/dune-gdt
+// Copyright 2010-2018 dune-gdt developers and contributors. All rights reserved.
+// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
+//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
+//          with "runtime exception" (http://www.dune-project.org/license.html)
+// Authors:
+//   Felix Schindler (2018)
+
+#include "config.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <exception>
+#include <utility>
+
+#include <dune/common/dynvector.hh>
+#include <dune/common/exceptions.hh>
+#include <dune/common/parallel/mpihelper.hh>
+#include <dune/grid/common/rangegenerators.hh>
+#include <dune/grid/utility/persistentcontainer.hh>
+
+#include <dune/xt/common/float_cmp.hh>
+#include <dune/xt/common/string.hh>
+#include <dune/xt/common/timedlogging.hh>
+#include <dune/xt/la/container/conversion.hh>
+#include <dune/xt/la/container/common/vector/dense.hh>
+#include <dune/xt/grid/entity.hh>
+#include <dune/xt/grid/grids.hh>
+#include <dune/xt/grid/gridprovider/cube.hh>
+#include <dune/xt/grid/type_traits.hh>
+#include <dune/xt/grid/walker.hh>
+#include <dune/xt/functions/lambda/function.hh>
+
+#include <dune/gdt/discretefunction/default.hh>
+#include <dune/gdt/interpolations.hh>
+#include <dune/gdt/local/bilinear-forms/integrals.hh>
+#include <dune/gdt/local/integrands/product.hh>
+#include <dune/gdt/spaces/l2/finite-volume.hh>
+
+using namespace Dune;
+
+
+// some global defines
+using G = ONED_1D;
+static const constexpr size_t d = G::dimension;
+using GV = typename G::LeafGridView;
+using E = XT::Grid::extract_entity_t<GV>;
+using V = XT::LA::CommonDenseVector<double>;
+
+
+V compute_local_l2_norms(const XT::Functions::GridFunctionInterface<E>& func, const GV& grid_view)
+{
+  // the best index set for a grid view of arbitrary elements is a scalar FV space ...
+  auto fv_space = GDT::make_finite_volume_space<1>(grid_view);
+  // ... and the best way to associate data with grid elements is a corresponding discrete function
+  auto df = GDT::make_discrete_function<V>(fv_space);
+  // compute local L^2 products
+  auto walker = XT::Grid::make_walker(grid_view);
+  walker.append(/*prepare nothing*/ []() {},
+                /*apply local*/
+                [&](const auto& element) {
+                  auto local_func = func.local_function();
+                  local_func->bind(element);
+                  // models \int_element 1*phi*psi dx for any phi/psi
+                  const GDT::LocalElementIntegralBilinearForm<E> local_l2_product(
+                      GDT::LocalElementProductIntegrand<E>(1.));
+                  // evaluate local product with phi = psi = local_func
+                  const auto element_l2_error2 = local_l2_product.apply2(*local_func, *local_func)[0][0];
+                  // store in entry for this element (we keep them unsquared, makes for easier computation of total)
+                  df.local_discrete_function(element)->dofs()[0] = std::sqrt(element_l2_error2);
+                },
+                /*finalize*/ []() {});
+  walker.walk(/*thread_parallel=*/true);
+  return df.dofs().vector();
+} // ... compute_local_l2_norms(...)
+
+
+void mark_elements(
+    G& grid, const GV& grid_view, const V& local_indicators, const double& refine_factor, const double& coarsen_factor)
+{
+  // mark all elements with a contribution above coarsen_threashhold for coarsening
+  // * therefore: as above, use a scalar FV space as index mapper
+  auto fv_space = GDT::make_finite_volume_space<1>(grid_view);
+  //              ... and a corresponding discrete function to associate data with grid elements
+  //  auto local_indicators_function = GDT::make_discrete_function(fv_space, local_indicators);
+  //  auto local_indicators_local_function = local_indicators_function.local_discrete_function();
+  // prepare dörfler marking
+  std::vector<std::pair<double, size_t>> accumulated_sorted_local_indicators(local_indicators.size());
+  for (size_t ii = 0; ii < local_indicators.size(); ++ii)
+    accumulated_sorted_local_indicators[ii] = {std::pow(local_indicators[ii], 2), ii};
+  std::sort(accumulated_sorted_local_indicators.begin(),
+            accumulated_sorted_local_indicators.end(),
+            [](const auto& a, const auto& b) { return a.first < b.first; });
+  for (size_t ii = 1; ii < accumulated_sorted_local_indicators.size(); ++ii)
+    accumulated_sorted_local_indicators[ii].first += accumulated_sorted_local_indicators[ii - 1].first;
+  // select smallest coarsen_factor contributions for coarsening
+  std::set<size_t> elements_to_be_coarsened;
+  const double total_indicators = std::pow(local_indicators.l2_norm(), 2);
+  for (const auto& indicator : accumulated_sorted_local_indicators)
+    if (indicator.first < (coarsen_factor * total_indicators))
+      elements_to_be_coarsened.insert(indicator.second);
+  // select largest refine_factor contributions
+  std::set<size_t> elements_to_be_refined;
+  for (const auto& indicator : accumulated_sorted_local_indicators)
+    if (indicator.first > ((1 - refine_factor) * total_indicators))
+      elements_to_be_refined.insert(indicator.second);
+  // mark elements
+  size_t corsend_elements = 0;
+  size_t refined_elements = 0;
+  for (auto&& element : elements(grid_view)) {
+    const size_t index = fv_space.mapper().global_indices(element)[0];
+    bool coarsened = false;
+    if (std::find(elements_to_be_coarsened.begin(), elements_to_be_coarsened.end(), index)
+        != elements_to_be_coarsened.end()) {
+      grid.mark(/*coarsen*/ -1, element);
+      coarsened = true;
+    }
+    if (std::find(elements_to_be_refined.begin(), elements_to_be_refined.end(), index)
+        != elements_to_be_refined.end()) {
+      grid.mark(/*refine and overwrite coarsening if present*/ 2, element);
+      refined_elements += 1;
+      coarsened = false;
+    }
+    if (coarsened)
+      ++corsend_elements;
+  }
+  auto logger = XT::Common::TimedLogger().get("mark_elements");
+  logger.info() << "marked " << corsend_elements << "/" << fv_space.mapper().size() << " elements for coarsening and "
+                << refined_elements << "/" << fv_space.mapper().size() << " elements for refinement" << std::endl;
+} // ... mark_elements(...)
+
+
+/**
+ * \note At some point this functionality should find its way into the spaces.
+ *
+ * On the domain covered by the coarser element, this computes an L^2 projection of the function defined on the finer
+ * elements (which corresponds to weighted averaging in the FV case).
+ */
+void compute_restriction(const E& element, PersistentContainer<G, DynamicVector<double>>& persistent_data)
+{
+  auto& element_restriction_data = persistent_data[element];
+  if (element_restriction_data.size() == 0) {
+    DUNE_THROW_IF(element.isLeaf(), InvalidStateException, "");
+    for (auto&& child_element : descendantElements(element, element.level() + 1)) {
+      // ensure we have data on all descendant elements of the next level
+      compute_restriction(child_element, persistent_data);
+      // compute restriction
+      auto child_restriction_data = persistent_data[child_element];
+      child_restriction_data *= child_element.geometry().volume();
+      if (element_restriction_data.size() == 0)
+        element_restriction_data = child_restriction_data; // initialize with child data
+      else
+        element_restriction_data += child_restriction_data;
+    }
+    // now we have assembled h*value
+    element_restriction_data /= element.geometry().volume();
+  }
+} // ... compute_restriction(...)
+
+
+int main(int argc, char* argv[])
+{
+  try {
+    MPIHelper::instance(argc, argv);
+    if (argc > 1)
+      DXTC_CONFIG.read_options(argc, argv);
+    XT::Common::TimedLogger().create(DXTC_CONFIG_GET("logger.info", 1), DXTC_CONFIG_GET("logger.debug", -1));
+    auto logger = XT::Common::TimedLogger().get("main");
+
+    // initial grid
+    const double domain_length = 10;
+    auto grid_ptr =
+        XT::Grid::make_cube_grid<G>(/*lower_left=*/0., /*upper_right=*/domain_length, /*num_elements=*/1).grid_ptr();
+    auto& grid = *grid_ptr;
+    auto grid_view = grid.leafGridView();
+
+    // funtion to interpolate
+    const XT::Functions::LambdaFunction<d> cosh(/*approx_pol_order=*/10,
+                                                [](const auto& x, const auto& /*param*/) { return std::cosh(x); });
+    const auto reference_norm = compute_local_l2_norms(cosh.as_grid_function(grid_view), grid_view).l2_norm();
+
+    // fake solution vector to demonstrate prolongation
+    V current_solution_vector = GDT::interpolate<V>(cosh, GDT::make_finite_volume_space<1>(grid_view)).dofs().vector();
+
+    // the main adaptation loop
+    const double tolerance = DXTC_CONFIG_GET("tolerance", 1e-3);
+    size_t counter = 0;
+    while (true) {
+      // interpret current_solution as a discrete FV function on the current grid
+      auto fv_space = GDT::make_finite_volume_space<1>(grid_view);
+      auto current_solution = GDT::make_discrete_function(fv_space, current_solution_vector);
+      logger.info() << "step " << counter << ", space has " << fv_space.mapper().size() << " DoFs" << std::endl;
+      current_solution.visualize("interpolated_function_" + XT::Common::to_string(counter));
+
+      // compute local L^2 errors
+      const auto local_errors = compute_local_l2_norms(current_solution - cosh.as_grid_function(grid_view), grid_view);
+      logger.info() << "  relative interpolation error: " << local_errors.l2_norm() / reference_norm << std::endl;
+      if (local_errors.l2_norm() / reference_norm < tolerance) {
+        logger.info() << "target accuracy reached, terminating!" << std::endl;
+        break;
+      }
+
+      // use these as indicators for grid refinement
+      mark_elements(grid,
+                    grid_view,
+                    local_errors,
+                    DXTC_CONFIG_GET("mark.refine_factor", 0.25),
+                    DXTC_CONFIG_GET("mark.coarsen_factor", 0.01));
+
+      // now the actual adaptation
+      // * call preadapt, this will mark elements which might vanish due to coarsening
+      const bool elements_may_be_coarsened = grid.preAdapt();
+      //   - now we need some persistent storage to keep our data: all kept leaf elements might change their indices and
+      //                                                           all coarsened elements might vanish
+      //     this should keep local DoF vectors, which can be converted to DynamicVector<double>
+      PersistentContainer<G, DynamicVector<double>> persistent_data(grid, 0);
+      //   - we also need a container to recall those elements where we need to restrict to
+      PersistentContainer<G, bool> restriction_required(grid, 0, false);
+      //   - first of all, walk the current leaf of the grid ...
+      auto current_local_solution = current_solution.local_discrete_function();
+      for (auto&& element : elements(grid_view)) {
+        current_local_solution->bind(element);
+        //   ... to store the local DoFs ...
+        persistent_data[element] = XT::LA::convert_to<DynamicVector<double>>(current_local_solution->dofs());
+        //   ... and to mark father elements
+        if (element.mightVanish())
+          restriction_required[element.father()] = true;
+      }
+      //   - now walk the grid up all coarser levels ...
+      if (elements_may_be_coarsened) {
+        for (int level = grid.maxLevel() - 1; level >= 0; --level) {
+          auto level_view = grid.levelGridView(level);
+          for (auto&& element : elements(level_view)) {
+            // ... to compute restrictions ...
+            if (restriction_required[element])
+              compute_restriction(element, persistent_data);
+            // ... and to mark father elements
+            if (element.mightVanish())
+              restriction_required[element.father()] = true;
+          }
+        }
+      }
+      // from here on, restriction_required is not required any more
+      // * next, we actually adapt the grid
+      grid.adapt();
+      //   - from now on, fv_space, current_solution and current_local_solution must not be used any more,
+      //     this is not yet implemented!
+      //   - clean up data structures
+      persistent_data.resize();
+      persistent_data.shrinkToFit();
+      // * create a new space, resize the solution vector and create a new discrete function to hold the
+      //   prolongated/restricted values
+      auto new_fv_space = GDT::make_finite_volume_space<1>(grid_view);
+      current_solution_vector.resize(new_fv_space.mapper().size());
+      auto new_solution = GDT::make_discrete_function(new_fv_space, current_solution_vector);
+      // * copy the persistent data back to the discrete function ...
+      auto new_local_solution = new_solution.local_discrete_function();
+      for (auto&& element : elements(grid_view)) {
+        new_local_solution->bind(element);
+        if (element.isNew()) {
+          // ... by prolongation from the father element ...
+          const auto& father_data = persistent_data[element.father()];
+          DUNE_THROW_IF(father_data.size() == 0, InvalidStateException, "");
+          new_local_solution->dofs().assign_from(father_data);
+        } else {
+          // ... or by copying the data from this unchanged element
+          const auto& element_data = persistent_data[element];
+          DUNE_THROW_IF(element_data.size() == 0, InvalidStateException, "");
+          new_local_solution->dofs().assign_from(element_data);
+        }
+      }
+      // * clean up the grid
+      grid.postAdapt();
+      // from here on, persistent_data is not required any more
+      new_solution.visualize("interpolated_function_after_refinement_" + XT::Common::to_string(counter));
+
+      // now that the grid is adapted, interpolate the function on the new grid
+      GDT::interpolate(cosh, new_solution);
+
+      ++counter;
+    }
+
+    double min_h = std::numeric_limits<double>::max();
+    for (auto&& element : elements(grid_view))
+      min_h = std::min(min_h, XT::Grid::diameter(element));
+
+    logger.info() << "\nA uniformly refinement grid of similar accuracy would have roughly required "
+                  << int(domain_length / min_h) << " DoFs" << std::endl;
+
+  } catch (Exception& e) {
+    std::cerr << "\nDUNE reported error: " << e.what() << std::endl;
+    return EXIT_FAILURE;
+  } catch (std::exception& e) {
+    std::cerr << "\nstl reported error: " << e.what() << std::endl;
+    return EXIT_FAILURE;
+  } catch (...) {
+    std::cerr << "Unknown error occured!" << std::endl;
+    return EXIT_FAILURE;
+  } // try
+  return EXIT_SUCCESS;
+} // ... main(...)
-- 
GitLab