#include #include #include import mean_field; import test_helpers; using namespace mean_field; TEST_CASE( "Gravity Force Integrator Jacobian Matches Residual Linearization", tags::gravity_integrator_unit ) { constexpr int dim = 3; constexpr double finite_difference_step = 1.0e-3; constexpr double jacobian_tolerance = 1.0e-8; constexpr double zero_tolerance = 1.0e-14; constexpr int velocity_block = solver::block_index(solver::FieldBlock::velocity); constexpr int density_block = solver::block_index(solver::FieldBlock::density); constexpr int gravity_gradient_block = solver::block_index(solver::FieldBlock::gravity_gradient); constexpr int gravity_potential_block = solver::block_index(solver::FieldBlock::gravity_potential); constexpr int displacement_block = solver::block_index(solver::FieldBlock::displacement); constexpr int block_count = solver::field_block_count; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(2, dim); mfem::L2_FECollection density_fec(1, dim); mfem::RT_FECollection gravity_gradient_fec(1, dim); mfem::L2_FECollection gravity_potential_fec(1, dim); mfem::H1_FECollection displacement_fec(2, dim); mfem::H1_FECollection compactification_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace gravity_potential_fes(&mesh, &gravity_potential_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; mfem::GridFunction compactification_coordinate(&compactification_fes); compactification_coordinate = 0.0; mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *gravity_gradient_element = gravity_gradient_fes.GetFE(0); const mfem::FiniteElement *gravity_potential_element = gravity_potential_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); const int velocity_dofs_count = velocity_element->GetDof(); const int density_dofs_count = density_element->GetDof(); const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof(); const int gravity_potential_dofs_count = gravity_potential_element->GetDof(); const int displacement_dofs_count = displacement_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; const int displacement_size = dim * displacement_dofs_count; mfem::Vector velocity_dofs(velocity_size); mfem::Vector density_dofs(density_dofs_count); mfem::Vector gravity_gradient_dofs(gravity_gradient_dofs_count); mfem::Vector gravity_potential_dofs(gravity_potential_dofs_count); mfem::Vector displacement_dofs(displacement_size); velocity_dofs = 0.0; gravity_potential_dofs = 0.0; displacement_dofs = 0.0; for (int i = 0; i < density_dofs_count; ++i) { density_dofs(i) = 0.8 + 0.07 * static_cast(i + 1); } for (int i = 0; i < gravity_gradient_dofs_count; ++i) { const double sign = i % 2 == 0 ? 1.0 : -1.0; gravity_gradient_dofs(i) = sign * 0.04 * static_cast(i + 1); } mfem::Vector density_direction(density_dofs_count); mfem::Vector gravity_gradient_direction(gravity_gradient_dofs_count); mfem::Vector velocity_direction(velocity_size); mfem::Vector displacement_direction(displacement_size); for (int i = 0; i < density_dofs_count; ++i) { density_direction(i) = 0.13 - 0.02 * static_cast(i); } for (int i = 0; i < gravity_gradient_dofs_count; ++i) { const double sign = i % 3 == 0 ? -1.0 : 1.0; gravity_gradient_direction(i) = sign * (0.03 + 0.005 * static_cast(i)); } for (int i = 0; i < velocity_size; ++i) { velocity_direction(i) = 0.01 * static_cast(i + 1); } for (int i = 0; i < displacement_size; ++i) { displacement_direction(i) = -0.008 * static_cast(i + 1); } mfem::Array elements(block_count); elements[velocity_block] = velocity_element; elements[density_block] = density_element; elements[gravity_gradient_block] = gravity_gradient_element; elements[gravity_potential_block] = gravity_potential_element; elements[displacement_block] = displacement_element; mfem::Array element_state(block_count); element_state[velocity_block] = &velocity_dofs; element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &gravity_gradient_dofs; element_state[gravity_potential_block] = &gravity_potential_dofs; element_state[displacement_block] = &displacement_dofs; mfem::Vector velocity_residual(velocity_size); mfem::Vector density_residual(density_dofs_count); mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count); mfem::Vector gravity_potential_residual(gravity_potential_dofs_count); mfem::Vector displacement_residual(displacement_size); mfem::Array element_residual(block_count); element_residual[velocity_block] = &velocity_residual; element_residual[density_block] = &density_residual; element_residual[gravity_gradient_block] = &gravity_gradient_residual; element_residual[gravity_potential_block] = &gravity_potential_residual; element_residual[displacement_block] = &displacement_residual; integrators::GravityMomentumIntegrator integrator( domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled ); const int maximum_order = std::max( velocity_element->GetOrder(), std::max(density_element->GetOrder(), gravity_gradient_element->GetOrder()) ); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 2 * maximum_order + 8); integrator.SetIntegrationRule(integration_rule); mfem::DenseMatrix dv_dv(velocity_size, velocity_size); mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count); mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count); mfem::DenseMatrix dv_ddisplacement(velocity_size, displacement_size); dv_dv = 1.0; dv_drho = 1.0; dv_dgrad_phi = 1.0; dv_ddisplacement = 1.0; mfem::Array2D element_matrices(block_count, block_count); for (int row = 0; row < block_count; ++row) { for (int column = 0; column < block_count; ++column) { element_matrices(row, column) = nullptr; } } element_matrices(velocity_block, velocity_block) = &dv_dv; element_matrices(velocity_block, density_block) = &dv_drho; element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi; element_matrices(velocity_block, displacement_block) = &dv_ddisplacement; integrator.AssembleElementGrad(elements, *transformation, element_state, element_matrices); auto assemble_velocity_residual = [&](const mfem::Vector &density_state, const mfem::Vector &gravity_gradient_state) { element_state[density_block] = &density_state; element_state[gravity_gradient_block] = &gravity_gradient_state; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return mfem::Vector(velocity_residual); }; auto relative_error = [](mfem::Vector computed, const mfem::Vector &reference) { computed -= reference; return computed.Norml2() / std::max(reference.Norml2(), 1.0e-30); }; mfem::Vector density_plus(density_dofs); mfem::Vector density_minus(density_dofs); density_plus.Add(finite_difference_step, density_direction); density_minus.Add(-finite_difference_step, density_direction); mfem::Vector density_residual_plus = assemble_velocity_residual(density_plus, gravity_gradient_dofs); mfem::Vector density_residual_minus = assemble_velocity_residual(density_minus, gravity_gradient_dofs); mfem::Vector density_finite_difference(density_residual_plus); density_finite_difference -= density_residual_minus; density_finite_difference *= 0.5 / finite_difference_step; mfem::Vector density_jacobian_action(velocity_size); dv_drho.Mult(density_direction, density_jacobian_action); mfem::Vector gravity_gradient_plus(gravity_gradient_dofs); mfem::Vector gravity_gradient_minus(gravity_gradient_dofs); gravity_gradient_plus.Add(finite_difference_step, gravity_gradient_direction); gravity_gradient_minus.Add(-finite_difference_step, gravity_gradient_direction); mfem::Vector gravity_residual_plus = assemble_velocity_residual(density_dofs, gravity_gradient_plus); mfem::Vector gravity_residual_minus = assemble_velocity_residual(density_dofs, gravity_gradient_minus); mfem::Vector gravity_finite_difference(gravity_residual_plus); gravity_finite_difference -= gravity_residual_minus; gravity_finite_difference *= 0.5 / finite_difference_step; mfem::Vector gravity_jacobian_action(velocity_size); dv_dgrad_phi.Mult(gravity_gradient_direction, gravity_jacobian_action); mfem::Vector combined_density_plus(density_dofs); mfem::Vector combined_density_minus(density_dofs); mfem::Vector combined_gravity_plus(gravity_gradient_dofs); mfem::Vector combined_gravity_minus(gravity_gradient_dofs); combined_density_plus.Add(finite_difference_step, density_direction); combined_density_minus.Add(-finite_difference_step, density_direction); combined_gravity_plus.Add(finite_difference_step, gravity_gradient_direction); combined_gravity_minus.Add(-finite_difference_step, gravity_gradient_direction); mfem::Vector combined_residual_plus = assemble_velocity_residual(combined_density_plus, combined_gravity_plus); mfem::Vector combined_residual_minus = assemble_velocity_residual(combined_density_minus, combined_gravity_minus); mfem::Vector combined_finite_difference(combined_residual_plus); combined_finite_difference -= combined_residual_minus; combined_finite_difference *= 0.5 / finite_difference_step; mfem::Vector combined_jacobian_action(density_jacobian_action); combined_jacobian_action += gravity_jacobian_action; mfem::Vector inactive_velocity_action(velocity_size); mfem::Vector inactive_displacement_action(velocity_size); dv_dv.Mult(velocity_direction, inactive_velocity_action); dv_ddisplacement.Mult(displacement_direction, inactive_displacement_action); const double density_relative_error = relative_error(density_finite_difference, density_jacobian_action); const double gravity_relative_error = relative_error(gravity_finite_difference, gravity_jacobian_action); const double combined_relative_error = relative_error(combined_finite_difference, combined_jacobian_action); INFO("Density Jacobian relative error = " << density_relative_error); INFO("Gravity-gradient Jacobian relative error = " << gravity_relative_error); INFO("Combined Jacobian relative error = " << combined_relative_error); CHECK_THAT(density_relative_error, Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)); CHECK_THAT(gravity_relative_error, Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)); CHECK_THAT(combined_relative_error, Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)); CHECK_THAT(inactive_velocity_action.Norml2(), Catch::Matchers::WithinAbs(0.0, zero_tolerance)); CHECK_THAT(inactive_displacement_action.Norml2(), Catch::Matchers::WithinAbs(0.0, zero_tolerance)); mfem::Vector field_coupled_density_action(density_jacobian_action); integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal); dv_dv = 1.0; dv_drho = 1.0; dv_dgrad_phi = 1.0; dv_ddisplacement = 1.0; element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &gravity_gradient_dofs; integrator.AssembleElementGrad(elements, *transformation, element_state, element_matrices); mfem::Vector minimal_density_action(velocity_size); mfem::Vector minimal_gravity_action(velocity_size); dv_drho.Mult(density_direction, minimal_density_action); dv_dgrad_phi.Mult(gravity_gradient_direction, minimal_gravity_action); const double minimal_density_difference = relative_error(minimal_density_action, field_coupled_density_action); INFO("Minimal-mode density-block difference = " << minimal_density_difference); CHECK_THAT(minimal_density_difference, Catch::Matchers::WithinAbs(0.0, zero_tolerance)); CHECK_THAT(minimal_gravity_action.Norml2(), Catch::Matchers::WithinAbs(0.0, zero_tolerance)); } TEST_CASE( "Gravity Force Integrator Matches Manufactured Cartesian Load", tags::gravity_integrator_unit ) { constexpr int dim = 3; constexpr double tolerance = 1.0e-12; constexpr int velocity_block = solver::block_index(solver::FieldBlock::velocity); constexpr int density_block = solver::block_index(solver::FieldBlock::density); constexpr int gravity_gradient_block = solver::block_index(solver::FieldBlock::gravity_gradient); constexpr int gravity_potential_block = solver::block_index(solver::FieldBlock::gravity_potential); constexpr int displacement_block = solver::block_index(solver::FieldBlock::displacement); constexpr int block_count = solver::field_block_count; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(1, dim); mfem::L2_FECollection density_fec(1, dim); mfem::RT_FECollection gravity_gradient_fec(0, dim); mfem::H1_FECollection displacement_fec(1, dim); mfem::H1_FECollection compactification_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; mfem::GridFunction compactification_coordinate(&compactification_fes); compactification_coordinate = 0.0; mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate); auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; auto reference_gravity_gradient = [](const mfem::Vector &x, mfem::Vector &gradient) { gradient.SetSize(3); gradient(0) = 2.0 * x(0); gradient(1) = 3.0 * x(1); gradient(2) = 4.0 * x(2); }; mfem::FunctionCoefficient density_coefficient(reference_density); mfem::VectorFunctionCoefficient gravity_gradient_coefficient(dim, reference_gravity_gradient); mfem::GridFunction density(&density_fes); mfem::GridFunction gravity_gradient(&gravity_gradient_fes); density.ProjectCoefficient(density_coefficient); gravity_gradient.ProjectCoefficient(gravity_gradient_coefficient); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *gravity_gradient_element = gravity_gradient_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); const int velocity_dofs_count = velocity_element->GetDof(); const int density_dofs_count = density_element->GetDof(); const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof(); const int displacement_dofs_count = displacement_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; const int displacement_size = dim * displacement_dofs_count; mfem::Array density_dof_indices; mfem::Array gravity_gradient_dof_indices; mfem::Vector density_dofs; mfem::Vector gravity_gradient_dofs; density_fes.GetElementDofs(0, density_dof_indices); gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices); density.GetSubVector(density_dof_indices, density_dofs); gravity_gradient.GetSubVector(gravity_gradient_dof_indices, gravity_gradient_dofs); REQUIRE(density_dofs.Size() == density_dofs_count); REQUIRE(gravity_gradient_dofs.Size() == gravity_gradient_dofs_count); mfem::Vector velocity_dofs(velocity_size); mfem::Vector gravity_potential_dofs(density_dofs_count); mfem::Vector displacement_dofs(displacement_size); velocity_dofs = 0.0; gravity_potential_dofs = 0.0; displacement_dofs = 0.0; mfem::Array elements(block_count); elements[velocity_block] = velocity_element; elements[density_block] = density_element; elements[gravity_gradient_block] = gravity_gradient_element; elements[gravity_potential_block] = density_element; elements[displacement_block] = displacement_element; mfem::Array element_state(block_count); element_state[velocity_block] = &velocity_dofs; element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &gravity_gradient_dofs; element_state[gravity_potential_block] = &gravity_potential_dofs; element_state[displacement_block] = &displacement_dofs; mfem::Vector velocity_residual(velocity_size); mfem::Vector density_residual(density_dofs_count); mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count); mfem::Vector gravity_potential_residual(density_dofs_count); mfem::Vector displacement_residual(displacement_size); mfem::Array element_residual(block_count); element_residual[velocity_block] = &velocity_residual; element_residual[density_block] = &density_residual; element_residual[gravity_gradient_block] = &gravity_gradient_residual; element_residual[gravity_potential_block] = &gravity_potential_residual; element_residual[displacement_block] = &displacement_residual; integrators::GravityMomentumIntegrator integrator( domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled ); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); integrator.SetIntegrationRule(integration_rule); integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); mfem::Vector reference_velocity_residual(velocity_residual); auto residual_action = [&](const int component, const int coordinate_weight) { mfem::Vector test_dofs(velocity_size); mfem::Vector x_physical(dim); test_dofs = 0.0; const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical); test_dofs(i + component * velocity_dofs_count) = coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight); } return test_dofs * velocity_residual; }; CHECK_THAT(residual_action(0, -1), Catch::Matchers::WithinAbs(5.0 / 3.0, tolerance)); CHECK_THAT(residual_action(1, -1), Catch::Matchers::WithinAbs(9.0 / 4.0, tolerance)); CHECK_THAT(residual_action(2, -1), Catch::Matchers::WithinAbs(3.0, tolerance)); CHECK_THAT(residual_action(0, 0), Catch::Matchers::WithinAbs(7.0 / 6.0, tolerance)); CHECK_THAT(residual_action(1, 1), Catch::Matchers::WithinAbs(3.0 / 2.0, tolerance)); CHECK_THAT(residual_action(2, 2), Catch::Matchers::WithinAbs(2.0, tolerance)); CHECK_THAT(residual_action(1, 0), Catch::Matchers::WithinAbs(5.0 / 4.0, tolerance)); CHECK_THAT(density_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(gravity_gradient_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(gravity_potential_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(displacement_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal); integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); mfem::Vector minimal_difference(velocity_residual); minimal_difference -= reference_velocity_residual; integrator.SetJacobianMode(integrators::GravityForceJacobianMode::exact); integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); mfem::Vector exact_difference(velocity_residual); exact_difference -= reference_velocity_residual; CHECK_THAT(minimal_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(exact_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); } TEST_CASE( "Gravity Force Integrator Preserves Gravity Identities", tags::gravity_integrator_unit ) { constexpr int dim = 3; constexpr double density_value = 1.7; constexpr double gravity_scale = 2.4; constexpr double density_scale = 0.6; constexpr double tolerance = 1.0e-12; constexpr int velocity_block = solver::block_index(solver::FieldBlock::velocity); constexpr int density_block = solver::block_index(solver::FieldBlock::density); constexpr int gravity_gradient_block = solver::block_index(solver::FieldBlock::gravity_gradient); constexpr int gravity_potential_block = solver::block_index(solver::FieldBlock::gravity_potential); constexpr int displacement_block = solver::block_index(solver::FieldBlock::displacement); constexpr int block_count = solver::field_block_count; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(1, dim); mfem::L2_FECollection density_fec(0, dim); mfem::RT_FECollection gravity_gradient_fec(0, dim); mfem::H1_FECollection displacement_fec(1, dim); mfem::H1_FECollection compactification_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::FiniteElementSpace compactification_fes(&mesh, &compactification_fec); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; mfem::GridFunction compactification_coordinate(&compactification_fes); compactification_coordinate = 0.0; mapping::DomainMapper domain_mapper = field_dof_test_utils::make_domain_mapper(); mapping::GridFunctionMappingEvaluator mapping_evaluator(domain_mapper, displacement, compactification_coordinate); auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) { gravity.SetSize(3); gravity(0) = x(0) - 0.5; gravity(1) = x(1) - 0.5; gravity(2) = x(2) - 0.5; }; mfem::ConstantCoefficient density_coefficient(density_value); mfem::VectorFunctionCoefficient gravity_coefficient(dim, radial_gravity); mfem::GridFunction density(&density_fes); mfem::GridFunction gravity_gradient(&gravity_gradient_fes); density.ProjectCoefficient(density_coefficient); gravity_gradient.ProjectCoefficient(gravity_coefficient); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *gravity_gradient_element = gravity_gradient_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); const int velocity_dofs_count = velocity_element->GetDof(); const int density_dofs_count = density_element->GetDof(); const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof(); const int displacement_dofs_count = displacement_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; const int displacement_size = dim * displacement_dofs_count; mfem::Array density_dof_indices; mfem::Array gravity_gradient_dof_indices; mfem::Vector density_dofs; mfem::Vector gravity_gradient_dofs; density_fes.GetElementDofs(0, density_dof_indices); gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices); density.GetSubVector(density_dof_indices, density_dofs); gravity_gradient.GetSubVector(gravity_gradient_dof_indices, gravity_gradient_dofs); mfem::Vector zero_density(density_dofs_count); mfem::Vector zero_gravity(gravity_gradient_dofs_count); mfem::Vector velocity_dofs(velocity_size); mfem::Vector gravity_potential_dofs(density_dofs_count); mfem::Vector displacement_dofs(displacement_size); zero_density = 0.0; zero_gravity = 0.0; velocity_dofs = 0.0; gravity_potential_dofs = 0.0; displacement_dofs = 0.0; mfem::Array elements(block_count); elements[velocity_block] = velocity_element; elements[density_block] = density_element; elements[gravity_gradient_block] = gravity_gradient_element; elements[gravity_potential_block] = density_element; elements[displacement_block] = displacement_element; mfem::Array element_state(block_count); element_state[velocity_block] = &velocity_dofs; element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &gravity_gradient_dofs; element_state[gravity_potential_block] = &gravity_potential_dofs; element_state[displacement_block] = &displacement_dofs; mfem::Vector velocity_residual(velocity_size); mfem::Vector density_residual(density_dofs_count); mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count); mfem::Vector gravity_potential_residual(density_dofs_count); mfem::Vector displacement_residual(displacement_size); mfem::Array element_residual(block_count); element_residual[velocity_block] = &velocity_residual; element_residual[density_block] = &density_residual; element_residual[gravity_gradient_block] = &gravity_gradient_residual; element_residual[gravity_potential_block] = &gravity_potential_residual; element_residual[displacement_block] = &displacement_residual; integrators::GravityMomentumIntegrator integrator( domain_mapper, displacement, compactification_coordinate, integrators::GravityForceJacobianMode::field_coupled ); const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); integrator.SetIntegrationRule(integration_rule); auto assemble_velocity_residual = [&](const mfem::Vector &density_state, const mfem::Vector &gravity_state) { element_state[density_block] = &density_state; element_state[gravity_gradient_block] = &gravity_state; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return mfem::Vector(velocity_residual); }; auto scaled_difference_norm = [](mfem::Vector computed, const mfem::Vector &reference, const double scale) { computed.Add(-scale, reference); return computed.Norml2(); }; const mfem::Vector base_residual = assemble_velocity_residual(density_dofs, gravity_gradient_dofs); REQUIRE(base_residual.Norml2() > tolerance); const mfem::Vector zero_field_residual = assemble_velocity_residual(density_dofs, zero_gravity); mfem::Vector reversed_gravity(gravity_gradient_dofs); reversed_gravity *= -1.0; const mfem::Vector reversed_residual = assemble_velocity_residual(density_dofs, reversed_gravity); mfem::Vector scaled_gravity(gravity_gradient_dofs); scaled_gravity *= gravity_scale; const mfem::Vector gravity_scaled_residual = assemble_velocity_residual(density_dofs, scaled_gravity); mfem::Vector scaled_density(density_dofs); scaled_density *= density_scale; const mfem::Vector density_scaled_residual = assemble_velocity_residual(scaled_density, gravity_gradient_dofs); const mfem::Vector jointly_scaled_residual = assemble_velocity_residual(scaled_density, scaled_gravity); CHECK_THAT(zero_field_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT( scaled_difference_norm(reversed_residual, base_residual, -1.0), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT( scaled_difference_norm(gravity_scaled_residual, base_residual, gravity_scale), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT( scaled_difference_norm(density_scaled_residual, base_residual, density_scale), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT( scaled_difference_norm(jointly_scaled_residual, base_residual, density_scale * gravity_scale), Catch::Matchers::WithinAbs(0.0, tolerance) ); auto make_test_dofs = [&](auto &&test_function) { mfem::Vector test_dofs(velocity_size); mfem::Vector x_physical(dim); mfem::Vector centered_position(dim); mfem::Vector test_value(dim); test_dofs = 0.0; const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical); for (int component = 0; component < dim; ++component) { centered_position(component) = x_physical(component) - 0.5; } test_function(centered_position, test_value); for (int component = 0; component < dim; ++component) { test_dofs(i + component * velocity_dofs_count) = test_value(component); } } return test_dofs; }; const mfem::Vector force_x_test = make_test_dofs([](const mfem::Vector &, mfem::Vector &value) { value.SetSize(3); value = 0.0; value(0) = 1.0; }); const mfem::Vector force_y_test = make_test_dofs([](const mfem::Vector &, mfem::Vector &value) { value.SetSize(3); value = 0.0; value(1) = 1.0; }); const mfem::Vector force_z_test = make_test_dofs([](const mfem::Vector &, mfem::Vector &value) { value.SetSize(3); value = 0.0; value(2) = 1.0; }); const mfem::Vector torque_x_test = make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = 0.0; value(1) = -position(2); value(2) = position(1); }); const mfem::Vector torque_y_test = make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = position(2); value(1) = 0.0; value(2) = -position(0); }); const mfem::Vector torque_z_test = make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = -position(1); value(1) = position(0); value(2) = 0.0; }); CHECK_THAT(force_x_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(force_y_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(force_z_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(torque_x_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(torque_y_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(torque_z_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count); mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count); mfem::Array2D element_matrices(block_count, block_count); for (int row = 0; row < block_count; ++row) { for (int column = 0; column < block_count; ++column) { element_matrices(row, column) = nullptr; } } element_matrices(velocity_block, density_block) = &dv_drho; element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi; element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &zero_gravity; integrator.AssembleElementGrad(elements, *transformation, element_state, element_matrices); mfem::Vector zero_gravity_density_action(velocity_size); mfem::Vector zero_gravity_field_action(velocity_size); dv_drho.Mult(density_dofs, zero_gravity_density_action); dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_gravity_field_action); CHECK_THAT(zero_gravity_density_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT( scaled_difference_norm(zero_gravity_field_action, base_residual, 1.0), Catch::Matchers::WithinAbs(0.0, tolerance) ); element_state[density_block] = &zero_density; element_state[gravity_gradient_block] = &gravity_gradient_dofs; integrator.AssembleElementGrad(elements, *transformation, element_state, element_matrices); mfem::Vector zero_density_density_action(velocity_size); mfem::Vector zero_density_field_action(velocity_size); dv_drho.Mult(density_dofs, zero_density_density_action); dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_density_field_action); CHECK_THAT( scaled_difference_norm(zero_density_density_action, base_residual, 1.0), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT(zero_density_field_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); }