#include #include #include #include #include #include #include #include import mean_field; import test_helpers; using namespace mean_field; using Catch::Matchers::WithinAbs; namespace { constexpr int dimension = 3; mfem::DenseMatrix make_matrix( const std::array< double, 9> &values ) { mfem::DenseMatrix matrix(dimension); for (int row = 0; row < dimension; ++row) { for (int column = 0; column < dimension; ++column) matrix(row, column) = values[row * dimension + column]; } return matrix; } mfem::DenseMatrix make_identity_matrix() { mfem::DenseMatrix identity(dimension); identity = 0.0; for (int i = 0; i < dimension; ++i) identity(i, i) = 1.0; return identity; } mapping::MappingPointContext make_context(const mfem::DenseMatrix &jacobian) { mapping::MappingPointContext context; context.mapping_jacobian = jacobian; context.mapping_determinant = jacobian.Det(); context.inverse_mapping_jacobian.SetSize(dimension); mfem::CalcInverse(jacobian, context.inverse_mapping_jacobian); context.physical_position.SetSize(dimension); context.physical_position = 0.0; return context; } double determinant_variation( const mfem::DenseMatrix &jacobian, const mfem::DenseMatrix &jacobian_variation ) { mfem::DenseMatrix inverse_jacobian(dimension); mfem::DenseMatrix product(dimension); mfem::CalcInverse(jacobian, inverse_jacobian); mfem::Mult(inverse_jacobian, jacobian_variation, product); double trace = 0.0; for (int i = 0; i < dimension; ++i) trace += product(i, i); return jacobian.Det() * trace; } mapping::MappingPointVariation make_variation( const mfem::DenseMatrix &jacobian, const mfem::DenseMatrix &jacobian_variation ) { mapping::MappingPointVariation variation; variation.mapping_jacobian_variation = jacobian_variation; variation.mapping_determinant_variation = determinant_variation(jacobian, jacobian_variation); variation.physical_position_variation.SetSize(dimension); variation.physical_position_variation = 0.0; return variation; } double matrix_norm(const mfem::DenseMatrix &matrix) { double norm_squared = 0.0; for (int row = 0; row < matrix.Height(); ++row) { for (int column = 0; column < matrix.Width(); ++column) norm_squared += matrix(row, column) * matrix(row, column); } return std::sqrt(norm_squared); } double relative_matrix_error( const mfem::DenseMatrix &computed, const mfem::DenseMatrix &reference ) { REQUIRE(computed.Height() == reference.Height()); REQUIRE(computed.Width() == reference.Width()); mfem::DenseMatrix difference(computed); difference -= reference; return matrix_norm(difference) / std::max( matrix_norm(reference), std::numeric_limits::epsilon() ); } double matrix_asymmetry(const mfem::DenseMatrix &matrix) { double asymmetry_squared = 0.0; for (int row = 0; row < matrix.Height(); ++row) { for (int column = 0; column < matrix.Width(); ++column) { const double difference = matrix(row, column) - matrix(column, row); asymmetry_squared += difference * difference; } } return std::sqrt(asymmetry_squared); } mfem::DenseMatrix centered_mass_tensor_difference( const mfem::DenseMatrix &jacobian, const mfem::DenseMatrix &jacobian_variation, const double step ) { mfem::DenseMatrix plus_jacobian(jacobian); mfem::DenseMatrix minus_jacobian(jacobian); for (int row = 0; row < dimension; ++row) { for (int column = 0; column < dimension; ++column) { plus_jacobian(row, column) += step * jacobian_variation(row, column); minus_jacobian(row, column) -= step * jacobian_variation(row, column); } } REQUIRE(plus_jacobian.Det() > 0.0); REQUIRE(minus_jacobian.Det() > 0.0); const mapping::MappingPointContext plus_context = make_context(plus_jacobian); const mapping::MappingPointContext minus_context = make_context(minus_jacobian); mfem::DenseMatrix plus_tensor; mfem::DenseMatrix minus_tensor; mapping::ComputeHDivMassTensor(plus_context, plus_tensor); mapping::ComputeHDivMassTensor(minus_context, minus_tensor); plus_tensor -= minus_tensor; plus_tensor *= 1 / (2.0 * step); return plus_tensor; } void check_zero_matrix( const mfem::DenseMatrix &matrix, const double tolerance ) { for (int row = 0; row < matrix.Height(); ++row) { for (int column = 0; column < matrix.Width(); ++column) CHECK_THAT(matrix(row, column), WithinAbs(0.0, tolerance)); } } struct TensorVariationCase { std::string name; mfem::DenseMatrix jacobian; mfem::DenseMatrix jacobian_variation; }; } // namespace TEST_CASE( "Hdiv Mass Tensor Variation Matches Centered Differences", tags::unit &tags::transformations ) { std::vector cases; cases.push_back( {"identity with general variation", make_identity_matrix(), make_matrix({0.12, -0.07, 0.03, 0.05, -0.09, 0.04, -0.02, 0.08, 0.06})} ); cases.push_back( {"anisotropic stretch", make_matrix({1.20, 0.00, 0.00, 0.00, 0.85, 0.00, 0.00, 0.00, 1.10}), make_matrix({0.08, 0.01, -0.03, 0.02, -0.05, 0.04, 0.01, -0.02, 0.07})} ); cases.push_back( {"sheared mapping", make_matrix({1.10, 0.20, -0.05, 0.04, 0.90, 0.12, -0.03, 0.08, 1.15}), make_matrix( {0.06, -0.04, 0.02, 0.03, 0.05, -0.07, -0.01, 0.04, -0.02} )} ); cases.push_back( {"strong general mapping", make_matrix({1.35, 0.31, -0.18, -0.12, 0.78, 0.22, 0.09, -0.16, 1.27}), make_matrix({-0.11, 0.08, 0.05, 0.07, 0.09, -0.04, -0.06, 0.03, 0.12})} ); for (const TensorVariationCase &test_case : cases) { DYNAMIC_SECTION(test_case.name) { REQUIRE(test_case.jacobian.Det() > 0.0); const mapping::MappingPointContext context = make_context(test_case.jacobian); const mapping::MappingPointVariation variation = make_variation( test_case.jacobian, test_case.jacobian_variation ); mfem::DenseMatrix analytic_variation; mapping::ComputeHDivMassTensorVariation( context, variation, analytic_variation ); const mfem::DenseMatrix finite_difference = centered_mass_tensor_difference( test_case.jacobian, test_case.jacobian_variation, 1.0e-6 ); const double relative_error = relative_matrix_error(analytic_variation, finite_difference); const double asymmetry = matrix_asymmetry(analytic_variation); INFO("Mapping determinant = " << context.mapping_determinant); INFO( "Determinant variation = " << variation.mapping_determinant_variation ); INFO( "Analytic variation norm = " << matrix_norm(analytic_variation) ); INFO( "Finite-difference variation norm = " << matrix_norm(finite_difference) ); INFO("Relative tensor-variation error = " << relative_error); INFO("Tensor-variation asymmetry = " << asymmetry); CHECK(relative_error < 2.0e-9); CHECK(asymmetry < 2.0e-14); } } } TEST_CASE( "Hdiv Mass Tensor Variation Has Second Order Centered Difference " "Convergence", tags::unit &tags::transformations &tags::convergence ) { const mfem::DenseMatrix jacobian = make_matrix({1.18, 0.17, -0.09, -0.04, 0.92, 0.14, 0.07, -0.11, 1.23}); const mfem::DenseMatrix jacobian_variation = make_matrix({0.09, -0.06, 0.04, 0.03, 0.07, -0.05, -0.02, 0.08, -0.03}); const mapping::MappingPointContext context = make_context(jacobian); const mapping::MappingPointVariation variation = make_variation(jacobian, jacobian_variation); mfem::DenseMatrix analytic_variation; mapping::ComputeHDivMassTensorVariation( context, variation, analytic_variation ); const std::array steps{4.0e-2, 2.0e-2, 1.0e-2}; std::array errors{}; for (int i = 0; i < static_cast(steps.size()); ++i) { const mfem::DenseMatrix finite_difference = centered_mass_tensor_difference( jacobian, jacobian_variation, steps[i] ); errors[i] = relative_matrix_error(finite_difference, analytic_variation); INFO("Step = " << steps[i] << ", relative error = " << errors[i]); } const double first_reduction = errors[1] / errors[0]; const double second_reduction = errors[2] / errors[1]; INFO("First error-reduction ratio = " << first_reduction); INFO("Second error-reduction ratio = " << second_reduction); CHECK(first_reduction < 0.30); CHECK(second_reduction < 0.30); CHECK(errors[2] < 1.0e-5); } TEST_CASE( "Hdiv Mass Tensor Variation Vanishes For Translation", tags::unit &tags::transformations ) { const mfem::DenseMatrix jacobian = make_matrix({1.12, 0.08, -0.03, 0.02, 0.94, 0.07, -0.01, 0.05, 1.09}); mfem::DenseMatrix zero_jacobian_variation(dimension); zero_jacobian_variation = 0.0; mapping::MappingPointContext context = make_context(jacobian); mapping::MappingPointVariation variation = make_variation(jacobian, zero_jacobian_variation); variation.physical_position_variation.SetSize(dimension); variation.physical_position_variation(0) = 0.7; variation.physical_position_variation(1) = -0.4; variation.physical_position_variation(2) = 0.9; mfem::DenseMatrix tensor_variation; mapping::ComputeHDivMassTensorVariation( context, variation, tensor_variation ); CHECK_THAT(variation.mapping_determinant_variation, WithinAbs(0.0, 0.0)); check_zero_matrix(tensor_variation, 1.0e-14); } TEST_CASE( "Hdiv Mass Tensor Variation Vanishes For Infinitesimal Rotation At " "Identity", tags::unit &tags::transformations ) { const mfem::DenseMatrix identity = make_identity_matrix(); const mfem::DenseMatrix rotation_variation = make_matrix({0.0, -0.30, 0.20, 0.30, 0.0, -0.15, -0.20, 0.15, 0.0}); const mapping::MappingPointContext context = make_context(identity); const mapping::MappingPointVariation variation = make_variation(identity, rotation_variation); mfem::DenseMatrix tensor_variation; mapping::ComputeHDivMassTensorVariation( context, variation, tensor_variation ); CHECK_THAT( variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15) ); check_zero_matrix(tensor_variation, 1.0e-14); } TEST_CASE( "Hdiv Mass Tensor Variation Matches Isotropic Scaling At Identity", tags::unit &tags::transformations ) { constexpr double scaling_variation = 0.17; const mfem::DenseMatrix identity = make_identity_matrix(); mfem::DenseMatrix jacobian_variation(dimension); jacobian_variation = 0.0; for (int i = 0; i < dimension; ++i) jacobian_variation(i, i) = scaling_variation; const mapping::MappingPointContext context = make_context(identity); const mapping::MappingPointVariation variation = make_variation(identity, jacobian_variation); mfem::DenseMatrix tensor_variation; mapping::ComputeHDivMassTensorVariation( context, variation, tensor_variation ); CHECK_THAT( variation.mapping_determinant_variation, WithinAbs(3.0 * scaling_variation, 1.0e-14) ); for (int row = 0; row < dimension; ++row) { for (int column = 0; column < dimension; ++column) { const double expected = row == column ? -scaling_variation : 0.0; CHECK_THAT( tensor_variation(row, column), WithinAbs(expected, 1.0e-14) ); } } } TEST_CASE( "Hdiv Mass Tensor Variation Symmetrizes Simple Shear At Identity", tags::unit &tags::transformations ) { constexpr double shear_variation = 0.23; const mfem::DenseMatrix identity = make_identity_matrix(); mfem::DenseMatrix jacobian_variation(dimension); jacobian_variation = 0.0; jacobian_variation(0, 1) = shear_variation; const mapping::MappingPointContext context = make_context(identity); const mapping::MappingPointVariation variation = make_variation(identity, jacobian_variation); mfem::DenseMatrix tensor_variation; mapping::ComputeHDivMassTensorVariation( context, variation, tensor_variation ); CHECK_THAT( variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15) ); CHECK_THAT(tensor_variation(0, 1), WithinAbs(shear_variation, 1.0e-14)); CHECK_THAT(tensor_variation(1, 0), WithinAbs(shear_variation, 1.0e-14)); for (int row = 0; row < dimension; ++row) { for (int column = 0; column < dimension; ++column) { if ((row == 0 && column == 1) || (row == 1 && column == 0)) continue; CHECK_THAT(tensor_variation(row, column), WithinAbs(0.0, 1.0e-14)); } } } TEST_CASE( "Mapping Determinant Variation Matches Jacobi Formula", tags::unit &tags::transformations ) { const mfem::DenseMatrix jacobian = make_matrix({1.24, 0.19, -0.07, -0.06, 0.88, 0.16, 0.04, -0.12, 1.19}); const mfem::DenseMatrix jacobian_variation = make_matrix({0.08, -0.03, 0.05, 0.02, 0.06, -0.04, -0.01, 0.07, -0.02}); const mapping::MappingPointContext context = make_context(jacobian); const mapping::MappingPointVariation variation = make_variation(jacobian, jacobian_variation); constexpr double difference_step = 1.0e-3; mfem::DenseMatrix plus_one(context.mapping_jacobian); mfem::DenseMatrix plus_two(context.mapping_jacobian); mfem::DenseMatrix minus_one(context.mapping_jacobian); mfem::DenseMatrix minus_two(context.mapping_jacobian); plus_one.Add(difference_step, variation.mapping_jacobian_variation); plus_two.Add(2.0 * difference_step, variation.mapping_jacobian_variation); minus_one.Add(-difference_step, variation.mapping_jacobian_variation); minus_two.Add(-2.0 * difference_step, variation.mapping_jacobian_variation); const double finite_difference = (minus_two.Det() - 8.0 * minus_one.Det() + 8.0 * plus_one.Det() - plus_two.Det()) / (12.0 * difference_step); const double analytic = variation.mapping_determinant_variation; const double relative_error = std::abs(finite_difference - analytic) / std::max(std::abs(analytic), 1.0e-14); INFO("Analytic determinant variation = " << analytic); INFO("Finite-difference determinant variation = " << finite_difference); INFO("Relative determinant-variation error = " << relative_error); CHECK(relative_error < 2.0e-11); }