diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3a1fa9e574f722291d8d5129137aec17d0dbe862..18735e31f903a258203f56e7611765d5415df94b 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -17,6 +17,7 @@ APPEND_SOURCE_FILES(TEST_SOURCES MathLib) APPEND_SOURCE_FILES(TEST_SOURCES MeshLib) APPEND_SOURCE_FILES(TEST_SOURCES MeshGeoToolsLib) APPEND_SOURCE_FILES(TEST_SOURCES NumLib) +APPEND_SOURCE_FILES(TEST_SOURCES ProcessLib) if(QT4_FOUND) APPEND_SOURCE_FILES(TEST_SOURCES FileIO_Qt) @@ -41,6 +42,7 @@ target_link_libraries(testrunner MaterialLib MeshLib NumLib + ProcessLib Threads::Threads ) ADD_VTK_DEPENDENCY(testrunner) diff --git a/Tests/ProcessLib/TestJacobianAssembler.cpp b/Tests/ProcessLib/TestJacobianAssembler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f503e371acc64c104e0ce53c081961f6de46f604 --- /dev/null +++ b/Tests/ProcessLib/TestJacobianAssembler.cpp @@ -0,0 +1,575 @@ +/** + * \copyright + * Copyright (c) 2012-2016, 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 <limits> +#include <random> +#include <gtest/gtest.h> + +#include "MathLib/LinAlg/Eigen/EigenMapTools.h" +#include "ProcessLib/LocalAssemblerInterface.h" +#include "ProcessLib/AnalyticalJacobianAssembler.h" +#include "ProcessLib/CentralDifferencesJacobianAssembler.h" + +//! Fills a vector with values whose absolute value is between \c abs_min and +//! \c abs_max. +void fillRandomlyConstrainedAbsoluteValues(std::vector<double>& xs, + double const abs_min, + double const abs_max) +{ + std::random_device rd; + std::mt19937 random_number_generator(rd()); + double const abs_range = abs_max - abs_min; + std::uniform_real_distribution<double> rnd(abs_min, + abs_min + 2.0 * abs_range); + + for (auto& x : xs) { + // v in [ abs_min, abs_min + 2 abs_range ] + auto v = rnd(random_number_generator); + if (v > abs_max) { + // produce negative values + // (v - abs_range) in [ abs_min, abs_max ] + v = -(v - abs_range); + } + x = v; + } +} + +struct MatDiagX +{ + // M = diag(x1, x2, x3, ...) + static void getMat(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + mat.diagonal().noalias() = MathLib::toVector(x_data, x_data.size()); + } + + // dM/dx * y = diag(y1, y2, y3, ...) + static void getDMatDxTimesY(std::vector<double> const& x_data, + std::vector<double> const& y_data, + std::vector<double>& dMdxTy_data) + { + auto dMdxTy = + MathLib::toZeroedMatrix(dMdxTy_data, x_data.size(), x_data.size()); + dMdxTy.diagonal().noalias() = MathLib::toVector(y_data, y_data.size()); + } +}; + +struct VecX +{ + // v = (x1, x2, x3, ...) + static void getVec(std::vector<double> const& x_data, + std::vector<double>& vec_data) + { + vec_data = x_data; + } + + // dv/dx = Identity + static void getDVecDx(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + mat = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, + Eigen::RowMajor>::Identity(x_data.size(), + x_data.size()); + } +}; + +struct MatXY +{ + /* / xx xy xz ... \ + * M = | yx yy yz ... | + * | zx zy zz ... | + * \ ... ... ... ... / + */ + static void getMat(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + for (std::size_t r=0; r<x_data.size(); ++r) { + for (std::size_t c=0; c<x_data.size(); ++c) { + mat(r, c) = x_data[r] * x_data[c]; + } + } + } + + /* / xX xY xZ ... \ + * dM/dx * y = | yX yY yZ ... | + (x . y) . Identity + * | zX zY zZ ... | + * \ ... ... ... ... / + * where x = (x, y, z, ...); y = (X, Y, Z, ...) + */ + static void getDMatDxTimesY(std::vector<double> const& x_data, + std::vector<double> const& y_data, + std::vector<double>& dMdxTy_data) + { + auto const N = x_data.size(); + auto const x_dot_y = + MathLib::toVector(x_data, N).dot(MathLib::toVector(y_data, N)); + auto dMdxTy = + MathLib::toZeroedMatrix(dMdxTy_data, N, N); + for (std::size_t r=0; r<N; ++r) { + for (std::size_t c=0; c<N; ++c) { + dMdxTy(r, c) = x_data[r] * y_data[c]; + } + dMdxTy(r, r) += x_dot_y; + } + } +}; + +struct VecXRevX +{ + // v = (wz, xy, yx, zw) + static void getVec(std::vector<double> const& x_data, + std::vector<double>& vec_data) + { + vec_data = x_data; + auto const N = x_data.size(); + for (std::size_t i=0; i<N; ++i) { + vec_data[i] *= x_data[N-i-1]; + } + } + + /* / z w \ + * dv/dx = | y x | + * | y x | + * \ z w / + */ + static void getDVecDx(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + auto const N = x_data.size(); + for (std::size_t i=0; i<N; ++i) { + mat(i, i) += x_data[N-i-1]; + mat(N-i-1, i) += x_data[N-i-1]; + } + } +}; + +struct MatDiagXSquared +{ + // M = diag(x^2, y^2, z^2, ...) + static void getMat(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + + for (std::size_t i=0; i<x_data.size(); ++i) + mat(i, i) = x_data[i] * x_data[i]; + } + + // dM/dx * y = diag(2*x*X, 2*y*Y, 2*z*Z, ...) + static void getDMatDxTimesY(std::vector<double> const& x_data, + std::vector<double> const& y_data, + std::vector<double>& dMdxTy_data) + { + auto dMdxTy = + MathLib::toZeroedMatrix(dMdxTy_data, x_data.size(), x_data.size()); + + for (std::size_t i=0; i<x_data.size(); ++i) + dMdxTy(i, i) = 2.0 * x_data[i] * y_data[i]; + } +}; + +struct VecXSquared +{ + // v = (x^2, y^2, z^2, ...) + static void getVec(std::vector<double> const& x_data, + std::vector<double>& vec_data) + { + vec_data = x_data; + for (auto& v : vec_data) + v *= v; + } + + // dv/dx = diag(2x, 2y, 2z, ...) + static void getDVecDx(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto mat = + MathLib::toZeroedMatrix(mat_data, x_data.size(), x_data.size()); + for (std::size_t i=0; i< x_data.size(); ++i) { + mat(i, i) = 2.0 * x_data[i]; + } + } +}; + +struct MatXSquaredShifted +{ + /* Tests an asymmetric matrix. + * + * / x^2 y^2 z^2 \ + * M = | z^2 x^2 y^2 | + * \ y^2 z^2 x^2 / + */ + static void getMat(std::vector<double> const& x_data, + std::vector<double>& mat_data) + { + auto const N = x_data.size(); + auto mat = + MathLib::toZeroedMatrix(mat_data, N, N); + for (std::size_t r=0; r<N; ++r) { + for (std::size_t c=0; c<N; ++c) { + auto const i = (r + c) % N; + mat(r, i) = x_data[c] * x_data[c]; + } + } + } + + /* / 2xX 2yY 2zZ \ + * dM/dx * y = | 2xY 2yZ 2zX | + * \ 2xZ 2yX 2zY / + * where x = (x, y, z, ...); y = (X, Y, Z, ...) + */ + static void getDMatDxTimesY(std::vector<double> const& x_data, + std::vector<double> const& y_data, + std::vector<double>& dMdxTy_data) + { + auto const N = x_data.size(); + auto dMdxTy = + MathLib::toZeroedMatrix(dMdxTy_data, N, N); + for (std::size_t r=0; r<N; ++r) { + for (std::size_t c=0; c<N; ++c) { + auto const i = (r + c) % N; + dMdxTy(r, c) = 2.0 * x_data[c] * y_data[i]; + } + } + } +}; + +struct MatVecDiagX +{ + using Mat = MatDiagX; + using Vec = VecX; + static const double tolerance; +}; +const double MatVecDiagX::tolerance = 3e-8; + +struct MatVecDiagXSquared +{ + using Mat = MatDiagXSquared; + using Vec = VecXSquared; + static const double tolerance; +}; +const double MatVecDiagXSquared::tolerance = 2e-7; + +struct MatVecXY +{ + using Mat = MatXY; + using Vec = VecXRevX; + static const double tolerance; +}; +const double MatVecXY::tolerance = 9e-7; + +struct MatVecXSquaredShifted +{ + using Mat = MatXSquaredShifted; + using Vec = VecXRevX; + static const double tolerance; +}; +const double MatVecXSquaredShifted::tolerance = 7e-7; + +template <typename MatVec> +class LocalAssemblerM final : public ProcessLib::LocalAssemblerInterface +{ +public: + void assemble(double const /*t*/, std::vector<double> const& local_x, + std::vector<double>& local_M_data, + std::vector<double>& /*local_K_data*/, + std::vector<double>& /*local_b_data*/) override + { + MatVec::Mat::getMat(local_x, local_M_data); + } + + void assembleWithJacobian(double const t, + std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + const double dxdot_dx, const double /*dx_dx*/, + std::vector<double>& local_M_data, + std::vector<double>& local_K_data, + std::vector<double>& local_b_data, + std::vector<double>& local_Jac_data) override + { + assemble(t, local_x, local_M_data, local_K_data, local_b_data); + + // dM/dx * xdot + MatVec::Mat::getDMatDxTimesY(local_x, local_xdot, local_Jac_data); + + auto local_Jac = + MathLib::toMatrix(local_Jac_data, local_x.size(), local_x.size()); + auto local_M = + MathLib::toMatrix(local_M_data, local_x.size(), local_x.size()); + local_Jac.noalias() += dxdot_dx * local_M; + } + + static double getTol() { return MatVec::tolerance; } + + static const bool asmM = true; + static const bool asmK = false; + static const bool asmb = false; +}; + +template <typename MatVec> +class LocalAssemblerK final : public ProcessLib::LocalAssemblerInterface +{ +public: + void assemble(double const /*t*/, std::vector<double> const& local_x, + std::vector<double>& /*local_M_data*/, + std::vector<double>& local_K_data, + std::vector<double>& /*local_b_data*/) override + { + MatVec::Mat::getMat(local_x, local_K_data); + } + + void assembleWithJacobian(double const t, + std::vector<double> const& local_x, + std::vector<double> const& /*local_xdot*/, + const double /*dxdot_dx*/, const double dx_dx, + std::vector<double>& local_M_data, + std::vector<double>& local_K_data, + std::vector<double>& local_b_data, + std::vector<double>& local_Jac_data) override + { + assemble(t, local_x, local_M_data, local_K_data, local_b_data); + + // dK/dx * x + MatVec::Mat::getDMatDxTimesY(local_x, local_x, local_Jac_data); + + auto local_Jac = + MathLib::toMatrix(local_Jac_data, local_x.size(), local_x.size()); + auto local_K = + MathLib::toMatrix(local_K_data, local_x.size(), local_x.size()); + local_Jac.noalias() += dx_dx * local_K; + } + + static double getTol() { return MatVec::tolerance; } + + static const bool asmM = false; + static const bool asmK = true; + static const bool asmb = false; +}; + +template <typename MatVec> +class LocalAssemblerB final : public ProcessLib::LocalAssemblerInterface +{ +public: + void assemble(double const /*t*/, std::vector<double> const& local_x, + std::vector<double>& /*local_M_data*/, + std::vector<double>& /*local_K_data*/, + std::vector<double>& local_b_data) override + { + MatVec::Vec::getVec(local_x, local_b_data); + } + + void assembleWithJacobian(double const t, + std::vector<double> const& local_x, + std::vector<double> const& /*local_xdot*/, + const double /*dxdot_dx*/, const double /*dx_dx*/, + std::vector<double>& local_M_data, + std::vector<double>& local_K_data, + std::vector<double>& local_b_data, + std::vector<double>& local_Jac_data) override + { + assemble(t, local_x, local_M_data, local_K_data, local_b_data); + + // db/dx + MatVec::Vec::getDVecDx(local_x, local_Jac_data); + + auto local_Jac = + MathLib::toMatrix(local_Jac_data, local_x.size(), local_x.size()); + // J = -db/dx !! + local_Jac = -local_Jac; + } + + static double getTol() { return MatVec::tolerance; } + + static const bool asmM = false; + static const bool asmK = false; + static const bool asmb = true; +}; + +template <typename MatVecM, typename MatVecK, typename MatVecB> +class LocalAssemblerMKb final : public ProcessLib::LocalAssemblerInterface +{ +public: + void assemble(double const /*t*/, std::vector<double> const& local_x, + std::vector<double>& local_M_data, + std::vector<double>& local_K_data, + std::vector<double>& local_b_data) override + { + MatVecM::Mat::getMat(local_x, local_M_data); + MatVecK::Mat::getMat(local_x, local_K_data); + MatVecB::Vec::getVec(local_x, local_b_data); + } + + void assembleWithJacobian(double const t, + std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + const double dxdot_dx, const double dx_dx, + std::vector<double>& local_M_data, + std::vector<double>& local_K_data, + std::vector<double>& local_b_data, + std::vector<double>& local_Jac_data) override + { + assemble(t, local_x, local_M_data, local_K_data, local_b_data); + + std::vector<double> local_JacM_data; + // dM/dx * xdot + MatVecM::Mat::getDMatDxTimesY(local_x, local_xdot, local_JacM_data); + + std::vector<double> local_JacK_data; + // dK/dx * x + MatVecK::Mat::getDMatDxTimesY(local_x, local_x, local_JacK_data); + + std::vector<double> local_JacB_data; + // db/dx + MatVecB::Vec::getDVecDx(local_x, local_JacB_data); + + local_Jac_data = local_JacM_data; + auto const N = local_Jac_data.size(); + for (std::size_t i = 0; i < N; ++i) { + local_Jac_data[i] += local_JacK_data[i] - local_JacB_data[i]; + } + + auto local_Jac = + MathLib::toMatrix(local_Jac_data, local_x.size(), local_x.size()); + auto local_M = + MathLib::toMatrix(local_M_data, local_x.size(), local_x.size()); + auto local_K = + MathLib::toMatrix(local_K_data, local_x.size(), local_x.size()); + local_Jac.noalias() += dxdot_dx * local_M + dx_dx * local_K; + } + + static double getTol() + { + return std::max( + {MatVecM::tolerance, MatVecK::tolerance, MatVecB::tolerance}); + } + + static const bool asmM = true; + static const bool asmK = true; + static const bool asmb = true; +}; + +template<class LocAsm> +struct ProcessLibCentralDifferencesJacobianAssembler : public ::testing::Test +{ + static void test() + { + // these four local variables will be filled randomly + std::vector<double> x, xdot; + double dxdot_dx, dx_dx; + + std::random_device rd; + std::mt19937 random_number_generator(rd()); + { + std::uniform_int_distribution<std::size_t> rnd(3, 64); + + auto const size = rnd(random_number_generator); + x.resize(size); + xdot.resize(size); + + // all components will be of order of magnitude one + fillRandomlyConstrainedAbsoluteValues(x, 0.5, 1.5); + fillRandomlyConstrainedAbsoluteValues(xdot, 0.5, 1.5); + } + { + std::uniform_real_distribution<double> rnd; + dxdot_dx = rnd(random_number_generator); + dx_dx = rnd(random_number_generator); + } + + testInner(x, xdot, dxdot_dx, dx_dx); + } + +private: + static void testInner(std::vector<double> const& x, + std::vector<double> const& xdot, + const double dxdot_dx, const double dx_dx) + { + ProcessLib::AnalyticalJacobianAssembler jac_asm_ana; + ProcessLib::CentralDifferencesJacobianAssembler jac_asm_cd({ 1e-8 }); + LocAsm loc_asm; + + double const eps = std::numeric_limits<double>::epsilon(); + + std::vector<double> M_data_cd, K_data_cd, b_data_cd, Jac_data_cd, + M_data_ana, K_data_ana, b_data_ana, Jac_data_ana; + double const t = 0.0; + + jac_asm_cd.assembleWithJacobian(loc_asm, t, x, xdot, dxdot_dx, dx_dx, M_data_cd, + K_data_cd, b_data_cd, Jac_data_cd); + + jac_asm_ana.assembleWithJacobian(loc_asm, t, x, xdot, dxdot_dx, dx_dx, + M_data_ana, K_data_ana, b_data_ana, + Jac_data_ana); + + if (LocAsm::asmM) { + ASSERT_EQ(x.size()*x.size(), M_data_cd.size()); + ASSERT_EQ(x.size()*x.size(), M_data_ana.size()); + for (std::size_t i=0; i<x.size()*x.size(); ++i) + EXPECT_NEAR(M_data_ana[i], M_data_cd[i], eps); + } + + if (LocAsm::asmK) { + ASSERT_EQ(x.size()*x.size(), K_data_cd.size()); + ASSERT_EQ(x.size()*x.size(), K_data_ana.size()); + for (std::size_t i=0; i<x.size()*x.size(); ++i) + EXPECT_NEAR(K_data_ana[i], K_data_cd[i], eps); + } + + if (LocAsm::asmb) { + ASSERT_EQ(x.size(), b_data_cd.size()); + ASSERT_EQ(x.size(), b_data_ana.size()); + for (std::size_t i=0; i<x.size(); ++i) + EXPECT_NEAR(b_data_ana[i], b_data_cd[i], eps); + } + + ASSERT_EQ(x.size()*x.size(), Jac_data_cd.size()); + ASSERT_EQ(x.size()*x.size(), Jac_data_ana.size()); + for (std::size_t i=0; i<x.size()*x.size(); ++i) { + // DBUG("%lu, %g, %g", i, Jac_data_ana[i], Jac_data_cd[i]); + EXPECT_NEAR(Jac_data_ana[i], Jac_data_cd[i], LocAsm::getTol()); + } + } +}; + +typedef ::testing::Types< + // DiagX + LocalAssemblerM<MatVecDiagX>, LocalAssemblerK<MatVecDiagX>, + LocalAssemblerB<MatVecDiagX>, + LocalAssemblerMKb<MatVecDiagX, MatVecDiagX, MatVecDiagX>, + // DiagXSquared + LocalAssemblerM<MatVecDiagXSquared>, LocalAssemblerK<MatVecDiagXSquared>, + LocalAssemblerB<MatVecDiagXSquared>, + LocalAssemblerMKb<MatVecDiagXSquared, MatVecDiagXSquared, + MatVecDiagXSquared>, + // XY + LocalAssemblerM<MatVecXY>, LocalAssemblerK<MatVecXY>, + LocalAssemblerB<MatVecXY>, LocalAssemblerMKb<MatVecXY, MatVecXY, MatVecXY>, + // XSquaredShifted + LocalAssemblerM<MatVecXSquaredShifted>, + LocalAssemblerK<MatVecXSquaredShifted>, + LocalAssemblerB<MatVecXSquaredShifted>, + LocalAssemblerMKb<MatVecXSquaredShifted, MatVecXSquaredShifted, + MatVecXSquaredShifted>> + TestCases; + +TYPED_TEST_CASE(ProcessLibCentralDifferencesJacobianAssembler, TestCases); + +TYPED_TEST(ProcessLibCentralDifferencesJacobianAssembler, Test) +{ + TestFixture::test(); +}