diff --git a/dune/gfe/riemanniantrsolver.cc b/dune/gfe/riemanniantrsolver.cc index 778effaa91120f11ee753d52aadeb13c716011ca..8feb344f72223c699bc0ad7ca13b35801efaaf1c 100644 --- a/dune/gfe/riemanniantrsolver.cc +++ b/dune/gfe/riemanniantrsolver.cc @@ -131,11 +131,21 @@ setup(const GridType& grid, LocalMapper, LocalMapper> matrixComm(*globalMapper_, grid_->leafGridView(), localMapper, localMapper, 0); - auto A = std::make_shared<ScalarMatrixType>(matrixComm.reduceAdd(localA)); +#if !DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + auto +#endif + A = std::make_shared<ScalarMatrixType>(matrixComm.reduceAdd(localA)); #else - auto A = std::make_shared<ScalarMatrixType>(localA); +#if !DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + auto +#endif + A = std::make_shared<ScalarMatrixType>(localA); #endif +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + h1SemiNorm_ = std::make_shared<H1SemiNorm<CorrectionType> >(*A); +#else h1SemiNorm_ = std::make_shared<H1SemiNorm<CorrectionType> >(A); +#endif innerSolver_ = std::make_shared<::LoopSolver<CorrectionType> >(mmgStep, innerIterations_, @@ -153,12 +163,19 @@ setup(const GridType& grid, operatorAssembler.assemble(massStiffness, localMassMatrix); +#if !DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + auto +#endif #if HAVE_MPI - auto massMatrix = std::make_shared<ScalarMatrixType>(matrixComm.reduceAdd(localMassMatrix)); + massMatrix = std::make_shared<ScalarMatrixType>(matrixComm.reduceAdd(localMassMatrix)); #else - auto massMatrix = std::make_shared<ScalarMatrixType>(localMassMatrix); + massMatrix = std::make_shared<ScalarMatrixType>(localMassMatrix); #endif +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + l2Norm_ = std::make_shared<H1SemiNorm<CorrectionType> >(*massMatrix); +#else l2Norm_ = std::make_shared<H1SemiNorm<CorrectionType> >(massMatrix); +#endif // Write all intermediate solutions, if requested if (instrumented_ @@ -351,7 +368,8 @@ void RiemannianTrustRegionSolver<Basis,TargetSpace>::solve() localMapper, 0); #endif - for (int i=0; i<maxTrustRegionSteps_; i++) { + auto& i = statistics_.finalIteration; + for (i=0; i<maxTrustRegionSteps_; i++) { /* std::cout << "current iterate:\n"; for (size_t j=0; j<x_.size(); j++) @@ -643,4 +661,6 @@ void RiemannianTrustRegionSolver<Basis,TargetSpace>::solve() // ////////////////////////////////////////////// if (instrumented_) fclose(fp); + + statistics_.finalEnergy = oldEnergy; } diff --git a/dune/gfe/riemanniantrsolver.hh b/dune/gfe/riemanniantrsolver.hh index add3d56d61c31853ab262531d23a93892bd08b1a..a3f5ec3d28a80c2cfdef844ccfb3d5f19e5a7670 100644 --- a/dune/gfe/riemanniantrsolver.hh +++ b/dune/gfe/riemanniantrsolver.hh @@ -92,6 +92,16 @@ class RiemannianTrustRegionSolver typedef typename MapperFactory<typename Basis::GridView,Basis>::LocalMapper LocalMapper; #endif + /** \brief Records information about the last run of the RiemannianTrustRegionSolver + * + * This is used primarily for unit testing. + */ + struct Statistics + { + std::size_t finalIteration; + + field_type finalEnergy; + }; public: @@ -144,6 +154,8 @@ public: SolutionType getSol() const {return x_;} + const Statistics& getStatistics() const {return statistics_;} + protected: #if HAVE_MPI @@ -163,7 +175,7 @@ protected: Dune::FieldVector<double,blocksize> scaling_; /** \brief Maximum number of trust-region steps */ - int maxTrustRegionSteps_; + std::size_t maxTrustRegionSteps_; /** \brief Maximum number of multigrid iterations */ int innerIterations_; @@ -195,6 +207,14 @@ protected: /** \brief If set to true we log convergence speed and other stuff */ bool instrumented_; + /** \brief Store information about solver runs for unit testing */ + Statistics statistics_; + +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + std::shared_ptr<Dune::BCRSMatrix<Dune::FieldMatrix<double,1,1> > > A; + std::shared_ptr<Dune::BCRSMatrix<Dune::FieldMatrix<double,1,1> > > massMatrix; +#endif + }; #include "riemanniantrsolver.cc" diff --git a/dune/gfe/sumcosseratenergy.hh b/dune/gfe/sumcosseratenergy.hh index 2011d909604194c646996056ea814ad55253d8ca..9cf25de0b61431351e1667e7bba8d9b4658e4508 100644 --- a/dune/gfe/sumcosseratenergy.hh +++ b/dune/gfe/sumcosseratenergy.hh @@ -27,7 +27,11 @@ public: * \param elasticEnergy The elastic energy * \param cosseratEnergy The cosserat energy */ +#if DUNE_VERSION_LT(DUNE_ELASTICITY, 2, 7) + SumCosseratEnergy(std::shared_ptr<LocalFEStiffness<GridView,LocalFiniteElement,std::vector<Dune::FieldVector<field_type,dim> > > > elasticEnergy, +#else SumCosseratEnergy(std::shared_ptr<Elasticity::LocalEnergy<GridView,LocalFiniteElement,std::vector<Dune::FieldVector<field_type,dim> > > > elasticEnergy, +#endif std::shared_ptr<GFE::LocalEnergy<Basis, TargetSpace>> cosseratEnergy) : elasticEnergy_(elasticEnergy), @@ -49,7 +53,11 @@ public: private: +#if DUNE_VERSION_LT(DUNE_ELASTICITY, 2, 7) + std::shared_ptr<LocalFEStiffness<GridView,LocalFiniteElement,std::vector<Dune::FieldVector<field_type,dim> > > > elasticEnergy_; +#else std::shared_ptr<Elasticity::LocalEnergy<GridView,LocalFiniteElement,std::vector<Dune::FieldVector<field_type,dim> > > > elasticEnergy_; +#endif std::shared_ptr<GFE::LocalEnergy<Basis, TargetSpace> > cosseratEnergy_; }; diff --git a/src/cosserat-continuum.cc b/src/cosserat-continuum.cc index 832d3955894819ccfb705c659bfe8a9cf5870082..e34df722a89618c99398b706ec56d4a6ed30295a 100644 --- a/src/cosserat-continuum.cc +++ b/src/cosserat-continuum.cc @@ -304,10 +304,18 @@ int main (int argc, char *argv[]) try orientationDirichletDofs[i][j] = true; #else BitSetVector<1> dirichletNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + constructBoundaryDofs(dirichletBoundary,fufemFeBasis,dirichletNodes); +#else constructBoundaryDofs(dirichletBoundary,feBasis,dirichletNodes); +#endif BitSetVector<1> neumannNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + constructBoundaryDofs(neumannBoundary,fufemFeBasis,neumannNodes); +#else constructBoundaryDofs(neumannBoundary,feBasis,neumannNodes); +#endif BitSetVector<blocksize> dirichletDofs(feBasis.size(), false); for (size_t i=0; i<feBasis.size(); i++) diff --git a/src/film-on-substrate.cc b/src/film-on-substrate.cc index fd9e1fbe4fdaa05ccc06a5eac834b2d739a3b221..dbd81056278ea589e34e932c598e18bc23183136 100644 --- a/src/film-on-substrate.cc +++ b/src/film-on-substrate.cc @@ -14,6 +14,7 @@ #include <dune/common/bitsetvector.hh> #include <dune/common/parametertree.hh> #include <dune/common/parametertreeparser.hh> +#include <dune/common/version.hh> #include <dune/grid/uggrid.hh> #include <dune/grid/utility/structuredgridfactory.hh> @@ -23,7 +24,9 @@ #include <dune/elasticity/materials/exphenckyenergy.hh> #include <dune/elasticity/materials/henckyenergy.hh> +#if !DUNE_VERSION_LT(DUNE_ELASTICITY, 2, 7) #include <dune/elasticity/materials/mooneyrivlinenergy.hh> +#endif #include <dune/elasticity/materials/neohookeenergy.hh> #include <dune/elasticity/materials/neumannenergy.hh> #include <dune/elasticity/materials/sumenergy.hh> @@ -59,6 +62,19 @@ const int dim = WORLD_DIM; const int order = 1; +#if DUNE_VERSION_LT(DUNE_COMMON, 2, 7) +template<> +struct Dune::MathematicalConstants<adouble> +{ + static const adouble pi () + { + using std::acos; + static const adouble pi = acos( adouble( -1 ) ); + return pi; + } +}; +#endif + //differentiation method typedef adouble ValueType; @@ -214,16 +230,13 @@ int main (int argc, char *argv[]) try PythonFunction<FieldVector<double,dim>, bool> pythonSurfaceShellVertices(Python::evaluate(lambda)); for (auto&& v : vertices(gridView)) { - bool isDirichlet; - pythonDirichletVertices.evaluate(v.geometry().corner(0), isDirichlet); + bool isDirichlet = pythonDirichletVertices(v.geometry().corner(0)); dirichletVertices[indexSet.index(v)] = isDirichlet; - bool isNeumann; - pythonNeumannVertices.evaluate(v.geometry().corner(0), isNeumann); + bool isNeumann = pythonNeumannVertices(v.geometry().corner(0)); neumannVertices[indexSet.index(v)] = isNeumann; - bool isSurfaceShell; - pythonSurfaceShellVertices.evaluate(v.geometry().corner(0), isSurfaceShell); + bool isSurfaceShell = pythonSurfaceShellVertices(v.geometry().corner(0)); surfaceShellVertices[indexSet.index(v)] = isSurfaceShell; } @@ -236,13 +249,27 @@ int main (int argc, char *argv[]) try BitSetVector<1> dirichletNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_FUFEM, 2, 7) + using FufemFEBasis = DuneFunctionsBasis<FEBasis>; + FufemFEBasis fufemFeBasis(feBasis); + constructBoundaryDofs(dirichletBoundary,fufemFeBasis,dirichletNodes); +#else constructBoundaryDofs(dirichletBoundary,feBasis,dirichletNodes); +#endif BitSetVector<1> neumannNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_FUFEM, 2, 7) + constructBoundaryDofs(neumannBoundary,fufemFeBasis,neumannNodes); +#else constructBoundaryDofs(neumannBoundary,feBasis,neumannNodes); +#endif BitSetVector<1> surfaceShellNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_FUFEM, 2, 7) + constructBoundaryDofs(surfaceShellBoundary,fufemFeBasis,surfaceShellNodes); +#else constructBoundaryDofs(surfaceShellBoundary,feBasis,surfaceShellNodes); +#endif BitSetVector<blocksize> dirichletDofs(feBasis.size(), false); for (size_t i=0; i<feBasis.size(); i++) @@ -336,7 +363,11 @@ int main (int argc, char *argv[]) try // Assembler using ADOL-C std::cout << "Selected energy is: " << parameterSet.get<std::string>("energy") << std::endl; +#if DUNE_VERSION_LT(DUNE_ELASTICITY, 2, 7) + std::shared_ptr<LocalFEStiffness<GridView, +#else std::shared_ptr<Elasticity::LocalEnergy<GridView, +#endif FEBasis::LocalView::Tree::FiniteElement, std::vector<Dune::FieldVector<ValueType, dim>> > > elasticEnergy; @@ -344,10 +375,12 @@ int main (int argc, char *argv[]) try elasticEnergy = std::make_shared<StVenantKirchhoffEnergy<GridView, FEBasis::LocalView::Tree::FiniteElement, ValueType> >(materialParameters); +#if !DUNE_VERSION_LT(DUNE_ELASTICITY, 2, 7) if (parameterSet.get<std::string>("energy") == "mooneyrivlin") elasticEnergy = std::make_shared<MooneyRivlinEnergy<GridView, FEBasis::LocalView::Tree::FiniteElement, ValueType> >(materialParameters); +#endif if (parameterSet.get<std::string>("energy") == "neohooke") elasticEnergy = std::make_shared<NeoHookeEnergy<GridView, @@ -477,4 +510,4 @@ int main (int argc, char *argv[]) try } } catch (Exception& e) { std::cout << e.what() << std::endl; -} \ No newline at end of file +} diff --git a/src/harmonicmaps.cc b/src/harmonicmaps.cc index e8c84d8be0c97181fd0d8d3fa97c4853673fb7ea..78cbc34011206c5a1443bdb5aecbf52bdf257457 100644 --- a/src/harmonicmaps.cc +++ b/src/harmonicmaps.cc @@ -234,8 +234,12 @@ int main (int argc, char *argv[]) BitSetVector<blocksize> dirichletNodes(feBasis.size(), false); +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + DuneFunctionsBasis<FEBasis> fufemBasis(feBasis); + constructBoundaryDofs(dirichletBoundary,fufemBasis,dirichletNodes); +#else constructBoundaryDofs(dirichletBoundary,feBasis,dirichletNodes); - +#endif // ////////////////////////// // Initial iterate diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8b4c20ff302f3f6ba957e044fa1a5da58899bcaf..e28375730ca3df2d1322b4ba0f9d0586ba21583d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,3 +19,12 @@ foreach(_test ${TESTS}) dune_add_test(SOURCES ${_test}.cc) target_compile_options(${_test} PRIVATE -g) endforeach() + +# Run distributed tests +dune_add_test(SOURCES harmonicmaptest.cc + MPI_RANKS 1 4 + TIMEOUT 600 + CMAKE_GUARD MPI_FOUND) + +# Copy the example grid used for testing into the build dir +file(COPY grids/irregular-square.msh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/grids) diff --git a/test/adolctest.cc b/test/adolctest.cc index bb04d8fa380a0c094b7f6510b95e04a471a9def6..be7ac34c8dfa24d1331cedd4fafc207f054537c8 100644 --- a/test/adolctest.cc +++ b/test/adolctest.cc @@ -412,6 +412,8 @@ void compareMatrices(const Matrix<FieldMatrix<double,N,N> >& matrixA, std::strin int main (int argc, char *argv[]) try { + MPIHelper::instance(argc, argv); + typedef std::vector<TargetSpace> SolutionType; enum { embeddedBlocksize = TargetSpace::EmbeddedTangentVector::dimension }; enum { blocksize = TargetSpace::TangentVector::dimension }; diff --git a/test/grids/irregular-square.msh b/test/grids/irregular-square.msh new file mode 100644 index 0000000000000000000000000000000000000000..2cf3522da82264d2dd27e793e8c217e41e553972 --- /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 0000000000000000000000000000000000000000..2dbe77293ec5fa54031f577c2bc45bbddf876644 --- /dev/null +++ b/test/harmonicmaptest.cc @@ -0,0 +1,205 @@ +#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>; + +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + std::shared_ptr<GridType> grid(GmshReader<GridType>::read("grids/irregular-square.msh")); +#else + std::shared_ptr<GridType> grid = GmshReader<GridType>::read("grids/irregular-square.msh"); +#endif + + grid->globalRefine(numLevels-1); + + grid->loadBalance(); + + 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); +#if DUNE_VERSION_LT(DUNE_GEOMETRY, 2, 7) + DuneFunctionsBasis<FEBasis> fufemBasis(feBasis); + constructBoundaryDofs(dirichletBoundary,fufemBasis,dirichletNodes); +#else + constructBoundaryDofs(dirichletBoundary,feBasis,dirichletNodes); +#endif + + //////////////////////////// + // 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; +}