From b0519a3ef8bb566439c087194392506f07665254 Mon Sep 17 00:00:00 2001
From: Christoph Lehmann <christoph.lehmann@ufz.de>
Date: Mon, 17 Oct 2022 07:30:07 +0200
Subject: [PATCH] [T] Test reflection functionality

---
 Tests/ProcessLib/TestReflectIPData.cpp | 487 +++++++++++++++++++++++++
 1 file changed, 487 insertions(+)
 create mode 100644 Tests/ProcessLib/TestReflectIPData.cpp

diff --git a/Tests/ProcessLib/TestReflectIPData.cpp b/Tests/ProcessLib/TestReflectIPData.cpp
new file mode 100644
index 00000000000..3dd1f8b47c7
--- /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);
+}
-- 
GitLab