#include #include #include #include #include import mean_field; import test_helpers; namespace pressure_force_kernel_test_utils { [[nodiscard]] mfem::Vector make_deterministic_vector( const int size, const double phase ) { mfem::Vector vector(size); for (int index = 0; index < size; ++index) { const double position = static_cast(index + 1); vector(index) = 0.71 + 0.19 * std::sin(0.31 * position + phase) + 0.08 * std::cos(0.17 * position - 0.5 * phase); } return vector; } [[nodiscard]] mfem::Vector make_zero_displacement(const mean_field::fem::FEM &f) { mfem::Vector displacementTrue(f.displacementFes->GetTrueVSize()); displacementTrue = 0.0; return displacementTrue; } [[nodiscard]] mfem::Vector make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) { mfem::Vector enthalpyTrue = make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43); mfem::Array stellarElementMask; mean_field::utils::populate_element_mask( f.mesh.get(), mean_field::utils::DOMAINS::STELLAR, stellarElementMask ); mfem::Array stellarEnthalpyTrueDofs; mean_field::utils::populate_domain_tdofs( f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs ); for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size(); ++listIndex) { const int trueDof = stellarEnthalpyTrueDofs[listIndex]; MFEM_VERIFY( trueDof >= 0 && trueDof < enthalpyTrue.Size(), "The stellar enthalpy true-DOF mask contains an " "invalid index." ); enthalpyTrue(trueDof) = 0.0; } return enthalpyTrue; } [[nodiscard]] mfem::Vector make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { return 1.10 + 0.07 * position(0) - 0.04 * position(1) + 0.03 * position(2); }); mfem::ParGridFunction enthalpyField(f.enthalpyFes.get()); enthalpyField.ProjectCoefficient(coefficient); mfem::Vector enthalpyTrue; enthalpyField.GetTrueDofs(enthalpyTrue); return enthalpyTrue; } [[nodiscard]] mfem::Vector make_component_test_field( const mean_field::fem::FEM &f, const int component, const int coordinate ) { const int dimension = f.mesh->Dimension(); MFEM_VERIFY( component >= 0 && component < dimension, "The requested vector component is invalid." ); MFEM_VERIFY( coordinate >= -1 && coordinate < dimension, "The requested coordinate is invalid." ); /* * coordinate == -1 gives the rigid translation e_component. * * Otherwise this gives * * w = x_coordinate e_component. */ mfem::VectorFunctionCoefficient coefficient( dimension, [component, coordinate, dimension](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(dimension); value = 0.0; value(component) = coordinate < 0 ? 1.0 : position(coordinate); } ); mfem::ParGridFunction field(f.displacementFes.get()); field.ProjectCoefficient(coefficient); mfem::Vector fieldTrue; field.GetTrueDofs(fieldTrue); return fieldTrue; } [[nodiscard]] double global_dot( const mfem::Vector &left, const mfem::Vector &right, MPI_Comm communicator ) { MFEM_VERIFY( left.Size() == right.Size(), "The global dot-product vectors have different sizes." ); const double localDot = left * right; double globalDot = 0.0; MPI_Allreduce( &localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator ); return globalDot; } } // namespace pressure_force_kernel_test_utils TEST_CASE( "Pressure Force Residual Vanishes For Zero Enthalpy", tags::barotrope &tags::pressure &tags::kernels &tags::integration ) { mean_field::utils::Args args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize()); enthalpyTrue = 0.0; const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f); mfem::Vector residualTrue; mean_field::operators::kernels::apply_pressure_force_residual( f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize()); const double residualNorm = gravity_prepared_test_utils::global_norm( residualTrue, f.mesh->GetComm() ); CHECK(residualNorm == 0.0); } TEST_CASE( "Pressure Force Residual Excludes Vacuum Enthalpy Exactly", tags::barotrope &tags::pressure &tags::kernels &tags::integration ) { mean_field::utils::Args args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f); const double enthalpyNorm = gravity_prepared_test_utils::global_norm( enthalpyTrue, f.mesh->GetComm() ); /* * Ensure this is a real exclusion test rather than another * all-zero-input test. */ REQUIRE(enthalpyNorm > 0.0); const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f); mfem::Vector residualTrue; mean_field::operators::kernels::apply_pressure_force_residual( f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize()); const double residualNorm = gravity_prepared_test_utils::global_norm( residualTrue, f.mesh->GetComm() ); CHECK(residualNorm == 0.0); } TEST_CASE( "Pressure Force Residual Is Nonzero For Positive Stellar Pressure", tags::barotrope &tags::pressure &tags::kernels &tags::integration ) { mean_field::utils::Args args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); /* * With n = 3 and K = 1/4: * * P(1) = 1/4. */ mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize()); enthalpyTrue = 1.0; const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f); mfem::Vector residualTrue; mean_field::operators::kernels::apply_pressure_force_residual( f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); const double residualNorm = gravity_prepared_test_utils::global_norm( residualTrue, f.mesh->GetComm() ); INFO("Positive-pressure residual norm = " << residualNorm); CHECK(std::isfinite(residualNorm)); CHECK(residualNorm > 100.0 * std::numeric_limits::epsilon()); } TEST_CASE( "Pressure Force Residual Does No Work Against Rigid Translations", tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::accuracy ) { mean_field::utils::Args args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f); const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f); mfem::Vector residualTrue; mean_field::operators::kernels::apply_pressure_force_residual( f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); const double residualNorm = gravity_prepared_test_utils::global_norm( residualTrue, f.mesh->GetComm() ); REQUIRE(residualNorm > 0.0); const int dimension = f.mesh->Dimension(); for (int component = 0; component < dimension; ++component) { const mfem::Vector translationTrue = pressure_force_kernel_test_utils::make_component_test_field( f, component, -1 ); const double translationNorm = gravity_prepared_test_utils::global_norm( translationTrue, f.mesh->GetComm() ); const double translationWork = pressure_force_kernel_test_utils::global_dot( translationTrue, residualTrue, f.mesh->GetComm() ); const double dotProductScale = std::fmax(residualNorm * translationNorm, 1.0); CAPTURE(component, translationWork, dotProductScale); CHECK(std::abs(translationWork) <= 5.0e-12 * dotProductScale); } } TEST_CASE( "Pressure Force Residual Respects byNODES Component Layout", tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::accuracy ) { mean_field::utils::Args args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); const mfem::Vector enthalpyTrue = pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f); const mfem::Vector displacementTrue = pressure_force_kernel_test_utils::make_zero_displacement(f); mfem::Vector residualTrue; mean_field::operators::kernels::apply_pressure_force_residual( f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); const int dimension = f.mesh->Dimension(); REQUIRE(dimension == 3); mfem::DenseMatrix virtualWork(dimension, dimension); for (int component = 0; component < dimension; ++component) { for (int coordinate = 0; coordinate < dimension; ++coordinate) { const mfem::Vector affineTestTrue = pressure_force_kernel_test_utils::make_component_test_field( f, component, coordinate ); virtualWork(component, coordinate) = pressure_force_kernel_test_utils::global_dot( affineTestTrue, residualTrue, f.mesh->GetComm() ); } } double meanDiagonalWork = 0.0; for (int component = 0; component < dimension; ++component) { meanDiagonalWork += virtualWork(component, component); } meanDiagonalWork /= static_cast(dimension); // INFO( // "Affine pressure virtual-work tensor:\n" // << virtualWork // ); INFO("Mean diagonal virtual work = " << meanDiagonalWork); REQUIRE( std::abs(meanDiagonalWork) > 100.0 * std::numeric_limits::epsilon() ); const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork); for (int component = 0; component < dimension; ++component) { for (int coordinate = 0; coordinate < dimension; ++coordinate) { const double computedWork = virtualWork(component, coordinate); CAPTURE( component, coordinate, computedWork, meanDiagonalWork, comparisonTolerance ); if (component == coordinate) { CHECK( std::abs(computedWork - meanDiagonalWork) <= comparisonTolerance ); } else { CHECK(std::abs(computedWork) <= comparisonTolerance); } } } }