#include #include #include #include #include #include #include #include #include #include import mean_field; import test_helpers; namespace prepared_barotropic_closure_test_utils { namespace field = mean_field::field; namespace domain = mean_field::utils::domain; using Schema = domain::CoreEnvelopeVacuumDomainSchema; struct Maps final { field::FieldDofMap density; field::FieldDofMap enthalpy; field::FieldDofMap displacement; explicit Maps(const mean_field::fem::FEM &f) : density( field::make_field_dof_map< field::Density, Schema>(*f.densityFes) ), enthalpy( field::make_field_dof_map< field::Enthalpy, Schema>(*f.enthalpyFes) ), displacement( field::make_field_dof_map< field::Displacement, Schema>(*f.displacementFes) ) { } }; using ClosureDependencies = mean_field::operators::context::barotropic::BarotropicClosureDependencies; [[nodiscard]] ClosureDependencies make_dependencies(const std::uint64_t revisionOffset = 0) { return { .discretization = {.identity = 201, .revision = 3 + revisionOffset}, .density = {.identity = 211, .revision = 5 + revisionOffset}, .enthalpy = {.identity = 223, .revision = 7 + revisionOffset}, .displacement = {.identity = 227, .revision = 11 + revisionOffset} }; } [[nodiscard]] mean_field::operators::context::barotropic::BarotropicClosureStateView make_state_view( const mfem::Vector &density, const mfem::Vector &enthalpy, const mfem::Vector &displacement ) { return {.density = density, .enthalpy = enthalpy, .displacement = displacement}; } [[nodiscard]] mfem::Vector project_scalar( mfem::ParFiniteElementSpace &finiteElementSpace, mfem::Coefficient &coefficient ) { mfem::ParGridFunction fieldValue(&finiteElementSpace); fieldValue.ProjectCoefficient(coefficient); mfem::Vector trueVector; fieldValue.GetTrueDofs(trueVector); return trueVector; } [[nodiscard]] mfem::Vector make_base_density(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { return 0.42 + 0.025 * position(0) - 0.012 * position(1) + 0.007 * position(2); }); return project_scalar(*f.densityFes, coefficient); } [[nodiscard]] mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { return 0.92 + 0.018 * position(0) - 0.011 * position(1) + 0.006 * position(2); }); return project_scalar(*f.enthalpyFes, coefficient); } [[nodiscard]] mfem::Vector make_enthalpy_variation(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { return 0.065 + 0.014 * position(0) + 0.009 * position(2); }); return project_scalar(*f.enthalpyFes, coefficient); } [[nodiscard]] mfem::Vector make_constant_field( mfem::ParFiniteElementSpace &finiteElementSpace, const double value ) { mfem::ConstantCoefficient coefficient(value); return project_scalar(finiteElementSpace, coefficient); } [[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) { mfem::ParGridFunction fieldValue(f.displacementFes.get()); mfem::VectorFunctionCoefficient coefficient( f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(position.Size()); value = 0.0; value(0) = -2.0 * position(0); } ); fieldValue.ProjectCoefficient(coefficient); mfem::Vector result; fieldValue.GetTrueDofs(result); return result; } [[nodiscard]] mfem::Vector reduce( const field::FieldDofMap &map, const mfem::Vector &full ) { return map.gather(full); } [[nodiscard]] mfem::Vector expand( const field::FieldDofMap &map, const mfem::Vector &reduced ) { return map.scatter(reduced); } [[nodiscard]] mfem::Vector make_combined_variation( const mfem::Vector &densityVariation, const mfem::Vector &enthalpyVariation, const mfem::Vector &displacementVariation ) { mfem::Vector combined(densityVariation.Size() + enthalpyVariation.Size() + displacementVariation.Size()); int offset = 0; for (int index = 0; index < densityVariation.Size(); ++index) { combined(offset + index) = densityVariation(index); } offset += densityVariation.Size(); for (int index = 0; index < enthalpyVariation.Size(); ++index) { combined(offset + index) = enthalpyVariation(index); } offset += enthalpyVariation.Size(); for (int index = 0; index < displacementVariation.Size(); ++index) { combined(offset + index) = displacementVariation(index); } return combined; } [[nodiscard]] double relative_error( const mfem::Vector &left, const mfem::Vector &right, const MPI_Comm communicator ) { return gravity_prepared_test_utils::relative_error(left, right, communicator); } [[nodiscard]] double global_norm( const mfem::Vector &vector, const MPI_Comm communicator ) { return gravity_prepared_test_utils::global_norm(vector, communicator); } [[nodiscard]] long long global_sum( const int localValue, const MPI_Comm communicator ) { const long long local = static_cast(localValue); long long global = 0; MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, communicator); return global; } [[nodiscard]] mfem::Vector gather_reference( const field::FieldDofMap &densityMap, const mfem::Vector &fullReference ) { return densityMap.gather(fullReference); } struct ClosureCondition final { const char *name; double polytropicIndex; double polytropicConstant; double enthalpyOffset; double enthalpyGradient; double densityFactor; double densityOffset; double densityGradient; double deformationScale; double directionPhase; }; inline constexpr std::array conditions{ {{.name = "Linear polytrope on identity geometry", .polytropicIndex = 1.0, .polytropicConstant = 0.8, .enthalpyOffset = 0.65, .enthalpyGradient = 0.06, .densityFactor = 0.80, .densityOffset = 0.015, .densityGradient = 0.004, .deformationScale = 0.0, .directionPhase = 0.31}, {.name = "Fractional polytrope on moderate deformation", .polytropicIndex = 1.5, .polytropicConstant = 1.2, .enthalpyOffset = 0.90, .enthalpyGradient = 0.09, .densityFactor = 1.15, .densityOffset = -0.003, .densityGradient = 0.003, .deformationScale = 0.45, .directionPhase = 0.53}, {.name = "Target n=3 polytrope on strong deformation", .polytropicIndex = 3.0, .polytropicConstant = 1.5, .enthalpyOffset = 1.20, .enthalpyGradient = 0.12, .densityFactor = 1.40, .densityOffset = 0.006, .densityGradient = 0.002, .deformationScale = 1.0, .directionPhase = 0.79}} }; [[nodiscard]] double evaluate_enthalpy( const mfem::Vector &position, const ClosureCondition &condition ) { return condition.enthalpyOffset + condition.enthalpyGradient * (0.50 * position(0) - 0.30 * position(1) + 0.20 * position(2)); } [[nodiscard]] mfem::Vector make_enthalpy( const mean_field::fem::FEM &f, const ClosureCondition &condition ) { mfem::FunctionCoefficient coefficient([condition](const mfem::Vector &position) { return evaluate_enthalpy(position, condition); }); return project_scalar(*f.enthalpyFes, coefficient); } [[nodiscard]] mfem::Vector make_density( const mean_field::fem::FEM &f, const mean_field::eos::Polytrope &equationOfState, const ClosureCondition &condition ) { mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) { const double enthalpy = evaluate_enthalpy(position, condition); const double equationOfStateDensity = mean_field::eos::evaluate( equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpy} ) .value(); return condition.densityFactor * equationOfStateDensity + condition.densityOffset + condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2)); }); return project_scalar(*f.densityFes, coefficient); } TEST_CASE( "Prepared Barotropic Closure Uses FieldDof Supported Dimensions", tags::barotrope &tags::closure &tags::prepared &tags::field &tags::unit ) { using Operator = mean_field::operators::PreparedBarotropicClosureOperator; STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); STATIC_REQUIRE_FALSE(std::is_copy_assignable_v); STATIC_REQUIRE_FALSE(std::is_move_constructible_v); STATIC_REQUIRE_FALSE(std::is_move_assignable_v); STATIC_REQUIRE(std::is_trivially_copyable_v); auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const prepared_barotropic_closure_test_utils::Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); Operator preparedOperator(f, *f.domainMapperStateless, equationOfState); CHECK_FALSE(preparedOperator.IsPrepared()); CHECK(preparedOperator.GetPreparationCount() == 0); CHECK(preparedOperator.GetDensitySize() == maps.density.reduced_size()); CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size()); CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size()); CHECK(preparedOperator.Height() == maps.density.reduced_size()); CHECK( preparedOperator.Width() == maps.density.reduced_size() + maps.enthalpy.reduced_size() + maps.displacement.reduced_size() ); CHECK(maps.displacement.is_identity()); const MPI_Comm communicator = f.mesh->GetComm(); const long long globalDensityFull = prepared_barotropic_closure_test_utils::global_sum(maps.density.full_size(), communicator); const long long globalDensityReduced = prepared_barotropic_closure_test_utils::global_sum(maps.density.reduced_size(), communicator); const long long globalEnthalpyFull = prepared_barotropic_closure_test_utils::global_sum(maps.enthalpy.full_size(), communicator); const long long globalEnthalpyReduced = prepared_barotropic_closure_test_utils::global_sum(maps.enthalpy.reduced_size(), communicator); REQUIRE(globalDensityReduced > 0); REQUIRE(globalEnthalpyReduced > 0); CHECK(globalDensityReduced < globalDensityFull); CHECK(globalEnthalpyReduced < globalEnthalpyFull); } TEST_CASE( "Prepared Barotropic Closure Reports Expected EOS Rejections Without Unwinding", tags::barotrope &tags::closure &tags::prepared &tags::unit ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); mfem::Vector density(maps.density.reduced_size()); mfem::Vector enthalpy(maps.enthalpy.reduced_size()); mfem::Vector displacement(maps.displacement.reduced_size()); density = 0.0; enthalpy = -1.0; displacement = 0.0; auto dependencies = make_dependencies(); const auto outsideDomain = preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies); REQUIRE_FALSE(outsideDomain.has_value()); CHECK( outsideDomain.error().reason == mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state ); CHECK(outsideDomain.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::outside_domain); CHECK_FALSE(preparedOperator.IsPrepared()); try { (void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies); FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input."); } catch (const mean_field::eos::EvaluationError &error) { CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain); } // Keep the interpolated input finite while forcing the n = 3 // polytropic density evaluation to overflow. enthalpy = 1.0e150; ++dependencies.enthalpy.revision; const auto rejected = preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies); REQUIRE_FALSE(rejected.has_value()); CHECK( rejected.error().reason == mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state ); CHECK(rejected.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::nonfinite_result); CHECK_FALSE(preparedOperator.IsPrepared()); try { (void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies); FAIL("The compatibility Prepare overload accepted a non-finite EOS result."); } catch (const mean_field::eos::EvaluationError &error) { CHECK(error.code() == mean_field::eos::EvaluationErrorCode::nonfinite_result); } enthalpy = 1.0; ++dependencies.enthalpy.revision; const auto accepted = preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies); REQUIRE(accepted.has_value()); CHECK(preparedOperator.IsPrepared()); } TEST_CASE( "Prepared Barotropic Closure Reports Invalid Candidate Geometry Without Unwinding", tags::barotrope &tags::closure &tags::prepared &tags::geometry &tags::unit ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); mfem::Vector density(maps.density.reduced_size()); mfem::Vector enthalpy(maps.enthalpy.reduced_size()); density = 1.0; enthalpy = 1.0; mfem::Vector displacement = reduce(maps.displacement, make_folding_displacement(f)); auto dependencies = make_dependencies(); const auto rejected = preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies); REQUIRE_FALSE(rejected.has_value()); CHECK( rejected.error().reason == mean_field::operators::BarotropicClosurePreparationRejectionReason::mapping_failure ); CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant); CHECK_FALSE(preparedOperator.IsPrepared()); CHECK_THROWS_AS( preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies), std::domain_error ); displacement = 0.0; ++dependencies.displacement.revision; const auto accepted = preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies); REQUIRE(accepted.has_value()); CHECK(preparedOperator.IsPrepared()); } TEST_CASE( "Prepared Barotropic Closure Matches Full Stateless Kernels Through FieldDof Restriction", tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::integration ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); const mfem::Vector fullBaseDensity = make_base_density(f); const mfem::Vector fullBaseEnthalpy = make_base_enthalpy(f); const mfem::Vector fullDisplacement = gravity_prepared_test_utils::make_displacement(f, 1.0); const mfem::Vector density = reduce(maps.density, fullBaseDensity); const mfem::Vector enthalpy = reduce(maps.enthalpy, fullBaseEnthalpy); const mfem::Vector displacement = reduce(maps.displacement, fullDisplacement); const mfem::Vector rawDensityVariation = gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.43); const mfem::Vector rawEnthalpyVariation = make_enthalpy_variation(f); const mfem::Vector rawDisplacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.63); const mfem::Vector densityVariation = reduce(maps.density, rawDensityVariation); const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, rawEnthalpyVariation); const mfem::Vector displacementVariation = reduce(maps.displacement, rawDisplacementVariation); const mfem::Vector fullDensityVariation = expand(maps.density, densityVariation); const mfem::Vector fullEnthalpyVariation = expand(maps.enthalpy, enthalpyVariation); const mfem::Vector fullDisplacementVariation = expand(maps.displacement, displacementVariation); mfem::Vector zeroDensity(maps.density.reduced_size()); mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size()); mfem::Vector zeroDisplacement(maps.displacement.reduced_size()); zeroDensity = 0.0; zeroEnthalpy = 0.0; zeroDisplacement = 0.0; preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies()); mfem::Vector preparedResidual; mfem::Vector preparedDensityAction; mfem::Vector preparedEnthalpyAction; mfem::Vector preparedDisplacementAction; mfem::Vector preparedCompleteAction; mfem::Vector preparedPackedAction; preparedOperator.BuildResidual(preparedResidual); preparedOperator.Mult(densityVariation, zeroEnthalpy, zeroDisplacement, preparedDensityAction); preparedOperator.Mult(zeroDensity, enthalpyVariation, zeroDisplacement, preparedEnthalpyAction); preparedOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, preparedDisplacementAction); preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedCompleteAction); const mfem::Vector packedDirection = make_combined_variation(densityVariation, enthalpyVariation, displacementVariation); preparedOperator.Mult(packedDirection, preparedPackedAction); mfem::Vector fullReferenceResidual; mfem::Vector fullReferenceDensityAction; mfem::Vector fullReferenceEnthalpyAction; mfem::Vector fullReferenceDisplacementAction; mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, density), expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullReferenceResidual ); mean_field::operators::kernels::apply_barotropic_closure_density_action( f, *f.domainMapperStateless, equationOfState, fullDensityVariation, expand(maps.displacement, displacement), fullReferenceDensityAction ); mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action( f, *f.domainMapperStateless, equationOfState, expand(maps.enthalpy, enthalpy), fullEnthalpyVariation, expand(maps.displacement, displacement), fullReferenceEnthalpyAction ); mean_field::operators::kernels::apply_barotropic_closure_displacement_action( f, *f.domainMapperStateless, equationOfState, expand(maps.density, density), expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullDisplacementVariation, fullReferenceDisplacementAction ); const mfem::Vector referenceResidual = gather_reference(maps.density, fullReferenceResidual); const mfem::Vector referenceDensityAction = gather_reference(maps.density, fullReferenceDensityAction); const mfem::Vector referenceEnthalpyAction = gather_reference(maps.density, fullReferenceEnthalpyAction); const mfem::Vector referenceDisplacementAction = gather_reference(maps.density, fullReferenceDisplacementAction); mfem::Vector referenceCompleteAction(referenceDensityAction); referenceCompleteAction += referenceEnthalpyAction; referenceCompleteAction += referenceDisplacementAction; const MPI_Comm communicator = f.mesh->GetComm(); CHECK(relative_error(preparedResidual, referenceResidual, communicator) < 2.0e-12); CHECK(relative_error(preparedDensityAction, referenceDensityAction, communicator) < 2.0e-12); CHECK(relative_error(preparedEnthalpyAction, referenceEnthalpyAction, communicator) < 2.0e-12); CHECK(relative_error(preparedDisplacementAction, referenceDisplacementAction, communicator) < 2.0e-12); CHECK(relative_error(preparedCompleteAction, referenceCompleteAction, communicator) < 2.0e-12); CHECK(relative_error(preparedPackedAction, referenceCompleteAction, communicator) < 2.0e-12); CHECK(preparedOperator.GetPreparationCount() == 1); } TEST_CASE( "Prepared Barotropic Closure Jacobian Matches A Reduced Coordinate Centered Difference", tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::jacobian &tags::accuracy ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); const mfem::Vector density = reduce(maps.density, make_base_density(f)); const mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f)); const mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.8)); const mfem::Vector densityVariation = reduce( maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.71) ); const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f)); const mfem::Vector displacementVariation = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.63)); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies()); mfem::Vector analyticAction; preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, analyticAction); constexpr double differenceStep = 1.0e-5; mfem::Vector plusDensity(density); mfem::Vector minusDensity(density); mfem::Vector plusEnthalpy(enthalpy); mfem::Vector minusEnthalpy(enthalpy); mfem::Vector plusDisplacement(displacement); mfem::Vector minusDisplacement(displacement); plusDensity.Add(differenceStep, densityVariation); minusDensity.Add(-differenceStep, densityVariation); plusEnthalpy.Add(differenceStep, enthalpyVariation); minusEnthalpy.Add(-differenceStep, enthalpyVariation); plusDisplacement.Add(differenceStep, displacementVariation); minusDisplacement.Add(-differenceStep, displacementVariation); mfem::Vector plusFullResidual; mfem::Vector minusFullResidual; mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, plusDensity), expand(maps.enthalpy, plusEnthalpy), expand(maps.displacement, plusDisplacement), plusFullResidual ); mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, minusDensity), expand(maps.enthalpy, minusEnthalpy), expand(maps.displacement, minusDisplacement), minusFullResidual ); mfem::Vector finiteDifference = maps.density.gather(plusFullResidual); mfem::Vector minusReduced = maps.density.gather(minusFullResidual); finiteDifference -= minusReduced; finiteDifference /= 2.0 * differenceStep; const double error = relative_error(analyticAction, finiteDifference, f.mesh->GetComm()); INFO("Reduced closure centered-difference error = " << error); CHECK(error < 2.0e-7); } TEST_CASE( "Prepared Barotropic Closure Reuses Its Frozen Expanded State", tags::barotrope &tags::closure &tags::prepared &tags::field &tags::unit ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); mfem::Vector density = reduce(maps.density, make_base_density(f)); mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f)); mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 1.0)); const mfem::Vector densityVariation = reduce( maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.31) ); const mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f)); const mfem::Vector displacementVariation = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.63)); preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies()); REQUIRE(preparedOperator.GetPreparationCount() == 1); mfem::Vector firstResidual; mfem::Vector firstAction; preparedOperator.BuildResidual(firstResidual); preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, firstAction); density = 7.0; enthalpy = 3.0; displacement *= -4.0; const std::uint64_t preparationCount = preparedOperator.GetPreparationCount(); mfem::Vector repeatedResidual; mfem::Vector repeatedAction; preparedOperator.BuildResidual(repeatedResidual); preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, repeatedAction); const MPI_Comm communicator = f.mesh->GetComm(); CHECK(relative_error(repeatedResidual, firstResidual, communicator) < 2.0e-14); CHECK(relative_error(repeatedAction, firstAction, communicator) < 2.0e-14); CHECK(preparedOperator.GetPreparationCount() == preparationCount); } TEST_CASE( "Prepared Barotropic Closure Reprepares Correctly For New Geometry", tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::prepared &tags::field &tags::integration ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); const mfem::Vector density = reduce(maps.density, make_base_density(f)); const mfem::Vector enthalpy = reduce(maps.enthalpy, make_base_enthalpy(f)); const mfem::Vector densityVariation = reduce( maps.density, gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.59) ); mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size()); mfem::Vector zeroDisplacementVariation(maps.displacement.reduced_size()); zeroEnthalpy = 0.0; zeroDisplacementVariation = 0.0; mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); mfem::Vector identityAction; mfem::Vector deformedAction; ClosureDependencies dependencies = make_dependencies(); for (const double deformationScale : {0.0, 1.0}) { CAPTURE(deformationScale); const mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, deformationScale)); preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies); mfem::Vector preparedResidual; mfem::Vector preparedAction; preparedOperator.BuildResidual(preparedResidual); preparedOperator.Mult(densityVariation, zeroEnthalpy, zeroDisplacementVariation, preparedAction); mfem::Vector fullReferenceResidual; mfem::Vector fullReferenceAction; mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, density), expand(maps.enthalpy, enthalpy), expand(maps.displacement, displacement), fullReferenceResidual ); mean_field::operators::kernels::apply_barotropic_closure_density_action( f, *f.domainMapperStateless, equationOfState, expand(maps.density, densityVariation), expand(maps.displacement, displacement), fullReferenceAction ); const mfem::Vector referenceResidual = maps.density.gather(fullReferenceResidual); const mfem::Vector referenceAction = maps.density.gather(fullReferenceAction); const MPI_Comm communicator = f.mesh->GetComm(); CHECK(relative_error(preparedResidual, referenceResidual, communicator) < 2.0e-12); CHECK(relative_error(preparedAction, referenceAction, communicator) < 2.0e-12); if (deformationScale == 0.0) { identityAction = preparedAction; } else { deformedAction = preparedAction; } ++dependencies.displacement.revision; } const double geometryChange = relative_error(deformedAction, identityAction, f.mesh->GetComm()); INFO("Prepared closure geometry change = " << geometryChange); CHECK(preparedOperator.GetPreparationCount() == 2); CHECK(geometryChange > 1.0e-5); } TEST_CASE( "Prepared Barotropic Closure Covers Multiple EOS And Geometry Conditions In Reduced Coordinates", tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::prepared &tags::field ) { auto 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.domainMapperStateless != nullptr); const Maps maps(f); const MPI_Comm communicator = f.mesh->GetComm(); constexpr double differenceStep = 1.0e-5; for (std::size_t conditionIndex = 0; conditionIndex < conditions.size(); ++conditionIndex) { const ClosureCondition &condition = conditions[conditionIndex]; DYNAMIC_SECTION(condition.name) { const mean_field::eos::Polytrope equationOfState( condition.polytropicIndex, condition.polytropicConstant ); const mfem::Vector fullBaseDensityRaw = make_density(f, equationOfState, condition); const mfem::Vector fullBaseEnthalpyRaw = make_enthalpy(f, condition); const mfem::Vector fullBaseDisplacementRaw = gravity_prepared_test_utils::make_displacement(f, condition.deformationScale); const mfem::Vector baseDensity = reduce(maps.density, fullBaseDensityRaw); const mfem::Vector baseEnthalpy = reduce(maps.enthalpy, fullBaseEnthalpyRaw); const mfem::Vector baseDisplacement = reduce(maps.displacement, fullBaseDisplacementRaw); mfem::Vector densityVariation = reduce( maps.density, gravity_prepared_test_utils::make_deterministic_vector( f.densityFes->GetTrueVSize(), condition.directionPhase ) ); mfem::Vector enthalpyVariation = reduce(maps.enthalpy, make_enthalpy_variation(f)); mfem::Vector displacementVariation = reduce( maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.55 + 0.1 * condition.directionPhase) ); mfem::Vector fullDensityAction; mfem::Vector fullEnthalpyAction; mfem::Vector fullDisplacementAction; mean_field::operators::kernels::apply_barotropic_closure_density_action( f, *f.domainMapperStateless, equationOfState, expand(maps.density, densityVariation), expand(maps.displacement, baseDisplacement), fullDensityAction ); mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action( f, *f.domainMapperStateless, equationOfState, expand(maps.enthalpy, baseEnthalpy), expand(maps.enthalpy, enthalpyVariation), expand(maps.displacement, baseDisplacement), fullEnthalpyAction ); mean_field::operators::kernels::apply_barotropic_closure_displacement_action( f, *f.domainMapperStateless, equationOfState, expand(maps.density, baseDensity), expand(maps.enthalpy, baseEnthalpy), expand(maps.displacement, baseDisplacement), expand(maps.displacement, displacementVariation), fullDisplacementAction ); mfem::Vector densityAction = maps.density.gather(fullDensityAction); mfem::Vector enthalpyAction = maps.density.gather(fullEnthalpyAction); mfem::Vector displacementAction = maps.density.gather(fullDisplacementAction); double densityNorm = global_norm(densityAction, communicator); double enthalpyNorm = global_norm(enthalpyAction, communicator); double displacementNorm = global_norm(displacementAction, communicator); REQUIRE(densityNorm > 1.0e-12); REQUIRE(enthalpyNorm > 1.0e-12); REQUIRE(displacementNorm > 1.0e-12); const double targetNorm = std::min({densityNorm, enthalpyNorm, displacementNorm}); const double densityScale = targetNorm / densityNorm; const double enthalpyScale = targetNorm / enthalpyNorm; const double displacementScale = targetNorm / displacementNorm; densityVariation *= densityScale; enthalpyVariation *= enthalpyScale; displacementVariation *= displacementScale; densityAction *= densityScale; enthalpyAction *= enthalpyScale; displacementAction *= displacementScale; densityNorm = global_norm(densityAction, communicator); enthalpyNorm = global_norm(enthalpyAction, communicator); displacementNorm = global_norm(displacementAction, communicator); mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( f, *f.domainMapperStateless, equationOfState ); preparedOperator.Prepare( make_state_view(baseDensity, baseEnthalpy, baseDisplacement), make_dependencies() ); mfem::Vector preparedResidual; mfem::Vector preparedAction; preparedOperator.BuildResidual(preparedResidual); preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedAction); mfem::Vector fullReferenceResidual; mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, baseDensity), expand(maps.enthalpy, baseEnthalpy), expand(maps.displacement, baseDisplacement), fullReferenceResidual ); const mfem::Vector referenceResidual = maps.density.gather(fullReferenceResidual); mfem::Vector blockSum(densityAction); blockSum += enthalpyAction; blockSum += displacementAction; const double residualEvaluationError = relative_error(preparedResidual, referenceResidual, communicator); const double blockAssemblyError = relative_error(preparedAction, blockSum, communicator); mfem::Vector plusDensity(baseDensity); mfem::Vector minusDensity(baseDensity); mfem::Vector plusEnthalpy(baseEnthalpy); mfem::Vector minusEnthalpy(baseEnthalpy); mfem::Vector plusDisplacement(baseDisplacement); mfem::Vector minusDisplacement(baseDisplacement); plusDensity.Add(differenceStep, densityVariation); minusDensity.Add(-differenceStep, densityVariation); plusEnthalpy.Add(differenceStep, enthalpyVariation); minusEnthalpy.Add(-differenceStep, enthalpyVariation); plusDisplacement.Add(differenceStep, displacementVariation); minusDisplacement.Add(-differenceStep, displacementVariation); mfem::Vector fullPlusResidual; mfem::Vector fullMinusResidual; mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, plusDensity), expand(maps.enthalpy, plusEnthalpy), expand(maps.displacement, plusDisplacement), fullPlusResidual ); mean_field::operators::kernels::apply_barotropic_closure( f, *f.domainMapperStateless, equationOfState, expand(maps.density, minusDensity), expand(maps.enthalpy, minusEnthalpy), expand(maps.displacement, minusDisplacement), fullMinusResidual ); mfem::Vector finiteDifference = maps.density.gather(fullPlusResidual); const mfem::Vector minusReduced = maps.density.gather(fullMinusResidual); finiteDifference -= minusReduced; finiteDifference /= 2.0 * differenceStep; mfem::Vector finiteDifferenceError(preparedAction); finiteDifferenceError -= finiteDifference; const double finiteDifferenceErrorNorm = global_norm(finiteDifferenceError, communicator); const double blockNormSum = densityNorm + enthalpyNorm + displacementNorm; const double blockScaledDifferenceError = finiteDifferenceErrorNorm / blockNormSum; const double completeRelativeError = relative_error(preparedAction, finiteDifference, communicator); INFO("Condition = " << condition.name); INFO("Polytropic index = " << condition.polytropicIndex); INFO("Deformation scale = " << condition.deformationScale); INFO("Prepared residual error = " << residualEvaluationError); INFO("Complete block-assembly error = " << blockAssemblyError); INFO("Complete centered-difference relative error = " << completeRelativeError); INFO("Block-scaled centered-difference error = " << blockScaledDifferenceError); CHECK(preparedOperator.GetPreparationCount() == 1); CHECK(residualEvaluationError < 5.0e-12); CHECK(blockAssemblyError < 5.0e-12); CHECK(blockScaledDifferenceError < 2.0e-7); } } } TEST_CASE( "Exact Constant Prepared Barotropic Closure Remains Zero Under Deformation", tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::prepared &tags::field ) { auto args = test_utils::setup_args(); mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.okay()); const Maps maps(f); const mean_field::eos::Polytrope equationOfState(3.0, 1.5); constexpr double enthalpyValue = 1.20; const double equilibriumDensityValue = mean_field::eos::evaluate( equationOfState, mean_field::eos::SpecificEnthalpyValue{enthalpyValue} ) .value(); const mfem::Vector enthalpy = reduce(maps.enthalpy, make_constant_field(*f.enthalpyFes, enthalpyValue)); const mfem::Vector equilibriumDensity = reduce(maps.density, make_constant_field(*f.densityFes, equilibriumDensityValue)); const mfem::Vector referenceDensity = reduce(maps.density, make_constant_field(*f.densityFes, equilibriumDensityValue + 1.0)); const mfem::Vector displacementVariation = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.67)); mfem::Vector zeroDensity(maps.density.reduced_size()); mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size()); zeroDensity = 0.0; zeroEnthalpy = 0.0; const MPI_Comm communicator = f.mesh->GetComm(); for (const double deformationScale : {0.0, 0.5, 1.0}) { DYNAMIC_SECTION("Deformation scale = " << deformationScale) { const mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, deformationScale)); mean_field::operators::PreparedBarotropicClosureOperator exactOperator( f, *f.domainMapperStateless, equationOfState ); mean_field::operators::PreparedBarotropicClosureOperator referenceOperator( f, *f.domainMapperStateless, equationOfState ); exactOperator.Prepare(make_state_view(equilibriumDensity, enthalpy, displacement), make_dependencies()); referenceOperator.Prepare( make_state_view(referenceDensity, enthalpy, displacement), make_dependencies() ); mfem::Vector exactResidual; mfem::Vector referenceResidual; mfem::Vector exactGeometryAction; mfem::Vector referenceGeometryAction; exactOperator.BuildResidual(exactResidual); referenceOperator.BuildResidual(referenceResidual); exactOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, exactGeometryAction); referenceOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, referenceGeometryAction); const double exactResidualNorm = global_norm(exactResidual, communicator); const double referenceResidualNorm = global_norm(referenceResidual, communicator); const double exactGeometryNorm = global_norm(exactGeometryAction, communicator); const double referenceGeometryNorm = global_norm(referenceGeometryAction, communicator); INFO("Deformation scale = " << deformationScale); INFO("Exact reduced closure residual norm = " << exactResidualNorm); INFO("Reference reduced residual norm = " << referenceResidualNorm); INFO("Exact reduced geometry-action norm = " << exactGeometryNorm); INFO("Reference reduced geometry-action norm = " << referenceGeometryNorm); REQUIRE(referenceResidualNorm > 1.0e-12); REQUIRE(referenceGeometryNorm > 1.0e-14); CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm); CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm); } } } } // namespace prepared_barotropic_closure_test_utils