From c417302e37b41bc0e5a4b49efecbb4851af91373 Mon Sep 17 00:00:00 2001 From: Oliver Sander <oliver.sander@tu-dresden.de> Date: Sat, 18 Jan 2020 15:12:09 +0100 Subject: [PATCH] Add a system test for harmonic maps This new test computes a harmonic map from a square to a sphere. In other words, rather than testing some specific aspect of some class, it does a whole simulation. It should therefore cover much more of the dune-gfe code. --- test/CMakeLists.txt | 3 + test/grids/irregular-square.msh | 42 +++++++ test/harmonicmaptest.cc | 195 ++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 test/grids/irregular-square.msh create mode 100644 test/harmonicmaptest.cc diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8b4c20ff..78251121 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,7 @@ set(TESTS cosseratenergytest frameinvariancetest harmonicenergytest + harmonicmaptest localgeodesicfefunctiontest localgfetestfunctiontest localprojectedfefunctiontest @@ -19,3 +20,5 @@ foreach(_test ${TESTS}) dune_add_test(SOURCES ${_test}.cc) target_compile_options(${_test} PRIVATE -g) endforeach() + +file(COPY grids/irregular-square.msh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/grids) diff --git a/test/grids/irregular-square.msh b/test/grids/irregular-square.msh new file mode 100644 index 00000000..2cf3522d --- /dev/null +++ b/test/grids/irregular-square.msh @@ -0,0 +1,42 @@ +$MeshFormat +2.1 0 8 +$EndMeshFormat + +$Nodes +17 + 1 -5 -5 0 + 2 -2 -5 0 + 3 3 -5 0 + 4 5 -5 0 + 5 -5 -2 0 + 6 -2.8 -1.8 0 + 7 1 -1.5 0 + 8 5 -2 0 + 9 -5 4 0 +10 -3 1.5 0 +11 1 2.5 0 +12 4 3.1 0 +13 -5 5 0 +14 -1 5 0 +15 1 5 0 +16 5 5 0 +17 5 0 0 +$EndNodes + +$Elements +14 + 1 2 3 0 1 0 1 2 6 + 2 2 3 0 1 0 2 7 6 + 3 3 3 0 1 0 2 3 8 7 + 4 2 3 0 1 0 3 4 8 + 5 3 3 0 1 0 1 6 10 5 + 6 3 3 0 1 0 6 7 11 10 + 7 3 3 0 1 0 7 17 12 11 + 8 2 3 0 1 0 7 8 17 + 9 2 3 0 1 0 5 10 9 +10 3 3 0 1 0 9 10 14 13 +11 2 3 0 1 0 10 11 14 +12 3 3 0 1 0 11 12 15 14 +13 2 3 0 1 0 12 16 15 +14 2 3 0 1 0 12 17 16 +$EndElements diff --git a/test/harmonicmaptest.cc b/test/harmonicmaptest.cc new file mode 100644 index 00000000..996588be --- /dev/null +++ b/test/harmonicmaptest.cc @@ -0,0 +1,195 @@ +#include <config.h> + +#include <array> + +// Includes for the ADOL-C automatic differentiation library +// Need to come before (almost) all others. +#include <adolc/adouble.h> +#include <dune/fufem/utilities/adolcnamespaceinjections.hh> + +#include <dune/common/typetraits.hh> +#include <dune/common/bitsetvector.hh> + +#include <dune/grid/uggrid.hh> +#include <dune/grid/io/file/gmshreader.hh> + +#include <dune/functions/gridfunctions/discreteglobalbasisfunction.hh> +#include <dune/functions/functionspacebases/lagrangebasis.hh> +#include <dune/functions/functionspacebases/powerbasis.hh> +#include <dune/functions/functionspacebases/interpolate.hh> + +#include <dune/fufem/boundarypatch.hh> +#include <dune/fufem/functiontools/boundarydofs.hh> + +#include <dune/gfe/unitvector.hh> +#include <dune/gfe/localgeodesicfefunction.hh> +#include <dune/gfe/localprojectedfefunction.hh> +#include <dune/gfe/localgeodesicfeadolcstiffness.hh> +#include <dune/gfe/harmonicenergy.hh> +#include <dune/gfe/geodesicfeassembler.hh> +#include <dune/gfe/riemanniantrsolver.hh> + +// grid dimension +const int dim = 2; + +// Image space of the geodesic fe functions +typedef UnitVector<double,3> TargetSpace; + +// Tangent vector of the image space +const int blocksize = TargetSpace::TangentVector::dimension; + +const int order = 1; + +using namespace Dune; + + +int main (int argc, char *argv[]) +{ + MPIHelper::instance(argc, argv); + + using SolutionType = std::vector<TargetSpace>; + + // read problem settings + const int numLevels = 4; + + // read solver settings + const double tolerance = 1e-6; + const int maxTrustRegionSteps = 1000; + const double initialTrustRegionRadius = 1; + const int multigridIterations = 200; + const int baseIterations = 100; + const double mgTolerance = 1e-10; + const double baseTolerance = 1e-8; + + // /////////////////////////////////////// + // Create the grid + // /////////////////////////////////////// + using GridType = UGGrid<dim>; + + std::shared_ptr<GridType> grid = GmshReader<GridType>::read("grids/irregular-square.msh"); + + grid->globalRefine(numLevels-1); + + using GridView = GridType::LeafGridView; + GridView gridView = grid->leafGridView(); + + ////////////////////////////////////////////////////////////////////////////////// + // Construct the scalar function space basis corresponding to the GFE space + ////////////////////////////////////////////////////////////////////////////////// + + using FEBasis = Functions::LagrangeBasis<GridView, order>; + FEBasis feBasis(gridView); + SolutionType x(feBasis.size()); + + /////////////////////////////////////////// + // Determine Dirichlet values + /////////////////////////////////////////// + BitSetVector<1> dirichletVertices(gridView.size(dim), false); + const GridView::IndexSet& indexSet = gridView.indexSet(); + + // Make predicate function that computes which vertices are on the Dirichlet boundary, + // based on the vertex positions. + auto dirichletVerticesPredicate = [](FieldVector<double,dim> x) + { + return (x[0] < -4.9999 or x[0] > 4.9999 or x[1] < -4.9999 or x[1] > 4.9999); + }; + + for (auto&& vertex : vertices(gridView)) + { + bool isDirichlet = dirichletVerticesPredicate(vertex.geometry().corner(0)); + dirichletVertices[indexSet.index(vertex)] = isDirichlet; + } + + BoundaryPatch<GridView> dirichletBoundary(gridView, dirichletVertices); + BitSetVector<blocksize> dirichletNodes(feBasis.size(), false); + constructBoundaryDofs(dirichletBoundary,feBasis,dirichletNodes); + + + //////////////////////////// + // Initial iterate + //////////////////////////// + + // The inverse stereographic projection through the north pole + auto initialIterateFunction = [](FieldVector<double,dim> x) -> TargetSpace::CoordinateType + { + auto normSquared = x.two_norm2(); + return {2*x[0] / (normSquared+1), 2*x[1] / (normSquared+1), (normSquared-1)/ (normSquared+1)}; + }; + + std::vector<TargetSpace::CoordinateType> v; + using namespace Functions::BasisFactory; + + auto powerBasis = makeBasis( + gridView, + power<TargetSpace::CoordinateType::dimension>( + lagrange<order>(), + blockedInterleaved() + )); + + Dune::Functions::interpolate(powerBasis, v, initialIterateFunction); + + for (size_t i=0; i<x.size(); i++) + x[i] = v[i]; + + ////////////////////////////////////////////////////////////// + // Create an assembler for the Harmonic Energy Functional + ////////////////////////////////////////////////////////////// + + using ATargetSpace = TargetSpace::rebind<adouble>::other; + //using GeodesicInterpolationRule = LocalGeodesicFEFunction<dim, double, FEBasis::LocalView::Tree::FiniteElement, ATargetSpace>; + using ProjectedInterpolationRule = GFE::LocalProjectedFEFunction<dim, double, FEBasis::LocalView::Tree::FiniteElement, ATargetSpace>; + + //std::shared_ptr<GFE::LocalEnergy<FEBasis,ATargetSpace> > localEnergy = std::make_shared<HarmonicEnergy<FEBasis, GeodesicInterpolationRule, ATargetSpace> >(); + std::shared_ptr<GFE::LocalEnergy<FEBasis,ATargetSpace> > localEnergy = std::make_shared<HarmonicEnergy<FEBasis, ProjectedInterpolationRule, ATargetSpace> >(); + + LocalGeodesicFEADOLCStiffness<FEBasis,TargetSpace> localGFEADOLCStiffness(localEnergy.get()); + + GeodesicFEAssembler<FEBasis,TargetSpace> assembler(feBasis, &localGFEADOLCStiffness); + + /////////////////////////////////////////////////// + // Create a Riemannian trust-region solver + /////////////////////////////////////////////////// + + RiemannianTrustRegionSolver<FEBasis,TargetSpace> solver; + solver.setup(*grid, + &assembler, + x, + dirichletNodes, + tolerance, + maxTrustRegionSteps, + initialTrustRegionRadius, + multigridIterations, + mgTolerance, + 3, 3, 1, // Multigrid V-cycle + baseIterations, + baseTolerance, + false); // Do not write intermediate iterates + + /////////////////////////////////////////////////////// + // Solve! + /////////////////////////////////////////////////////// + + solver.setInitialIterate(x); + solver.solve(); + + x = solver.getSol(); + + std::size_t expectedFinalIteration = 12; + if (solver.getStatistics().finalIteration != expectedFinalIteration) + { + std::cerr << "Trust-region solver did " << solver.getStatistics().finalIteration+1 + << " iterations, instead of the expected '" << expectedFinalIteration+1 << "'!" << std::endl; + return 1; + } + + double expectedEnergy = 12.2927849; + if ( std::abs(solver.getStatistics().finalEnergy - expectedEnergy) > 1e-7) + { + std::cerr << std::setprecision(9); + std::cerr << "Final energy is " << solver.getStatistics().finalEnergy + << " but '" << expectedEnergy << "' was expected!" << std::endl; + return 1; + } + + return 0; +} -- GitLab