diff --git a/Tests/ProcessLib/TestReflectIPData.cpp b/Tests/ProcessLib/TestReflectIPData.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3dd1f8b47c7bcc5a40e32dacf39c3891451267f8 --- /dev/null +++ b/Tests/ProcessLib/TestReflectIPData.cpp @@ -0,0 +1,487 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2022, 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 <gmock/gmock-matchers.h> +#include <gtest/gtest.h> + +#include <numeric> +#include <random> + +#include "ProcessLib/Reflection/ReflectionIPData.h" + +template <int Dim> +struct Level3 +{ + MathLib::KelvinVector::KelvinVectorType<Dim> kelvin3; + Eigen::Vector<double, Dim> vector3; + double scalar3; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"kelvin3", &Level3::kelvin3}, + ReflectionData{"vector3", &Level3::vector3}, + ReflectionData{"scalar3", &Level3::scalar3}}; + } +}; + +template <int Dim> +struct Level3b +{ + double scalar3b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"scalar3b", &Level3b::scalar3b}}; + } +}; + +template <int Dim> +struct Level2 +{ + Level3<Dim> level3; + Level3b<Dim> level3b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{&Level2::level3}, + ReflectionData{&Level2::level3b}}; + } +}; + +template <int Dim> +struct Level2b +{ + double scalar2b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"scalar2b", &Level2b::scalar2b}}; + } +}; + +template <int Dim> +struct Level1 +{ + MathLib::KelvinVector::KelvinVectorType<Dim> kelvin1; + Eigen::Vector<double, Dim> vector1; + double scalar1; + Level2<Dim> level2; + Level2b<Dim> level2b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"kelvin1", &Level1::kelvin1}, + ReflectionData{"vector1", &Level1::vector1}, + ReflectionData{"scalar1", &Level1::scalar1}, + ReflectionData{&Level1::level2}, + ReflectionData{&Level1::level2b}}; + } +}; + +template <int Dim> +struct Level1b +{ + double scalar1b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"scalar1b", &Level1b::scalar1b}}; + } +}; + +template <int Dim> +struct LocAsmIF +{ + explicit LocAsmIF(unsigned const num_ips) + : ip_data_scalar(num_ips), + ip_data_vector(num_ips), + ip_data_kelvin(num_ips), + ip_data_level1(num_ips), + ip_data_level1b(num_ips) + { + } + + std::size_t numIPs() const { return ip_data_scalar.size(); } + + std::vector<double> ip_data_scalar; + std::vector<Eigen::Vector<double, Dim>> ip_data_vector; + std::vector<MathLib::KelvinVector::KelvinVectorType<Dim>> ip_data_kelvin; + std::vector<Level1<Dim>> ip_data_level1; + std::vector<Level1b<Dim>> ip_data_level1b; + + static auto reflect() + { + using namespace ProcessLib::Reflection; + return std::tuple{ReflectionData{"scalar", &LocAsmIF::ip_data_scalar}, + ReflectionData{"vector", &LocAsmIF::ip_data_vector}, + ReflectionData{"kelvin", &LocAsmIF::ip_data_kelvin}, + ReflectionData{&LocAsmIF::ip_data_level1}, + ReflectionData{&LocAsmIF::ip_data_level1b}}; + } +}; + +template <int dim> +struct NumCompAndFunction +{ + unsigned num_comp; + std::function<std::vector<double>(LocAsmIF<dim> const&)> function; +}; + +// Prepares scalar IP data for the passed local assembler. +// +// The IP data are a sequence of double values starting at the passed start +// value and incremented by one for each integration point. +// +// The location of the prepared data is specified by the IP data accessor +// callback function. +// +// Returns the expected data for use in unit test checks. +template <int dim> +std::vector<double> initScalar(LocAsmIF<dim>& loc_asm, + double const start_value, + auto const ip_data_accessor, + bool const for_read_test) +{ + auto const num_int_pts = loc_asm.numIPs(); + + // init ip data in the local assembler + if (for_read_test) + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = start_value + ip; + } + } + else + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = + std::numeric_limits<double>::quiet_NaN(); + } + } + + // prepare reference data + std::vector<double> scalar_expected(num_int_pts); + iota(begin(scalar_expected), end(scalar_expected), start_value); + return scalar_expected; +} + +// Prepares vector valued IP data for the passed local assembler. +// +// The IP data are a sequence of double values starting at the passed start +// value and incremented by one for each integration point and vector +// component. +// +// The location of the prepared data is specified by the IP data accessor +// callback function. +// +// Returns the expected data for use in unit test checks. +template <int dim> +std::vector<double> initVector(LocAsmIF<dim>& loc_asm, + double const start_value, + auto const ip_data_accessor, + bool const for_read_test) +{ + auto const num_int_pts = loc_asm.numIPs(); + + // init ip data in the local assembler + if (for_read_test) + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = + Eigen::Vector<double, dim>::LinSpaced( + dim, ip * dim + start_value, + ip * dim + start_value - 1 + dim); + } + } + else + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = + Eigen::Vector<double, dim>::Constant( + std::numeric_limits<double>::quiet_NaN()); + } + } + + // prepare reference data + std::vector<double> vector_expected(num_int_pts * dim); + iota(begin(vector_expected), end(vector_expected), start_value); + return vector_expected; +} + +// Prepares Kelvin vector valued IP data for the passed local assembler. +// +// The IP data are a sequence of double values starting at the passed start +// value and incremented by one for each integration point and Kelvin vector +// component. +// +// The location of the prepared data is specified by the IP data accessor +// callback function. +// +// Returns the expected data for use in unit test checks. +template <int dim> +std::vector<double> initKelvin(LocAsmIF<dim>& loc_asm, + double const start_value, + auto const ip_data_accessor, + bool const for_read_test) +{ + auto constexpr kv_size = + MathLib::KelvinVector::kelvin_vector_dimensions(dim); + + auto const num_int_pts = loc_asm.numIPs(); + + // init ip data in the local assembler + if (for_read_test) + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = + MathLib::KelvinVector::symmetricTensorToKelvinVector( + Eigen::Vector<double, kv_size>::LinSpaced( + kv_size, ip * kv_size + start_value, + ip * kv_size + start_value - 1 + kv_size)); + } + } + else + { + for (std::size_t ip = 0; ip < num_int_pts; ++ip) + { + ip_data_accessor(loc_asm, ip) = + Eigen::Vector<double, kv_size>::Constant( + std::numeric_limits<double>::quiet_NaN()); + } + } + + // prepare reference data + std::vector<double> vector_expected(num_int_pts * kv_size); + iota(begin(vector_expected), end(vector_expected), start_value); + return vector_expected; +} + +template <int dim> +struct ReferenceData +{ + std::vector<double> scalar; + std::vector<double> vector; + std::vector<double> kelvin; + + std::vector<double> scalar1; + std::vector<double> vector1; + std::vector<double> kelvin1; + + std::vector<double> scalar3; + std::vector<double> vector3; + std::vector<double> kelvin3; + + std::vector<double> scalar1b; + std::vector<double> scalar2b; + std::vector<double> scalar3b; + + static ReferenceData<dim> create(LocAsmIF<dim>& loc_asm, + bool const for_read_test) + { + std::random_device ran_dev; + std::mt19937 ran_gen(ran_dev()); + std::uniform_real_distribution<> ran_dist(1.0, 2.0); + auto start_value = [&]() { return ran_dist(ran_gen); }; + + ReferenceData<dim> ref; + + // level 0 - data preparation ////////////////////////////////////////// + + ref.scalar = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_scalar[ip]; + }, + for_read_test); + + ref.vector = initVector( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_vector[ip]; + }, + for_read_test); + + ref.kelvin = initKelvin( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_kelvin[ip]; + }, + for_read_test); + + // level 1 - data preparation ////////////////////////////////////////// + + ref.scalar1 = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].scalar1; + }, + for_read_test); + + ref.vector1 = initVector( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].vector1; + }, + for_read_test); + + ref.kelvin1 = initKelvin( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].kelvin1; + }, + for_read_test); + + // level 3 - data preparation ////////////////////////////////////////// + + ref.scalar3 = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].level2.level3.scalar3; + }, + for_read_test); + + ref.vector3 = initVector( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].level2.level3.vector3; + }, + for_read_test); + + ref.kelvin3 = initKelvin( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].level2.level3.kelvin3; + }, + for_read_test); + + // b levels - data preparation ///////////////////////////////////////// + // b levels test that the reflection implementation recurses on multiple + // members, not only on one. + + ref.scalar1b = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1b[ip].scalar1b; + }, + for_read_test); + + ref.scalar2b = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].level2b.scalar2b; + }, + for_read_test); + + ref.scalar3b = initScalar( + loc_asm, start_value(), + [](auto& loc_asm, unsigned const ip) -> auto& { + return loc_asm.ip_data_level1[ip].level2.level3b.scalar3b; + }, + for_read_test); + + return ref; + } +}; + +template <class Dim> +struct ProcessLib_ReflectIPData : ::testing::Test +{ + static constexpr auto dim = Dim::value; +}; + +using ProcessLib_ReflectIPData_TestCases = + ::testing::Types<std::integral_constant<int, 2>, + std::integral_constant<int, 3>>; + +TYPED_TEST_SUITE(ProcessLib_ReflectIPData, ProcessLib_ReflectIPData_TestCases); + +TYPED_TEST(ProcessLib_ReflectIPData, ReadTest) +{ + constexpr int dim = TypeParam::value; + auto constexpr kv_size = + MathLib::KelvinVector::kelvin_vector_dimensions(dim); + + using LocAsm = LocAsmIF<dim>; + + std::size_t const num_int_pts = 8; + LocAsm loc_asm(num_int_pts); + + auto const ref = ReferenceData<dim>::create(loc_asm, true); + + // function under test ///////////////////////////////////////////////////// + + std::map<std::string, NumCompAndFunction<dim>> + map_name_to_num_comp_and_function; + + ProcessLib::Reflection::forEachReflectedFlattenedIPDataAccessor<dim, + LocAsm>( + LocAsm::reflect(), + [&map_name_to_num_comp_and_function](std::string const& name, + unsigned const num_comp, + auto&& double_vec_from_loc_asm) + { + EXPECT_FALSE(map_name_to_num_comp_and_function.contains(name)); + map_name_to_num_comp_and_function[name] = { + num_comp, std::move(double_vec_from_loc_asm)}; + }); + + // checks ////////////////////////////////////////////////////////////////// + + auto check = [&map_name_to_num_comp_and_function, &loc_asm]( + std::string const& name, + unsigned const num_comp_expected, + std::vector<double> const& values_expected) + { + auto const it = map_name_to_num_comp_and_function.find(name); + + ASSERT_NE(map_name_to_num_comp_and_function.end(), it) + << "No accessor found for ip data with name '" << name << "'"; + + auto const& [num_comp, fct] = it->second; + + EXPECT_EQ(num_comp_expected, num_comp) + << "Number of components differs for ip data with name '" << name + << "'"; + EXPECT_THAT(fct(loc_asm), + testing::Pointwise(testing::DoubleEq(), values_expected)) + << "Values differ for ip data with name '" << name << "'"; + }; + + // level 0 + check("scalar", 1, ref.scalar); + check("vector", dim, ref.vector); + check("kelvin", kv_size, ref.kelvin); + + // level 1 + check("scalar1", 1, ref.scalar1); + check("vector1", dim, ref.vector1); + check("kelvin1", kv_size, ref.kelvin1); + + // level 3 + check("scalar3", 1, ref.scalar3); + check("vector3", dim, ref.vector3); + check("kelvin3", kv_size, ref.kelvin3); + + // b levels + check("scalar1b", 1, ref.scalar1b); + check("scalar2b", 1, ref.scalar2b); + check("scalar3b", 1, ref.scalar3b); +}