/** * \copyright * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) * Distributed under a Modified BSD License. * See accompanying file LICENSE.txt or * http://www.opengeosys.org/project/license * */ #include #include "MathLib/LinAlg/LinAlg.h" #include "MeshLib/IO/writeMeshToFile.h" #include "MeshLib/MeshGenerators/MeshGenerator.h" #include "NumLib/DOF/DOFTableUtil.h" #include "NumLib/DOF/MatrixProviderUser.h" #include "NumLib/Extrapolation/ExtrapolatableElementCollection.h" #include "NumLib/Extrapolation/Extrapolator.h" #include "NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h" #include "NumLib/Fem/FiniteElement/TemplateIsoparametric.h" #include "NumLib/Fem/InitShapeMatrices.h" #include "NumLib/Fem/ShapeMatrixPolicy.h" #include "NumLib/Function/Interpolation.h" #include "NumLib/NumericsConfig.h" #include "ProcessLib/Utils/CreateLocalAssemblers.h" #include "ProcessLib/Utils/LocalDataInitializer.h" #include "Tests/VectorUtils.h" namespace ExtrapolationTest { template void interpolateNodalValuesToIntegrationPoints( std::vector const& local_nodal_values, std::vector> const& shape_matrices, std::vector& interpolated_values) { for (unsigned ip = 0; ip < shape_matrices.size(); ++ip) { NumLib::shapeFunctionInterpolate( local_nodal_values, shape_matrices[ip].N, interpolated_values[ip]); } } class LocalAssemblerDataInterface : public NumLib::ExtrapolatableElement { public: virtual void interpolateNodalValuesToIntegrationPoints( std::vector const& local_nodal_values) = 0; virtual std::vector const& getStoredQuantity( const double /*t*/, std::vector const& /*x*/, std::vector const& /*dof_table*/, std::vector& /*cache*/) const = 0; virtual std::vector const& getDerivedQuantity( const double /*t*/, std::vector const& /*x*/, std::vector const& /*dof_table*/, std::vector& cache) const = 0; }; using IntegrationPointValuesMethod = std::vector const& ( LocalAssemblerDataInterface::*)(const double /*t*/, std::vector const& /*x*/, std::vector< NumLib:: LocalToGlobalIndexMap const*> const& /*dof_table*/ , std::vector& /*cache*/) const; template class LocalAssemblerData : public LocalAssemblerDataInterface { using ShapeMatricesType = ShapeMatrixPolicyType; using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices; public: LocalAssemblerData(MeshLib::Element const& e, std::size_t const /*local_matrix_size*/, bool is_axially_symmetric, unsigned const integration_order) : _shape_matrices(NumLib::initShapeMatrices< ShapeFunction, ShapeMatricesType, GlobalDim>( e, is_axially_symmetric, IntegrationMethod{integration_order})), _int_pt_values(_shape_matrices.size()) { } Eigen::Map getShapeMatrix( const unsigned integration_point) const override { auto const& N = _shape_matrices[integration_point].N; // assumes N is stored contiguously in memory return Eigen::Map(N.data(), N.size()); } std::vector const& getStoredQuantity( const double /*t*/, std::vector const& /*x*/, std::vector const& /*dof_table*/, std::vector& /*cache*/) const override { return _int_pt_values; } std::vector const& getDerivedQuantity( const double /*t*/, std::vector const& /*x*/, std::vector const& /*dof_table*/, std::vector& cache) const override { cache.clear(); for (auto value : _int_pt_values) { cache.push_back(2.0 * value); } return cache; } void interpolateNodalValuesToIntegrationPoints( std::vector const& local_nodal_values) override { ExtrapolationTest::interpolateNodalValuesToIntegrationPoints( local_nodal_values, _shape_matrices, _int_pt_values); } private: std::vector> _shape_matrices; std::vector _int_pt_values; }; class ExtrapolationTestProcess { public: using LocalAssembler = LocalAssemblerDataInterface; using ExtrapolatorInterface = NumLib::Extrapolator; using ExtrapolatorImplementation = NumLib::LocalLinearLeastSquaresExtrapolator; ExtrapolationTestProcess(MeshLib::Mesh const& mesh, unsigned const integration_order) : _integration_order(integration_order), _mesh_subset_all_nodes(mesh, mesh.getNodes()) { std::vector all_mesh_subsets{ _mesh_subset_all_nodes}; _dof_table = std::make_unique( std::move(all_mesh_subsets), NumLib::ComponentOrder::BY_COMPONENT); // Passing _dof_table works, because this process has only one variable // and the variable has exactly one component. _extrapolator = std::make_unique(*_dof_table); // createAssemblers(mesh); ProcessLib::createLocalAssemblers( mesh.getDimension(), mesh.getElements(), *_dof_table, 1, _local_assemblers, mesh.isAxiallySymmetric(), _integration_order); } void interpolateNodalValuesToIntegrationPoints( GlobalVector const& global_nodal_values) const { auto cb = [](std::size_t id, LocalAssembler& loc_asm, NumLib::LocalToGlobalIndexMap const& dof_table, GlobalVector const& x) { auto const indices = NumLib::getIndices(id, dof_table); auto const local_x = x.get(indices); loc_asm.interpolateNodalValuesToIntegrationPoints(local_x); }; GlobalExecutor::executeDereferenced(cb, _local_assemblers, *_dof_table, global_nodal_values); } std::pair extrapolate( IntegrationPointValuesMethod method, const double t, std::vector const& x) const { auto const extrapolatables = NumLib::makeExtrapolatable(_local_assemblers, method); _extrapolator->extrapolate(1, extrapolatables, t, x, {_dof_table.get()}); _extrapolator->calculateResiduals(1, extrapolatables, t, x, {_dof_table.get()}); return {&_extrapolator->getNodalValues(), &_extrapolator->getElementResiduals()}; } private: unsigned const _integration_order; MeshLib::MeshSubset _mesh_subset_all_nodes; std::unique_ptr _dof_table; std::vector> _local_assemblers; std::unique_ptr _extrapolator; }; void extrapolate( ExtrapolationTestProcess const& pcs, IntegrationPointValuesMethod method, std::vector const& expected_extrapolated_global_nodal_values, std::size_t const nnodes, std::size_t const nelements) { namespace LinAlg = MathLib::LinAlg; auto const tolerance_dx = 31.0 * std::numeric_limits::epsilon(); auto const tolerance_res = 15.0 * std::numeric_limits::epsilon(); const double t = 0.0; auto const result = pcs.extrapolate(method, t, expected_extrapolated_global_nodal_values); auto const& x_extra = *result.first; auto const& residual = *result.second; ASSERT_EQ(nnodes, x_extra.size()); ASSERT_EQ(nelements, residual.size()); auto const res_norm = LinAlg::normMax(residual); DBUG("maximum norm of residual: {:g}", res_norm); EXPECT_GT(tolerance_res, res_norm); auto delta_x = MathLib::MatrixVectorTraits::newInstance( *expected_extrapolated_global_nodal_values[0]); LinAlg::axpy(*delta_x, -1.0, x_extra); // delta_x = x_expected - x_extra auto const dx_norm = LinAlg::normMax(*delta_x); DBUG("maximum norm of delta x: {:g}", dx_norm); EXPECT_GT(tolerance_dx, dx_norm); } } // namespace ExtrapolationTest #ifndef USE_PETSC TEST(NumLib, Extrapolation) #else TEST(NumLib, DISABLED_Extrapolation) #endif { /* In this test a random vector x of nodal values is created. * This vector is interpolated to the integration points using each * element's the shape functions. * Afterwards the integration point values y are extrapolated to the mesh * nodes again. * Since y have been obtained from x via interpolation, it is expected, that * the interpolation result nearly exactly matches the original nodal values * x. */ const double mesh_length = 1.0; const std::size_t mesh_elements_in_each_direction = 5; // generate mesh std::unique_ptr mesh( MeshLib::MeshGenerator::generateRegularHexMesh( mesh_length, mesh_elements_in_each_direction)); for (unsigned integration_order : {2, 3, 4}) { namespace LinAlg = MathLib::LinAlg; auto const nnodes = mesh->getNumberOfNodes(); auto const nelements = mesh->getNumberOfElements(); DBUG("number of nodes: {:d}, number of elements: {:d}", nnodes, nelements); ExtrapolationTest::ExtrapolationTestProcess pcs(*mesh, integration_order); // generate random nodal values MathLib::MatrixSpecifications spec{nnodes, nnodes, nullptr, nullptr}; auto const x = MathLib::MatrixVectorTraits::newInstance(spec); fillVectorRandomly(*x); pcs.interpolateNodalValuesToIntegrationPoints(*x); // test extrapolation of a quantity that is stored in the local // assembler std::vector xs{x.get()}; ExtrapolationTest::extrapolate( pcs, &ExtrapolationTest::LocalAssemblerDataInterface::getStoredQuantity, xs, nnodes, nelements); // expect 2*x as extraplation result for derived quantity auto two_x = MathLib::MatrixVectorTraits::newInstance(*x); LinAlg::axpy(*two_x, 1.0, *x); // two_x = x + x // test extrapolation of a quantity that is derived from some // integration point values std::vector two_xs{two_x.get()}; ExtrapolationTest::extrapolate( pcs, &ExtrapolationTest::LocalAssemblerDataInterface::getDerivedQuantity, two_xs, nnodes, nelements); } }